<!--onlyoffice 编辑器-->
|
<template>
|
<div class="document-preview">
|
<iframe
|
v-if="isPdf"
|
:key="option.url"
|
:src="option.url"
|
class="native-pdf-preview"
|
title="PDF预览"
|
></iframe>
|
<template v-else>
|
<div :id="editorId"></div>
|
<div v-if="editorWarning" class="editor-warning">{{ editorWarning }}</div>
|
<div v-if="editorState !== 'ready'" class="editor-status">
|
<template v-if="editorState === 'failed'">
|
<p>{{ editorError }}</p>
|
<el-button type="primary" @click="setEditor(option)">重新加载</el-button>
|
</template>
|
<span v-else>正在连接文档服务并加载报告...</span>
|
</div>
|
</template>
|
</div>
|
</template>
|
|
<script>
|
// OnlyOffice 文档服务地址,按环境配置;脚本在真正需要预览时才加载
|
const OFFICE_API_BASE = import.meta.env.VITE_APP_ONLYOFFICE_API || "http://192.168.21.53:9001";
|
|
function normalizeOptions(source = {}) {
|
return {
|
url: source.url || "",
|
isEdit: source.isEdit === true || source.isEdit === "true",
|
fileType: source.fileType || "",
|
title: source.title || "",
|
lang: source.lang || "zh-CN",
|
isPrint: source.isPrint !== false,
|
user: { id: source.user_id, name: source.user_name },
|
editUrl: source.editUrl || "",
|
key: source.key || "",
|
model: source.model,
|
token: source.token
|
};
|
}
|
|
// 多个页面共用编辑器,api.js 只加载一次
|
let docsApiPromise = null;
|
function loadDocsApi() {
|
if (window.DocsAPI) {
|
return Promise.resolve(window.DocsAPI);
|
}
|
if (!docsApiPromise) {
|
docsApiPromise = new Promise((resolve, reject) => {
|
const script = document.createElement("script");
|
script.src = OFFICE_API_BASE.replace(/\/$/, "") + "/web-apps/apps/api/documents/api.js";
|
const timeout = window.setTimeout(() => {
|
script.remove();
|
reject(new Error("OnlyOffice 文档服务连接超时"));
|
}, 15000);
|
script.onload = () => {
|
window.clearTimeout(timeout);
|
if (window.DocsAPI) {
|
resolve(window.DocsAPI);
|
} else {
|
reject(new Error("api.js 加载完成但未注册 DocsAPI"));
|
}
|
};
|
script.onerror = () => {
|
window.clearTimeout(timeout);
|
script.remove();
|
reject(new Error("OnlyOffice 文档服务脚本加载失败"));
|
};
|
document.head.appendChild(script);
|
});
|
// 加载失败后允许下次重试
|
docsApiPromise.catch(() => {
|
docsApiPromise = null;
|
});
|
}
|
return docsApiPromise;
|
}
|
|
export default {
|
name: "VabOnlyOffice",
|
props: ['options'],
|
emits: ['ready', 'error'],
|
data() {
|
return {
|
editorId: `onlyoffice-${this._uid || Math.random().toString(36).slice(2)}`,
|
doctype: "",
|
docEditor: null,
|
editorGeneration: 0,
|
editorState: 'loading',
|
editorError: '',
|
editorWarning: '',
|
readyTimer: null,
|
option: normalizeOptions(this.options || this.$route.query)
|
};
|
},
|
computed: {
|
isPdf() {
|
return this.isPdfFile(this.option);
|
}
|
},
|
beforeUnmount() {
|
this.editorGeneration++;
|
window.clearTimeout(this.readyTimer);
|
this.destroyEditor();
|
},
|
watch: {
|
options: {
|
handler(newOptions) {
|
this.option = normalizeOptions(newOptions || this.$route.query);
|
this.setEditor(this.option);
|
},
|
deep: true
|
},
|
'$route.query'(query) {
|
if (!this.options) {
|
this.option = normalizeOptions(query);
|
this.setEditor(this.option);
|
}
|
}
|
},
|
mounted() {
|
if (this.option.url) {
|
this.setEditor(this.option);
|
}
|
},
|
methods: {
|
destroyEditor() {
|
if (this.docEditor !== null) {
|
try {
|
this.docEditor.destroyEditor();
|
} catch (error) {
|
console.error('销毁 OnlyOffice 编辑器失败', error);
|
}
|
this.docEditor = null;
|
}
|
},
|
async setEditor(option) {
|
const generation = ++this.editorGeneration;
|
window.clearTimeout(this.readyTimer);
|
this.destroyEditor();
|
// PDF 使用浏览器原生预览;其他文档才初始化 OnlyOffice。
|
if (!option || !option.url || this.isPdfFile(option)) {
|
return;
|
}
|
this.editorState = 'loading';
|
this.editorError = '';
|
this.editorWarning = '';
|
// 等待 v-else 中的 OnlyOffice 容器完成渲染。
|
await this.$nextTick();
|
this.doctype = this.getFileType(option.fileType);
|
if (!this.doctype) {
|
this.failEditor('报告文件格式不受支持', generation);
|
return;
|
}
|
let config = {
|
document: {
|
//后缀
|
fileType: option.fileType,
|
key: option.key || "",
|
title: option.title,
|
permissions: {
|
edit: option.isEdit, //是否可以编辑: 只能查看,传false
|
print: option.isPrint,
|
download: false
|
// "fillForms": true,//是否可以填写表格,如果将mode参数设置为edit,则填写表单仅对文档编辑器可用。 默认值与edit或review参数的值一致。
|
// "review": true //跟踪变化
|
},
|
url: option.url
|
},
|
documentType: this.doctype,
|
editorConfig: {
|
callbackUrl: option.editUrl, //"编辑word后保存时回调的地址,这个api需要自己写了,将编辑后的文件通过这个api保存到自己想要的位置
|
lang: option.lang, //语言设置
|
//定制
|
customization: {
|
autosave: true, //是否自动保存
|
forcesave: true, //点击保存时立即回调业务服务
|
chat: true,
|
comments: false,
|
help: false,
|
"hideRightMenu": false,//定义在第一次加载时是显示还是隐藏右侧菜单。 默认值为false
|
//是否显示插件
|
plugins: false
|
},
|
user: {
|
id: option.user.id,
|
name: option.user.name
|
},
|
mode: option.model ? option.model : "edit"
|
},
|
width: "100%",
|
height: "100%",
|
token: option.token || "",
|
events: {
|
onDocumentReady: () => {
|
if (generation === this.editorGeneration) {
|
window.clearTimeout(this.readyTimer);
|
this.editorState = 'ready';
|
this.$emit('ready');
|
}
|
},
|
onError: (event) => {
|
const detail = event && event.data && (event.data.errorDescription || event.data.errorCode);
|
this.failEditor(detail ? `报告加载失败:${detail}` : '报告加载失败,请检查文档服务', generation);
|
}
|
}
|
};
|
|
try {
|
await loadDocsApi();
|
} catch (error) {
|
console.error(error);
|
this.failEditor(error.message || '文档服务暂时不可用', generation);
|
return;
|
}
|
if (generation !== this.editorGeneration || !this.$el?.isConnected) {
|
return;
|
}
|
try {
|
this.readyTimer = window.setTimeout(() => {
|
this.failEditor('报告加载超时,请检查文档服务与报告地址', generation);
|
}, 30000);
|
this.docEditor = new window.DocsAPI.DocEditor(this.editorId, config);
|
} catch (error) {
|
window.clearTimeout(this.readyTimer);
|
console.error(error);
|
this.failEditor('文档编辑器初始化失败,请重试', generation);
|
}
|
},
|
failEditor(message, generation) {
|
if (generation !== this.editorGeneration) return;
|
window.clearTimeout(this.readyTimer);
|
if (this.editorState === 'ready') {
|
this.editorWarning = String(message);
|
this.$emit('error', this.editorWarning);
|
return;
|
}
|
this.editorError = String(message);
|
this.editorState = 'failed';
|
this.$emit('error', this.editorError);
|
},
|
isPdfFile(option) {
|
if (!option) {
|
return false;
|
}
|
const fileType = String(option.fileType || "").toLowerCase();
|
const url = String(option.url || "").split(/[?#]/)[0].toLowerCase();
|
return fileType === "pdf" || url.endsWith(".pdf");
|
},
|
getFileType(fileType) {
|
let docType = "";
|
let fileTypesDoc = [
|
"doc",
|
"docm",
|
"docx",
|
"dot",
|
"dotm",
|
"dotx",
|
"epub",
|
"fodt",
|
"htm",
|
"html",
|
"mht",
|
"odt",
|
"ott",
|
"pdf",
|
"rtf",
|
"txt",
|
"djvu",
|
"xps"
|
];
|
let fileTypesCsv = [
|
"csv",
|
"fods",
|
"ods",
|
"ots",
|
"xls",
|
"xlsm",
|
"xlsx",
|
"xlt",
|
"xltm",
|
"xltx"
|
];
|
let fileTypesPPt = [
|
"fodp",
|
"odp",
|
"otp",
|
"pot",
|
"potm",
|
"potx",
|
"pps",
|
"ppsm",
|
"ppsx",
|
"ppt",
|
"pptm",
|
"pptx"
|
];
|
if (fileTypesDoc.includes(fileType)) {
|
docType = "text";
|
}
|
if (fileTypesCsv.includes(fileType)) {
|
docType = "spreadsheet";
|
}
|
if (fileTypesPPt.includes(fileType)) {
|
docType = "presentation";
|
}
|
return docType;
|
}
|
}
|
};
|
</script>
|
|
<style scoped>
|
.document-preview,
|
.native-pdf-preview,
|
.document-preview > div {
|
width: 100%;
|
height: 100%;
|
}
|
.document-preview {
|
position: relative;
|
}
|
.editor-status {
|
position: absolute;
|
inset: 0;
|
z-index: 2;
|
display: flex;
|
flex-direction: column;
|
justify-content: center;
|
align-items: center;
|
gap: 12px;
|
background: #fff;
|
color: #606266;
|
}
|
.editor-warning {
|
position: absolute;
|
top: 0;
|
left: 0;
|
right: 0;
|
z-index: 2;
|
height: auto !important;
|
padding: 8px 12px;
|
background: #fff3e0;
|
color: #a35d00;
|
}
|
.native-pdf-preview {
|
display: block;
|
border: 0;
|
}
|
html,
|
body {
|
height: 100%;
|
}
|
#app {
|
font-family: Avenir, Helvetica, Arial, sans-serif;
|
-webkit-font-smoothing: antialiased;
|
-moz-osx-font-smoothing: grayscale;
|
text-align: center;
|
color: #2c3e50;
|
height: 100%;
|
}
|
.qualityManual-container {
|
padding: 0 !important;
|
height: 100%;
|
}
|
.qualityManual-container-office {
|
width: 100%;
|
height: calc(100% - 55px);
|
}
|
</style>
|