@ -1,223 +1,468 @@
< 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 round
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 round size = "mini" type = "text" @click ="cancelRecovery(scope.row.id)" > 取消 < / el -button >
< el-button round type = "primary" size = "mini" @click ="confirmRecoveryHistoryFile(scope.row)" > 确定 < / el -button >
< / div >
< el-button round 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 round size = "mini" type = "text" @click ="cancelDelete(scope.row.id)" > 取消 < / el -button >
< el-button round type = "primary" size = "mini" @click ="confirmDeleteHistoryFile(scope.row.id)" > 确定 < / el -button >
< / div >
< el-button round slot = "reference" size = "mini" :id = "'deleteVersionBtn-'+scope.row.id" type = "danger" :loading = "historyOperationLoading" > 删除 < / el-button >
< / el-popover >
< el-popover
ref = "historyPopover"
: value = "historyListPopoverVisible"
style = "margin-right: 15px"
: visible - arrow = "false"
: popper - class = "(lightTheme ? 'file-history-popover' : 'dark-file-history-popover') + ' version-timeline-popover'"
placement = "bottom"
width = "400"
trigger = "manual"
@ show = "popoverShow"
@ hide = "popoverHide"
>
< div : class = "lightTheme ? 'version-history-timeline' : 'dark-version-history-timeline'" >
< div class = "scrollbar-wrapper" @scroll ="handleScroll" >
< div class = "timeline-container-scrollbar" >
< div v-if = "loading && fileHistoryList.length === 0" class="timeline-loading" > 加载中... < / div >
< div v-if = "!loading && fileHistoryList.length === 0 && finished" class="timeline-empty" > 暂无历史版本 < / div >
< div v-for = "(item, index) in fileHistoryList" :key="item.id" class="timeline-item" >
< div class = "timeline-icon" >
< el-avatar : src = "imageUrl + item.metadata.operator" size = "medium" icon = "el-icon-user-solid" > < / el-avatar >
< / div >
< / template >
< / el-table-column >
< / el-table >
< div class = "timeline-line" v-if = "index !== fileHistoryList.length - 1" > < / div >
< div class = "timeline-content" >
< div class = "timeline-header" >
< span class = "timeline-date" > { { item . metadata . time } } < / span >
< span class = "version-filename" :title = "item.metadata.filename" > { { item . metadata . filename } } < / span >
< / div >
< div class = "timeline-second-line" >
< div > < span class = "timeline-operator" > { { item . metadata . operator } } < / span > < / div >
< div class = "timeline-actions" >
< el-button round size = "mini" : class = "lightTheme ? '' : 'dark-button'"
@ click = "viewHistoryFile(item, true, false)" : loading = "historyOperationLoading" > 预览
< / el-button >
< el-button round 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 >
< el-popover placement = "bottom" width = "310"
: popper - class = "(lightTheme ? 'file-history-popover' : 'dark-file-history-popover') + ' recovery-' + item.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 > { { '确定要恢复到' + item . metadata . time + '吗?当前内容将被覆盖!'
} }
< / p >
< div style = "text-align: right; margin: 0" >
< el-button round size = "mini" type = "text" @click ="cancelRecovery(item.id)" > 取消 < / el -button >
< el-button round type = "primary" size = "mini" @click ="confirmRecoveryHistoryFile(item)" > 确定
< / el-button >
< / div >
< el-button round slot = "reference" size = "mini" :id = "'recoveryVersionBtn-'+item.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-' + item.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 > { { '确定要删除' + item . metadata . time + '吗?这将永久删除该历史版本!'
} }
< / p >
< div style = "text-align: right; margin: 0" >
< el-button round size = "mini" type = "text" @click ="cancelDelete(item.id)" > 取消 < / el -button >
< el-button round type = "primary" size = "mini" @click ="confirmDeleteHistoryFile(item.id)" > 确定
< / el-button >
< / div >
< el-button round slot = "reference" size = "mini" :id = "'deleteVersionBtn-'+item.id" type = "danger"
: loading = "historyOperationLoading" > 删除
< / el-button >
< / el-popover >
< / div >
< / div >
< / div >
< / div >
< div v-if = "loading && fileHistoryList.length > 0" class="timeline-loading-more" > 加载中... < / div >
< / div >
< / div >
< / div >
< el-button
slot = "reference"
round
: style = "versionBtnStyle"
: size = "buttonSize"
: class = "lightTheme ? ' history-version-btn' : 'dark-button history-version-btn'"
@ click = "showOrHidePopover"
>
历史版本
< / el-button >
< / el-popover >
< / template >
< script >
import historyApi from "@/api/file-history"
import historyApi from '@/api/file-history'
export default {
name : "history-popover" ,
name : 'history-popover' ,
props : {
hasHistoryVersion : {
type : Object ,
default : function ( ) {
return { metadata : { } }
}
type : Object , / / 注 意 : 这 里 是 O b j e c t , 虽 然 通 常 用 B o o l e a n , 但 为 保 持 兼 容 性 , 维 持 原 样
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
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 } ,
} ,
data ( ) {
return {
imageUrl : ` ${ process . env . VUE _APP _BASE _API } /view/thumbnail/user/ ` ,
fileHistoryList : [ ] ,
loading : false ,
finished : false ,
historyPageIndex : 1 ,
historyPageSize : 20 ,
loadApiFunction : null ,
loadApiParams : { } ,
}
} ,
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
}
return ''
} ,
} ,
watch : {
historyListPopoverVisible ( value ) {
if ( value ) {
document . getElementById ( 'app' ) . addEventListener ( 'click' , this . onGlobalClick )
} else {
/ / d e s t r o y e l e m e n t L i s t e n e r s
document . getElementById ( 'app' ) . removeEventListener ( 'click' , this . onGlobalClick )
}
} ,
} ,
methods : {
/ * *
* @ public
* /
loadHistoryList ( id ) {
if ( ! this . $store . state . user . token || ! this . $store . state . user . name ) {
return
if ( ! this . $store . state . user . token || ! this . $store . state . user . name ) return
this . resetState ( )
this . loadApiFunction = historyApi . fileHistoryList
this . loadApiParams = { fileId : id , username : this . $store . state . user . name }
this . _internalLoadHistory ( )
} ,
/ * *
* @ public
* /
loadHistoryPathList ( pathname ) {
if ( ! this . $store . getters . token ) return
this . resetState ( )
this . loadApiFunction = historyApi . fileHistoryPathList
this . loadApiParams = { path : encodeURI ( pathname ) , username : this . $store . state . user . name }
this . _internalLoadHistory ( )
} ,
/ / - - - 内 部 方 法 - - -
_internalLoadHistory ( isLoadMore = false ) {
if ( this . loading || ( isLoadMore && this . finished ) ) return
if ( ! isLoadMore ) {
this . historyPageIndex = 1
this . fileHistoryList = [ ]
} else {
this . historyPageIndex ++
}
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 )
this . loading = true
const params = {
... this . loadApiParams ,
pageIndex : this . historyPageIndex ,
pageSize : this . historyPageSize ,
}
this . loadApiFunction ( params ) . then ( res => {
if ( res . data && res . data . length > 0 ) {
this . fileHistoryList . push ( ... res . data )
}
this . finished = this . fileHistoryList . length >= res . count
if ( this . loadApiFunction === historyApi . fileHistoryList ) {
this . $emit ( 'update:hasHistoryVersion' , res . count > 0 )
} else {
this . $emit ( 'loadHistoryPathListSuccess' , { res , pathname : this . loadApiParams . path } )
}
} ) . catch ( err => {
console . error ( '加载历史版本失败' , err )
if ( isLoadMore ) this . historyPageIndex --
} ) . finally ( ( ) => {
this . loading = false
} )
} ,
loadHistoryPathList ( pathname ) {
if ( ! this . $store . getters . token ) {
return
resetState ( ) {
this . fileHistoryList = [ ]
this . historyPageIndex = 1
this . finished = false
this . loading = false
const scrollWrapper = this . $el ? this . $el . querySelector ( '.scrollbar-wrapper' ) : null
if ( scrollWrapper ) scrollWrapper . scrollTop = 0
} ,
handleScroll ( e ) {
const { scrollTop , scrollHeight , clientHeight } = e . target
if ( scrollHeight - scrollTop - clientHeight < 100 ) {
this . _internalLoadHistory ( true )
}
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 ) {
let eventClass = event . target . getAttribute ( 'class' ) || ( event . target . parentElement ? event . target . parentElement . getAttribute ( 'class' ) : '' )
if ( eventClass && eventClass . includes ( 'history-version-btn' ) ) {
return
}
if ( this . historyListPopoverVisible ) {
this . $emit ( 'update:historyListPopoverVisible' , false )
}
if ( event . target . getAttribute ( "data-tab" ) === "file" ) {
/ / 这 段 逻 辑 非 常 特 定 , 必 须 保 留
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'
if ( parentDoc ) {
let doc = parentDoc . getElementsByTagName ( 'iframe' ) [ 0 ] . contentWindow . document
let btn = doc . getElementById ( 'fm-btn-history' )
if ( btn ) btn . 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 } )
showOrHidePopover ( ) {
this . $emit ( 'update:historyListPopoverVisible' , ! this . historyListPopoverVisible )
} ,
viewHistoryFile ( historyInfo , diff , recovery ) {
this . $emit ( 'viewHistoryFile' , { historyInfo , diff , recovery } )
} ,
cancelDelete ( id ) {
document . getElementById ( 'deleteVersionBtn-' + id ) . click ( )
document . getElementById ( 'deleteVersionBtn-' + id ) . click ( )
} ,
cancelRecovery ( id ) {
document . getElementById ( 'recoveryVersionBtn-' + id ) . click ( )
document . getElementById ( 'recoveryVersionBtn-' + id ) . click ( )
} ,
confirmRecoveryHistoryFile ( historyInfo ) {
if ( ! this . saved ) {
this . $message ( { type : 'info' , message : "请先保存当前修改的内容" } )
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 } )
historyApi . recoveryHistory ( { id : historyInfo . id } ) . then ( ( res ) => {
this . $emit ( 'recoverySuccess' , { historyInfo , result : res } )
} )
} ,
confirmDeleteHistoryFile ( id ) {
this . cancelDelete ( id )
this . $emit ( 'update:historyOperationLoading' , true )
historyApi . deleteHistory ( { id : id } ) . then ( ( ) => {
historyApi . deleteHistory ( { id : id } ) . then ( ( ) => {
this . $emit ( 'update:historyOperationLoading' , false )
this . $message ( { message : '删除成功' , type : 'success' } )
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 >
/* 非 Scoped 样式 */
. file - history - popover {
padding : 0 ! important ;
border - radius : 8 px ! important ;
background - color : rgba ( 255 , 255 , 255 , .75 ) ;
- webkit - backdrop - filter : blur ( 16 px ) saturate ( 180 % ) ;
backdrop - filter : blur ( 16 px ) saturate ( 180 % ) ;
. el - popconfirm {
width : 310 px ;
}
}
. dark - file - history - popover {
background - color : rgba ( 46 , 46 , 46 , .75 ) ;
- webkit - backdrop - filter : blur ( 16 px ) saturate ( 180 % ) ;
backdrop - filter : blur ( 16 px ) saturate ( 180 % ) ;
color : # b7b7b7 ;
border : 1 px solid # 2 e2e30 ;
}
< / style >
< style scoped >
. version - history - timeline {
height : 100 % ;
}
. dark - version - history - timeline {
. timeline - date {
color : # b7b7b7 ;
}
. timeline - icon {
background : # 3 e3e3e ;
}
. timeline - operator {
color : # b7b7b7 ;
}
. version - filename {
color : # b7b7b7 ;
}
. timeline - line {
background : # 3 e3e3e ;
}
. dark - button {
background : # 3 e3e3e ;
border : 1 px solid # 3 e3e3e ;
color : # ffffff ;
}
. dark - button : hover {
color : # 409 EFF ;
background - color : # 181818 ;
}
. el - avatar {
background : # 181818 ;
}
}
. scrollbar - wrapper {
height : 400 px ;
overflow - y : auto ;
position : relative ;
}
. scrollbar - wrapper : : - webkit - scrollbar {
width : 6 px ;
background : transparent ;
}
. scrollbar - wrapper : : - webkit - scrollbar - thumb {
background : rgba ( 180 , 180 , 180 , 0.48 ) ;
border - radius : 6 px ;
}
. timeline - container - scrollbar {
padding : 10 px 15 px 10 px 20 px ;
}
. timeline - item {
position : relative ;
display : flex ;
padding : 15 px 0 ;
}
. timeline - icon {
position : relative ;
z - index : 2 ;
width : 40 px ;
height : 40 px ;
border - radius : 50 % ;
display : flex ;
align - items : center ;
justify - content : center ;
flex - shrink : 0 ;
background - color : # f0f2f5 ;
}
. timeline - line {
position : absolute ;
left : 20 px ;
top : 45 px ;
bottom : - 35 px ;
width : 1 px ;
background - color : # e1e4e8 ;
z - index : 1 ;
}
. timeline - content {
margin - left : 16 px ;
flex : 1 ;
display : flex ;
flex - direction : column ;
}
. timeline - header {
display : flex ;
justify - content : space - between ;
align - items : center ;
margin - bottom : 12 px ;
}
. timeline - date {
font - size : 14 px ;
color : # 24292 e ;
font - weight : 500 ;
white - space : nowrap ;
overflow : hidden ;
text - overflow : ellipsis ;
max - width : 220 px ;
}
. version - filename {
font - size : 12 px ;
color : # 586069 ;
flex - shrink : 0 ;
max - width : 120 px ;
white - space : nowrap ;
overflow : hidden ;
text - overflow : ellipsis ;
}
. timeline - second - line {
display : flex ;
align - items : center ;
gap : 10 px ;
justify - content : space - between ;
}
. timeline - operator {
color : # 586069 ;
font - weight : bold ;
}
. timeline - actions {
display : flex ;
align - items : center ;
gap : 10 px ;
}
. timeline - actions . el - button {
margin : 0 ! important ;
}
. timeline - loading , . timeline - empty , . timeline - loading - more {
text - align : center ;
color : # 909399 ;
padding : 20 px 0 ;
font - size : 14 px ;
}
. el - popconfirm _ _main {
margin : 12 px 0 ;
}
< / style >