feat: 新增文件版本管理
This commit is contained in:
parent
a95b427d73
commit
b4354b66a0
@ -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
64
src/api/file-history.js
Normal 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
|
||||
})
|
||||
},
|
||||
}
|
||||
11
src/assets/img/history.svg
Normal file
11
src/assets/img/history.svg
Normal 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 |
217
src/components/HistoryPopover/index.vue
Normal file
217
src/components/HistoryPopover/index.vue
Normal 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>
|
||||
@ -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> ${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;
|
||||
|
||||
@ -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 => {
|
||||
|
||||
@ -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
|
||||
}
|
||||
},
|
||||
|
||||
@ -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> ${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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@ -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> ${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>
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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}`
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user