vue基于onlyoffice实现DOC、DOCX、XLSX、PPT、PDF文档编辑预览
vue基于onlyoffice实现DOC、DOCX、XLSX、PPT、PDF文档编辑预览
背景
大佬:帮我看看这个复杂exel有什么好的插件预览不?现在用的太丑了。类似这种扔来个陈年老博客... 我:好吧,我看看,于是开启了...我的文档躺坑之路
方案一
太老太丑了直接放弃,还是后端部署的
方案二
demo运行一放exel,看起来效果不错,多个sheet、图片、等都支持样式还可以。注意:其实还是有些错乱的,不过可接受范围内。然后屁颠屁颠汇报去了,过几天业务回复:我想在线编辑后给我保存...我瞬间炸裂,于是基于原demo又研究了一下导出exel(想着只要导出个
blob
传给后端覆盖原文件就可以了嘛,那么简单),不过我错了,原来导出依赖exceljs
的具体看demo中的export文件,生成的样式和原来样式压根不一样还要自己处理如图片公式等,导出来的excel和原来的基本不可看,最终这个方案直接放弃了。
方案三
微软在线文档
https://view.officeapps.live.com/op/view.aspx?src=链接
缺点
- 文件一定是对外公开的(这个直接就pass了)
- 文件有暴露风险
- 只能预览
最终方案
- docker部署简单
- 支持DOC、DOCX、ODT、RTF、TXT、PDF、HTML、EPUB、XPS、DjVu、XLS、XLSX、ODS、CSV、PPT
- 文件放本地,编辑后回调文件url(需要开发)
- 可以协同编辑
准备工作
- 安装docker
# 关闭防火墙
systemctl stop firewalld
systemctl disable firewalld
# 关闭selinux
sed -i 's/enforcing/disabled/' /etc/selinux/config # 永久
setenforce 0 # 临时
# 关闭swap
swapoff -a # 临时
sed -ri 's/.*swap.*/#&/' /etc/fstab # 永久
wget https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo -O /etc/yum.repos.d/docker-ce.repo
yum -y install docker-ce
cat > /etc/docker/daemon.json << EOF
{
"registry-mirrors": ["https://b9pmyelo.mirror.aliyuncs.com"]
}
EOF
systemctl enable docker && systemctl start docker
- 安装onlyoffice 安装参考
docker run -i -t -d -p 8089:80 --restart=always onlyoffice/documentserver
注意默认现在我使用7.2版本默认是开启jwt 的,所以打开回提示
打开显示文档安全令牌未正确形成
,就需要关闭jwt校验,如果不想麻烦后端开发先暂时去掉,后面决定用以后再加上,这个是需要开发的。signature参考,下面是关闭的方法:
## 更改/etc/onlyoffice/documentserver/local.json
docker exec -it [容器id] bin/bash
cd /etc/onlyoffice/documentserver
sed -i 's/true/false/g' local.json
supervisorctl restart all
使用方法
拓展vue2 使用方式
- loadScript
const loadScript = async (url, id) => new Promise((resolve, reject) => {
try {
if (document.getElementById(id)) {
if (window.DocsAPI) return resolve(null);
const intervalHandler = setInterval(() => {
if (!window.DocsAPI) return;
clearInterval(intervalHandler);
return resolve(null);
}, 500);
} else {
const script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('id', id);
script.onload = resolve;
script.onerror = reject;
script.src = url;
script.async = true;
document.body.appendChild(script);
}
} catch (e) {
console.error(e);
}
});
export default loadScript;
- 组件
<template>
<div :class="s.view">
<div :id="id"></div>
</div>
</template>
<script>
import loadScript from './loadScript.js';
export default {
name: 'DocumentEditor',
props: {
id: {
type: String,
default: '',
},
documentServerUrl: {
type: String,
default: '',
},
config: {
type: Object,
default: () => {},
},
document_fileType: {
type: String,
default: '',
},
document_title: {
type: String,
default: '',
},
documentType: {
type: String,
default: '',
},
editorConfig_lang: {
type: String,
default: '',
},
height: {
type: String,
default: '',
},
type: {
type: String,
default: '',
},
width: {
type: String,
default: '',
},
events_onAppReady: {
type: Function,
default: () => {},
},
events_onDocumentStateChange: {
type: Function,
default: () => {},
},
events_onMetaChange: {
type: Function,
default: () => {},
},
events_onDocumentReady: {
type: Function,
default: () => {},
},
events_onInfo: {
type: Function,
default: () => {},
},
events_onWarning: {
type: Function,
default: () => {},
},
events_onError: {
type: Function,
default: () => {},
},
events_onRequestSharingSettings: {
type: Function,
default: () => {},
},
events_onRequestRename: {
type: Function,
default: () => {},
},
events_onMakeActionLink: {
type: Function,
default: () => {},
},
events_onRequestInsertImage: {
type: Function,
default: () => {},
},
events_onRequestSaveAs: {
type: Function,
default: () => {},
},
events_onRequestMailMergeRecipients: {
type: Function,
default: () => {},
},
events_onRequestCompareFile: {
type: Function,
default: () => {},
},
events_onRequestEditRights: {
type: Function,
default: () => {},
},
events_onRequestHistory: {
type: Function,
default: () => {},
},
events_onRequestHistoryClose: {
type: Function,
default: () => {},
},
events_onRequestHistoryData: {
type: Function,
default: () => {},
},
events_onRequestRestore: {
type: Function,
default: () => {},
},
},
data() {
return {};
},
watch: {
config: {
handler() {
this.onChangeProps();
},
deep: true,
},
document_fileType() {
this.onChangeProps();
},
document_title() {
this.onChangeProps();
},
documentType() {
this.onChangeProps();
},
editorConfig_lang() {
this.onChangeProps();
},
height() {
this.onChangeProps();
},
type() {
this.onChangeProps();
},
width() {
this.onChangeProps();
},
},
mounted() {
let url = this.documentServerUrl;
if (!url.endsWith('/')) {
url += '/';
}
const docApiUrl = `${url}web-apps/apps/api/documents/api.js`;
loadScript(docApiUrl, 'onlyoffice-api-script')
.then(() => this.onLoad())
.catch((err) => console.error(err));
},
beforeDestroy() {
const id = this.id || '';
if (window?.DocEditor?.instances[id]) {
window.DocEditor.instances[id].destroyEditor();
window.DocEditor.instances[id] = undefined;
}
},
methods: {
onLoad() {
try {
const id = this.id || '';
if (!window.DocsAPI) throw new Error('DocsAPI is not defined');
if (window?.DocEditor?.instances[id]) {
console.log('Skip loading. Instance already exists', id);
return;
}
if (!window?.DocEditor?.instances) {
window.DocEditor = { instances: {} };
}
const initConfig = {
document: {
fileType: this.document_fileType,
title: this.document_title,
},
documentType: this.documentType,
editorConfig: {
lang: this.editorConfig_lang,
},
events: {
onAppReady: this.onAppReady,
onDocumentStateChange: this.events_onDocumentStateChange,
onMetaChange: this.events_onMetaChange,
onDocumentReady: this.events_onDocumentReady,
onInfo: this.events_onInfo,
onWarning: this.events_onWarning,
onError: this.events_onError,
onRequestSharingSettings: this.events_onRequestSharingSettings,
onRequestRename: this.events_onRequestRename,
onMakeActionLink: this.events_onMakeActionLink,
onRequestInsertImage: this.events_onRequestInsertImage,
onRequestSaveAs: this.events_onRequestSaveAs,
onRequestMailMergeRecipients: this.events_onRequestMailMergeRecipients,
onRequestCompareFile: this.events_onRequestCompareFile,
onRequestEditRights: this.events_onRequestEditRights,
onRequestHistory: this.events_onRequestHistory,
onRequestHistoryClose: this.events_onRequestHistoryClose,
onRequestHistoryData: this.events_onRequestHistoryData,
onRequestRestore: this.events_onRequestRestore,
},
height: this.height,
type: this.type,
width: this.width,
...(this.config || {}),
};
const editor = window.DocsAPI.DocEditor(id, initConfig);
window.DocEditor.instances[id] = editor;
} catch (err) {
console.error(err);
this.events_onError(err);
}
},
onAppReady() {
const id = this.id || '';
this.events_onAppReady(window.DocEditor.instances[id]);
},
onChangeProps() {
const id = this.id || '';
if (window?.DocEditor?.instances[id]) {
window.DocEditor.instances[id].destroyEditor();
window.DocEditor.instances[id] = undefined;
console.log('Important props have been changed. Load new Editor.');
this.onLoad();
}
},
},
};
</script>
<style lang="scss" module="s">
.view {
width: 100%;
height: 100%;
iframe {
width: 100%;
height: 100%;
}
}
</style>
配置 总结
config.editorConfig.callbackUrl
callbackUrl
后端回调地址 具体开发参考 返回{\"error\":0}
很关键- 后端回调地址,如果填回调地址后端没有实现会就会提示:
这份文件无法保存。请检查连接设置或联系你的管理员。
- java 代码实现参考
public class WebEditor : IHttpHandler
{
public void ProcessRequest(HttpContext context)
{
string body;
using (var reader = new StreamReader(context.Request.InputStream))
body = reader.ReadToEnd();
var fileData = new JavaScriptSerializer().Deserialize<Dictionary<string, object>>(body);
if ((int) fileData["status"] == 2)
{
var req = WebRequest.Create((string) fileData["url"]);
using (var stream = req.GetResponse().GetResponseStream())
using (var fs = File.Open(PATH_FOR_SAVE, FileMode.Create))
{
var buffer = new byte[4096];
int readed;
while ((readed = stream.Read(buffer, 0, 4096)) != 0)
fs.Write(buffer, 0, readed);
}
}
context.Response.Write("{\"error\":0}");
}
}
config.editorConfig.autosave
编辑后是否自动保存config.editorConfig.forcesave
回影响回调,true 一保存就会回调document.permissions.edit
false预览文件,true可编辑 拓展:如果是浏览器传过来的参数可以用这个方式转化false: JSON.parse('false')
总结
最后分享的躺坑之路就到这里啦!!!具体实现参考onlyoffice官网很详细的
转载自:https://juejin.cn/post/7166197009291378702