2023-04-25 12:03:59 +08:00

529 lines
19 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div v-if="ready" class="file-content">
<IFrame v-if="isPreview" class="preview-iframe" :src="previewUrl" @on-load="onFrameLoad"/>
<template v-else-if="contentDetail">
<EPopover
v-if="['word', 'excel', 'ppt'].includes(file.type)"
v-model="historyShow"
trigger="click">
<div class="file-content-history">
<FileHistory :value="historyShow" :file="file" @on-restore="onRestoreHistory"/>
</div>
<div slot="reference" ref="officeHeader" class="office-header"></div>
</EPopover>
<div v-else class="edit-header">
<div class="header-title">
<EPopover v-if="!equalContent" v-model="unsaveTip" class="file-unsave-tip">
<div class="task-detail-delete-file-popover">
<p>{{$L('未保存当前修改内容?')}}</p>
<div class="buttons">
<Button size="small" type="text" @click="unSaveGive">{{$L('放弃')}}</Button>
<Button size="small" type="primary" @click="onSaveSave">{{$L('保存')}}</Button>
</div>
</div>
<span slot="reference">[{{$L('未保存')}}*]</span>
</EPopover>
{{fileName}}
</div>
<div class="header-user">
<ul>
<li v-for="(userid, index) in editUser" :key="index" v-if="index <= 10">
<UserAvatar :userid="userid" :size="28" :border-witdh="2"/>
</li>
<li v-if="editUser.length > 10" class="more" :title="editUser.length">{{editUser.length > 999 ? '...' : editUser.length}}</li>
</ul>
</div>
<div v-if="file.type=='document' && contentDetail" class="header-hint">
<ButtonGroup size="small" shape="circle">
<Button :type="`${contentDetail.type=='md'?'primary':'default'}`" @click="setTextType('md')">{{$L('MD编辑器')}}</Button>
<Button :type="`${contentDetail.type!='md'?'primary':'default'}`" @click="setTextType('text')">{{$L('文本编辑器')}}</Button>
</ButtonGroup>
</div>
<div v-if="file.type=='mind'" class="header-hint">
{{$L('选中节点按enter键添加同级节点tab键添加子节点')}}
</div>
<Dropdown v-if="file.type=='mind'"
trigger="click"
class="header-hint"
@on-click="exportMenu"
transfer>
<a href="javascript:void(0)">{{$L('导出')}}<Icon type="ios-arrow-down"></Icon></a>
<DropdownMenu slot="list">
<DropdownItem name="png">{{$L('导出PNG图片')}}</DropdownItem>
<DropdownItem name="pdf">{{$L('导出PDF文件')}}</DropdownItem>
</DropdownMenu>
</Dropdown>
<template v-if="!file.only_view">
<div class="header-icons">
<ETooltip :disabled="$isEEUiApp || windowTouch" :content="$L('文件链接')">
<div class="header-icon" @click="handleClick('link')"><i class="taskfont">&#xe785;</i></div>
</ETooltip>
<EPopover v-model="historyShow" trigger="click">
<div class="file-content-history">
<FileHistory :value="historyShow" :file="file" @on-restore="onRestoreHistory"/>
</div>
<ETooltip slot="reference" ref="historyTip" :disabled="$isEEUiApp || windowTouch || historyShow" :content="$L('历史版本')">
<div class="header-icon"><i class="taskfont">&#xe71d;</i></div>
</ETooltip>
</EPopover>
</div>
<Button :disabled="equalContent" :loading="loadSave > 0" class="header-button" size="small" type="primary" @click="handleClick('save')">{{$L('保存')}}</Button>
</template>
</div>
<div class="content-body">
<div v-if="historyShow" class="content-mask"></div>
<template v-if="file.type=='document'">
<MDEditor v-if="contentDetail.type=='md'" v-model="contentDetail.content" height="100%"/>
<TEditor v-else v-model="contentDetail.content" height="100%" @editorSave="handleClick('saveBefore')"/>
</template>
<Drawio v-else-if="file.type=='drawio'" ref="myFlow" v-model="contentDetail" :title="file.name" @saveData="handleClick('saveBefore')"/>
<Minder v-else-if="file.type=='mind'" ref="myMind" v-model="contentDetail" @saveData="handleClick('saveBefore')"/>
<AceEditor v-else-if="['code', 'txt'].includes(file.type)" v-model="contentDetail.content" :ext="file.ext" @saveData="handleClick('saveBefore')"/>
<OnlyOffice v-else-if="['word', 'excel', 'ppt'].includes(file.type)" v-model="contentDetail" :documentKey="documentKey" @on-document-ready="handleClick('officeReady')"/>
</div>
</template>
<div v-if="contentLoad" class="content-load"><Loading/></div>
<!--文件链接-->
<Modal
v-model="linkShow"
:title="$L('文件链接')"
:mask-closable="false">
<div>
<div style="margin:-10px 0 8px">{{$L('文件名称')}}: {{linkData.name}}</div>
<Input ref="linkInput" v-model="linkData.url" type="textarea" :rows="3" @on-focus="linkFocus" readonly/>
<div class="form-tip" style="padding-top:6px">{{$L('可通过此链接浏览文件。')}}<a href="javascript:void(0)" @click="linkCopy">{{$L('点击复制链接')}}</a></div>
</div>
<div slot="footer" class="adaption">
<Button type="default" @click="linkShow=false">{{$L('取消')}}</Button>
<Poptip
confirm
placement="bottom"
style="margin-left:8px"
:ok-text="$L('确定')"
:cancel-text="$L('取消')"
@on-ok="linkGet(true)"
transfer>
<div slot="title">
<p><strong>{{$L('注意:刷新将导致原来的链接失效!')}}</strong></p>
</div>
<Button type="primary" :loading="linkLoad > 0">{{$L('刷新')}}</Button>
</Poptip>
</div>
</Modal>
</div>
</template>
<script>
import {mapState} from "vuex";
import FileHistory from "./FileHistory";
import IFrame from "./IFrame";
const MDEditor = () => import('../../../components/MDEditor/index');
const TEditor = () => import('../../../components/TEditor');
const AceEditor = () => import('../../../components/AceEditor');
const OnlyOffice = () => import('../../../components/OnlyOffice');
const Drawio = () => import('../../../components/Drawio');
const Minder = () => import('../../../components/Minder');
export default {
name: "FileContent",
components: {IFrame, FileHistory, AceEditor, TEditor, MDEditor, OnlyOffice, Drawio, Minder},
props: {
value: {
type: Boolean,
default: false
},
file: {
type: Object,
default: () => {
return {};
}
},
},
data() {
return {
ready: false,
loadSave: 0,
loadContent: 0,
unsaveTip: false,
fileExt: null,
contentDetail: null,
contentBak: {},
editUser: [],
loadPreview: true,
linkShow: false,
linkData: {},
linkLoad: 0,
historyShow: false,
officeReady: false,
}
},
mounted() {
document.addEventListener('keydown', this.keySave)
window.addEventListener('message', this.handleOfficeMessage)
//
if (this.$isSubElectron) {
window.__onBeforeUnload = () => {
if (!this.equalContent) {
$A.modalConfirm({
content: '修改的内容尚未保存,确定要放弃修改吗?',
cancelText: '取消',
okText: '放弃',
onOk: () => {
this.$Electron.sendMessage('windowDestroy');
}
});
return true
}
}
}
},
beforeDestroy() {
document.removeEventListener('keydown', this.keySave)
window.removeEventListener('message', this.handleOfficeMessage)
},
watch: {
value: {
handler(val) {
if (val) {
this.ready = true;
this.editUser = [this.userId];
this.getContent();
} else {
this.linkShow = false;
this.historyShow = false;
this.officeReady = false;
this.fileExt = null;
}
},
immediate: true,
},
historyShow(val) {
if (!val && this.$refs.historyTip) {
this.$refs.historyTip.updatePopper()
}
},
wsMsg: {
handler(info) {
const {type, action, data} = info;
switch (type) {
case 'path':
if (data.path == '/single/file/' + this.fileId) {
this.editUser = data.userids;
}
break;
case 'file':
if (action == 'content') {
if (this.value && data.id == this.fileId) {
$A.modalConfirm({
title: "更新提示",
content: '团队成员(' + info.nickname + ')更新了内容,<br/>更新时间:' + $A.formatDate("Y-m-d H:i:s", info.time) + '。<br/><br/>点击【确定】加载最新内容。',
onOk: () => {
this.getContent();
}
});
}
}
break;
}
},
deep: true,
},
},
computed: {
...mapState(['wsMsg']),
fileId() {
return this.file.id || 0
},
fileName() {
if (this.fileExt) {
return $A.getFileName(Object.assign(this.file, {
ext: this.fileExt
}))
}
return $A.getFileName(this.file)
},
equalContent() {
return this.contentBak == $A.jsonStringify(this.contentDetail);
},
contentLoad() {
return this.loadContent > 0 || this.previewLoad;
},
isPreview() {
return this.contentDetail && this.contentDetail.preview === true;
},
previewLoad() {
return this.isPreview && this.loadPreview === true;
},
previewUrl() {
if (this.isPreview) {
const {name, key} = this.contentDetail;
return $A.apiUrl(`../online/preview/${name}?key=${key}`)
}
return '';
},
},
methods: {
handleOfficeMessage({data, source}) {
if (data.source === 'onlyoffice') {
switch (data.action) {
case 'ready':
source.postMessage("createMenu", "*");
break;
case 'link':
this.handleClick('link')
break;
case 'history':
const dom = this.$refs.officeHeader;
if (dom) {
dom.style.top = `${data.rect.top}px`;
dom.style.left = `${data.rect.left}px`;
dom.style.width = `${data.rect.width}px`;
dom.style.height = `${data.rect.height}px`;
dom.click();
}
break;
}
}
},
onFrameLoad() {
this.loadPreview = false;
},
keySave(e) {
if (this.value && e.keyCode === 83) {
if (e.metaKey || e.ctrlKey) {
e.preventDefault();
this.onSaveSave();
}
}
},
getContent() {
if (this.fileId === 0) {
this.contentDetail = {};
this.updateBak();
return;
}
if (['word', 'excel', 'ppt'].includes(this.file.type)) {
this.contentDetail = $A.cloneJSON(this.file);
this.updateBak();
return;
}
this.loadSave++;
setTimeout(_ => {
this.loadContent++;
}, 600)
this.$store.dispatch("call", {
url: 'file/content',
data: {
id: this.fileId,
},
}).then(({data}) => {
this.contentDetail = data.content;
this.updateBak();
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
this.loadSave--;
this.loadContent--;
})
},
updateBak() {
this.contentBak = $A.jsonStringify(this.contentDetail);
},
handleClick(act) {
switch (act) {
case "link":
this.linkData = {
id: this.fileId,
name: this.file.name
};
this.linkShow = true;
this.linkGet()
break;
case "saveBefore":
if (!this.equalContent && this.loadSave == 0) {
this.handleClick('save');
} else {
$A.messageWarning('没有任何修改!');
}
break;
case "save":
if (this.file.only_view) {
return;
}
this.updateBak();
this.loadSave++;
this.$store.dispatch("call", {
url: 'file/content/save',
method: 'post',
data: {
id: this.fileId,
content: this.contentBak
},
}).then(({data, msg}) => {
$A.messageSuccess(msg);
const newData = {
id: this.fileId,
size: data.size,
};
if (this.fileExt) {
newData.ext = this.fileExt;
this.fileExt = null;
}
this.$store.dispatch("saveFile", newData);
}).catch(({msg}) => {
$A.modalError(msg);
this.getContent();
}).finally(_ => {
this.loadSave--;
})
break;
case "officeReady":
this.officeReady = true
break;
}
},
onRestoreHistory(item) {
this.historyShow = false;
$A.modalConfirm({
content: `你确定文件还原至【${item.created_at}】吗?`,
cancelText: '取消',
okText: '确定',
loading: true,
onOk: () => {
return new Promise((resolve, reject) => {
this.$store.dispatch("call", {
url: 'file/content/restore',
data: {
id: this.fileId,
history_id: item.id,
}
}).then(({msg}) => {
resolve(msg);
this.contentDetail = null;
this.getContent();
}).catch(({msg}) => {
reject(msg);
});
})
}
});
},
linkGet(refresh) {
this.linkLoad++;
this.$store.dispatch("call", {
url: 'file/link',
data: {
id: this.linkData.id,
refresh: refresh === true ? 'yes' : 'no'
},
}).then(({data}) => {
this.linkData = Object.assign(data, {
id: this.linkData.id,
name: this.linkData.name,
});
this.linkFocus();
}).catch(({msg}) => {
this.linkShow = false
$A.modalError(msg);
}).finally(_ => {
this.linkLoad--;
});
},
linkCopy() {
if (!this.linkData.url) {
return;
}
this.linkFocus();
this.$copyText(this.linkData.url).then(_ => {
$A.messageSuccess('复制成功');
}).catch(_ => {
$A.messageError('复制失败');
});
},
linkFocus() {
this.$nextTick(_ => {
this.$refs.linkInput.focus({cursor:'all'});
});
},
exportMenu(type) {
switch (this.file.type) {
case 'mind':
this.$refs.myMind.exportHandle(type, this.file.name);
break;
}
},
unSaveGive() {
this.getContent();
this.unsaveTip = false;
},
onSaveSave() {
this.handleClick('save');
this.unsaveTip = false;
},
setTextType(type) {
this.fileExt = type
this.$set(this.contentDetail, 'type', type)
},
documentKey() {
return new Promise(resolve => {
this.$store.dispatch("call", {
url: 'file/content',
data: {
id: this.fileId,
only_update_at: 'yes'
},
}).then(({data}) => {
resolve(`${data.id}-${$A.Time(data.update_at)}`)
}).catch(() => {
resolve(0)
});
})
},
}
}
</script>