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",
"version": "2.2.36",
"version": "2.2.40",
"description": "jmalcloud",
"author": "Jmal <zhushilun084@163.com>",
"license": "MIT",

View File

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

View File

@ -10,7 +10,7 @@
<svg-icon icon-class="warring"></svg-icon> <span class="dialog-msg">{{content}}</span>
<span slot="footer" class="dialog-footer">
<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>
</span>
</el-dialog>

View File

@ -1,13 +1,16 @@
<template>
<div class="component-only-office">
<div :id="this.id" class="placeholder"></div>
</div>
<di>
<div class="component-only-office">
<div :id="this.id" class="placeholder"></div>
</div>
</di>
</template>
<script>
import api from "@/api/file-api"
import fileConfig from "@/utils/file-config";
import Bus from "@/assets/js/bus";
export default {
name: "OnlyOfficeEditor",
@ -22,7 +25,7 @@ export default {
type: String,
default: ''
},
value: {
file: {
type: Object,
default: function () {
return {}
@ -34,10 +37,10 @@ export default {
},
documentKey: Function
},
data() {
return {
docEditor: null,
saved: true,
}
},
beforeDestroy() {
@ -48,31 +51,28 @@ export default {
},
computed: {
fileType() {
return this.getType(this.value.suffix)
},
fileName() {
return this.value.name
return this.getType(this.file.suffix)
},
},
watch: {
'value.id': {
'file.id': {
handler(id) {
if (!id) {
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) {
$J.modalAlert("组件加载失败!")
return
}
if(this.$store.state.user.token && this.$store.state.user.userId === this.value.userId){
api.getFileInfoById(this.value.id).then(res => {
this.value = res.data
if(this.$store.state.user.token && this.$store.state.user.userId === this.file.userId){
api.getFileInfoById(this.file.id).then(res => {
this.file = res.data
this.loadFile()
})
} else {
api.getPublicFileInfoById(this.value.id).then(res => {
this.value = res.data
api.getPublicFileInfoById(this.file.id).then(res => {
this.file = res.data
this.loadFile()
})
}
@ -81,6 +81,11 @@ export default {
immediate: true,
}
},
mounted() {
Bus.$on('previewSaveAndClose', () => {
this.requestClose()
})
},
methods: {
getType(type) {
switch (type) {
@ -97,25 +102,23 @@ export default {
}
return type
},
loadFile() {
if (this.docEditor !== null) {
this.docEditor.destroyEditor()
this.docEditor = null
}
let fileKey = `${new Date(this.value.updateDate).getTime()}-${this.value.id}`
let fileName = $J.strExists(this.fileName, '.') ? this.fileName : (this.fileName + '.' + this.fileType)
let fileKey = `${new Date(this.file.updateDate).getTime()}-${this.file.id}`
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){
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 = {
"document": {
"fileType": this.fileType,
"key": fileKey,
"title": fileName,
"title": this.file.name,
"url": url,
},
"editorConfig": {
@ -126,12 +129,8 @@ export default {
"name": this.$store.state.user.name
},
"customization": {
"logo": {
"image": "http://localhost:9528/favicon.ico",
"imageEmbedded": "",
"url": window.location.origin
},
"autosave": true,
"logo": null,
"autosave": false,
"comments": true,
"compactHeader": false,
"compactToolbar": false,
@ -141,10 +140,10 @@ export default {
"hideRightMenu": false,
"hideRulers": false,
"submitForm": false,
"about": false,
"about": null,
"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) {
@ -154,18 +153,40 @@ export default {
config.editorConfig.mode = "view"
config.editorConfig.callbackUrl = null
if (!config.editorConfig.user.id) {
let viewer = $J.getStorageInt("viewer")
if (!viewer) {
viewer = $J.randNum(1000, 99999)
$J.setStorage("viewer", viewer)
let visitor = $J.getStorageInt("visitor")
if (!visitor) {
visitor = $J.randNum(1000, 99999)
$J.setStorage("viewer", visitor)
}
config.editorConfig.user.id = "viewer_" + viewer
config.editorConfig.user.name = "Viewer_" + viewer
config.editorConfig.user.id = "visitor_" + visitor
config.editorConfig.user.name = "Visitor_" + visitor
}
}
config.events = {
"onDocumentReady": this.onDocumentReady,
"onDocumentStateChange": this.onDocumentStateChange,
}
this.$nextTick(() => {
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>
<div class="office-preview" v-if="show">
<van-overlay :show="show">
<div class="block">
<div class="close-bar" @click="close">
<svg-icon class="preview-close" icon-class="close"/>
<div>
<message-dialog
title="确认信息"
content="是否在关闭前保存修改?"
: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 class="wrapper">
<only-office-editor v-model="file" :read-only="readOnly"></only-office-editor>
</div>
</div>
</van-overlay>
</van-overlay>
</div>
</div>
</template>
<script>
import OnlyOfficeEditor from "@/components/office/OnlyOfficeEditor";
import MessageDialog from "@/components/message/MessageDialog";
import Bus from "@/assets/js/bus";
export default {
name: 'OfficePreview',
components: {
OnlyOfficeEditor,
},
components: {MessageDialog, OnlyOfficeEditor},
props: {
id: {
type: String,
default: () => {
return "office_" + Math.round(Math.random() * 10000)
}
},
file: {
type: Object,
default: function () {
@ -37,13 +56,13 @@ export default {
default: false
}
},
computed: {
},
data() {
return {
pc: this.$pc,
show: this.status,
readOnly: true,
isSaveDialogVisible: false,
saved: true,
}
},
watch: {
@ -55,10 +74,64 @@ export default {
}
},
methods: {
/**
* 点击关闭按钮
*/
beforeClose() {
if (!this.saved) {
this.isSaveDialogVisible = true
} else {
this.close()
}
},
/**
* 执行关闭预览窗口
*/
close() {
this.show = 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){
if(this.$store.state.user.token && this.$store.state.user.userId === fileUserId){
this.readOnly = false

View File

@ -7,6 +7,10 @@ export default {
webDAVUrl: function(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
previewUrl: function(username, file, token) {
let path = "/"

View File

@ -33,8 +33,15 @@ export default {
},
{ label: '刷新', operation: 'refresh'},
{ 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: 'createTextFile'},
]
}
},

View File

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