perf: 优化文件历史查看

This commit is contained in:
kuaifan 2022-04-26 08:47:58 +08:00
parent 00f80e8db8
commit 41e60ee990
14 changed files with 397 additions and 203 deletions

View File

@ -804,6 +804,46 @@ class FileController extends AbstractController
return Base::retSuccess('success', $data);
}
/**
* @api {get} api/file/content/restore 13. 恢复文件历史
*
* @apiDescription 需要token身份
* @apiVersion 1.0.0
* @apiGroup file
* @apiName content__restore
*
* @apiParam {Number} id 文件ID
* @apiParam {Number} history_id 历史数据ID
*
* @apiSuccess {Number} ret 返回状态码1正确、0错误
* @apiSuccess {String} msg 返回信息(错误描述)
* @apiSuccess {Object} data 返回数据
*/
public function content__restore()
{
$user = User::auth();
//
$id = intval(Request::input('id'));
$history_id = intval(Request::input('history_id'));
//
$file = File::permissionFind($id);
//
$history = FileContent::whereFid($file->id)->whereId($history_id)->first();
if (empty($history)) {
return Base::retError('历史数据不存在或已被删除');
}
//
$content = $history->replicate();
$content->userid = $user->userid;
$content->save();
//
$file->size = $content->size;
$file->save();
$file->pushMsg('content');
//
return Base::retSuccess('还原成功');
}
/**
* @api {get} api/file/share 13. 获取共享信息
*

View File

@ -73,6 +73,10 @@ export default {
type: String,
default: ''
},
historyId: {
type: Number,
default: 0
},
value: {
type: [Object, Array],
default: function () {
@ -178,14 +182,20 @@ export default {
break;
}
//
let fileKey = this.code || this.value.id;
let codeId = this.code || this.value.id;
let fileName = $A.strExists(this.fileName, '.') ? this.fileName : (this.fileName + '.' + this.fileType);
let fileKey = `${this.fileType}-${fileKey}-${keyAppend}`;
let fileUrl = `http://nginx/api/file/content/?id=${codeId}&token=${this.userToken}`;
if (this.historyId > 0) {
fileKey += `-${this.historyId}`
fileUrl += `&history_id=${this.historyId}`
}
const config = {
"document": {
"fileType": this.fileType,
"key": `${this.fileType}-${fileKey}-${keyAppend}`,
"title": fileName,
"url": `http://nginx/api/file/content/?id=${fileKey}&token=${this.userToken}`,
"key": fileKey,
"url": fileUrl,
},
"editorConfig": {
"mode": "edit",
@ -199,18 +209,21 @@ export default {
"forcesave": true,
"help": false,
},
"callbackUrl": `http://nginx/api/file/content/office?id=${fileKey}&token=${this.userToken}`,
}
"callbackUrl": `http://nginx/api/file/content/office?id=${codeId}&token=${this.userToken}`,
},
"events": {
"onDocumentReady": this.onDocumentReady,
},
};
if (/\/hideenOfficeTitle\//.test(window.navigator.userAgent)) {
config.document.title = " ";
}
if ($A.leftExists(fileKey, "msgFile_")) {
config.document.url = `http://nginx/api/dialog/msg/download/?msg_id=${$A.leftDelete(fileKey, "msgFile_")}&token=${this.userToken}`;
} else if ($A.leftExists(fileKey, "taskFile_")) {
config.document.url = `http://nginx/api/project/task/filedown/?file_id=${$A.leftDelete(fileKey, "taskFile_")}&token=${this.userToken}`;
if ($A.leftExists(codeId, "msgFile_")) {
config.document.url = `http://nginx/api/dialog/msg/download/?msg_id=${$A.leftDelete(codeId, "msgFile_")}&token=${this.userToken}`;
} else if ($A.leftExists(codeId, "taskFile_")) {
config.document.url = `http://nginx/api/project/task/filedown/?file_id=${$A.leftDelete(codeId, "taskFile_")}&token=${this.userToken}`;
}
if (this.readOnly) {
if (this.readOnly || this.historyId > 0) {
config.editorConfig.mode = "view";
config.editorConfig.callbackUrl = null;
if (!config.editorConfig.user.id) {
@ -226,6 +239,10 @@ export default {
this.$nextTick(() => {
this.docEditor = new DocsAPI.DocEditor(this.id, config);
})
},
onDocumentReady() {
this.$emit("on-document-ready", this.docEditor)
}
}
}

View File

@ -9,6 +9,7 @@
<slot></slot>
<ETooltip
v-for="(item, key) in menu"
v-if="item.hidden !== true"
placement="top"
:key="key"
:disabled="!item.title"
@ -21,8 +22,13 @@
trigger="click"
class="menu-dropdown"
@command="onClick">
<a
v-if="item.label"
:href="item.href || 'javascript:void(0)'"
:target="item.target || '_self'"
:style="item.style || {}">{{item.label}}</a>
<i
v-if="isAliIcon(item.icon)"
v-else-if="isAliIcon(item.icon)"
class="taskfont menu-icon"
v-html="item.icon"
:style="item.style || {}"/>
@ -34,14 +40,22 @@
<EDropdownMenu slot="dropdown">
<EDropdownItem
v-for="(d, k) in item.children"
v-if="d.hidden !== true"
:key="k"
:command="d.action"
:disabled="!!d.disabled"
:divided="!!d.divided"
:style="d.style || {}">
<div>{{d.title}}</div>
</EDropdownItem>
</EDropdownMenu>
</EDropdown>
<a
v-else-if="item.label"
:href="item.href || 'javascript:void(0)'"
:target="item.target || '_self'"
:style="item.style || {}"
@click="onClick(item.action)">{{item.label}}</a>
<i
v-else-if="isAliIcon(item.icon)"
class="taskfont menu-icon"
@ -64,111 +78,111 @@ import VueResizeObserver from "vue-resize-observer";
import Vue from 'vue'
Vue.use(VueResizeObserver);
export default {
name: 'TableAction',
props: {
column: {
type: Object,
default: () => {
return {};
}
},
autoWidth: {
type: Boolean,
default: true
},
minWidth: {
type: Number,
default: 80
},
align: {
type: String,
default: ''
},
menu: {
type: Array,
default: () => {
return [];
}
},
},
data() {
return {
width: 0,
height: 0,
export default {
name: 'TableAction',
props: {
column: {
type: Object,
default: () => {
return {};
}
},
mounted() {
this.onUpdate();
autoWidth: {
type: Boolean,
default: true
},
activated() {
this.onUpdate();
minWidth: {
type: Number,
default: 80
},
beforeUpdate() {
this.onUpdate();
align: {
type: String,
default: ''
},
computed: {
tdStyle() {
const style = {};
const {align} = this;
switch (align.toLowerCase()) {
case 'left':
style.justifyContent = 'flex-start';
break;
case 'center':
style.justifyContent = 'center';
break;
case 'right':
style.justifyContent = 'flex-end';
break;
}
return style;
menu: {
type: Array,
default: () => {
return [];
}
},
methods: {
isAliIcon(icon) {
return $A.leftExists(icon, '&#')
},
handleIn() {
if (this.$refs.action.offsetWidth != this.width) {
this.onUpdate();
},
data() {
return {
width: 0,
height: 0,
}
},
mounted() {
this.onUpdate();
},
activated() {
this.onUpdate();
},
beforeUpdate() {
this.onUpdate();
},
computed: {
tdStyle() {
const style = {};
const {align} = this;
switch (align.toLowerCase()) {
case 'left':
style.justifyContent = 'flex-start';
break;
case 'center':
style.justifyContent = 'center';
break;
case 'right':
style.justifyContent = 'flex-end';
break;
}
return style;
}
},
methods: {
isAliIcon(icon) {
return $A.leftExists(icon, '&#')
},
handleIn() {
if (this.$refs.action.offsetWidth != this.width) {
this.onUpdate();
}
},
onUpdate() {
this.onResize({
width: this.$refs.action.offsetWidth,
height: this.$refs.action.offsetHeight,
})
},
onResize({ width, height }) {
if (!this.autoWidth) {
return;
}
$A(".ivu-table-column-" + this.column.__id).each((index, el) => {
let action = $A(el).find(".td-action-container")
if (action.length > 0) {
width = Math.max(width, action[0].offsetWidth)
height = Math.max(height, action[0].offsetHeight)
}
},
onUpdate() {
this.onResize({
width: this.$refs.action.offsetWidth,
height: this.$refs.action.offsetHeight,
});
this.width = width;
this.height = height;
let newWidth = Math.max(this.minWidth, this.width + 26);
if (this.column.minWidth) {
newWidth = Math.max(this.column.minWidth, newWidth);
}
if (this.column.maxWidth) {
newWidth = Math.min(this.column.maxWidth, newWidth);
}
if (newWidth != this.column.width) {
this.$nextTick(() => {
this.$set(this.column, 'width', newWidth);
})
},
onResize({ width, height }) {
if (!this.autoWidth) {
return;
}
$A(".ivu-table-column-" + this.column.__id).each((index, el) => {
let action = $A(el).find(".td-action-container")
if (action.length > 0) {
width = Math.max(width, action[0].offsetWidth)
height = Math.max(height, action[0].offsetHeight)
}
});
this.width = width;
this.height = height;
let newWidth = Math.max(this.minWidth, this.width + 26);
if (this.column.minWidth) {
newWidth = Math.max(this.column.minWidth, newWidth);
}
if (this.column.maxWidth) {
newWidth = Math.min(this.column.maxWidth, newWidth);
}
if (newWidth != this.column.width) {
this.$nextTick(() => {
this.$set(this.column, 'width', newWidth);
})
}
},
onClick(action) {
this.$emit("action", action)
}
},
onClick(action) {
this.$emit("action", action)
}
}
}
</script>

View File

@ -388,6 +388,19 @@
text = text.replace(/<img\s+class="emoticon"[^>]*?>/g, `[${$A.L('表情')}]`)
text = text.replace(/<img\s+class="browse"[^>]*?>/g, `[${$A.L('图片')}]`)
return text.replace(/<[^>]+>/g,"")
},
/**
* 获取文件标题
* @param file
* @returns {*}
*/
getFileName(file) {
let {name, ext} = file;
if (ext != '') {
name += "." + ext;
}
return name;
}
});

View File

@ -302,8 +302,8 @@ export default {
}
if (this.$Electron) {
this.$Electron.sendMessage('windowRouter', {
name: 'file-msg-' + this.msgData.id,
path: "/single/file/msg/" + this.msgData.id,
name: `file-msg-${this.msgData.id}`,
path: `/single/file/msg/${this.msgData.id}`,
userAgent: "/hideenOfficeTitle/",
force: false,
config: {

View File

@ -2,7 +2,18 @@
<div v-if="ready" class="file-content">
<iframe v-if="isPreview" ref="myPreview" class="preview-iframe" :src="previewUrl"></iframe>
<template v-else>
<div v-show="!['word', 'excel', 'ppt'].includes(file.type)" class="edit-header">
<div v-if="['word', 'excel', 'ppt'].includes(file.type)" class="office-header">
<div v-if="!file.only_view && officeReady" class="header-icons">
<div class="header-icon" @click="handleClick('link')"><i class="taskfont">&#xe785;</i></div>
<EPopover v-model="historyShow" trigger="click">
<div class="file-content-history">
<FileHistory :value="historyShow" :file="file" @on-restore="onRestoreHistory"/>
</div>
<div slot="reference" class="header-icon"><i class="taskfont">&#xe71d;</i></div>
</EPopover>
</div>
</div>
<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">
@ -14,7 +25,7 @@
</div>
<span slot="reference">[{{$L('未保存')}}*]</span>
</EPopover>
{{formatName(file)}}
{{$A.getFileName(file)}}
</div>
<div class="header-user">
<ul>
@ -50,7 +61,7 @@
</ETooltip>
<EPopover v-model="historyShow" trigger="click">
<div class="file-content-history">
<FileHistory :value="historyShow" :fileId="fileId" @on-select="handleHistory"/>
<FileHistory :value="historyShow" :file="file" @on-restore="onRestoreHistory"/>
</div>
<ETooltip slot="reference" :disabled="historyShow" :content="$L('历史版本')">
<div class="header-icon"><i class="taskfont">&#xe71d;</i></div>
@ -61,6 +72,7 @@
</template>
</div>
<div v-if="contentDetail" 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')"/>
@ -68,8 +80,7 @@
<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"/>
<div v-if="historyShow" class="content-mask"></div>
<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>
@ -151,6 +162,7 @@ export default {
linkLoad: 0,
historyShow: false,
officeReady: false,
}
},
@ -190,6 +202,7 @@ export default {
} else {
this.linkShow = false;
this.historyShow = false;
this.officeReady = false;
}
},
immediate: true,
@ -275,7 +288,7 @@ export default {
}
},
getContent(history_id = 0) {
getContent() {
if (this.fileId === 0) {
this.contentDetail = {};
this.updateBak();
@ -292,13 +305,10 @@ export default {
url: 'file/content',
data: {
id: this.fileId,
history_id: history_id
},
}).then(({data}) => {
this.contentDetail = data.content;
if (!history_id) {
this.updateBak();
}
this.updateBak();
}).catch(({msg}) => {
$A.modalError(msg);
}).finally(_ => {
@ -355,23 +365,38 @@ export default {
this.loadSave--;
})
break;
case "officeReady":
this.officeReady = true
break;
}
},
handleHistory(item) {
onRestoreHistory(item) {
this.historyShow = false;
if (!this.equalContent) {
$A.modalConfirm({
content: '修改的内容尚未保存,确定要读取历史记录吗?',
cancelText: '取消',
okText: '确定',
onOk: () => {
this.getContent(item.id)
}
});
} else {
this.getContent(item.id)
}
$A.modalConfirm({
content: `你确定文件还原至【${item.created_at}】吗?`,
cancelText: '取消',
okText: '确定',
loading: true,
onOk: () => {
this.$store.dispatch("call", {
url: 'file/content/restore',
data: {
id: this.fileId,
history_id: item.id,
}
}).then(({msg}) => {
$A.messageSuccess(msg);
this.contentDetail = null;
this.getContent();
}).catch(({msg}) => {
$A.modalError(msg, 301);
}).finally(_ => {
this.$Modal.remove();
});
}
});
},
linkGet(refresh) {
@ -443,14 +468,6 @@ export default {
});
})
},
formatName(file) {
let {name, ext} = file;
if (ext != '') {
name += "." + ext;
}
return name;
},
}
}
</script>

View File

@ -1,12 +1,13 @@
<template>
<div class="file-history">
<Table
:width="460"
:width="480"
:max-height="windowHeight - 180"
:columns="columns"
:data="list"
:loading="loadIng > 0"
:no-data-text="$L(noText)"
highlight-row
stripe/>
<Page
v-if="total > pageSize"
@ -40,9 +41,11 @@ export default {
type: Boolean,
default: false
},
fileId: {
type: Number,
default: 0
file: {
type: Object,
default: () => {
return {};
}
},
},
@ -70,7 +73,7 @@ export default {
}, {
title: this.$L('大小'),
key: 'size',
width: 80,
width: 90,
render: (h, {row}) => {
return h('AutoTip', $A.bytesToSize(row.size));
}
@ -78,26 +81,29 @@ export default {
title: this.$L('操作'),
align: 'center',
width: 100,
render: (h, {row, column}) => {
const vNodes = [
h('div', {
style: {
fontSize: '13px',
cursor: 'pointer',
color: '#8bcf70',
},
on: {
'click': () => {
this.$emit('on-select', row)
}
},
}, this.$L('读取')),
];
render: (h, {index, row, column}) => {
if (index === 0) {
return h('div', '-');
}
return h('TableAction', {
props: {
column: column
column: column,
menu: [
{
label: this.$L('查看'),
action: "preview",
}, {
label: this.$L('还原'),
action: "restore",
}
]
},
on: {
action: (name) => {
this.onAction(name, row)
}
}
}, vNodes);
});
}
}
],
@ -126,7 +132,11 @@ export default {
},
computed: {
...mapState(['windowHeight'])
...mapState(['windowHeight']),
fileId() {
return this.file.id || 0
},
},
methods: {
@ -164,6 +174,37 @@ export default {
this.pageSize = pageSize;
this.getLists();
},
onAction(name, row) {
switch (name) {
case 'restore':
this.$emit('on-restore', row)
break;
case 'preview':
if (this.$Electron) {
this.$Electron.sendMessage('windowRouter', {
name: `file-${this.fileId}-${row.id}`,
path: `/single/file/${this.fileId}?history_id=${row.id}&history_at=${row.created_at}`,
userAgent: "/hideenOfficeTitle/",
force: false,
config: {
title: $A.getFileName(this.file) + ` [${row.created_at}]`,
titleFixed: true,
parent: null,
width: Math.min(window.screen.availWidth, 1440),
height: Math.min(window.screen.availHeight, 900),
},
webPreferences: {
nodeIntegrationInSubFrames: this.file.type === 'drawio'
},
});
} else {
window.open($A.apiUrl(`../single/file/${this.fileId}?history_id=${row.id}&history_at=${row.created_at}`))
}
break;
}
},
}
}
</script>

View File

@ -4,7 +4,7 @@
<template v-else>
<div v-show="!['word', 'excel', 'ppt'].includes(file.type)" class="edit-header">
<div class="header-title">
{{formatName(file)}}
{{$A.getFileName(file)}}
<Tag color="default">{{$L('只读')}}</Tag>
<div class="refresh">
<Loading v-if="contentLoad"/>
@ -30,7 +30,7 @@
<Drawio v-else-if="file.type=='drawio'" ref="myFlow" :value="contentDetail" :title="file.name" readOnly/>
<Minder v-else-if="file.type=='mind'" ref="myMind" :value="contentDetail" readOnly/>
<AceEditor v-else-if="['code', 'txt'].includes(file.type)" :value="contentDetail.content" :ext="file.ext" readOnly/>
<OnlyOffice v-else-if="['word', 'excel', 'ppt'].includes(file.type)" :value="contentDetail" :code="code" :documentKey="documentKey" readOnly/>
<OnlyOffice v-else-if="['word', 'excel', 'ppt'].includes(file.type)" :value="contentDetail" :code="code" :historyId="historyId" :documentKey="documentKey" readOnly/>
</div>
</template>
<div v-if="contentLoad" class="content-load"><Loading/></div>
@ -56,6 +56,10 @@ export default {
type: String,
default: ''
},
historyId: {
type: Number,
default: 0
},
file: {
type: Object,
default: () => {
@ -134,6 +138,7 @@ export default {
url: 'file/content',
data: {
id: this.code || this.file.id,
history_id: this.historyId
},
}).then(({data}) => {
this.contentDetail = data.content;
@ -167,14 +172,6 @@ export default {
break;
}
},
formatName(file) {
let {name, ext} = file;
if (ext != '') {
name += "." + ext;
}
return name;
},
}
}
</script>

View File

@ -103,8 +103,8 @@ export default {
height: Math.min(window.screen.availHeight, 900),
}
this.$Electron.sendMessage('windowRouter', {
name: 'report-' + row.id,
path: "/single/report/detail/" + row.id,
name: `report-detail-${row.id}`,
path: `/single/report/detail/${row.id}`,
force: false,
config
});
@ -122,8 +122,8 @@ export default {
height: Math.min(window.screen.availHeight, 900),
}
this.$Electron.sendMessage('windowRouter', {
name: 'report-' + id,
path: "/single/report/edit/" + id,
name: `report-edit-${id}`,
path: `/single/report/edit/${id}`,
force: false,
config
});

View File

@ -1206,8 +1206,8 @@ export default {
config.minHeight = 600;
}
this.$Electron.sendMessage('windowRouter', {
name: 'task-' + this.taskDetail.id,
path: "/single/task/" + this.taskDetail.id,
name: `task-${this.taskDetail.id}`,
path: `/single/task/${this.taskDetail.id}`,
force: false,
config
});
@ -1251,8 +1251,8 @@ export default {
}
if (this.$Electron) {
this.$Electron.sendMessage('windowRouter', {
name: 'file-task-' + file.id,
path: "/single/file/task/" + file.id,
name: `file-task-${file.id}`,
path: `/single/file/task/${file.id}`,
userAgent: "/hideenOfficeTitle/",
force: false,
config: {

View File

@ -114,7 +114,7 @@
@on-keyup="onKeyup($event, item)"/>
<div v-if="item._load" class="file-load"><Loading/></div>
</div>
<div v-else class="file-name" :title="item.name">{{formatName(item)}}</div>
<div v-else class="file-name" :title="item.name">{{$A.getFileName(item)}}</div>
</li>
</ul>
</div>
@ -364,10 +364,6 @@
</template>
<script>
import Vue from 'vue'
import VueClipboard from 'vue-clipboard2'
Vue.use(VueClipboard)
import {mapState} from "vuex";
import {sortBy} from "lodash";
import UserInput from "../../components/UserInput";
@ -377,7 +373,6 @@ import PreviewImage from "../../components/PreviewImage";
const FilePreview = () => import('./components/FilePreview');
const FileContent = () => import('./components/FileContent');
export default {
components: {PreviewImage, FilePreview, DrawerOverlay, UserInput, FileContent},
data() {
@ -749,7 +744,7 @@ export default {
}
}
}, [
h('AutoTip', this.formatName(row))
h('AutoTip', $A.getFileName(row))
]));
//
const iconArray = [];
@ -852,14 +847,6 @@ export default {
});
},
formatName(file) {
let {name, ext} = file;
if (ext != '') {
name += "." + ext;
}
return name;
},
getFileList() {
if (this.$route.name !== 'manage-file') {
return;
@ -976,12 +963,12 @@ export default {
openFileSingle(item) {
this.$Electron.sendMessage('windowRouter', {
name: 'file-' + item.id,
path: "/single/file/" + item.id,
name: `file-${item.id}`,
path: `/single/file/${item.id}`,
userAgent: "/hideenOfficeTitle/",
force: false, //
config: {
title: this.formatName(item),
title: $A.getFileName(item),
titleFixed: true,
parent: null,
width: Math.min(window.screen.availWidth, 1440),
@ -1103,11 +1090,11 @@ export default {
},
}).then(({msg}) => {
$A.messageSuccess(msg);
this.$Modal.remove();
this.$store.dispatch("forgetFile", item.id);
}).catch(({msg}) => {
this.$Modal.remove();
$A.modalError(msg, 301);
}).finally(_ => {
this.$Modal.remove();
});
}
});
@ -1221,11 +1208,11 @@ export default {
},
}).then(({msg}) => {
$A.messageSuccess(msg);
this.$Modal.remove();
this.$store.dispatch("forgetFile", ids);
this.selectIds = this.selectIds.filter(id => !ids.includes(id))
}).catch(({msg}) => {
$A.modalError(msg, 301);
}).finally(_ => {
this.$Modal.remove();
});
}

View File

@ -1,9 +1,9 @@
<template>
<div class="single-file">
<PageTitle :title="fileInfo.name"/>
<PageTitle :title="pageName"/>
<Loading v-if="loadIng > 0"/>
<template v-else>
<FilePreview v-if="code || fileInfo.permission === 0" :code="code" :file="fileInfo"/>
<FilePreview v-if="isPreview" :code="code" :file="fileInfo" :historyId="historyId"/>
<FileContent v-else v-model="fileShow" :file="fileInfo"/>
</template>
</div>
@ -38,6 +38,21 @@ export default {
mounted() {
//
},
computed: {
historyId() {
return this.$route.query ? $A.runNum(this.$route.query.history_id) : 0;
},
isPreview() {
return this.code || this.fileInfo.permission === 0 || this.historyId > 0
},
pageName() {
let name = this.fileInfo.name;
if (this.$route.query && this.$route.query.history_at) {
name += ` [${this.$route.query.history_at}]`
}
return name;
}
},
watch: {
'$route': {
handler() {

View File

@ -64,6 +64,16 @@ body.dark-mode-reverse {
.file-content,
.file-preview {
.office-header {
.header-icons {
.header-icon {
color: rgba(0, 0, 0, 0.8);
&:hover {
background-color: rgba(0, 0, 0, 0.05);
}
}
}
}
.content-body {
.tox {
.tox-edit-area__iframe {

View File

@ -25,6 +25,40 @@
padding: 0;
}
.office-header {
display: flex;
align-items: center;
position: absolute;
right: 40px;
top: 28px;
z-index: 1;
.header-icons {
display: flex;
align-items: center;
justify-content: center;
.header-icon {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 28px;
line-height: 28px;
cursor: pointer;
color: rgba(255, 255, 255, 0.9);
&:hover {
background-color: rgba(255, 255, 255, 0.2);
}
> i {
font-size: 16px;
}
}
}
}
.edit-header {
display: flex;
flex-direction: row;
@ -105,6 +139,7 @@
width: 44px;
height: 100%;
color: #777777;
cursor: pointer;
> i {
font-size: 20px;
}
@ -273,6 +308,14 @@
}
}
@media (max-width: 768px) {
.file-content {
.office-header {
display: none;
}
}
}
@media (max-width: 1200px) {
.file-content {
overflow: auto;