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

483 lines
12 KiB
Vue
Raw Normal View History

2025-11-04 13:22:28 +08:00
<template>
2025-11-07 13:34:04 +08:00
<div class="onlyoffice-container">
2025-11-07 15:08:41 +08:00
<!-- 加载状态 -->
<div v-if="loading" class="loading-state">
<div class="loading-spinner"></div>
<p>正在加载文档...</p>
</div>
2025-11-10 09:46:22 +08:00
2025-11-07 15:08:41 +08:00
<!-- 错误状态 -->
<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>
2025-11-10 09:46:22 +08:00
2025-11-07 15:08:41 +08:00
<!-- 编辑器容器始终存在通过样式控制显示 -->
<div v-show="!loading && !error" id="placeholder" class="editor-placeholder"></div>
2025-11-04 13:22:28 +08:00
</div>
</template>
<script>
2025-11-07 13:34:04 +08:00
import { getConfigAPI, generateCallbackTokenAPI } from '@/api/common/onlyOfficeViewer'
2025-11-04 13:22:28 +08:00
export default {
name: 'OnlyOfficeViewer',
props: {
2025-11-07 13:34:04 +08:00
// 可以添加 props 来动态配置
documentUrl: {
2025-11-04 13:22:28 +08:00
type: String,
2025-11-07 13:34:04 +08:00
default: 'http://192.168.0.14:9090/smart-bid/technicalSolutionDatabase/2025/11/03/716d9f3d89434c56bc49296dbbccc226.docx'
2025-11-04 13:22:28 +08:00
},
2025-11-07 13:34:04 +08:00
documentTitle: {
2025-11-04 13:22:28 +08:00
type: String,
2025-11-07 13:34:04 +08:00
default: '716d9f3d89434c56bc49296dbbccc226.docx'
2025-11-04 13:22:28 +08:00
},
2025-11-07 13:34:04 +08:00
documentKey: {
2025-11-04 13:22:28 +08:00
type: String,
2025-11-07 13:34:04 +08:00
default: '1'
2025-11-04 13:22:28 +08:00
},
2025-11-07 13:34:04 +08:00
fileName: {
type: String,
default: 'technicalSolutionDatabase/2025/11/03/716d9f3d89434c56bc49296dbbccc226.docx'
2025-11-04 13:22:28 +08:00
},
mode: {
type: String,
2025-11-07 13:34:04 +08:00
default: 'view', // 'view' 或 'edit'
2025-11-04 13:22:28 +08:00
validator: (value) => ['view', 'edit'].includes(value)
},
2025-11-07 13:34:04 +08:00
type: {
2025-11-04 13:22:28 +08:00
type: String,
2025-11-07 13:34:04 +08:00
default: 'desktop', // 'desktop', 'mobile', 'embedded'
validator: (value) => ['desktop', 'mobile', 'embedded'].includes(value)
2025-11-04 13:22:28 +08:00
}
},
2025-11-07 13:34:04 +08:00
2025-11-04 13:22:28 +08:00
data() {
return {
2025-11-07 13:34:04 +08:00
docEditor: null,
onlyOfficeScriptLoaded: false,
2025-11-07 15:08:41 +08:00
editorConfig: null,
configReady: false,
loading: false,
error: null
2025-11-04 13:22:28 +08:00
};
},
2025-11-07 15:08:41 +08:00
async mounted() {
try {
this.loading = true;
this.error = null;
2025-11-10 09:46:22 +08:00
if(this.docEditor){
this.destroyEditor();
}
2025-11-07 15:08:41 +08:00
// 先加载配置
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);
}
2025-11-04 13:22:28 +08:00
},
beforeDestroy() {
2025-11-07 13:34:04 +08:00
this.destroyEditor();
2025-11-04 13:22:28 +08:00
},
methods: {
2025-11-07 13:34:04 +08:00
// 加载编辑器配置
async getConfig() {
2025-11-04 13:22:28 +08:00
try {
2025-11-07 15:08:41 +08:00
console.log('开始获取编辑器配置...', {
fileId: this.documentKey,
mode: this.mode,
type: this.type
});
2025-11-07 13:34:04 +08:00
const res = await getConfigAPI({
fileId: this.documentKey,
mode: this.mode,
type: this.type
2025-11-07 15:08:41 +08:00
});
2025-11-10 09:46:22 +08:00
2025-11-07 15:08:41 +08:00
console.log('获取配置响应:', res);
2025-11-07 13:34:04 +08:00
if (res.code !== 200) {
throw new Error(res.msg || '获取编辑器配置失败');
2025-11-05 09:47:20 +08:00
}
2025-11-07 13:34:04 +08:00
if (!res.data) {
throw new Error('配置数据为空');
2025-11-04 13:22:28 +08:00
}
2025-11-07 15:08:41 +08:00
// 验证必要的配置字段
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),
2025-11-10 09:46:22 +08:00
onAppReady: () => this.onAppReady(),
2025-11-07 15:08:41 +08:00
}
};
2025-11-10 09:46:22 +08:00
2025-11-07 15:08:41 +08:00
this.configReady = true;
console.log('编辑器配置准备完成:', this.editorConfig);
2025-11-05 09:47:20 +08:00
2025-11-07 15:08:41 +08:00
} catch (error) {
2025-11-07 13:34:04 +08:00
console.error('获取编辑器配置失败:', error);
2025-11-07 15:08:41 +08:00
this.error = error.message || '获取编辑器配置失败';
throw error;
2025-11-07 13:34:04 +08:00
}
},
2025-11-05 09:47:20 +08:00
2025-11-07 13:34:04 +08:00
/**
* 初始化 OnlyOffice
*/
async initOnlyOffice() {
try {
2025-11-07 15:08:41 +08:00
// 确保配置已准备好
if (!this.configReady || !this.editorConfig) {
throw new Error('编辑器配置未准备好,请先加载配置');
}
2025-11-07 13:34:04 +08:00
// 加载 OnlyOffice 脚本
2025-11-07 15:08:41 +08:00
console.log('开始加载 OnlyOffice 脚本...');
2025-11-07 13:34:04 +08:00
await this.loadOnlyOfficeScript();
2025-11-07 15:08:41 +08:00
console.log('OnlyOffice 脚本加载完成');
2025-11-05 09:47:20 +08:00
2025-11-07 15:08:41 +08:00
// 等待 DOM 准备好
await this.$nextTick();
2025-11-10 09:46:22 +08:00
2025-11-07 13:34:04 +08:00
// 初始化编辑器
2025-11-07 15:08:41 +08:00
await this.initDocEditor();
2025-11-04 13:22:28 +08:00
} catch (error) {
2025-11-07 13:34:04 +08:00
console.error('初始化 OnlyOffice 失败:', error);
2025-11-07 15:08:41 +08:00
this.error = error.message || '初始化 OnlyOffice 失败';
2025-11-07 13:34:04 +08:00
this.$emit('error', error);
2025-11-07 15:08:41 +08:00
throw error;
2025-11-04 13:22:28 +08:00
}
},
/**
* 加载 OnlyOffice 脚本
*/
loadOnlyOfficeScript() {
return new Promise((resolve, reject) => {
// 如果已经加载,直接返回
if (window.DocsAPI) {
2025-11-07 13:34:04 +08:00
this.onlyOfficeScriptLoaded = true;
2025-11-04 13:22:28 +08:00
resolve();
return;
}
// 检查是否正在加载
2025-11-07 13:34:04 +08:00
const existingScript = document.querySelector('script[data-onlyoffice]');
2025-11-04 13:22:28 +08:00
if (existingScript) {
2025-11-07 13:34:04 +08:00
existingScript.addEventListener('load', () => {
this.onlyOfficeScriptLoaded = true;
resolve();
2025-11-04 13:22:28 +08:00
});
2025-11-07 13:34:04 +08:00
existingScript.addEventListener('error', reject);
2025-11-04 13:22:28 +08:00
return;
}
2025-11-07 13:34:04 +08:00
// 创建新的脚本元素
2025-11-04 13:22:28 +08:00
const script = document.createElement('script');
2025-11-07 13:34:04 +08:00
// onlyOfficeUrl 是 OnlyOffice 服务地址
2025-11-07 15:08:41 +08:00
const onlyOfficeUrl = process.env.VUE_APP_ONLYOFFICE_URL || process.env.VUE_APP_BASE_API || '';
2025-11-10 09:46:22 +08:00
2025-11-07 15:08:41 +08:00
if (!onlyOfficeUrl) {
reject(new Error('OnlyOffice 服务地址未配置,请检查环境变量 VUE_APP_ONLYOFFICE_URL'));
return;
}
2025-11-10 09:46:22 +08:00
2025-11-07 15:08:41 +08:00
const scriptUrl = `${onlyOfficeUrl}/web-apps/apps/api/documents/api.js`;
console.log('加载 OnlyOffice 脚本URL:', scriptUrl);
script.src = scriptUrl;
2025-11-07 13:34:04 +08:00
script.setAttribute('data-onlyoffice', 'true');
2025-11-04 13:22:28 +08:00
script.onload = () => {
2025-11-07 13:34:04 +08:00
this.onlyOfficeScriptLoaded = true;
// 等待 API 完全初始化
setTimeout(() => {
if (window.DocsAPI) {
resolve();
} else {
reject(new Error('OnlyOffice API 未正确加载'));
}
}, 100);
2025-11-04 13:22:28 +08:00
};
2025-11-07 13:34:04 +08:00
2025-11-04 13:22:28 +08:00
script.onerror = () => {
2025-11-07 13:34:04 +08:00
reject(new Error('加载 OnlyOffice 脚本失败'));
2025-11-04 13:22:28 +08:00
};
document.head.appendChild(script);
});
},
/**
2025-11-07 13:34:04 +08:00
* 初始化文档编辑器
2025-11-04 13:22:28 +08:00
*/
2025-11-07 15:08:41 +08:00
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');
2025-11-10 09:46:22 +08:00
2025-11-07 15:08:41 +08:00
// 如果容器不存在,等待一下再试
if (!container) {
console.warn('容器元素不存在,等待 DOM 渲染...');
await new Promise(resolve => setTimeout(resolve, 100));
await this.$nextTick();
container = document.getElementById('placeholder');
}
2025-11-10 09:46:22 +08:00
2025-11-07 15:08:41 +08:00
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));
2025-11-10 09:46:22 +08:00
2025-11-07 15:08:41 +08:00
if (container.offsetWidth === 0 || container.offsetHeight === 0) {
console.warn('容器尺寸仍为0但继续尝试创建编辑器');
}
2025-11-04 13:22:28 +08:00
}
try {
2025-11-07 15:08:41 +08:00
console.log('创建 OnlyOffice 编辑器,配置:', this.editorConfig);
2025-11-10 09:46:22 +08:00
2025-11-07 15:08:41 +08:00
// 清空容器
container.innerHTML = '';
2025-11-10 09:46:22 +08:00
2025-11-07 15:08:41 +08:00
// 创建编辑器实例
this.docEditor = new window.DocsAPI.DocEditor('placeholder', this.editorConfig);
2025-11-10 09:46:22 +08:00
2025-11-07 15:08:41 +08:00
console.log('编辑器实例创建成功:', this.docEditor);
2025-11-10 09:46:22 +08:00
2025-11-07 15:08:41 +08:00
// 编辑器创建成功,隐藏加载状态
this.loading = false;
2025-11-07 13:34:04 +08:00
this.$emit('initialized', this.docEditor);
2025-11-04 13:22:28 +08:00
} catch (error) {
2025-11-07 13:34:04 +08:00
console.error('创建 OnlyOffice 编辑器失败:', error);
2025-11-07 15:08:41 +08:00
console.error('错误详情:', {
message: error.message,
stack: error.stack,
config: this.editorConfig
});
this.loading = false;
this.error = error.message || '创建编辑器失败';
2025-11-07 13:34:04 +08:00
this.$emit('error', error);
2025-11-07 15:08:41 +08:00
throw error;
2025-11-04 13:22:28 +08:00
}
},
/**
2025-11-07 13:34:04 +08:00
* 文档准备就绪回调
2025-11-04 13:22:28 +08:00
*/
2025-11-07 13:34:04 +08:00
onDocumentReady() {
console.log("文档准备好了");
this.$emit('document-ready');
2025-11-04 13:22:28 +08:00
},
/**
2025-11-07 13:34:04 +08:00
* 应用准备就绪回调
2025-11-04 13:22:28 +08:00
*/
2025-11-07 13:34:04 +08:00
onAppReady() {
console.log("OnlyOffice 应用准备就绪");
this.$emit('app-ready');
2025-11-04 13:22:28 +08:00
},
/**
2025-11-07 13:34:04 +08:00
* 编辑器错误回调
2025-11-04 13:22:28 +08:00
*/
2025-11-07 13:34:04 +08:00
onEditorError(error) {
console.error("OnlyOffice 错误:", error);
this.$emit('error', error);
2025-11-04 13:22:28 +08:00
},
/**
* 销毁编辑器
*/
destroyEditor() {
2025-11-10 09:46:22 +08:00
if (this.docEditor) {
2025-11-07 13:34:04 +08:00
try {
2025-11-10 09:46:22 +08:00
this.docEditor.destroyEditor()
2025-11-07 13:34:04 +08:00
} catch (error) {
console.warn('销毁编辑器时出错:', error);
2025-11-04 13:22:28 +08:00
}
}
2025-11-07 13:34:04 +08:00
this.docEditor = null;
2025-11-04 13:22:28 +08:00
},
/**
2025-11-07 13:34:04 +08:00
* 重新加载文档
2025-11-04 13:22:28 +08:00
*/
2025-11-07 15:08:41 +08:00
async reloadDocument(newConfig = {}) {
2025-11-07 13:34:04 +08:00
this.destroyEditor();
2025-11-07 15:08:41 +08:00
this.configReady = false;
this.error = null;
2025-11-04 13:22:28 +08:00
2025-11-07 13:34:04 +08:00
// 可以在这里更新配置
if (newConfig.documentUrl) {
this.documentUrl = newConfig.documentUrl;
2025-11-04 13:22:28 +08:00
}
2025-11-07 13:34:04 +08:00
if (newConfig.documentTitle) {
this.documentTitle = newConfig.documentTitle;
}
if (newConfig.mode) {
this.mode = newConfig.mode;
2025-11-04 13:22:28 +08:00
}
2025-11-07 15:08:41 +08:00
// 重新加载配置并初始化
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;
}
2025-11-04 13:22:28 +08:00
}
}
};
</script>
2025-11-07 13:34:04 +08:00
<style scoped>
.onlyoffice-container {
width: 100%;
height: 90vh;
2025-11-04 13:22:28 +08:00
display: flex;
flex-direction: column;
2025-11-07 15:08:41 +08:00
position: relative;
2025-11-04 13:22:28 +08:00
}
2025-11-07 13:34:04 +08:00
.editor-placeholder {
2025-11-04 13:22:28 +08:00
width: 100%;
2025-11-07 15:08:41 +08:00
height: 100%;
2025-11-07 13:34:04 +08:00
min-height: 600px;
2025-11-04 13:22:28 +08:00
}
2025-11-07 15:08:41 +08:00
.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 {
2025-11-10 09:46:22 +08:00
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
2025-11-07 15:08:41 +08:00
}
.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;
}
2025-11-04 13:22:28 +08:00
</style>