perf: 优化office组件

This commit is contained in:
jmal 2022-08-14 00:04:26 +08:00
parent 3ccec0d7bd
commit 0276709027
8 changed files with 252 additions and 99 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "jmal-cloud-view", "name": "jmal-cloud-view",
"version": "2.2.36", "version": "2.2.40",
"description": "jmalcloud", "description": "jmalcloud",
"author": "Jmal <zhushilun084@163.com>", "author": "Jmal <zhushilun084@163.com>",
"license": "MIT", "license": "MIT",

View File

@ -233,7 +233,7 @@
<template slot-scope="scope"> <template slot-scope="scope">
<el-input v-if="scope.row.index === editingIndex" :span="10" <el-input v-if="scope.row.index === editingIndex" :span="10"
v-focus v-model="renameFileName" placeholder="" size="small" v-focus v-model="renameFileName" placeholder="" size="small"
@focus="renameInputFocus($event,scope.row.suffix)" @focus="renameInputFocus($event.currentTarget,scope.row.suffix)"
@blur="setInputBlur()" @blur="setInputBlur()"
@keyup.enter.native="rowRename(renameFileName, scope.row)"> @keyup.enter.native="rowRename(renameFileName, scope.row)">
</el-input> </el-input>
@ -343,7 +343,7 @@
type="textarea" type="textarea"
autosize autosize
size="small" size="small"
@focus="renameInputFocus($event, item.suffix)" @focus="renameInputFocus($event.currentTarget, item.suffix)"
@blur="setInputBlur()" @blur="setInputBlur()"
@keyup.enter.native="rowRename(renameFileName, item)"> @keyup.enter.native="rowRename(renameFileName, item)">
</el-input> </el-input>
@ -499,21 +499,21 @@
<el-dialog <el-dialog
class="new-text-file-dialog" class="new-text-file-dialog"
title="新建文本文件" :title="newCreateFileDialogTitle"
:close-on-click-modal="false" :close-on-click-modal="false"
:visible.sync="newTextFileDialog"> :visible.sync="newCreateFileDialog">
<el-input <el-input
ref="newTextFileName" size="small" ref="newCreateFileName" size="small"
v-model="newTextFileName" v-model="newCreateFileName"
class="dialog-msg" class="dialog-msg"
@focus="renameInputFocus($event,'')" :clearable="true" @focus="renameInputFocus($event.currentTarget,'')" :clearable="true"
@blur="setInputBlur()" @blur="setInputBlur()"
@keyup.enter.native="createTextFile(newTextFileName)" @keyup.enter.native="createFile(newCreateFileName)"
></el-input> ></el-input>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button size="small" @click="newTextFileDialog=false">取消</el-button> <el-button size="small" @click="newCreateFileDialog=false">取消</el-button>
<el-button size="small" type="primary" @click="createTextFile(newTextFileName)" <el-button size="small" type="primary" @click="createFile(newCreateFileName)"
v-loading="createTextFileLoading">确定</el-button> v-loading="createFileLoading">确定</el-button>
</span> </span>
</el-dialog> </el-dialog>
@ -764,9 +764,10 @@ export default {
allChecked: false, allChecked: false,
summaries: '', summaries: '',
shareDialog: false, shareDialog: false,
newTextFileDialog: false, newCreateFileDialog: false,
newTextFileName: '', newCreateFileName: '',
createTextFileLoading: false, newCreateFileDialogTitle: '',
createFileLoading: false,
shareLink: '', shareLink: '',
shareFileName: '', shareFileName: '',
generateShareLinkLoading: true, generateShareLinkLoading: true,
@ -1717,18 +1718,29 @@ export default {
this.$refs.newFolderName.select() this.$refs.newFolderName.select()
}) })
}, },
getNewFileName(fileList, newFolderName) { getNewFileName(fileList, newFileName) {
let append = 0 let append = 0
let filenameList = [] let filenameList = []
fileList.forEach(file => { fileList.forEach(file => {
filenameList.push(file.name || file.label) let fileName = file.name || file.label
// if (file.suffix) {
// fileName = fileName.substring(0, fileName.lastIndexOf(`.${file.suffix}`))
// }
filenameList.push(fileName)
}) })
const newName = newFolderName const newName = newFileName
while (filenameList.includes(newFolderName)) { while (filenameList.includes(newFileName)) {
append += 1 append += 1
newFolderName = newName + append if (newName.indexOf('.') > 0) {
const name = newName.substring(0, newName.lastIndexOf('.'))
const suffix = newName.substring(newName.lastIndexOf('.'))
console.log('name', name, 'suffix', suffix)
newFileName = `${name}${append}${suffix}`
} else {
newFileName = `${newName}${append}`
}
} }
return newFolderName return newFileName
}, },
hideNewFolderName() { hideNewFolderName() {
this.showNewFolder = false this.showNewFolder = false
@ -1796,8 +1808,8 @@ export default {
}); });
} }
}, },
// 新建文本文件 // 新建文件
createTextFile(newFileName) { createFile(newFileName) {
if (newFileName) { if (newFileName) {
if (/[\[\]\/\\"<>\?\*]/gi.test(newFileName)) { if (/[\[\]\/\\"<>\?\*]/gi.test(newFileName)) {
this.$message({ this.$message({
@ -1806,34 +1818,41 @@ export default {
}) })
return return
} }
this.createTextFileLoading = true this.createFileLoading = true
let parentPath = "/" let parentPath = "/"
if (this.path) { if (this.path) {
if (this.path.length > 0) { if (this.path.length > 0) {
parentPath = this.path parentPath = this.path
} }
} }
let suffix = newFileName.substring(newFileName.lastIndexOf('.') + 1);
api.addFile({ api.addFile({
fileName: encodeURI(newFileName), fileName: encodeURI(newFileName),
isFolder: false, isFolder: false,
username: this.$store.state.user.name, username: this.$store.state.user.name,
parentPath: encodeURI(parentPath) parentPath: encodeURI(parentPath)
}).then((res) => { }).then((res) => {
this.createTextFileLoading = false this.createFileLoading = false
switch (suffix) {
// 打开编辑器 case 'txt':
// let path = res.data.path // 打开编辑器
// path = '/' + path.substring(0,path.length - res.data.name.length) this.textPreviewRow = res.data
this.textPreviewRow = res.data this.textPreviewVisible = true
this.textPreviewVisible = true break
case 'docx':
case 'xlsx':
case 'pptx':
this.officePreviewRow = res.data
this.officePreviewVisible = true
break
}
this.$message.success(`新建文件成功`) this.$message.success(`新建文件成功`)
const that = this const that = this
setTimeout(function () { setTimeout(function () {
that.newTextFileDialog = false that.newCreateFileDialog = false
}, 200) }, 200)
}).catch(() => { }).catch(() => {
this.createTextFileLoading = false this.createFileLoading = false
}) })
} }
@ -2313,12 +2332,12 @@ export default {
} }
}, },
// 选取输入框部分内容 // 选取输入框部分内容
renameInputFocus(event, suffix) { renameInputFocus(doc, suffix) {
this.setInputFocus() doc.focus()
event.currentTarget.selectionStart = 0 doc.selectionStart = 0
event.currentTarget.selectionEnd = event.currentTarget.value.length doc.selectionEnd = doc.value.length
if (suffix) { if (suffix) {
event.currentTarget.selectionEnd -= suffix.length + 1 doc.selectionEnd -= suffix.length + 1
} }
}, },
// 重命名 // 重命名
@ -2539,18 +2558,36 @@ export default {
this.getFileList() this.getFileList()
break break
case 'createTextFile': case 'createTextFile':
this.newTextFileName = this.getNewFileName(this.fileList, '未命名文件') this.newCreateFileDialogTitle = "新建文本文件"
this.newTextFileDialog = true this.createNewFile('txt')
this.$nextTick(() => { break
this.$refs.newTextFileName.focus() case 'createWordFile':
this.$refs.newTextFileName.select() this.newCreateFileDialogTitle = "新建Word文档"
}) this.createNewFile('docx')
break
case 'createExcelFile':
this.newCreateFileDialogTitle = "新建Excel工作表"
this.createNewFile('xlsx')
break
case 'createPPTFile':
this.newCreateFileDialogTitle = "新建PPT演示文档"
this.createNewFile('pptx')
break break
case 'createMarkdownFile': case 'createMarkdownFile':
this.newDocument() this.newDocument()
break break
} }
}, },
// 新建文件
createNewFile(suffix) {
this.newCreateFileName = `未命名文件.${suffix}`
this.newCreateFileName = this.getNewFileName(this.fileList, this.newCreateFileName)
this.newCreateFileDialog = true
this.$nextTick(() => {
let newFileNameInput = this.$refs.newCreateFileName.$el.querySelector('.el-input__inner')
this.renameInputFocus(newFileNameInput, suffix)
})
},
// 列表右键菜单操作 // 列表右键菜单操作
menusOperations(operation) { menusOperations(operation) {
switch (operation) { switch (operation) {

View File

@ -10,7 +10,7 @@
<svg-icon icon-class="warring"></svg-icon> <span class="dialog-msg">{{content}}</span> <svg-icon icon-class="warring"></svg-icon> <span class="dialog-msg">{{content}}</span>
<span slot="footer" class="dialog-footer"> <span slot="footer" class="dialog-footer">
<el-button :size="buttonSize" @click="operating">{{operatButtonText}}</el-button> <el-button :size="buttonSize" @click="operating">{{operatButtonText}}</el-button>
<el-button :size="buttonSize" @click="cancel">{{cancelButtonText}}</el-button> <!-- <el-button :size="buttonSize" @click="cancel">{{cancelButtonText}}</el-button>-->
<el-button :size="buttonSize" type="primary" @click=confirm>{{confirmButtonText}}</el-button> <el-button :size="buttonSize" type="primary" @click=confirm>{{confirmButtonText}}</el-button>
</span> </span>
</el-dialog> </el-dialog>

View File

@ -1,13 +1,16 @@
<template> <template>
<div class="component-only-office"> <di>
<div :id="this.id" class="placeholder"></div> <div class="component-only-office">
</div> <div :id="this.id" class="placeholder"></div>
</div>
</di>
</template> </template>
<script> <script>
import api from "@/api/file-api" import api from "@/api/file-api"
import fileConfig from "@/utils/file-config"; import fileConfig from "@/utils/file-config";
import Bus from "@/assets/js/bus";
export default { export default {
name: "OnlyOfficeEditor", name: "OnlyOfficeEditor",
@ -22,7 +25,7 @@ export default {
type: String, type: String,
default: '' default: ''
}, },
value: { file: {
type: Object, type: Object,
default: function () { default: function () {
return {} return {}
@ -34,10 +37,10 @@ export default {
}, },
documentKey: Function documentKey: Function
}, },
data() { data() {
return { return {
docEditor: null, docEditor: null,
saved: true,
} }
}, },
beforeDestroy() { beforeDestroy() {
@ -48,31 +51,28 @@ export default {
}, },
computed: { computed: {
fileType() { fileType() {
return this.getType(this.value.suffix) return this.getType(this.file.suffix)
},
fileName() {
return this.value.name
}, },
}, },
watch: { watch: {
'value.id': { 'file.id': {
handler(id) { handler(id) {
if (!id) { if (!id) {
return return
} }
$J.loadScript($J.apiUrl("http://192.168.0.188:3454/web-apps/apps/api/documents/api.js"), (e) => { $J.loadScript($J.apiUrl("http://imac/office/web-apps/apps/api/documents/api.js"), (e) => {
if (e !== null) { if (e !== null) {
$J.modalAlert("组件加载失败!") $J.modalAlert("组件加载失败!")
return return
} }
if(this.$store.state.user.token && this.$store.state.user.userId === this.value.userId){ if(this.$store.state.user.token && this.$store.state.user.userId === this.file.userId){
api.getFileInfoById(this.value.id).then(res => { api.getFileInfoById(this.file.id).then(res => {
this.value = res.data this.file = res.data
this.loadFile() this.loadFile()
}) })
} else { } else {
api.getPublicFileInfoById(this.value.id).then(res => { api.getPublicFileInfoById(this.file.id).then(res => {
this.value = res.data this.file = res.data
this.loadFile() this.loadFile()
}) })
} }
@ -81,6 +81,11 @@ export default {
immediate: true, immediate: true,
} }
}, },
mounted() {
Bus.$on('previewSaveAndClose', () => {
this.requestClose()
})
},
methods: { methods: {
getType(type) { getType(type) {
switch (type) { switch (type) {
@ -97,25 +102,23 @@ export default {
} }
return type return type
}, },
loadFile() { loadFile() {
if (this.docEditor !== null) { if (this.docEditor !== null) {
this.docEditor.destroyEditor() this.docEditor.destroyEditor()
this.docEditor = null this.docEditor = null
} }
let fileKey = `${new Date(this.value.updateDate).getTime()}-${this.value.id}` let fileKey = `${new Date(this.file.updateDate).getTime()}-${this.file.id}`
let fileName = $J.strExists(this.fileName, '.') ? this.fileName : (this.fileName + '.' + this.fileType)
let url = window.location.origin + fileConfig.previewUrl(this.$store.state.user.name, this.value, this.$store.getters.token) let url = window.location.origin + fileConfig.previewUrl(this.$store.state.user.name, this.file, this.$store.getters.token)
if(this.readOnly){ if(this.readOnly){
url = window.location.origin + fileConfig.publicPreviewUrl(this.value.id, window.shareId); url = window.location.origin + fileConfig.publicPreviewUrl(this.file.id, window.shareId);
} }
let callbackUrl = fileConfig.officeCallBackUrl(this.$store.getters.token, this.file.id)
const config = { const config = {
"document": { "document": {
"fileType": this.fileType, "fileType": this.fileType,
"key": fileKey, "key": fileKey,
"title": fileName, "title": this.file.name,
"url": url, "url": url,
}, },
"editorConfig": { "editorConfig": {
@ -126,12 +129,8 @@ export default {
"name": this.$store.state.user.name "name": this.$store.state.user.name
}, },
"customization": { "customization": {
"logo": { "logo": null,
"image": "http://localhost:9528/favicon.ico", "autosave": false,
"imageEmbedded": "",
"url": window.location.origin
},
"autosave": true,
"comments": true, "comments": true,
"compactHeader": false, "compactHeader": false,
"compactToolbar": false, "compactToolbar": false,
@ -141,10 +140,10 @@ export default {
"hideRightMenu": false, "hideRightMenu": false,
"hideRulers": false, "hideRulers": false,
"submitForm": false, "submitForm": false,
"about": false, "about": null,
"feedback": false "feedback": false
}, },
"callbackUrl": `http://192.168.0.66:8088/office/track?jmal-token=${this.$store.getters.token}&fileId=${this.value.id}`, "callbackUrl": callbackUrl,
} }
} }
if (!this.$pc) { if (!this.$pc) {
@ -154,18 +153,40 @@ export default {
config.editorConfig.mode = "view" config.editorConfig.mode = "view"
config.editorConfig.callbackUrl = null config.editorConfig.callbackUrl = null
if (!config.editorConfig.user.id) { if (!config.editorConfig.user.id) {
let viewer = $J.getStorageInt("viewer") let visitor = $J.getStorageInt("visitor")
if (!viewer) { if (!visitor) {
viewer = $J.randNum(1000, 99999) visitor = $J.randNum(1000, 99999)
$J.setStorage("viewer", viewer) $J.setStorage("viewer", visitor)
} }
config.editorConfig.user.id = "viewer_" + viewer config.editorConfig.user.id = "visitor_" + visitor
config.editorConfig.user.name = "Viewer_" + viewer config.editorConfig.user.name = "Visitor_" + visitor
} }
} }
config.events = {
"onDocumentReady": this.onDocumentReady,
"onDocumentStateChange": this.onDocumentStateChange,
}
this.$nextTick(() => { this.$nextTick(() => {
this.docEditor = new DocsAPI.DocEditor(this.id, config) this.docEditor = new DocsAPI.DocEditor(this.id, config)
}) })
},
onDocumentReady() {
console.log('onDocumentReady')
let parentDoc = document.querySelector('.component-only-office')
let doc = parentDoc.getElementsByTagName('iframe')[0].contentWindow.document
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]
},
onDocumentStateChange() {
this.saved = this.saveBtnDoc.classList.contains('disabled')
this.$emit('onEdit', this.saved)
},
requestClose() {
this.$emit('manualSave')
} }
} }
} }

View File

@ -1,27 +1,46 @@
<template> <template>
<div class="office-preview" v-if="show"> <div>
<van-overlay :show="show"> <message-dialog
<div class="block"> title="确认信息"
<div class="close-bar" @click="close"> content="是否在关闭前保存修改?"
<svg-icon class="preview-close" icon-class="close"/> :show.sync="isSaveDialogVisible"
operatButtonText="放弃修改"
confirmButtonText="保存"
@operating="closeDialog"
@confirm="saveAndClose"
>
</message-dialog>
<div class="office-preview" v-if="show">
<van-overlay :show="show">
<div class="block">
<div class="close-bar" @click="beforeClose">
<svg-icon class="preview-close" icon-class="close"/>
</div>
<div class="wrapper">
<only-office-editor ref="officeEditor" :file="file" :read-only="readOnly" @onEdit="onEdit" @manualSave="manualSave"></only-office-editor>
</div>
</div> </div>
<div class="wrapper"> </van-overlay>
<only-office-editor v-model="file" :read-only="readOnly"></only-office-editor> </div>
</div>
</div>
</van-overlay>
</div> </div>
</template> </template>
<script> <script>
import OnlyOfficeEditor from "@/components/office/OnlyOfficeEditor"; import OnlyOfficeEditor from "@/components/office/OnlyOfficeEditor";
import MessageDialog from "@/components/message/MessageDialog";
import Bus from "@/assets/js/bus";
export default { export default {
name: 'OfficePreview', name: 'OfficePreview',
components: { components: {MessageDialog, OnlyOfficeEditor},
OnlyOfficeEditor,
},
props: { props: {
id: {
type: String,
default: () => {
return "office_" + Math.round(Math.random() * 10000)
}
},
file: { file: {
type: Object, type: Object,
default: function () { default: function () {
@ -37,13 +56,13 @@ export default {
default: false default: false
} }
}, },
computed: {
},
data() { data() {
return { return {
pc: this.$pc, pc: this.$pc,
show: this.status, show: this.status,
readOnly: true, readOnly: true,
isSaveDialogVisible: false,
saved: true,
} }
}, },
watch: { watch: {
@ -55,10 +74,64 @@ export default {
} }
}, },
methods: { methods: {
/**
* 点击关闭按钮
*/
beforeClose() {
if (!this.saved) {
this.isSaveDialogVisible = true
} else {
this.close()
}
},
/**
* 执行关闭预览窗口
*/
close() { close() {
this.show = false this.show = false
this.$emit('update:status', false) this.$emit('update:status', false)
}, },
/**
* 编辑回调
* @param saved 是否已经保存
*/
onEdit(saved) {
console.log('onEdit', saved)
this.saved = saved
if(saved){
window.onbeforeunload = function(e){
e.returnValue=("文件未保存, 确定退出吗?");
}
} else {
window.onbeforeunload = null
}
},
/**
* 放弃修改
*/
closeDialog() {
this.isSaveDialogVisible = false
this.close()
},
/**
* 保存并退出
*/
saveAndClose() {
// 通知插件保存
console.log('saveAndClose')
Bus.$emit('previewSaveAndClose')
this.isSaveDialogVisible = false
},
/**
* 手动保存提醒,有些组件没有自动保存
*/
manualSave() {
this.$message.info('请手动保存')
},
/**
* 检查要预览调文件是否为只读
* @param fileUserId userId
*/
checkReadOnly(fileUserId){ checkReadOnly(fileUserId){
if(this.$store.state.user.token && this.$store.state.user.userId === fileUserId){ if(this.$store.state.user.token && this.$store.state.user.userId === fileUserId){
this.readOnly = false this.readOnly = false

View File

@ -7,6 +7,10 @@ export default {
webDAVUrl: function(username) { webDAVUrl: function(username) {
return window.location.origin + `${this.baseUrl}/webDAV/${username}` return window.location.origin + `${this.baseUrl}/webDAV/${username}`
}, },
// webDAV的url
officeCallBackUrl: function(token, fileId) {
return `${window.location.origin}${this.baseUrl}/office/track?jmal-token=${token}&fileId=${fileId}`
},
// 预览文件的url // 预览文件的url
previewUrl: function(username, file, token) { previewUrl: function(username, file, token) {
let path = "/" let path = "/"

View File

@ -33,8 +33,15 @@ export default {
}, },
{ label: '刷新', operation: 'refresh'}, { label: '刷新', operation: 'refresh'},
{ divider: true, operation: 'divider' }, { divider: true, operation: 'divider' },
{ label: '新建', operation: 'create' ,child: [
{ label: '文本', operation: 'createTextFile', iconClass: 'file-txt'},
{ label: 'Word', operation: 'createWordFile', iconClass: 'file-word'},
{ label: 'Excel', operation: 'createExcelFile', iconClass: 'file-excel'},
{ label: 'PPT', operation: 'createPPTFile', iconClass: 'file-ppt'},
]
},
{ divider: true, operation: 'divider' },
{ label: '写文章', operation: 'createMarkdownFile'}, { label: '写文章', operation: 'createMarkdownFile'},
{ label: '新建文件', operation: 'createTextFile'},
] ]
} }
}, },

View File

@ -268,6 +268,7 @@
<sim-text-preview :file.sync="textPreviewRow" :status.sync="textPreviewVisible"></sim-text-preview> <sim-text-preview :file.sync="textPreviewRow" :status.sync="textPreviewVisible"></sim-text-preview>
<image-viewer :fileList="fileList" :file="imagePreviewRow" :status.sync="imagePreviewVisible"></image-viewer> <image-viewer :fileList="fileList" :file="imagePreviewRow" :status.sync="imagePreviewVisible"></image-viewer>
<video-preview :file="videoPreviewRow" :status.sync="videoPreviewVisible"></video-preview> <video-preview :file="videoPreviewRow" :status.sync="videoPreviewVisible"></video-preview>
<office-preview :file="officePreviewRow" :status.sync="officePreviewVisible"></office-preview>
</div> </div>
</template> </template>
<script> <script>
@ -283,11 +284,13 @@ import ImageViewer from "@/components/preview/ImageViewer";
import VideoPreview from "@/components/preview/VideoPreview"; import VideoPreview from "@/components/preview/VideoPreview";
import SimTextPreview from "@/components/preview/SimTextPreview"; import SimTextPreview from "@/components/preview/SimTextPreview";
import {suffix} from "@/utils/file-type"; import {suffix} from "@/utils/file-type";
import OfficePreview from "@/components/preview/OfficePreview";
let pinyin = require("pinyin"); let pinyin = require("pinyin");
export default { export default {
components: { components: {
OfficePreview,
SimTextPreview, SimTextPreview,
VideoPreview, VideoPreview,
ImageViewer, IconFile, ImageViewer, IconFile,
@ -375,6 +378,8 @@ export default {
imagePreviewVisible: false, imagePreviewVisible: false,
videoPreviewRow: {}, videoPreviewRow: {},
videoPreviewVisible: false, videoPreviewVisible: false,
officePreviewRow: {},
officePreviewVisible: false,
textPreviewVisible: false, textPreviewVisible: false,
textPreviewRow: {}, textPreviewRow: {},
}; };
@ -1090,6 +1095,12 @@ export default {
Bus.$emit('onAddAudio', row, this.audioCoverUrl) Bus.$emit('onAddAudio', row, this.audioCoverUrl)
return return
} }
if (row.contentType.indexOf('office') > -1 || row.suffix === 'csv') {
// office文件
this.officePreviewVisible = true
this.officePreviewRow = row
return
}
if (row.suffix === 'pdf') { if (row.suffix === 'pdf') {
// pdf文件 // pdf文件
fileConfig.preview(this.$store.state.user.name, row, this.$store.getters.token) fileConfig.preview(this.$store.state.user.name, row, this.$store.getters.token)