jmalcloud-view/src/components/office/OnlyOfficeEditor.vue
2024-06-20 18:21:12 +08:00

420 lines
13 KiB
Vue

<template>
<div>
<history-popover
ref="historyPopover"
style="right: 20px;top: 61px;position: absolute;"
:transparent="true"
:has-history-version.sync="hasHistoryVersion"
:history-list-popover-visible.sync="historyListPopoverVisible"
:history-operation-loading.sync="historyOperationLoading"
:saved.sync="saved"
@viewHistoryFile="viewHistoryFile"
@recoverySuccess="recoverySuccess"
>
</history-popover>
<div class="component-only-office">
<div :id="this.id" class="placeholder"></div>
</div>
</div>
</template>
<script>
import api from "@/api/file-api"
import settingApi from "@/api/setting-api"
import fileConfig from "@/utils/file-config";
import SparkMD5 from 'spark-md5'
import HistoryPopover from "@/components/HistoryPopover/index.vue";
import {mapState} from "vuex";
export default {
name: "OnlyOfficeEditor",
components: {HistoryPopover},
props: {
id: {
type: String,
default: () => {
return "office_" + Math.round(Math.random() * 10000)
}
},
fileUrl: {
type: String,
default: ''
},
code: {
type: String,
default: ''
},
shareId: {
type: String,
default: ''
},
sharer: {
type: String,
default: undefined
},
file: {
type: Object,
default: function () {
return {}
}
},
readOnly: {
type: Boolean,
default: false
},
documentKey: Function
},
data() {
return {
docEditorConfig: {},
docEditor: null,
fileKey: '',
saved: true,
hasHistoryVersion: false,
historyVersion: {metadata: {}},
titleObserver: null,
fileHistoryDateList: [],
historyListPopoverVisible: false,
historyPageIndex: 1,
historyPageSize: 50,
viewHistory: false,
historyOperationLoading: false,
officeServerConfig: {
documentServer: undefined,
callbackServer: undefined,
tokenEnabled: false
}
}
},
beforeDestroy() {
this.destroyEditor()
},
computed: {
...mapState(['message']),
fileType() {
return this.getType(this.file.suffix)
},
},
watch: {
'file.id': {
handler(id) {
if (!id) {
return
}
if (window.shareId) {
settingApi.getPublicOfficeConfig({shareId: window.shareId, shareToken: this.$store.getters.shareToken}).then(res => {
this.officeServerConfig = res.data
this.loadOfficeApi()
})
} else {
settingApi.getOfficeConfig().then(res => {
this.officeServerConfig = res.data
this.loadOfficeApi()
})
}
},
immediate: true,
},
hasHistoryVersion(value) {
if (!value) {
let parentDoc = document.querySelector('.component-only-office')
let doc = parentDoc.getElementsByTagName('iframe')[0].contentWindow.document
let versionBtn = doc.getElementById('box-doc-name-history-btn')
if (versionBtn) {
versionBtn.style.display = 'none'
}
}
},
message(msg) {
if (msg.event === 'previewSaveAndClose') {
this.requestClose()
}
}
},
destroyed() {
},
methods: {
loadOfficeApi() {
let officeApiUrl = fileConfig.officeApiUrl(this.officeServerConfig.documentServer)
$J.loadScript($J.apiUrl(officeApiUrl), (e) => {
if (e !== null) {
this.$emit('onClose')
this.$store.dispatch('updateMessage', { event: 'loadFileFailed'})
return
}
if (this.shareId) {
api.getPublicFileInfoById({fileId: this.file.id, shareId: this.shareId}).then(res => {
this.file = res.data
this.loadFile()
})
} else {
api.getFileInfoById({id: this.file.id}).then(res => {
this.file = res.data
this.loadFile()
})
}
})
},
viewHistoryFile({historyInfo}) {
const historyUrl = window.location.origin + fileConfig.previewHistoryUrl(historyInfo.id, this.$store.state.user.name, this.$store.state.user.token)
this.onRequestHistoryData(null, historyInfo, historyUrl)
this.historyListPopoverVisible = false
let parentDoc = document.querySelector('.component-only-office')
let doc = parentDoc.getElementsByTagName('iframe')[0].contentWindow.document
const toolbar = doc.getElementById('box-doc-name')
// add cancelPreview button
if (!doc.getElementById('box-doc-name-cancel-preview')) {
let newButton = document.createElement("button")
newButton.setAttribute('id', 'box-doc-name-cancel-preview')
newButton.classList.add('btn', 'btn-header')
newButton.innerHTML = '取消预览'
newButton.style.color = '#fff'
newButton.style.width = '60px'
newButton.addEventListener('click', this.cancelPreview)
toolbar.prepend(newButton)
}
// update title
this.updateTitle(doc, historyInfo)
},
updateTitle(doc, historyInfo) {
this.historyVersion = historyInfo
let docTitle = doc.getElementById('title-doc-name')
// Monitor title changes
if (this.titleObserver === null) {
let that = this
this.titleObserver = new MutationObserver(function(mutations) {
let el = mutations[mutations.length-1].target
setTimeout(() => {
el.innerHTML = `历史版本: ${that.historyVersion.metadata.time}`
}, 0)
})
let config = { childList: true, characterData: true }
this.titleObserver.observe(docTitle, config)
}
},
recoverySuccess({result}) {
this.reloadDocument(`${result.data}-${SparkMD5.hash(this.file.id)}`)
this.$message({message: '恢复成功',type: 'success'})
this.historyListPopoverVisible = false
this.historyOperationLoading = false
},
cancelPreview() {
this.reloadDocument()
},
reloadDocument(key) {
this.destroyEditor()
this.docEditorConfig.document.key = key ? key : this.fileKey
this.docEditor = new DocsAPI.DocEditor(this.id, this.docEditorConfig)
},
onRequestHistoryData(event, historyInfo, url) {
let historyConfig = {
"fileType": this.fileType,
"key": `${new Date(historyInfo.metadata.time).getTime()}-${SparkMD5.hash(this.file.id)}`,
"url": url,
"version": historyInfo.metadata.time,
}
api.getOfficeJwt(historyConfig).then(res => {
historyConfig.token = res.data
this.docEditor.setHistoryData(historyConfig)
})
},
onRequestHistory() {
this.docEditor.refreshHistory({
"currentVersion": 'currentVersion',
"history": []
})
},
getType(type) {
switch (type) {
case 'doc':
return 'docx'
case 'word':
return 'docx'
case 'excel':
return 'xlsx'
case 'xls':
return 'xlsx'
case 'ppt':
return 'pptx'
}
return type
},
async loadFile() {
this.$nextTick(()=> {
this.$refs.historyPopover.loadHistoryList(this.file.id)
})
this.destroyEditor()
const file_username = this.sharer ? this.sharer : this.$store.state.user.name
let callbackServer = this.officeServerConfig.callbackServer
if (callbackServer) {
if (callbackServer.endsWith('/')) {
// 去掉最后的/
callbackServer = callbackServer.substring(0, this.officeServerConfig.callbackServer.length - 1)
}
} else {
callbackServer = 'http://jmalcloud:8088'
}
this.fileUrl = fileConfig.previewUrl(file_username, this.file, this.$store.getters.token, undefined, callbackServer)
this.fileKey = `${new Date(this.file.updateDate).getTime()}-${SparkMD5.hash(this.file.id + callbackServer)}`
if (this.readOnly && window.shareId) {
this.fileUrl = fileConfig.publicPreviewUrl(this.file, window.shareId, this.$store.getters.shareToken, callbackServer)
this.fileKey = `${new Date(this.file.updateDate).getTime()}-${SparkMD5.hash(window.shareId + callbackServer)}`
}
let callbackUrl = fileConfig.officeCallBackUrl(this.officeServerConfig.callbackServer, this.$store.getters.token, this.$store.getters.name, this.file.id)
this.docEditorConfig = {
document: {
fileType: this.fileType,
key: this.fileKey,
title: this.file.name,
url: this.fileUrl,
},
editorConfig: {
mode: "edit",
lang: "zh",
user: {
id: this.$store.state.user.userId,
name: this.$store.state.user.name,
image: `${window.location.origin}${process.env.VUE_APP_BASE_API}/public/s/view/thumbnail?id=${this.$store.state.user.avatar}`
},
customization: {
autosave: false,
comments: true,
compactHeader: false,
compactToolbar: false,
compatibleFeatures: false,
forcesave: false,
help: false,
hideRightMenu: false,
hideRulers: false,
submitForm: false,
about: null,
feedback: false
},
callbackUrl: callbackUrl,
}
}
if (this.readOnly && window.shareId) {
const shareToken = this.$store.getters.shareToken ? this.$store.getters.shareToken : 'token'
this.docEditorConfig.editorConfig.callbackUrl = null
api.getPublicOfficeJwt({shareId: window.shareId, shareToken: shareToken}, this.docEditorConfig).then(res => {
this.loadOffice(res.data)
})
} else {
if (this.officeServerConfig.tokenEnabled) {
api.getOfficeJwt(this.docEditorConfig).then(res => {
this.loadOffice(res.data)
})
} else {
this.loadOffice()
}
}
},
loadOffice(token) {
if (token) {
this.docEditorConfig.token = token
}
if (!this.$pc) {
this.docEditorConfig.type = 'mobile'
}
if (this.readOnly) {
this.docEditorConfig.editorConfig.mode = "view"
this.docEditorConfig.editorConfig.callbackUrl = null
if (!this.docEditorConfig.editorConfig.user.id) {
let visitor = $J.getStorageInt("visitor")
if (!visitor) {
visitor = $J.randNum(1000, 99999)
$J.setStorage("viewer", visitor)
}
this.docEditorConfig.editorConfig.user.id = "visitor_" + visitor
this.docEditorConfig.editorConfig.user.name = "Visitor_" + visitor
}
}
this.docEditorConfig.events = {
"onAppReady": this.onAppReady,
"onDocumentReady": this.onDocumentReady,
"onDocumentStateChange": this.onDocumentStateChange,
"onRequestHistory": this.onRequestHistory,
"onRequestHistoryData": this.onRequestHistoryData,
}
this.$nextTick(() => {
this.titleObserver = null
this.docEditor = new DocsAPI.DocEditor(this.id, this.docEditorConfig)
})
},
onClickHistoryVersion(event) {
this.historyListPopoverVisible = !this.historyListPopoverVisible
event.stopPropagation()
},
onAppReady() {
this.$emit('onReady')
},
onDocumentReady() {
let parentDoc = document.querySelector('.component-only-office')
let doc = parentDoc.getElementsByTagName('iframe')[0].contentWindow.document
if (!this.$pc) {
parentDoc.style.top = '5rem'
doc.querySelector('.navbar.main-navbar.navbar-with-logo').style.height = '0'
}
let logo = doc.querySelector('.extra .logo')
// 隐藏logo,about
logo.style.display = 'none'
let about = doc.getElementById('left-btn-about')
about.style.display = 'none'
this.saveBtnDoc = doc.getElementById('slot-btn-dt-save').getElementsByTagName('button')[0]
doc.getElementById('id-box-doc-name').style.paddingRight = '0'
if (this.hasHistoryVersion && !doc.getElementById('box-doc-name-history-btn')) {
const toolbar = doc.getElementById('box-doc-name')
toolbar.style.justifyContent = 'right'
toolbar.style.padding = '0'
let versionButton = document.createElement("button")
versionButton.classList.add('btn', 'btn-header')
versionButton.setAttribute('id', 'box-doc-name-history-btn')
fetch(require("@/assets/img/history.svg"))
.then(response => response.text())
.then(data => {
let parser = new DOMParser()
let svgDoc = parser.parseFromString(data, "image/svg+xml")
let svgElement = svgDoc.querySelector('svg')
svgElement.setAttribute('style', 'width:16px;height:16px;')
versionButton.appendChild(svgElement)
})
versionButton.addEventListener('click', this.onClickHistoryVersion)
doc.addEventListener('click', this.$refs.historyPopover.onGlobalClick)
toolbar.appendChild(versionButton)
}
},
onDocumentStateChange() {
this.saved = this.saveBtnDoc.classList.contains('disabled')
this.$emit('onEdit', this.saved)
},
requestClose() {
this.$emit('manualSave')
},
destroyEditor() {
if (this.docEditor !== null) {
this.docEditor.destroyEditor()
this.docEditor = null
this.titleObserver = null
}
},
}
}
</script>
<style lang="scss" scoped>
</style>