feat: 新增文件版本管理

This commit is contained in:
jmal 2023-05-26 23:10:39 +08:00
parent a95b427d73
commit b4354b66a0
13 changed files with 992 additions and 162 deletions

View File

@ -88,8 +88,7 @@
"vue-template-compiler": "^2.6.13"
},
"engines": {
"node": ">=8.9",
"npm": ">= 3.0.0"
"node": "^16"
},
"browserslist": [
"> 1%",

64
src/api/file-history.js Normal file
View File

@ -0,0 +1,64 @@
import request from '@/utils/request'
export default {
/**
* 查询历史文件列表
* @param params
* @returns {*}
*/
fileHistoryList: function(params) {
return request({
url: '/history/list',
method: 'get',
params
})
},
/**
* 查询历史文件列表(根据path)
* @param params
* @returns {*}
*/
fileHistoryPathList: function(params) {
return request({
url: '/history/path/list',
method: 'get',
params
})
},
/**
* 预览某版本的历史文件
* @param params
* @returns {*}
*/
previewHistoryText: function(params) {
return request({
url: '/history/preview/text',
method: 'get',
params
})
},
/**
* 恢复该历史版本
* @param params
* @returns {*}
*/
recoveryHistory: function(params) {
return request({
url: '/history/recovery',
method: 'put',
params
})
},
/**
* 删除该历史版本
* @param params
* @returns {*}
*/
deleteHistory: function(params) {
return request({
url: '/history/delete',
method: 'delete',
params
})
},
}

View File

@ -0,0 +1,11 @@
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"
"http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg t="1684983182221" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5240"
xmlns:xlink="http://www.w3.org/1999/xlink">
<path
d="M748.896 624.16l-221.44-137.888 0-223.648c0-14.624-11.84-26.496-26.496-26.496s-26.496 11.84-26.496 26.496l0 238.336c0 9.152 4.736 17.664 12.48 22.464l233.92 145.664c4.352 2.688 9.184 4 13.984 4 8.832 0 17.472-4.416 22.496-12.48 7.744-12.416 3.936-28.768-8.48-36.48z"
fill="#ffffff" p-id="5241"></path>
<path
d="M512 0c-282.336 0-512 229.664-512 512s229.664 512 512 512c282.304 0 512-229.696 512-512s-229.696-512-512-512zM512 971.04c-253.12 0-459.04-205.92-459.04-459.04s205.92-459.04 459.04-459.04c253.12 0 459.04 205.92 459.04 459.04s-205.92 459.04-459.04 459.04z"
fill="#ffffff" p-id="5242"></path>
</svg>

After

Width:  |  Height:  |  Size: 945 B

View File

@ -0,0 +1,217 @@
<template>
<div>
<el-popover
v-if="hasHistoryVersion"
style="margin-right: 15px"
:visible-arrow="false"
:popper-class="(lightTheme ? 'file-history-popover' : 'dark-file-history-popover') + ' history-popover'"
placement="bottom"
@show="popoverShow"
@hide="popoverHide"
v-model="historyListPopoverVisible"
trigger="manual">
<el-table :data="fileHistoryList" height="300" row-class-name="file-history-row" :class="lightTheme? 'file-history-list' : 'dark-file-history-list'">
<el-table-column show-overflow-tooltip width="200" property="metadata.filename" label="文件名"></el-table-column>
<el-table-column width="190" property="metadata.time" label="版本日期"></el-table-column>
<el-table-column label="操作" width="210">
<template slot-scope="scope">
<div style="display: flex">
<el-button
size="mini"
:class="lightTheme?'':'dark-button'"
@click="viewHistoryFile(scope.row, true, false)" :loading="historyOperationLoading">预览</el-button>
<el-popover
placement="bottom"
width="310"
:popper-class="(lightTheme ? 'file-history-popover' : 'dark-file-history-popover') + ' delete-' + scope.row.id"
style="margin: 0 10px;"
:visible-arrow="false"
trigger="click">
<p class="el-popconfirm__main">
<i class="el-popconfirm__icon el-icon-question" style="color: rgb(255, 153, 0);"></i>
{{'确定要恢复到'+ scope.row.metadata.time +'吗?当前内容将被覆盖!'}}
</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="cancelRecovery(scope.row.id)">取消</el-button>
<el-button type="primary" size="mini" @click="confirmRecoveryHistoryFile(scope.row)">确定</el-button>
</div>
<el-button slot="reference" size="mini" :id="'recoveryVersionBtn-'+scope.row.id" type="warning" :loading="historyOperationLoading">恢复</el-button>
</el-popover>
<el-popover
placement="bottom"
width="310"
:popper-class="(lightTheme ? 'file-history-popover' : 'dark-file-history-popover') + ' delete-' + scope.row.id"
:visible-arrow="false"
trigger="click">
<p class="el-popconfirm__main">
<i class="el-popconfirm__icon el-icon-question" style="color: rgb(255, 153, 0);"></i>
{{'确定要删除'+ scope.row.metadata.time +'吗?这将永久删除该历史版本!'}}
</p>
<div style="text-align: right; margin: 0">
<el-button size="mini" type="text" @click="cancelDelete(scope.row.id)">取消</el-button>
<el-button type="primary" size="mini" @click="confirmDeleteHistoryFile(scope.row.id)">确定</el-button>
</div>
<el-button slot="reference" size="mini" :id="'deleteVersionBtn-'+scope.row.id" type="danger" :loading="historyOperationLoading">删除</el-button>
</el-popover>
</div>
</template>
</el-table-column>
</el-table>
<el-button slot="reference" :style="versionBtnStyle" ref="versionBtn" :size="buttonSize" :class="lightTheme?' history-version-btn':'dark-button history-version-btn'" @click="showOrHidePopover">历史版本</el-button>
</el-popover>
</div>
</template>
<script>
import historyApi from "@/api/file-history"
export default {
name: "history-popover",
props: {
hasHistoryVersion: {
type: Object,
default: function () {
return {metadata: {}}
}
},
lightTheme: {
type: Boolean,
default: true
},
historyListPopoverVisible: {
type: Boolean,
default: false
},
historyOperationLoading: {
type: Boolean,
default: false
},
saved: {
type: Boolean,
default: true
},
buttonSize: {
type: String,
default: 'mini'
},
transparent: {
type: Boolean,
default: false
}
},
computed: {
versionBtnStyle() {
if (this.transparent) {
return 'background: #00000000;border: 0;'
} else {
return ''
}
}
},
data() {
return {
fileHistoryList: [],
recoveryHistoryListenerMap: new Map(),
deleteHistoryListenerMap: new Map(),
historyPageIndex: 1,
historyPageSize: 50
}
},
watch: {
historyListPopoverVisible(value) {
if (value) {
document.getElementById('app').addEventListener('click', this.onGlobalClick)
} else {
// destroy elementListeners
document.getElementById('app').removeEventListener('click', this.onGlobalClick)
}
},
},
methods: {
loadHistoryList(id) {
historyApi.fileHistoryList({fileId: id, username: this.$store.state.user.name, pageIndex: this.historyPageIndex, pageSize: this.historyPageSize}).then((res) => {
if (res.count && res.count > 0) {
this.fileHistoryList = res.data
this.$emit('update:hasHistoryVersion', true)
} else {
this.$emit('update:hasHistoryVersion', false)
}
})
},
loadHistoryPathList(pathname) {
historyApi.fileHistoryPathList({path: encodeURI(pathname), username: this.$store.state.user.name, pageIndex: this.historyPageIndex, pageSize: this.historyPageSize}).then((res) => {
this.fileHistoryList = res.data
this.$emit('loadHistoryPathListSuccess', {res: res, pathname: pathname})
})
},
onGlobalClick(event) {
let eventClass = event.target.getAttribute('class') || event.target.parentElement.getAttribute('class')
if (eventClass && eventClass.indexOf('history-version-btn') > -1) {
return
}
if (this.historyListPopoverVisible) {
this.$emit('update:historyListPopoverVisible', false)
}
if (event.target.getAttribute("data-tab") === "file") {
setTimeout(() => {
let parentDoc = document.querySelector('.component-only-office')
let doc = parentDoc.getElementsByTagName('iframe')[0].contentWindow.document
doc.getElementById('fm-btn-history').style.display = 'none'
})
}
},
popoverShow() {
this.$emit('update:historyListPopoverVisible', true)
},
showOrHidePopover() {
if (this.historyListPopoverVisible) {
this.$emit('update:historyListPopoverVisible', false)
} else {
this.$emit('update:historyListPopoverVisible', true)
}
},
popoverHide() {
this.$emit('update:historyListPopoverVisible', false)
},
viewHistoryFile(historyInfo, diff, recovery) {
this.$emit('viewHistoryFile', {historyInfo: historyInfo, diff: diff, recovery: recovery})
},
cancelDelete(id) {
document.getElementById('deleteVersionBtn-'+id).click()
},
cancelRecovery(id) {
document.getElementById('recoveryVersionBtn-'+id).click()
},
confirmRecoveryHistoryFile(historyInfo) {
if (!this.saved) {
this.$message({type: 'info', message: "请先保存当前修改的内容"})
return
}
this.cancelRecovery(historyInfo.id)
this.$emit('update:historyOperationLoading', true)
historyApi.recoveryHistory({id: historyInfo.id}).then((res) => {
this.$emit('recoverySuccess', {historyInfo: historyInfo, result: res})
})
},
confirmDeleteHistoryFile(id) {
this.cancelDelete(id)
this.$emit('update:historyOperationLoading', true)
historyApi.deleteHistory({id: id}).then(() => {
this.$emit('update:historyOperationLoading', false)
this.$message({message: '删除成功',type: 'success'})
let deleteIndex = this.fileHistoryList.findIndex(fileHistory => fileHistory.id === id)
this.fileHistoryList.splice(deleteIndex, 1)
if (this.fileHistoryList.length < 1) {
this.$emit('update:hasHistoryVersion', false)
}
})
}
}
}
</script>
<style scoped>
</style>

View File

@ -1,8 +1,25 @@
<template>
<div class="minder-editor-container">
<div class="minder-title" v-show="titleShow">
<div class="minder-title-name" :style="{'color': saved ? '': '#ff8200'}">{{title}}</div>
<div class="minder-save"><el-button v-if="!saved" @click="save" size="mini" :loading="saveBtnUpdating">保存</el-button></div>
<div class="minder-title-name" :style="{'color': titleColor}">{{title}}</div>
<div class="minder-operation">
<div style="margin-right: 15px">
<el-button v-if="historyVersion.metadata.time" @click="cancelPreview" size="mini">取消预览</el-button>
</div>
<history-popover
ref="historyPopover"
:has-history-version.sync="hasHistoryVersion"
:history-list-popover-visible.sync="historyListPopoverVisible"
:history-operation-loading="!loading.closed"
:saved.sync="saved"
@viewHistoryFile="viewHistoryFile"
@recoverySuccess="recoverySuccess"
>
</history-popover>
<div>
<el-button v-if="!saved" @click="save" size="mini" :loading="saveBtnUpdating">保存</el-button>
</div>
</div>
</div>
<div class="quickbar">
<el-tooltip placement="top" effect="light">
@ -13,7 +30,7 @@
<el-slider v-model="zoom" :min="10" :max="300" input-size="mini" tooltip-class="slider-tooltip"></el-slider>
</div>
</el-tooltip>
<el-tooltip v-if="readOnly!==true" placement="top" effect="light">
<el-tooltip v-if="!hasReadOnly" placement="top" effect="light">
<div>
<em class="ft icon" title="图形">
<svg-icon icon-class="graph1"/>
@ -21,16 +38,16 @@
</div>
<div slot="content">
<ul class="minder-editor-quickul mold" style="margin-bottom: 0;">
<li @click="execCommand('template', 'default')"><span class="default"></span></li>
<li @click="execCommand('template', 'structure')"><span class="structure"></span></li>
<li @click="execCommand('template', 'filetree')"><span class="filetree"></span></li>
<li @click="execCommand('template', 'right')"><span class="right"></span></li>
<li @click="execCommand('template', 'fish-bone')"><span class="fish-bone"></span></li>
<li @click="execCommand('template', 'tianpan')"><span class="tianpan"></span></li>
<li @click="execCommand('template', 'default')" title="默认"><span class="default"></span></li>
<li @click="execCommand('template', 'structure')" title="结构"><span class="structure"></span></li>
<li @click="execCommand('template', 'filetree')" title="树形"><span class="filetree"></span></li>
<li @click="execCommand('template', 'right')" title="靠右"><span class="right"></span></li>
<li @click="execCommand('template', 'fish-bone')" title="鱼骨"><span class="fish-bone"></span></li>
<li @click="execCommand('template', 'tianpan')" title="天盘"><span class="tianpan"></span></li>
</ul>
</div>
</el-tooltip>
<el-tooltip v-if="readOnly!==true" placement="top" effect="light">
<el-tooltip v-if="!hasReadOnly" placement="top" effect="light">
<div>
<em class="ft icon" title="样式">
<svg-icon icon-class="clothes"/>
@ -94,10 +111,13 @@ import JSPDF from 'jspdf';
import api from "@/api/file-api";
import txtApi from "@/api/markdown-api";
import Bus from "@/assets/js/bus";
import historyApi from "@/api/file-history";
import HistoryPopover from "@/components/HistoryPopover/index.vue";
export default {
name: 'my-mind-editor',
components: {HistoryPopover},
props: {
file: {
type: Object,
@ -118,6 +138,17 @@ export default {
default: 'minder-component-' + generateMixed(12)
},
},
computed: {
hasReadOnly() {
return this.readOnly || this.viewHistory
},
titleColor() {
if (!this.saved) {
return '#ff8200'
}
return ''
}
},
data() {
return {
minder: null,
@ -129,7 +160,16 @@ export default {
zoom: 100,
saved: true,
titleShow: false,
};
currentContext: undefined,
currentTitle: undefined,
historyVersion: {metadata: {}},
hasHistoryVersion: false,
historyListPopoverVisible: false,
viewHistory: false,
loading: {
closed: true
},
}
},
watch: {
'file.id': {
@ -138,7 +178,7 @@ export default {
return
}
let request = 'previewText'
if (this.readOnly) {
if (this.hasReadOnly) {
request = 'sharePreviewText'
}
this.content = null
@ -158,6 +198,9 @@ export default {
this.init(this.content)
this.$emit('onReady')
})
this.$nextTick(()=> {
this.$refs.historyPopover.loadHistoryList(this.file.id)
})
},
deep: true,
immediate: true
@ -175,9 +218,70 @@ export default {
document.removeEventListener('keydown', this.ctrlAndS)
},
methods: {
/**
* 保存并关闭
*/
viewHistoryFile({historyInfo, recovery}) {
if (!this.saved) {
this.$message({type: 'info', message: "请先保存当前修改的内容"})
return
}
let loadingInfo = recovery ? '恢复中...' : '加载中...'
this.loading = this.$message({
iconClass: 'el-icon-loading',
type: 'info',
duration: 0,
dangerouslyUseHTMLString: true,
message: `<span>&nbsp;&nbsp;${loadingInfo}</span>`
})
historyApi.previewHistoryText({id: historyInfo.id}).then((res) => {
this.loading.close()
if (this.historyListPopoverVisible) {
this.historyListPopoverVisible = false
}
if (recovery) {
this.cancelPreview(null, res.data.contentText)
this.$message({message: '恢复成功',type: 'success'})
} else {
this.currentContext = JSON.stringify(this.content)
this.currentTitle = this.title
this.content = JSON.parse(res.data.contentText)
this.bakValue = res.data.contentText
this.minder.importJson(this.content)
this.historyVersion = historyInfo
this.title = `历史版本:${historyInfo.metadata.time}`
this.viewHistory = true
this.setReadOnly()
}
}).catch(() => {
this.loading.close()
})
},
recoverySuccess({historyInfo}) {
this.viewHistoryFile({historyInfo: historyInfo, recovery: true})
},
cancelPreview(event, currentContext) {
if (!currentContext) {
currentContext = this.currentContext
}
this.historyVersion = {metadata: {}}
this.content = JSON.parse(currentContext)
this.bakValue = currentContext
this.minder.importJson(this.content)
if (this.currentTitle) {
this.title = this.currentTitle
}
this.viewHistory = false
this.setReadOnly()
},
setReadOnly() {
window.__minderReadOnly = this.hasReadOnly
if (this.hasReadOnly === true) {
this.minder.disable()
this.minder.execCommand('Hand')
this.isHand = true
} else {
this.minder.enable()
}
},
saveAndClose() {
this.save()
this.$emit('onClose')
@ -247,31 +351,30 @@ export default {
this.minder.importJson(this.content)
return
}
window.__minderReadOnly = this.readOnly
const Editor = require('./editor')
this.minder = window.editor = new Editor(document.getElementById(this.id)).minder
this.bakValue = JSON.stringify(this.content)
this.titleShow = true
this.minder.importJson(this.content)
if (this.readOnly === true) {
this.minder.disable()
this.minder.execCommand('Hand')
this.isHand = true
}
this.minder.on('contentchange', () => {
const newJson = this.minder.exportJson()
if (this.bakValue === JSON.stringify(newJson)) {
return
}
this.bakValue = JSON.stringify(newJson)
this.content = newJson
this.saved = false
this.title = `*${this.file.name}`
this.$emit('onEdit', this.saved)
})
this.setReadOnly()
this.minder.on('contentchange', () => this.onContentChange())
}, 300)
})
},
onContentChange() {
const newJson = this.minder.exportJson()
if (this.bakValue === JSON.stringify(newJson)) {
this.saved = true
if (this.title === `*${this.file.name}`) {
this.title = this.file.name
}
} else {
this.content = newJson
this.saved = false
this.title = `*${this.file.name}`
}
this.$emit('onEdit', this.saved)
},
init(newObj) {
if (typeof newObj !== "object" || newObj === null) {
newObj = {
@ -314,6 +417,7 @@ export default {
this.saved = true
this.title = this.file.name
this.$emit('onEdit', this.saved)
this.$refs.historyPopover.loadHistoryList(this.file.id)
}).catch(() => {
this.saveBtnUpdating = false
})
@ -466,7 +570,8 @@ export default {
line-height: 40px;
}
.minder-save {
.minder-operation {
display: flex;
float: right;
margin-top: -40px;
margin-right: 30px;

View File

@ -162,7 +162,13 @@ export default {
_getValue() {
let editor = this._getEditor();
if(!editor) return '';
if(!editor) {
if (this.diffEditor) {
return this.editor.getModel().modified.getValue()
} else {
return ''
}
}
return editor.getValue();
},
@ -175,8 +181,8 @@ export default {
this.editorMounted(editor, monaco);
if(this.diffEditor){
editor.onDidUpdateDiff((event) => {
const value = this._getValue();
this._emitChange(value, event);
const value = this._getValue()
this._emitChange(value, event)
});
}else{
editor.onDidChangeModelContent(event => {

View File

@ -3222,6 +3222,12 @@ export default {
this.officePreviewRow = row
return
}
if (row.contentType.indexOf('utf-8') > -1) {
// 文本文件
this.textPreviewRow = row
this.textPreviewVisible = true
return
}
this.notPreviewDialogVisible = true
}
},

View File

@ -2,7 +2,23 @@
<div class="drawio-content" :style="{'top': readOnly ? '0': '2.5rem'}">
<div class="drawio-title">
<div class="drawio-title-name" :style="{'color': saved ? '': '#ff8200'}">{{title}}</div>
<div class="drawio-save"><el-button v-if="!saved" @click="save" size="mini" :loading="saveBtnUpdating">保存</el-button></div>
<div class="drawio-operation">
<div style="margin-right: 15px">
<el-button v-if="historyVersion.metadata.time" @click="cancelPreview" size="mini">取消预览</el-button>
</div>
<history-popover
ref="historyPopover"
:has-history-version.sync="hasHistoryVersion"
:history-list-popover-visible.sync="historyListPopoverVisible"
:history-operation-loading="!loading.closed"
:saved.sync="saved"
@viewHistoryFile="viewHistoryFile"
@recoverySuccess="recoverySuccess"
>
</history-popover>
<div><el-button v-if="!saved" @click="save" size="mini" :loading="saveBtnUpdating">保存</el-button></div>
</div>
</div>
<iframe ref="myFlow" class="drawio-iframe" :src="url" :title="file.name"></iframe>
</div>
@ -13,9 +29,12 @@
import api from '@/api/file-api'
import txtApi from "@/api/markdown-api"
import Bus from "@/assets/js/bus";
import HistoryPopover from "@/components/HistoryPopover/index.vue";
import historyApi from "@/api/file-history";
export default {
name: "Drawio",
components: {HistoryPopover},
props: {
file: {
type: Object,
@ -40,7 +59,16 @@ export default {
saveBtnUpdating: false,
title: this.file.name,
saved: true,
reday: false
ready: false,
currentContext: undefined,
currentTitle: undefined,
historyVersion: {metadata: {}},
hasHistoryVersion: false,
historyListPopoverVisible: false,
viewHistory: false,
loading: {
closed: true
},
}
},
created() {
@ -90,15 +118,67 @@ export default {
this.bakData = res.data.contentText
this.updateContent()
})
this.$nextTick(()=> {
this.$refs.historyPopover.loadHistoryList(this.file.id)
})
},
immediate: true,
deep: true,
},
},
methods: {
/**
* 保存并关闭
*/
viewHistoryFile({historyInfo, recovery}) {
if (!this.saved) {
this.$message({type: 'info', message: "请先保存当前修改的内容"})
return
}
let loadingInfo = recovery ? '恢复中...' : '加载中...'
this.loading = this.$message({
iconClass: 'el-icon-loading',
type: 'info',
duration: 0,
dangerouslyUseHTMLString: true,
message: `<span>&nbsp;&nbsp;${loadingInfo}</span>`
})
historyApi.previewHistoryText({id: historyInfo.id}).then((res) => {
this.loading.close()
if (this.historyListPopoverVisible) {
this.historyListPopoverVisible = false
}
if (recovery) {
this.cancelPreview(null, res.data.contentText)
this.$message({message: '恢复成功',type: 'success'})
} else {
this.currentContext = this.xml
this.currentTitle = this.title
this.bakData = res.data.contentText
this.xml = res.data.contentText
this.updateContent()
this.historyVersion = historyInfo
this.title = `历史版本:${historyInfo.metadata.time}`
this.viewHistory = true
}
}).catch(() => {
this.loading.close()
})
},
recoverySuccess({historyInfo}) {
this.viewHistoryFile({historyInfo: historyInfo, recovery: true})
},
cancelPreview(event, currentContext) {
if (!currentContext) {
currentContext = this.currentContext
}
this.historyVersion = {metadata: {}}
this.xml = currentContext
this.bakData = currentContext
this.updateContent()
if (this.currentTitle) {
this.title = this.currentTitle
}
this.viewHistory = false
},
saveAndClose() {
this.save()
this.$emit('onClose')
@ -127,6 +207,7 @@ export default {
this.saved = true
this.title = this.file.name
this.$emit('onEdit', this.saved)
this.$refs.historyPopover.loadHistoryList(this.file.id)
}).catch(() => {
this.saveBtnUpdating = false
})
@ -136,13 +217,13 @@ export default {
if (event.source !== editWindow) {
return
}
let doc = this.$refs.myFlow.contentWindow.document
const payload = $J.jsonParse(event.data)
switch (payload.event) {
case "init":
this.reday = true
this.ready = true
this.$emit('onReady')
this.updateContent()
let doc = this.$refs.myFlow.contentWindow.document
let helpMenu = doc.querySelector('.geMenubarContainer .geMenubar').childNodes[5]
helpMenu.style.display = 'none'
break
@ -155,15 +236,24 @@ export default {
break
case "autosave":
this.bakData = payload.xml
this.xml = payload.xml
this.saved = false
this.title = `*${this.file.name}`
this.$emit('onEdit', this.saved)
let undo = doc.querySelector('.geSprite.geSprite-undo')
if (undo && !this.viewHistory) {
let undoClass = undo.parentElement.getAttribute('class')
if (undoClass && undoClass.indexOf('mxDisabled') > -1) {
this.saved = true
if (this.title === `*${this.file.name}`) {
this.title = this.file.name
}
} else {
this.xml = payload.xml
this.saved = false
this.title = `*${this.file.name}`
}
this.$emit('onEdit', this.saved)
}
break
case "save":
// save
this.save()
break
}
@ -187,18 +277,19 @@ export default {
z-index: 2001;
position: absolute;
top: 0;
width: 50%;
margin-left: 50%;
width: calc(50% + 121px);
margin-left: calc(50% - 121px);
height: 0;
.drawio-title-name {
line-height: 32px;
}
.drawio-save {
.drawio-operation {
display: flex;
float: right;
margin-top: -32px;
margin-right: 32px;
margin-right: 15px;
line-height: 32px;
}
}

View File

@ -1,9 +1,22 @@
<template>
<di>
<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>
</di>
</div>
</template>
<script>
@ -12,9 +25,11 @@ import api from "@/api/file-api"
import fileConfig from "@/utils/file-config";
import Bus from "@/assets/js/bus";
import SparkMD5 from 'spark-md5'
import HistoryPopover from "@/components/HistoryPopover/index.vue";
export default {
name: "OnlyOfficeEditor",
components: {HistoryPopover},
props: {
id: {
type: String,
@ -48,15 +63,23 @@ export default {
},
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
}
},
beforeDestroy() {
if (this.docEditor !== null) {
this.docEditor.destroyEditor()
this.docEditor = null
}
this.destroyEditor()
},
computed: {
fileType() {
@ -90,6 +113,16 @@ export default {
})
},
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'
}
}
}
},
mounted() {
@ -101,6 +134,74 @@ export default {
Bus.$off('previewSaveAndClose')
},
methods: {
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}) {
console.log('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) {
this.docEditor.setHistoryData({
"fileType": this.fileType,
"key": `${new Date(historyInfo.metadata.time).getTime()}-${SparkMD5.hash(this.file.id)}`,
"url": url,
"version": historyInfo.metadata.time,
})
},
onRequestHistory() {
this.docEditor.refreshHistory({
"currentVersion": 'currentVersion',
"history": []
})
},
getType(type) {
switch (type) {
case 'doc':
@ -116,91 +217,92 @@ export default {
}
return type
},
loadFile() {
if (this.docEditor !== null) {
this.docEditor.destroyEditor()
this.docEditor = null
}
async loadFile() {
this.$nextTick(()=> {
this.$refs.historyPopover.loadHistoryList(this.file.id)
})
this.destroyEditor()
this.fileUrl = window.location.origin + fileConfig.previewUrl(this.$store.state.user.name, this.file, this.$store.getters.token)
if(this.readOnly && window.shareId){
if (this.readOnly && window.shareId) {
this.fileUrl = window.location.origin + fileConfig.publicPreviewUrl(this.file, window.shareId, this.$store.getters.shareToken)
}
let fileKey = `${new Date(this.file.updateDate).getTime()}-${SparkMD5.hash(this.file.id)}`
this.fileKey = `${new Date(this.file.updateDate).getTime()}-${SparkMD5.hash(this.file.id)}`
let callbackUrl = fileConfig.officeCallBackUrl(this.$store.getters.token, this.$store.getters.name, this.file.id)
const config = {
"document": {
"fileType": this.fileType,
"key": fileKey,
"title": this.file.name,
"url": this.fileUrl,
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
editorConfig: {
mode: "edit",
lang: "zh",
user: {
id: this.$store.state.user.userId,
name: this.$store.state.user.name
},
"customization": {
"logo": null,
"autosave": false,
"comments": true,
"compactHeader": false,
"compactToolbar": false,
"compatibleFeatures": false,
"forcesave": false,
"help": false,
"hideRightMenu": false,
"hideRulers": false,
"submitForm": false,
"about": null,
"feedback": false
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,
callbackUrl: callbackUrl,
}
}
if (!this.$pc) {
config.type = 'mobile'
this.docEditorConfig.type = 'mobile'
}
if (this.readOnly) {
config.editorConfig.mode = "view"
config.editorConfig.callbackUrl = null
if (!config.editorConfig.user.id) {
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)
}
config.editorConfig.user.id = "visitor_" + visitor
config.editorConfig.user.name = "Visitor_" + visitor
this.docEditorConfig.editorConfig.user.id = "visitor_" + visitor
this.docEditorConfig.editorConfig.user.name = "Visitor_" + visitor
}
}
config.events = {
this.docEditorConfig.events = {
"onAppReady": this.onAppReady,
"onDocumentReady": this.onDocumentReady,
"onDocumentStateChange": this.onDocumentStateChange,
"onRequestHistory": this.onRequestHistory,
"onRequestHistoryData": this.onRequestHistoryData,
}
this.$nextTick(() => {
this.docEditor = new DocsAPI.DocEditor(this.id, config)
this.titleObserver = null
this.docEditor = new DocsAPI.DocEditor(this.id, this.docEditorConfig)
})
},
onClickHistoryVersion(event) {
this.historyListPopoverVisible = !this.historyListPopoverVisible
event.stopPropagation()
},
onAppReady() {
this.$emit('onReady')
console.log('onAppReady')
},
onDocumentReady() {
console.log('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 editorNavbar = doc.getElementById("editor-navbar")
console.log(editorNavbar)
// editorNavbar.style.display = 'none'
}
let logo = doc.querySelector('.extra .logo')
@ -210,6 +312,27 @@ export default {
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'
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')
@ -217,7 +340,14 @@ export default {
},
requestClose() {
this.$emit('manualSave')
}
},
destroyEditor() {
if (this.docEditor !== null) {
this.docEditor.destroyEditor()
this.docEditor = null
this.titleObserver = null
}
},
}
}
</script>

View File

@ -94,13 +94,13 @@ export default {
this.previewDocument = document.querySelector('.preview-block')
})
this.checkReadOnly(this.file.userId)
// 6秒后还没加载出来视为加载失败
// 12秒后还没加载出来视为加载失败
let that = this
this.delayClosing = setTimeout(function () {
if (!that.readyShow) {
that.loadFileFaild()
}
},6000)
},12000)
}
}
},

View File

@ -38,17 +38,33 @@
<div slot="title" class="simtext-header-title">
<span v-show="!moblie"><file-path-nav class="title-name" :path="filePathNav" @loadPath="loadPath"></file-path-nav></span>
<div class="title-extension">
<el-button v-if="isShowUpdateBtn" @click="saveAll(false)" :class="lightTheme?'':'dark-button'" size="small" :loading="updating">保存所有</el-button>
<el-button
v-if="editableTabsValue.endsWith('.md')"
:class="lightTheme?'':'dark-button'"
size="small"
circle
@click="changePreviewMode"
:title="previewMode?'编辑':'预览'"
<history-popover
ref="historyPopover"
:saved="!isShowUpdateBtn"
:has-history-version.sync="hasHistoryVersion"
:history-list-popover-visible.sync="historyListPopoverVisible"
:history-operation-loading="!loading.closed"
:light-theme.sync="lightTheme"
buttonSize="small"
@viewHistoryFile="viewHistoryFile"
@recoverySuccess="recoverySuccess"
@loadHistoryPathListSuccess="loadHistoryPathListSuccess"
>
<icon :type="previewMode?lightTheme?'icon-quxiaoyulan1':'icon-quxiaoyulan1-copy':lightTheme?'icon-yulan':'icon-yulan-copy'"></icon>
</el-button>
</history-popover>
<el-button v-if="isShowUpdateBtn" @click="saveAll(false)" :class="lightTheme?'':'dark-button'" size="small" :loading="updating">保存所有</el-button>
<!-- <el-button-->
<!-- v-if="editableTabsValue.endsWith('.md')"-->
<!-- :class="lightTheme?'':'dark-button'"-->
<!-- size="small"-->
<!-- circle-->
<!-- @click="changePreviewMode"-->
<!-- :title="previewMode?'编辑':'预览'"-->
<!-- >-->
<!-- <icon :type="previewMode?lightTheme?'icon-quxiaoyulan1':'icon-quxiaoyulan1-copy':lightTheme?'icon-yulan':'icon-yulan-copy'"></icon>-->
<!-- </el-button>-->
<el-button
:class="lightTheme?'':'dark-button'"
size="small"
@ -86,7 +102,7 @@
</div>
<div class="editor-resize" :style="{marginLeft: panelReadOnly || moblie ?`0px`:`${contentsWidth}px`,transition: transition}">
<div class="darg-resize-conter"></div>
<i v-show="!panelReadOnly && !moblie " class="editor-resize-conter" icon="el-icon-arrow-right" title="影藏文件目录" @click="hideContents"/>
<i v-show="!panelReadOnly && !moblie " class="editor-resize-conter" icon="el-icon-arrow-right" title="隐藏文件目录" @click="hideContents"/>
</div>
<div :style="{width: editorWidth-2+'px'}">
<el-tabs v-model="editableTabsValue" type="card" closable @tab-remove="removeTab" @tab-click="clickTab">
@ -111,7 +127,7 @@
:height="editorHieght"
:theme="lightTheme?'vs':'vs-dark'"
:language="item.language"
:diffEditor="diffEditor"
:diffEditor="item.hasHistoryVersion"
original="..."
:options="options"
@change="change($event,index)"
@ -132,6 +148,7 @@
import _ from "lodash"
import '@/utils/directives.js'
import api from '@/api/file-api'
import historyApi from '@/api/file-history'
import markdownApi from '@/api/markdown-api'
import { iconClass,lineWrapping,suffix } from '@/utils/file-type'
@ -146,10 +163,12 @@
import Icon from '@/components/Icon/Icon.vue'
import VditorPreview from "@/components/preview/VditorPreview";
import HistoryPopover from "@/components/HistoryPopover/index.vue";
export default {
name: "SimTextPreview",
components: {
HistoryPopover,
Icon,MonacoEditor,FileTree,FancyTree,MessageDialog,FilePathNav,VditorPreview
},
props: {
@ -174,6 +193,8 @@
}
}
},
computed: {
},
data(){
return{
lightTheme: true,
@ -194,19 +215,20 @@
// 换行
wordWrap: this.lineWrapping ? 'wordWrapColumn':'',
wordWrapMinified: true,
wrappingIndent: "indent"
wrappingIndent: "indent",
preview: true,
previewMode: 'subwordSmart'
},
diffEditor: false,
textPreviewVisible: false,
fullscreen: false,
dialogWidth: 0,
dialogWidth: 650,
minDialogWidth: 650,
dialogWidthPercent: 0.7,
lastTransform: undefined,
contentsWidth: 273,
editorWidth: 1035,
editorHieght: 640,
transition: 'all 500ms ease 0s',
content: '',
newContent: '',
previewMode: true,
isShowUpdateBtn: false,
@ -227,16 +249,20 @@
editableTabs: [],
editableValueMap: new Map(),
abortControllerMap: new Map(),
editableVersionMap: new Map(),
hasHistoryVersion: false,
historyListPopoverVisible: false,
historyVersion: {metadata: {}},
filePathNav: '',
toolbars: {
readmodel: true, // 沉浸式阅读
navigation: true, // 导航目录
}
},
}
},
mounted() {
this.$nextTick(()=>{
this.dialogWidth = document.body.clientWidth * this.dialogWidthPercent
this.setDialogWidth()
})
},
directives: {
@ -311,13 +337,10 @@
}
this.textPreviewVisible = true
this.requestStream(request, params, 0)
let pathname = res.data.path.substring(1, res.data.path.length) + res.data.name
if ('previewTextByPath' === request) {
pathname = `/${res.data.path}/${res.data.name}`
}
// 加载tabs
this.editableTabs.push({
title: res.data.name,
@ -326,11 +349,16 @@
status: undefined,//标签状态
name: pathname
})
this.requestStream(request, params, 0)
this.editableTabsValue = pathname
// 加载历史版本
this.$refs.historyPopover.loadHistoryPathList(pathname)
// 界面的渲染后的初始化工作
this.$nextTick(() => {
this.onDialogDblClick()
this.dargDialogSize()
this.dragDialogSize()
this.dragControllerDiv()
this.setTheme()
})
@ -347,9 +375,90 @@
if(value.endsWith('.md')){
this.previewMode = false
}
this.hasHistoryVersion = this.editableVersionMap.get(value)
if (this.hasHistoryVersion) {
let currentIndex = this.editableTabs.findIndex(editable => editable.name === value)
const tab = this.editableTabs[currentIndex]
this.historyVersion = tab.historyVersion || {metadata: {}}
} else {
this.historyVersion = {metadata: {}}
}
}
},
methods:{
setDialogWidth() {
this.dialogWidth = document.body.clientWidth * this.dialogWidthPercent
},
loadHistoryPathListSuccess({res, pathname}) {
if (res.count && res.count > 0) {
this.editableVersionMap.set(pathname, true)
} else {
this.editableVersionMap.set(pathname, false)
}
this.hasHistoryVersion = this.editableVersionMap.get(pathname)
},
// loading
viewHistoryFile({historyInfo, diff, recovery}) {
const pathname = this.editableTabsValue
let currentIndex = this.editableTabs.findIndex(editable => editable.name === pathname)
const tab = this.editableTabs[currentIndex]
if (this.isShowUpdateBtn) {
this.$message({type: 'info', message: "请先保存当前修改的内容"})
return
}
const currentContentText = this.getContentText(currentIndex)
let loadingInfo = recovery ? '恢复中...' : '加载中...'
this.loading = this.$message({
iconClass: 'el-icon-loading',
type: 'info',
duration: 0,
dangerouslyUseHTMLString: true,
message: `<span>&nbsp;&nbsp;${loadingInfo}</span>`
})
historyApi.previewHistoryText({id: historyInfo.id}).then((res) => {
// add new tab
let newTab = {
title: tab.title,
copyTitle: tab.copyTitle,
status: undefined,
language: tab.language,
name: pathname,
hasHistoryVersion: !recovery || diff,
historyVersion: historyInfo
}
// remove
this.removeTab(pathname)
this.$nextTick(() => {
this.loading.close()
this.editableTabs.splice(currentIndex, 0, newTab)
if (diff) {
this.setEditMap(currentIndex, currentContentText, res.data.contentText, historyInfo.metadata.time)
} else {
this.setEditMap(currentIndex, res.data.contentText)
}
this.editableTabsValue = pathname
if (this.historyListPopoverVisible) {
this.historyListPopoverVisible = false
}
if (recovery) {
this.historyVersion = {metadata: {}}
} else {
this.historyVersion = newTab.historyVersion
}
if (recovery) {
this.$message({message: '恢复成功',type: 'success'})
}
})
}).catch(() => {
this.loading.close()
})
},
recoverySuccess({historyInfo}) {
this.viewHistoryFile({historyInfo: historyInfo, diff: false, recovery: true})
},
abortControllerAbort(index) {
const map = this.abortControllerMap.get(index)
if (map !== undefined && map.abort !== undefined) {
@ -362,7 +471,7 @@
const readOnly = this.panelReadOnly
this.options.readOnly = true
this.abortControllerAbort(index)
const queryString = this.buildQueryString(params);
const queryString = this.buildQueryString(params)
let url = "/api/preview/text/stream"
if (request === 'previewTextByPath') {
url = "/api/preview/path/text/stream"
@ -374,8 +483,7 @@
let result = "";
// 创建一个节流函数,限制更新频率为每 1500 毫秒一次
const throttledUpdateContent = _.throttle(text => {
this.content = text;
this.setEditMap(index, this.content)
this.setEditMap(index, text)
}, 1500);
this.abortControllerMap.set(index, {abort: abortController, throttle: throttledUpdateContent})
@ -387,8 +495,7 @@
if (done) {
// 确保在流结束时取消节流
throttledUpdateContent.cancel();
this.content = result;
this.setEditMap(index, this.content)
this.setEditMap(index, result)
this.options.readOnly = readOnly
return;
}
@ -454,7 +561,7 @@
}
},
// 拖动调整窗口大小
dargDialogSize(){
dragDialogSize(){
// 右下角
let nwse = document.querySelector('.el-dialog__body .nwse-resize')
if(nwse){
@ -567,7 +674,7 @@
if(row.isFolder){
return
}
if(!suffix.simText.includes(row.suffix)){
if(!suffix.simText.includes(row.suffix) && row.contentType.indexOf('utf-8') < 0){
return
}
let languages = monaco.languages.getLanguages();
@ -621,6 +728,9 @@
}
}
this.editableTabsValue = row.path
// 加载历史版本
this.$refs.historyPopover.loadHistoryPathList(row.path)
}).catch(() => {
this.loading.close()
})
@ -629,15 +739,50 @@
this.clickTab()
}
},
setEditMap(index, text) {
getContentText(index) {
let ref = 'monacoEditor' + index
return this.$refs[ref][0]._getValue()
},
setEditMap(index, text, historyText, historyVersion) {
this.editableValueMap.set(index, text)
this.$nextTick(()=> {
let ref = 'monacoEditor' + index
this.$refs[ref][0]._setValue(text)
if (historyText) {
this.$refs[ref][0]._setModel(text, historyText)
} else {
this.$refs[ref][0]._setValue(text)
}
if (!this.$pc && this.dialogWidthPercent < 1) {
this.fullScreen()
}
})
this.$nextTick(() => {
const pathname = this.editableTabs[index].name
let monacoDiffEditor = document.getElementById(`pane-${pathname}`)
let original = monacoDiffEditor.querySelector(".editor.original")
let current = monacoDiffEditor.querySelector(".editor.modified")
if (original !== null && current !== null) {
this.setVersionInfo(original, historyVersion)
this.setVersionInfo(current, "当前版本")
}
})
},
setVersionInfo(editor, versionInfo) {
const existingDiv = editor.querySelector('.version-info');
if (!existingDiv) {
const div = document.createElement('div');
div.className = 'version-info';
div.textContent = versionInfo;
if (editor.firstChild) {
editor.insertBefore(div, editor.firstChild);
} else {
editor.appendChild(div);
}
} else {
existingDiv.textContent = versionInfo
}
},
containerResize() {
this.loadEditorSize()
@ -666,7 +811,7 @@
}
},
loadEditorSize(){
this.dialogWidth = document.body.clientWidth * this.dialogWidthPercent
this.setDialogWidth()
if(this.contentsHide){
this.editorWidth = this.dialogWidth
}else{
@ -701,7 +846,7 @@
this.isSaveDialogVisible = false
this.isShowUpdateBtn = false
},
// 放弃修改
// Abandon Modification
closeTabDialog(){
this.isTabSaveDialogVisible = false
this.editableTabsValue = this.nextActiveName
@ -716,24 +861,26 @@
this.$emit('update:status', this.textPreviewVisible)
this.isSaveDialogVisible = false
this.isShowUpdateBtn = false
this.historyVersion = {metadata: {}}
this.closeAllTabs()
},
change(value,index) {
let editableTabValue = this.editableTabs[index]
if(value === this.editableValueMap.get(index)){
if(this.editableTabs[index].copyTitle !== this.editableTabs[index].title){
this.editableTabs[index].title = this.editableTabs[index].copyTitle
this.editableTabs[index].status = 'Modified'
// 没有任何改变
if(editableTabValue.copyTitle !== editableTabValue.title){
editableTabValue.title = editableTabValue.copyTitle
editableTabValue.status = 'Modified'
// without any change
if(this.editableTabs.findIndex(tab=>tab.title !== tab.copyTitle) < 0){
this.isShowUpdateBtn = false
}
}
}else{
if(this.editableTabs[index].copyTitle === this.editableTabs[index].title){
this.editableTabs[index].title += '*'
this.editableTabs[index].status = 'Modifying'
if(editableTabValue.copyTitle === editableTabValue.title){
editableTabValue.title += '*'
editableTabValue.status = 'Modifying'
this.isShowUpdateBtn = true
this.editableTabs[index].change = value
editableTabValue.change = value
}
}
},
@ -758,7 +905,7 @@
this.abortControllerAbort(key)
}
},
save(value,index) {
save(value, index) {
if(value !== this.editableTabs[index].content && this.isShowUpdateBtn){
this.update(value,this.editableTabs[index].name,index)
}
@ -794,6 +941,7 @@
}
}
if(!this.modifyMsg){
this.$refs.historyPopover.loadHistoryPathList(this.editableTabsValue)
this.modifyMsg = this.$message({
message: "更新成功",
type: 'success',
@ -815,11 +963,11 @@
if(this.fullscreen){
this.lastTransform = dragDom.style.transform
this.dialogWidthPercent = 1
this.dialogWidth = document.body.clientWidth * this.dialogWidthPercent
this.setDialogWidth()
dragDom.style.transform="translate("+0+"px,"+0+"px)";
}else{
this.dialogWidthPercent = 0.7
this.dialogWidth = document.body.clientWidth * this.dialogWidthPercent
this.setDialogWidth()
let x = (document.body.clientWidth - this.dialogWidth)/2
let y = (document.body.clientHeight - this.dialogWidth)/2
if(this.lastTransform){
@ -880,8 +1028,8 @@
this.clickTab()
},
clickTab() {
this.editorWidth += 0.001
this.editorHieght += 0.001
// this.editorWidth += 0.001
// this.editorHieght += 0.001
},
removeTab(targetName) {
let tabs = this.editableTabs;
@ -900,13 +1048,19 @@
}
this.nextActiveName = activeName
if(this.editableTabs[removeIndex].status !== 'Modifying'){
this.editableTabsValue = activeName
this.editableTabs = tabs.filter(tab => tab.name !== targetName)
}else{
this.removeIndex = removeIndex
this.isTabSaveDialogVisible = true
}
this.editableTabsValue = activeName
this.abortControllerAbort(removeIndex)
setTimeout(()=> {
if (this.editableTabs.length < 1) {
this.hasHistoryVersion = false
this.historyVersion = {metadata: {}}
}
}, 100)
},
}
}
@ -983,15 +1137,14 @@
background: #ececec;
border: 1px solid #ececec;
}
.light-button:hover {
background: #DCDFE6;
}
.dark-button {
background: #3e3e3e;
border: 1px solid #3e3e3e;
color: #ffffff;
}
.light-button:hover {
background: #DCDFE6;
}
.dark-button:hover {
color: #409EFF;
background-color: #181818;
@ -1407,4 +1560,5 @@
padding: 20px 30px;
background-color: #ffffff;
}
</style>

View File

@ -183,3 +183,46 @@ input {
background-color:transparent;
}
.file-history-popover {
.el-popconfirm {
width: 310px;
}
}
.dark-file-history-popover {
background-color: #202021;
color: #b7b7b7;
border: 1px solid #2e2e30;
.el-popconfirm {
width: 310px;
}
}
.dark-file-history-list.el-table--enable-row-hover {
.el-table__body tr:hover>td {
background-color: #2e2e30;
color: #fff;
}
}
.dark-file-history-list.el-table {
background-color: #202021;
tr,th,td {
background-color: #202021;
color: #b7b7b7;
border-bottom: 1px solid #3e3e3e;
}
.dark-button {
background: #3e3e3e;
border: 1px solid #3e3e3e;
color: #ffffff;
}
.dark-button:hover {
color: #409EFF;
background-color: #181818;
}
}
.dark-file-history-list.el-table::before {
background-color: #3e3e3e;
}

View File

@ -30,6 +30,10 @@ export default {
}
return fileUrl
},
// 预览历史文件
previewHistoryUrl: function(historyId, name, token) {
return `${this.baseUrl}/history/preview/file?id=${historyId}&name=${name}&jmal-token=${token}`
},
// markdown里上传图片后的图片预览地址
markdownPreviewUrl: function (path){
return window.location.origin + `${this.baseUrl}${path}`