commit
641742e4b4
@ -51,6 +51,7 @@
|
|||||||
"sortablejs": "^1.10.2",
|
"sortablejs": "^1.10.2",
|
||||||
"spark-md5": "^3.0.0",
|
"spark-md5": "^3.0.0",
|
||||||
"stompjs": "^2.3.3",
|
"stompjs": "^2.3.3",
|
||||||
|
"tippy.js": "^6.3.7",
|
||||||
"v-contextmenu": "^2.9.0",
|
"v-contextmenu": "^2.9.0",
|
||||||
"v-viewer": "^1.5.1",
|
"v-viewer": "^1.5.1",
|
||||||
"vditor": "3.10.4",
|
"vditor": "3.10.4",
|
||||||
|
|||||||
@ -1,5 +1,4 @@
|
|||||||
import request from '@/utils/request'
|
import request from '@/utils/request'
|
||||||
const qs = require('qs')
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
logList(params) {
|
logList(params) {
|
||||||
@ -9,5 +8,12 @@ export default {
|
|||||||
params
|
params
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
getFileOperationHistory(params) {
|
||||||
|
return request({
|
||||||
|
url: '/log/getFileOperationHistory',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
})
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -53,10 +53,10 @@
|
|||||||
<svg-icon v-if="item.isFolder" icon-class="folder"/>
|
<svg-icon v-if="item.isFolder" icon-class="folder"/>
|
||||||
<div v-else-if="item.contentType && item.contentType.indexOf('video') > -1">
|
<div v-else-if="item.contentType && item.contentType.indexOf('video') > -1">
|
||||||
<div v-if="item.mediaCover === undefined || item.mediaCover === 'true'">
|
<div v-if="item.mediaCover === undefined || item.mediaCover === 'true'">
|
||||||
<div v-if="grid && pc" class="grid-play-icon">
|
<div v-if="grid && pc && !details" class="grid-play-icon">
|
||||||
<svg-icon icon-class="play1"/>
|
<svg-icon icon-class="play1"/>
|
||||||
</div>
|
</div>
|
||||||
<el-image :lazy="lazy" v-if="grid" :style="{'height':details?'110px':(gridWidth-50) + 'px'}" fit="contain"
|
<el-image :lazy="lazy" v-if="grid" :style="{'height': videoImageHeight}" fit="contain"
|
||||||
:src="item.fileId ? (audioCoverUrl+item.fileId) : (audioCoverUrl+item.id)">
|
:src="item.fileId ? (audioCoverUrl+item.fileId) : (audioCoverUrl+item.id)">
|
||||||
<div slot="error" class="image-slot-error">
|
<div slot="error" class="image-slot-error">
|
||||||
<svg-icon icon-class="video"/>
|
<svg-icon icon-class="video"/>
|
||||||
@ -91,7 +91,7 @@
|
|||||||
<svg-icon v-else icon-class="audio"/>
|
<svg-icon v-else icon-class="audio"/>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="(item.contentType && item.contentType.startsWith('image')) || item.showCover">
|
<div v-else-if="(item.contentType && item.contentType.startsWith('image')) || item.showCover">
|
||||||
<el-image v-if="grid || grid === 'details'" :style="{'height':details?'110px':(gridWidth-35) + 'px'}"
|
<el-image v-if="grid || grid === 'details'" :style="imageHeightStyle"
|
||||||
fit="contain" :src="item.fileId ? (imageUrl+item.fileId) : (imageUrl+item.id)" :class="item.showCover ? 'cover' : ''">
|
fit="contain" :src="item.fileId ? (imageUrl+item.fileId) : (imageUrl+item.id)" :class="item.showCover ? 'cover' : ''">
|
||||||
<div slot="error" class="image-slot-error">
|
<div slot="error" class="image-slot-error">
|
||||||
<svg-icon icon-class="image"/>
|
<svg-icon icon-class="image"/>
|
||||||
@ -153,6 +153,24 @@ export default {
|
|||||||
mounted() {
|
mounted() {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
videoImageHeight() {
|
||||||
|
if (this.details) {
|
||||||
|
return "110px"
|
||||||
|
}
|
||||||
|
if (this.item.video && this.item.video.width > this.item.video.height) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return this.gridWidth - 35 + "px"
|
||||||
|
},
|
||||||
|
imageHeightStyle() {
|
||||||
|
if (this.details) {
|
||||||
|
return "height: 110px;"
|
||||||
|
}
|
||||||
|
if (this.item.w > this.item.h) {
|
||||||
|
return `max-height: ${this.gridWidth - 35}px`
|
||||||
|
}
|
||||||
|
return `height: ${this.gridWidth - 35}px;`
|
||||||
|
},
|
||||||
findIconClass() {
|
findIconClass() {
|
||||||
let suffix = this.item.suffix;
|
let suffix = this.item.suffix;
|
||||||
if (!suffix && this.item.fileName) {
|
if (!suffix && this.item.fileName) {
|
||||||
|
|||||||
@ -89,6 +89,7 @@
|
|||||||
<div class="search-content">
|
<div class="search-content">
|
||||||
<div class="search-class">
|
<div class="search-class">
|
||||||
<el-popover
|
<el-popover
|
||||||
|
popper-class="upload-file"
|
||||||
v-if="showUploadButton"
|
v-if="showUploadButton"
|
||||||
v-show="!(isRootPath && homeHidden)"
|
v-show="!(isRootPath && homeHidden)"
|
||||||
v-model="isShowNewFolder"
|
v-model="isShowNewFolder"
|
||||||
@ -329,10 +330,7 @@
|
|||||||
>
|
>
|
||||||
<div class="popper-arrow"></div>
|
<div class="popper-arrow"></div>
|
||||||
<ul v-for="(item, index) in menus" :key="item.label">
|
<ul v-for="(item, index) in menus" :key="item.label">
|
||||||
<li
|
<li v-if="item.operation === 'unFavorite' || item.operation === 'favorite'"
|
||||||
v-if="
|
|
||||||
item.operation === 'unFavorite' || item.operation === 'favorite'
|
|
||||||
"
|
|
||||||
class="menu-option"
|
class="menu-option"
|
||||||
@click="menusOperations(item.operation, $event)"
|
@click="menusOperations(item.operation, $event)"
|
||||||
@mouseover.prevent.stop="
|
@mouseover.prevent.stop="
|
||||||
@ -342,19 +340,23 @@
|
|||||||
menuFavoriteLeave(index, rowContextData.isFavorite)
|
menuFavoriteLeave(index, rowContextData.isFavorite)
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<label class="menuitem">
|
<div class="menuitem home-contextmenu">
|
||||||
<svg-icon :icon-class="item.iconClass" />
|
<svg-icon :icon-class="item.iconClass" />
|
||||||
<span class="menuitem text">{{ item.label }}</span>
|
<div class="home-contextmenu-title">
|
||||||
</label>
|
<span class="menuitem text">{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
<li v-else @click="menusOperations(item.operation, $event)">
|
<li v-else @click="menusOperations(item.operation, $event)">
|
||||||
<label class="menuitem">
|
<div class="menuitem home-contextmenu">
|
||||||
<svg-icon :icon-class="item.iconClass" />
|
<svg-icon :icon-class="item.iconClass" />
|
||||||
<span class="menuitem text">{{ item.label }}</span>
|
<div class="home-contextmenu-title">
|
||||||
<span v-if="item.shortcut" style="position: absolute;right: 10px;">
|
<span class="menuitem text">{{ item.label }}</span>
|
||||||
|
<span v-if="item.shortcut">
|
||||||
<kbd v-for="key in item.shortcut" :style="{fontSize: key === '⌘' ? '14px' : '12px'}">{{ key }}</kbd>
|
<kbd v-for="key in item.shortcut" :style="{fontSize: key === '⌘' ? '14px' : '12px'}">{{ key }}</kbd>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</div>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</e-vue-contextmenu>
|
</e-vue-contextmenu>
|
||||||
@ -527,6 +529,7 @@
|
|||||||
minHeight: gridMinHeight + 'px',
|
minHeight: gridMinHeight + 'px',
|
||||||
rowGap: '10px',
|
rowGap: '10px',
|
||||||
overflow: 'auto',
|
overflow: 'auto',
|
||||||
|
paddingBottom: '12px',
|
||||||
'box-shadow':
|
'box-shadow':
|
||||||
fileListScrollTop > 0
|
fileListScrollTop > 0
|
||||||
? '-1px -1px 4px #00152914'
|
? '-1px -1px 4px #00152914'
|
||||||
@ -536,26 +539,6 @@
|
|||||||
<van-grid-item
|
<van-grid-item
|
||||||
v-for="(item, index) in fileList"
|
v-for="(item, index) in fileList"
|
||||||
:key="item.id"
|
:key="item.id"
|
||||||
:title="
|
|
||||||
'大小:' +
|
|
||||||
formatSize(item.size) +
|
|
||||||
'\r\n' +
|
|
||||||
(item.w && item.h
|
|
||||||
? '尺寸:' + item.w + 'x' + item.h + '\r\n'
|
|
||||||
: '') +
|
|
||||||
'名称:' +
|
|
||||||
item.name +
|
|
||||||
'\r\n' +
|
|
||||||
'上传时间:' +
|
|
||||||
item.uploadDate +
|
|
||||||
'\r\n' +
|
|
||||||
'修改时间:' +
|
|
||||||
item.updateDate +
|
|
||||||
'\r\n' +
|
|
||||||
'路径:' +
|
|
||||||
item.path +
|
|
||||||
extendedInfo(item)
|
|
||||||
"
|
|
||||||
:style="{paddingTop: 100/gridColumnNum + '%'}"
|
:style="{paddingTop: 100/gridColumnNum + '%'}"
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
@ -573,6 +556,7 @@
|
|||||||
? 'solid 1px var(--apple-shadow-color)'
|
? 'solid 1px var(--apple-shadow-color)'
|
||||||
: '',
|
: '',
|
||||||
}"
|
}"
|
||||||
|
v-tooltip="tooltipConfig(item)"
|
||||||
@click="gridItemClick(item, $event)"
|
@click="gridItemClick(item, $event)"
|
||||||
@dblclick="fileClick(item, $event)"
|
@dblclick="fileClick(item, $event)"
|
||||||
@contextmenu.prevent="rowContextmenu(item)"
|
@contextmenu.prevent="rowContextmenu(item)"
|
||||||
@ -846,7 +830,7 @@
|
|||||||
<div class="el-message-box__container delete-attention el-alert--warning is-light">
|
<div class="el-message-box__container delete-attention el-alert--warning is-light">
|
||||||
<div class="el-message-box__status el-icon-warning"></div>
|
<div class="el-message-box__status el-icon-warning"></div>
|
||||||
<div class="el-message-box__message">
|
<div class="el-message-box__message">
|
||||||
<p>所选目录已存在下列文件</p>
|
<p>同名文件如何处理</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<dialog-file-list class="dialog-file-list" :file-list="existsFileList" :image-url="imageUrl" :audio-cover-url="audioCoverUrl"></dialog-file-list>
|
<dialog-file-list class="dialog-file-list" :file-list="existsFileList" :image-url="imageUrl" :audio-cover-url="audioCoverUrl"></dialog-file-list>
|
||||||
@ -854,7 +838,7 @@
|
|||||||
<span slot="footer" class="dialog-footer">
|
<span slot="footer" class="dialog-footer">
|
||||||
<el-button round size="small" @click="copyOrMoveConfirmVisible = false">取 消</el-button>
|
<el-button round size="small" @click="copyOrMoveConfirmVisible = false">取 消</el-button>
|
||||||
<el-button round type="warning" size="small" @click="copyOrMoveApi(copyOrMoveParams.operating, copyOrMoveParams.froms, copyOrMoveParams.to, true, copyOrMoveParams.targetPath)">覆 盖</el-button>
|
<el-button round type="warning" size="small" @click="copyOrMoveApi(copyOrMoveParams.operating, copyOrMoveParams.froms, copyOrMoveParams.to, true, copyOrMoveParams.targetPath)">覆 盖</el-button>
|
||||||
<el-button round type="primary" size="small" @click="copyOrMoveApi(copyOrMoveParams.operating, copyOrMoveParams.froms, copyOrMoveParams.to, false, copyOrMoveParams.targetPath)">不覆盖</el-button>
|
<el-button round type="primary" size="small" @click="copyOrMoveApi(copyOrMoveParams.operating, copyOrMoveParams.froms, copyOrMoveParams.to, false, copyOrMoveParams.targetPath)">跳 过</el-button>
|
||||||
</span>
|
</span>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
@ -1243,7 +1227,9 @@ export default {
|
|||||||
if (item.isFolder || !this.grid) {
|
if (item.isFolder || !this.grid) {
|
||||||
return filename;
|
return filename;
|
||||||
}
|
}
|
||||||
const singleLine = (item.contentType && item.contentType.startsWith('image')) || item.showCover
|
const verticalImage = item.w && item.h && (item.w / item.h < 1.6)
|
||||||
|
const verticalVideo = item.video && (item.video.width / item.video.height < 1.6)
|
||||||
|
const singleLine = verticalImage || verticalVideo || item.showCover
|
||||||
const gridFilenameLength = singleLine ? 13 : 28
|
const gridFilenameLength = singleLine ? 13 : 28
|
||||||
// 分离文件名和后缀
|
// 分离文件名和后缀
|
||||||
let parts = filename.split('.');
|
let parts = filename.split('.');
|
||||||
@ -1466,6 +1452,49 @@ export default {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
tooltipConfig(item) {
|
||||||
|
// 使用模板字符串构建更清晰的 HTML
|
||||||
|
const contentHTML = `
|
||||||
|
<div class="apple-tooltip-content">
|
||||||
|
<div class="tooltip-row">
|
||||||
|
<span class="tooltip-label">大小:</span>
|
||||||
|
<span class="tooltip-value">${this.formatSize(item.size)}</span>
|
||||||
|
</div>
|
||||||
|
${item.w && item.h ? `
|
||||||
|
<div class="tooltip-row">
|
||||||
|
<span class="tooltip-label">尺寸:</span>
|
||||||
|
<span class="tooltip-value">${item.w}×${item.h}</span>
|
||||||
|
</div>` : ''}
|
||||||
|
<div class="tooltip-row">
|
||||||
|
<span class="tooltip-label">名称:</span>
|
||||||
|
<span class="tooltip-value tooltip-value-name">${item.name}</span>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip-row">
|
||||||
|
<span class="tooltip-label">上传时间:</span>
|
||||||
|
<span class="tooltip-value tooltip-value-date">${item.uploadDate}</span>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip-row">
|
||||||
|
<span class="tooltip-label">修改时间:</span>
|
||||||
|
<span class="tooltip-value tooltip-value-date">${item.updateDate}</span>
|
||||||
|
</div>
|
||||||
|
<div class="tooltip-row">
|
||||||
|
<span class="tooltip-label">路径:</span>
|
||||||
|
<span class="tooltip-value tooltip-value-path">${item.path}</span>
|
||||||
|
</div>
|
||||||
|
${this.extendedInfo(item)}
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: contentHTML,
|
||||||
|
// trigger: 'click',
|
||||||
|
placement: 'left',
|
||||||
|
allowHTML: true,
|
||||||
|
theme: 'light',
|
||||||
|
// appendTo: 'parent',
|
||||||
|
arrow: true
|
||||||
|
};
|
||||||
|
},
|
||||||
// 判断给定的字符是否是中文
|
// 判断给定的字符是否是中文
|
||||||
isChineseChar(char) {
|
isChineseChar(char) {
|
||||||
return char.charCodeAt(0) > 255
|
return char.charCodeAt(0) > 255
|
||||||
@ -2144,8 +2173,8 @@ export default {
|
|||||||
node.firstChild.style.marginRight = '20px'
|
node.firstChild.style.marginRight = '20px'
|
||||||
}
|
}
|
||||||
if (index === 2) {
|
if (index === 2) {
|
||||||
node.style.borderRadius = '0 5px 5px 0'
|
node.style.borderRadius = '0 12px 12px 0'
|
||||||
node.style.borderRight = '1px solid #409eff'
|
node.style.borderRight = 'solid 1px var(--apple-shadow-color)'
|
||||||
node.firstChild.style.height = '44px'
|
node.firstChild.style.height = '44px'
|
||||||
node.firstChild.style.lineHeight = '44px'
|
node.firstChild.style.lineHeight = '44px'
|
||||||
node.firstChild.style.width = '80px'
|
node.firstChild.style.width = '80px'
|
||||||
@ -2397,7 +2426,7 @@ export default {
|
|||||||
if (!file.exif && !file.video) {
|
if (!file.exif && !file.video) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return "\r\n" + formatExif(file.exif) + formatVideo(file.video)
|
return formatExif(file.exif, '<br>') + formatVideo(file.video, '<br>')
|
||||||
},
|
},
|
||||||
upload() {
|
upload() {
|
||||||
// 打开文件选择框
|
// 打开文件选择框
|
||||||
@ -3286,35 +3315,35 @@ export default {
|
|||||||
if (columnIndex === 0) {
|
if (columnIndex === 0) {
|
||||||
return {
|
return {
|
||||||
backgroundColor: '#e0f3fc !important',
|
backgroundColor: '#e0f3fc !important',
|
||||||
borderRadius: '50px 0 0 50px',
|
borderRadius: '12px 0 0 12px',
|
||||||
borderLeft: '1px solid #409eff',
|
borderLeft: 'solid 1px var(--apple-shadow-color)',
|
||||||
borderTop: '1px solid #409eff',
|
borderTop: 'solid 1px var(--apple-shadow-color)',
|
||||||
borderBottom: '1px solid #409eff',
|
borderBottom: 'solid 1px var(--apple-shadow-color)',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (columnIndex === 3) {
|
if (columnIndex === 3) {
|
||||||
return {
|
return {
|
||||||
backgroundColor: '#e0f3fc !important',
|
backgroundColor: '#e0f3fc !important',
|
||||||
borderRadius: '0 50px 50px 0',
|
borderRadius: '0 12px 12px 0',
|
||||||
borderRight: '1px solid #409eff',
|
borderRight: 'solid 1px var(--apple-shadow-color)',
|
||||||
borderTop: '1px solid #409eff',
|
borderTop: 'solid 1px var(--apple-shadow-color)',
|
||||||
borderBottom: '1px solid #409eff',
|
borderBottom: 'solid 1px var(--apple-shadow-color)',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
backgroundColor: '#e0f3fc !important',
|
backgroundColor: '#e0f3fc !important',
|
||||||
borderTop: '1px solid #409eff',
|
borderTop: 'solid 1px var(--apple-shadow-color)',
|
||||||
borderBottom: '1px solid #409eff',
|
borderBottom: 'solid 1px var(--apple-shadow-color)',
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (columnIndex === 0) {
|
if (columnIndex === 0) {
|
||||||
return {
|
return {
|
||||||
borderRadius: '50px 0 0 50px',
|
borderRadius: '12px 0 0 12px',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (columnIndex === 3) {
|
if (columnIndex === 3) {
|
||||||
return {
|
return {
|
||||||
borderRadius: '0 50px 50px 0',
|
borderRadius: '0 12px 12px 0',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -4666,7 +4695,7 @@ export default {
|
|||||||
|
|
||||||
.is-sortable:hover {
|
.is-sortable:hover {
|
||||||
background-color: #e0f3fc;
|
background-color: #e0f3fc;
|
||||||
border-radius: 40px;
|
border-radius: 12px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
175
src/components/Timeline/index.vue
Normal file
175
src/components/Timeline/index.vue
Normal file
@ -0,0 +1,175 @@
|
|||||||
|
<template>
|
||||||
|
<div class="timeline-container">
|
||||||
|
<el-scrollbar wrap-class="scrollbar-wrapper" @scroll="handleScroll">
|
||||||
|
<div class="timeline-container-scrollbar">
|
||||||
|
<div class="scroll-decoration" v-if="scrollDecorationTop"></div>
|
||||||
|
<div v-for="(item, index) in dataList" :key="index" class="timeline-item">
|
||||||
|
<div class="timeline-icon">
|
||||||
|
<el-avatar :src="imageUrl + item.avatar" size="medium" icon="el-icon-user-solid"></el-avatar>
|
||||||
|
</div>
|
||||||
|
<div class="timeline-line" v-if="index !== dataList.length - 1"></div>
|
||||||
|
<div class="timeline-content">
|
||||||
|
<div class="timeline-date">{{ item.createTime }}</div>
|
||||||
|
<div class="timeline-action">
|
||||||
|
<span v-if="item.showName" class="timeline-action-icon">{{ item.showName }}</span>
|
||||||
|
<el-tooltip v-if="isTooltip(item.operationFun)" class="item" effect="dark" :content="item.operationFun" placement="bottom">
|
||||||
|
<span class="timeline-action-text">{{ splitOperationStr(item.operationFun) }}</span>
|
||||||
|
</el-tooltip>
|
||||||
|
<span v-else class="timeline-action-text">{{ item.operationFun }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-scrollbar>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import logApi from '@/api/log'
|
||||||
|
import IconFile from '@/components/Icon/IconFile.vue'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: 'TimelineComponent',
|
||||||
|
components: { IconFile },
|
||||||
|
props: {
|
||||||
|
showOperationHistory: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
fileId: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data() {
|
||||||
|
return {
|
||||||
|
scrollDecorationTop: false,
|
||||||
|
imageUrl: `${process.env.VUE_APP_BASE_API}/view/thumbnail?id=`,
|
||||||
|
loading: false,
|
||||||
|
dataList: [],
|
||||||
|
pageIndex: 1,
|
||||||
|
pagination: {
|
||||||
|
pageIndex: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
pageTotal: 0
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
showOperationHistory(newValue) {
|
||||||
|
if (newValue) {
|
||||||
|
this.loading = true
|
||||||
|
logApi.getFileOperationHistory({
|
||||||
|
page: this.pagination.pageIndex,
|
||||||
|
pageSize: this.pagination.pageSize,
|
||||||
|
fileId: this.fileId
|
||||||
|
}).then(res => {
|
||||||
|
this.dataList = res.data
|
||||||
|
this.pagination.pageTotal = res.count
|
||||||
|
this.loading = false
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
const scrollbarWrapper = document.querySelector('.file-operation-history .scrollbar-wrapper')
|
||||||
|
scrollbarWrapper.onscroll = e => {
|
||||||
|
this.handleScroll(e)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleScroll(e) {
|
||||||
|
// this.scrollDecorationTop = e.target.scrollTop > 0;
|
||||||
|
},
|
||||||
|
isTooltip(str) {
|
||||||
|
return !!(str && str.includes(','));
|
||||||
|
},
|
||||||
|
splitOperationStr(str) {
|
||||||
|
return str.split(',')[0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.timeline-container {
|
||||||
|
|
||||||
|
.timeline-container-scrollbar {
|
||||||
|
height: 50vh;
|
||||||
|
min-height: 450px;
|
||||||
|
padding: 0 20px;
|
||||||
|
|
||||||
|
.scroll-decoration {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 6px;
|
||||||
|
box-shadow: #dddddd 0 6px 6px -6px inset;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-item {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
padding: 12px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-icon {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
border-radius: 50%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: #f0f0f0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-icon img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-line {
|
||||||
|
position: absolute;
|
||||||
|
left: 20px;
|
||||||
|
top: 40px;
|
||||||
|
bottom: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: 1px;
|
||||||
|
background-color: #e1e4e8;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-content {
|
||||||
|
margin-left: 16px;
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-date {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #24292e;
|
||||||
|
font-weight: 500;
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-action {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #586069;
|
||||||
|
}
|
||||||
|
|
||||||
|
.timeline-action-icon {
|
||||||
|
margin-right: 4px;
|
||||||
|
color: #586069;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -23,6 +23,7 @@
|
|||||||
<icon-file
|
<icon-file
|
||||||
v-else
|
v-else
|
||||||
class="drawer-icon-font"
|
class="drawer-icon-font"
|
||||||
|
:lazy="false"
|
||||||
:grid="true"
|
:grid="true"
|
||||||
:details="true"
|
:details="true"
|
||||||
:item="file"
|
:item="file"
|
||||||
@ -53,11 +54,11 @@
|
|||||||
<el-form-item label="名称:">
|
<el-form-item label="名称:">
|
||||||
<span>{{ file.name }}</span>
|
<span>{{ file.name }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="类型:">
|
<!-- <el-form-item label="类型:">-->
|
||||||
<span>{{
|
<!-- <span>{{-->
|
||||||
file.isFolder ? '文件夹' : file.contentType
|
<!-- file.isFolder ? '文件夹' : file.contentType-->
|
||||||
}}</span>
|
<!-- }}</span>-->
|
||||||
</el-form-item>
|
<!-- </el-form-item>-->
|
||||||
<div v-if="file.music">
|
<div v-if="file.music">
|
||||||
<el-form-item label="🎵 歌手:">
|
<el-form-item label="🎵 歌手:">
|
||||||
<span>{{ file.music.singer }}</span>
|
<span>{{ file.music.singer }}</span>
|
||||||
@ -80,13 +81,22 @@
|
|||||||
<span> {{ formatSize(file.size) }}</span>
|
<span> {{ formatSize(file.size) }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="位置:" class="details-position">
|
<el-form-item label="位置:" class="details-position">
|
||||||
<span><a :href="pathUrl">{{ filepath }}</a></span>
|
<span><a :href="pathUrl" v-tooltip="{content: '文件所在位置', placement: 'bottom'}">{{ filepath }}</a></span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="上传时间:">
|
<el-form-item label="上传时间:">
|
||||||
<span>{{ file.uploadDate }}</span>
|
<span>{{ file.uploadDate }}</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="修改时间:">
|
<el-form-item label="修改时间:">
|
||||||
<span>{{ file.updateDate }}</span>
|
<el-popover
|
||||||
|
placement="right"
|
||||||
|
popper-class="file-operation-history"
|
||||||
|
width="400"
|
||||||
|
trigger="click"
|
||||||
|
@show="showOperationHistory"
|
||||||
|
@hide="hideOperationHistory">
|
||||||
|
<timeline-component :show-operation-history="operationHistory" :file-id="file.id"></timeline-component>
|
||||||
|
<span slot="reference" v-tooltip="{content: '文件操作历史', placement: 'bottom'}" class="file-operation-history-btn">{{ file.updateDate }}</span>
|
||||||
|
</el-popover>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item v-if="file.exif" label="">
|
<el-form-item v-if="file.exif" label="">
|
||||||
<span style="white-space: break-spaces;">{{ formatExif(file.exif) }}</span>
|
<span style="white-space: break-spaces;">{{ formatExif(file.exif) }}</span>
|
||||||
@ -98,12 +108,15 @@
|
|||||||
<el-button round v-if="allowOpenFile" type="primary" size="small" @click="openFile" class="file-detail-btn open-file">打开文件</el-button>
|
<el-button round v-if="allowOpenFile" type="primary" size="small" @click="openFile" class="file-detail-btn open-file">打开文件</el-button>
|
||||||
<el-button round v-if="onlyOfficeSupportedFormats(file)" type="primary" size="small" @click="openOnlyOffice" class="file-detail-btn open-file">使用OnlyOffice打开</el-button>
|
<el-button round v-if="onlyOfficeSupportedFormats(file)" type="primary" size="small" @click="openOnlyOffice" class="file-detail-btn open-file">使用OnlyOffice打开</el-button>
|
||||||
</el-form>
|
</el-form>
|
||||||
|
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import tippy from 'tippy.js';
|
||||||
import fileApi from '@/api/file-api'
|
import fileApi from '@/api/file-api'
|
||||||
|
import TimelineComponent from '@/components/Timeline'
|
||||||
import { onlyOfficeSupportedFormats } from '@/utils/file-type'
|
import { onlyOfficeSupportedFormats } from '@/utils/file-type'
|
||||||
|
|
||||||
// 引入VueOfficeDocx 组件
|
// 引入VueOfficeDocx 组件
|
||||||
@ -126,6 +139,7 @@ import settingApi from "@/api/setting-api";
|
|||||||
export default {
|
export default {
|
||||||
name: 'FileDetails',
|
name: 'FileDetails',
|
||||||
components: {
|
components: {
|
||||||
|
TimelineComponent,
|
||||||
IconFile,
|
IconFile,
|
||||||
VueOfficeDocx,
|
VueOfficeDocx,
|
||||||
VueOfficeExcel
|
VueOfficeExcel
|
||||||
@ -166,11 +180,10 @@ export default {
|
|||||||
excel: window.location.origin + fileConfig.previewUrl(this.$store.state.user.name, this.file, this.$store.getters.token)
|
excel: window.location.origin + fileConfig.previewUrl(this.$store.state.user.name, this.file, this.$store.getters.token)
|
||||||
},
|
},
|
||||||
filepath: '',
|
filepath: '',
|
||||||
pathUrl: ''
|
pathUrl: '',
|
||||||
|
operationHistory: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
mounted() {
|
|
||||||
},
|
|
||||||
computed: {
|
computed: {
|
||||||
...mapState(['message']),
|
...mapState(['message']),
|
||||||
fleUrl() {
|
fleUrl() {
|
||||||
@ -212,6 +225,19 @@ export default {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
mounted() {
|
||||||
|
if (this.$refs.viewOperationHistoryTitle) {
|
||||||
|
tippy(this.$refs.viewOperationHistoryTitle, {
|
||||||
|
content: '这是一个即时显示的提示!',
|
||||||
|
delay: 0, // 关键:设置延迟为 0毫秒
|
||||||
|
placement: 'top', // 提示框位置 (可选)
|
||||||
|
// animation: 'scale', // 动画效果 (可选, 需要引入对应 css)
|
||||||
|
// theme: 'light', // 主题 (可选, 需要引入对应 css)
|
||||||
|
// interactive: true, // 允许鼠标与提示框交互 (可选)
|
||||||
|
// arrow: true, // 显示箭头 (可选)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
methods: {
|
methods: {
|
||||||
getLocalPath(path) {
|
getLocalPath(path) {
|
||||||
if (path === '/') {
|
if (path === '/') {
|
||||||
@ -229,6 +255,12 @@ export default {
|
|||||||
openFile() {
|
openFile() {
|
||||||
this.$emit('openFile', this.file)
|
this.$emit('openFile', this.file)
|
||||||
},
|
},
|
||||||
|
showOperationHistory() {
|
||||||
|
this.operationHistory = true
|
||||||
|
},
|
||||||
|
hideOperationHistory() {
|
||||||
|
this.operationHistory = false
|
||||||
|
},
|
||||||
openOnlyOffice() {
|
openOnlyOffice() {
|
||||||
this.$emit('openOnlyOffice', this.file)
|
this.$emit('openOnlyOffice', this.file)
|
||||||
},
|
},
|
||||||
@ -239,10 +271,10 @@ export default {
|
|||||||
return formatSize(size)
|
return formatSize(size)
|
||||||
},
|
},
|
||||||
formatExif(exifInfo) {
|
formatExif(exifInfo) {
|
||||||
return formatExif(exifInfo)
|
return formatExif(exifInfo, '\r\n')
|
||||||
},
|
},
|
||||||
formatVideo(videoInfo) {
|
formatVideo(videoInfo) {
|
||||||
return formatVideo(videoInfo)
|
return formatVideo(videoInfo, '\r\n')
|
||||||
},
|
},
|
||||||
getIsSync() {
|
getIsSync() {
|
||||||
settingApi.isSync({username: this.$store.state.user.name}).then((res) => {
|
settingApi.isSync({username: this.$store.state.user.name}).then((res) => {
|
||||||
@ -255,7 +287,22 @@ export default {
|
|||||||
}
|
}
|
||||||
return onlyOfficeSupportedFormats.includes(file.suffix.toLowerCase())
|
return onlyOfficeSupportedFormats.includes(file.suffix.toLowerCase())
|
||||||
},
|
},
|
||||||
|
setTip() {
|
||||||
|
if (this.$refs.updateDateTip) {
|
||||||
|
tippy(this.$refs.updateDateTip, {
|
||||||
|
content: '显示文件操作历史!',
|
||||||
|
delay: 0, // 关键:设置延迟为 0毫秒
|
||||||
|
placement: 'top', // 提示框位置 (可选)
|
||||||
|
// animation: 'scale', // 动画效果 (可选, 需要引入对应 css)
|
||||||
|
// theme: 'light', // 主题 (可选, 需要引入对应 css)
|
||||||
|
// interactive: true, // 允许鼠标与提示框交互 (可选)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
updateSyncStatus(dataPercent) {
|
updateSyncStatus(dataPercent) {
|
||||||
|
|
||||||
|
this.setTip()
|
||||||
|
|
||||||
if (this.clickSync) {
|
if (this.clickSync) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -494,11 +541,24 @@ $bg-blur: rgba(255, 255, 255, 0.75);
|
|||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
>>> .el-scrollbar__thumb {
|
>>> .el-scrollbar__thumb {
|
||||||
background: rgba(64,158,255,0.18);
|
background: #7c7c7c2e;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
transition: background 0.2s;
|
transition: background 0.2s;
|
||||||
&:hover {
|
&:hover {
|
||||||
background: rgba(64,158,255,0.32);
|
background: #7c7c7c2e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-operation-history-btn {
|
||||||
|
cursor: pointer;
|
||||||
|
color: $primary;
|
||||||
|
text-decoration: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 2px 4px;
|
||||||
|
transition: background 0.2s;
|
||||||
|
&:hover {
|
||||||
|
background: rgba(64,158,255,0.12);
|
||||||
|
color: #1a73e8;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -445,6 +445,9 @@ $tagMenuBorderRadius: 8px;
|
|||||||
padding: 0;
|
padding: 0;
|
||||||
min-width: 110px;
|
min-width: 110px;
|
||||||
}
|
}
|
||||||
|
.tag-menu {
|
||||||
|
padding: 5px 5px;
|
||||||
|
}
|
||||||
.tag-menu li:hover {
|
.tag-menu li:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
border-radius: $tagMenuBorderRadius;
|
border-radius: $tagMenuBorderRadius;
|
||||||
|
|||||||
@ -45,7 +45,6 @@
|
|||||||
padding-inline-start: 0;
|
padding-inline-start: 0;
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
padding: 0 5px;
|
|
||||||
}
|
}
|
||||||
.newFileMenu li {
|
.newFileMenu li {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -63,7 +62,6 @@
|
|||||||
.newFileMenu li > .menuitem {
|
.newFileMenu li > .menuitem {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
line-height: 38px;
|
line-height: 38px;
|
||||||
margin-left: 10%;
|
|
||||||
}
|
}
|
||||||
.newFileMenu li > .menuitem > .text {
|
.newFileMenu li > .menuitem > .text {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -137,6 +135,26 @@
|
|||||||
border: 0 solid rgba(0, 0, 0, 0);
|
border: 0 solid rgba(0, 0, 0, 0);
|
||||||
min-width: 160px;
|
min-width: 160px;
|
||||||
border-radius: $dialogBorderRadius;
|
border-radius: $dialogBorderRadius;
|
||||||
|
padding: 10px 10px;
|
||||||
|
box-shadow: 0 5px 12px #ccc;
|
||||||
|
|
||||||
|
.home-contextmenu {
|
||||||
|
padding: 0 10px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.svg-icon {
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-contextmenu-title {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu-triangle-bottom {
|
.menu-triangle-bottom {
|
||||||
|
|||||||
@ -28,13 +28,102 @@ body {
|
|||||||
border-radius: $popperBorderRadius;
|
border-radius: $popperBorderRadius;
|
||||||
}
|
}
|
||||||
|
|
||||||
.el-message, .el-notification .el-popover, .el-tooltip__popper, .el-dropdown-menu, .el-select-dropdown, .el-picker-panel, .el-picker-panel__body, .el-picker-panel__sidebar {
|
.el-message, .el-notification, .el-popover, .el-tooltip__popper, .el-dropdown-menu, .el-select-dropdown, .el-picker-panel, .el-picker-panel__body, .el-picker-panel__sidebar {
|
||||||
border-radius: $popperBorderRadius;
|
border-radius: $popperBorderRadius;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-popover.el-popper.upload-file {
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 10px 10px;
|
||||||
|
li {
|
||||||
|
padding: 0 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.v-contextmenu {
|
.v-contextmenu {
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.el-popper.file-operation-history {
|
||||||
|
padding: 0;
|
||||||
|
border-radius: 16px;
|
||||||
|
width: 300px !important;
|
||||||
|
background-color: rgba(255, 255, 255, .75);
|
||||||
|
-webkit-backdrop-filter: blur(16px) saturate(180%);
|
||||||
|
backdrop-filter: blur(16px) saturate(180%);
|
||||||
|
.el-scrollbar__wrap {
|
||||||
|
border-radius: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tippy-box[data-theme~=light] {
|
||||||
|
border-radius: 12px;
|
||||||
|
background-color: rgba(255,255,255,.75);
|
||||||
|
-webkit-backdrop-filter: blur(16px) saturate(180%);
|
||||||
|
backdrop-filter: blur(16px) saturate(180%);
|
||||||
|
/* 推荐放在全局 CSS 或使用 :deep() */
|
||||||
|
|
||||||
|
/* 应用于 Tippy 的浅色主题下的自定义内容容器 */
|
||||||
|
/* 注意:选择器可能需要根据你的具体情况调整 */
|
||||||
|
/* 如果 appendTo: 'parent', 可能不需要 :deep() */
|
||||||
|
/* 如果 appendTo: document.body, 并且样式在 scoped style 中,则需要 :deep() */
|
||||||
|
|
||||||
|
.apple-tooltip-content {
|
||||||
|
padding: 8px 12px; /* 增加内边距 */
|
||||||
|
font-size: 12px; /* 稍小的基础字号 */
|
||||||
|
line-height: 1.5; /* 增加行高提高可读性 */
|
||||||
|
color: #333; /* 深灰色文字 */
|
||||||
|
text-align: left; /* 确保左对齐 */
|
||||||
|
max-width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-row {
|
||||||
|
display: flex; /* 使用 flex 布局方便对齐 */
|
||||||
|
justify-content: space-between; /* 两端对齐,标签在左,值在右 */
|
||||||
|
margin-bottom: 4px; /* 行间距 */
|
||||||
|
/* 如果希望标签和值都左对齐,注释掉 justify-content,并可能给 label 固定宽度 */
|
||||||
|
/* align-items: center; */ /* 如果需要垂直居中 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-row:last-child {
|
||||||
|
margin-bottom: 0; /* 最后一行无下边距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-label {
|
||||||
|
color: #666; /* 标签使用浅灰色 */
|
||||||
|
margin-right: 8px; /* 标签和值之间的间距 */
|
||||||
|
/* font-weight: 500; */ /* 可以稍微加粗一点点 */
|
||||||
|
flex-shrink: 0; /* 防止标签被压缩 */
|
||||||
|
/* 如果想让标签右对齐: */
|
||||||
|
/* text-align: right; */
|
||||||
|
/* width: 70px; */ /* 可能需要固定宽度以对齐值 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-value {
|
||||||
|
color: #111; /* 值使用更深的颜色 */
|
||||||
|
word-break: break-all; /* 长内容(如路径、名称)自动换行 */
|
||||||
|
text-align: right; /* 值右对齐,配合标签左对齐 */
|
||||||
|
/* 如果标签和值都左对齐,移除 text-align: right */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-value-path, .tooltip-value-name {
|
||||||
|
word-break: break-all;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-divider {
|
||||||
|
border: none;
|
||||||
|
border-top: 1px solid #e0e0e0; /* 浅灰色分隔线 */
|
||||||
|
margin: 8px 0; /* 分隔线上下的间距 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip-extended {
|
||||||
|
margin-top: 4px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.time-filter-dropdown {
|
.time-filter-dropdown {
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
// import Vue from 'vue'
|
// import Vue from 'vue'
|
||||||
|
import tippy from 'tippy.js';
|
||||||
|
import 'tippy.js/themes/light.css';
|
||||||
|
import 'tippy.js/dist/tippy.css';
|
||||||
|
|
||||||
// v-draw-rectangle 画矩形选区
|
// v-draw-rectangle 画矩形选区
|
||||||
Vue.directive('drawRectangle', {
|
Vue.directive('drawRectangle', {
|
||||||
@ -249,6 +252,124 @@ Vue.directive('dialogDragWidth', {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const TOOLTIP_INSTANCE_KEY = '_tippyInstance'; // 用于在元素上存储 Tippy 实例
|
||||||
|
|
||||||
|
// 默认 Tippy 配置
|
||||||
|
const defaultTippyOptions = {
|
||||||
|
delay: 0, // 默认即时显示
|
||||||
|
arrow: true,
|
||||||
|
placement: 'top',
|
||||||
|
// 在这里可以添加更多你希望的全局默认配置
|
||||||
|
// theme: 'light',
|
||||||
|
// animation: 'scale',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析指令的绑定值,返回 Tippy 配置对象
|
||||||
|
* @param {*} value - binding.value
|
||||||
|
* @returns {object | null} - 返回 Tippy 配置对象,如果无效则返回 null
|
||||||
|
*/
|
||||||
|
function parseOptions(value) {
|
||||||
|
let options = {};
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
// 如果值是字符串,将其作为 content
|
||||||
|
options.content = value;
|
||||||
|
} else if (typeof value === 'object' && value !== null) {
|
||||||
|
// 如果值是对象,直接使用(允许覆盖默认值)
|
||||||
|
// 确保至少有 content 属性,除非 Tippy 允许无 content (通常不允许或表现怪异)
|
||||||
|
if (value.content === undefined || value.content === null) {
|
||||||
|
// console.warn('v-tooltip object value must contain a "content" property.');
|
||||||
|
// 允许 content 为空字符串 ''
|
||||||
|
if (value.content !== '') {
|
||||||
|
console.warn('v-tooltip object value is missing a "content" property.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
options = { ...value }; // 复制对象,避免直接修改原始绑定对象
|
||||||
|
} else {
|
||||||
|
// 无效的值
|
||||||
|
console.warn('v-tooltip directive requires a string or an object value.');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
Vue.directive('tooltip', {
|
||||||
|
/**
|
||||||
|
* 元素首次插入 DOM 时调用
|
||||||
|
*/
|
||||||
|
inserted(el, binding) {
|
||||||
|
const userOptions = parseOptions(binding.value);
|
||||||
|
if (userOptions === null) return; // 值无效,不初始化
|
||||||
|
|
||||||
|
// 合并默认配置和用户提供的配置
|
||||||
|
const finalOptions = { ...defaultTippyOptions, ...userOptions };
|
||||||
|
|
||||||
|
// 创建 Tippy 实例并存储在元素上
|
||||||
|
el[TOOLTIP_INSTANCE_KEY] = tippy(el, finalOptions);
|
||||||
|
// console.log('Tippy initialized:', el, finalOptions);
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 组件 VNode 更新后调用 (包括子 VNode)
|
||||||
|
* 这是处理绑定值变化比较可靠的地方
|
||||||
|
*/
|
||||||
|
componentUpdated(el, binding) {
|
||||||
|
const instance = el[TOOLTIP_INSTANCE_KEY];
|
||||||
|
|
||||||
|
// 检查绑定值是否有实际变化
|
||||||
|
// 注意:对于对象,这仍然是浅比较。
|
||||||
|
// 如果对象内部属性变了但引用没变,这里可能判断为 false。
|
||||||
|
// 解决方法是在父组件中确保每次更新都传递新的对象引用。
|
||||||
|
if (binding.value === binding.oldValue && typeof binding.value === 'object') {
|
||||||
|
// 如果值是对象且引用未变,我们仍然尝试更新,以防内部属性变化
|
||||||
|
// (这是对 Vue 浅比较的弥补,但依赖父组件正确传递数据)
|
||||||
|
// console.log('Object reference unchanged, but attempting update');
|
||||||
|
} else if (binding.value === binding.oldValue) {
|
||||||
|
// 如果值是字符串或其他基础类型,且值未变,则无需更新
|
||||||
|
// console.log('Value unchanged, skipping update.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const userOptions = parseOptions(binding.value);
|
||||||
|
|
||||||
|
if (instance) {
|
||||||
|
// 如果实例存在
|
||||||
|
if (userOptions === null) {
|
||||||
|
// 新值无效,销毁现有实例
|
||||||
|
// console.log('Destroying Tippy due to invalid new value.');
|
||||||
|
instance.destroy();
|
||||||
|
el[TOOLTIP_INSTANCE_KEY] = null; // 清除引用
|
||||||
|
} else {
|
||||||
|
// 新值有效,更新实例的 props
|
||||||
|
// console.log('Updating Tippy props:', userOptions);
|
||||||
|
// setProps 会智能地合并传入的选项与现有选项
|
||||||
|
instance.setProps(userOptions);
|
||||||
|
}
|
||||||
|
} else if (userOptions !== null) {
|
||||||
|
// 如果实例不存在 (可能之前的值无效或被销毁了),但新值有效,则重新创建
|
||||||
|
// console.log('Re-initializing Tippy with new valid value.');
|
||||||
|
const finalOptions = { ...defaultTippyOptions, ...userOptions };
|
||||||
|
el[TOOLTIP_INSTANCE_KEY] = tippy(el, finalOptions);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 元素解绑时调用
|
||||||
|
*/
|
||||||
|
unbind(el) {
|
||||||
|
const instance = el[TOOLTIP_INSTANCE_KEY];
|
||||||
|
if (instance) {
|
||||||
|
// console.log('Destroying Tippy on unbind.');
|
||||||
|
instance.destroy();
|
||||||
|
}
|
||||||
|
// 删除存储的实例引用,防止内存泄漏
|
||||||
|
delete el[TOOLTIP_INSTANCE_KEY];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// 除某元素外的点击事件
|
// 除某元素外的点击事件
|
||||||
Vue.directive('clickoutside', { // 初始化指令
|
Vue.directive('clickoutside', { // 初始化指令
|
||||||
bind(el, binding, vnode) {
|
bind(el, binding, vnode) {
|
||||||
|
|||||||
@ -1,87 +1,89 @@
|
|||||||
export function formatExif(exifInfo) {
|
export function formatExif(exifInfo, lineBreak) {
|
||||||
if (!exifInfo) {
|
if (!exifInfo) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
lineBreak = lineBreak || '\r\n'
|
||||||
let exifStr = ''
|
let exifStr = ''
|
||||||
if (exifInfo['dateTimeOriginal']) {
|
if (exifInfo['dateTimeOriginal']) {
|
||||||
exifStr += `内容创作时间: ${exifInfo['dateTimeOriginal']}`
|
exifStr += `内容创作时间: ${exifInfo['dateTimeOriginal']}`
|
||||||
}
|
}
|
||||||
if (exifInfo['resolution']) {
|
if (exifInfo['resolution']) {
|
||||||
exifStr += `\r\n分辨率: ${exifInfo['resolution']}`
|
exifStr += `${lineBreak}分辨率: ${exifInfo['resolution']}`
|
||||||
}
|
}
|
||||||
if (exifInfo['make']) {
|
if (exifInfo['make']) {
|
||||||
exifStr += `\r\n设备制造商: ${exifInfo['make']}`
|
exifStr += `${lineBreak}设备制造商: ${exifInfo['make']}`
|
||||||
}
|
}
|
||||||
if (exifInfo['model']) {
|
if (exifInfo['model']) {
|
||||||
exifStr += `\r\n设备型号: ${exifInfo['model']}`
|
exifStr += `${lineBreak}设备型号: ${exifInfo['model']}`
|
||||||
}
|
}
|
||||||
if (exifInfo['aperture']) {
|
if (exifInfo['aperture']) {
|
||||||
exifStr += `\r\n光圈值: ${exifInfo['aperture']}`
|
exifStr += `${lineBreak}光圈值: ${exifInfo['aperture']}`
|
||||||
}
|
}
|
||||||
if (exifInfo['exposureTime']) {
|
if (exifInfo['exposureTime']) {
|
||||||
exifStr += `\r\n曝光时间: ${exifInfo['exposureTime']}`
|
exifStr += `${lineBreak}曝光时间: ${exifInfo['exposureTime']}`
|
||||||
}
|
}
|
||||||
if (exifInfo['focalLength']) {
|
if (exifInfo['focalLength']) {
|
||||||
exifStr += `\r\n焦距: ${exifInfo['focalLength']} 毫米`
|
exifStr += `${lineBreak}焦距: ${exifInfo['focalLength']} 毫米`
|
||||||
}
|
}
|
||||||
if (exifInfo['isoEquivalent']) {
|
if (exifInfo['isoEquivalent']) {
|
||||||
exifStr += `\r\nISO感光度: ${exifInfo['isoEquivalent']}`
|
exifStr += `${lineBreak}ISO感光度: ${exifInfo['isoEquivalent']}`
|
||||||
}
|
}
|
||||||
if (exifInfo['flash']) {
|
if (exifInfo['flash']) {
|
||||||
exifStr += `\r\n闪光灯: ${exifInfo['flash']}`
|
exifStr += `${lineBreak}闪光灯: ${exifInfo['flash']}`
|
||||||
}
|
}
|
||||||
if (exifInfo['fNumber']) {
|
if (exifInfo['fNumber']) {
|
||||||
exifStr += `\r\n光圈数: ${exifInfo['fNumber']}`
|
exifStr += `${lineBreak}光圈数: ${exifInfo['fNumber']}`
|
||||||
}
|
}
|
||||||
if (exifInfo['exposureProgram']) {
|
if (exifInfo['exposureProgram']) {
|
||||||
exifStr += `\r\n曝光程序: ${exifInfo['exposureProgram']}`
|
exifStr += `${lineBreak}曝光程序: ${exifInfo['exposureProgram']}`
|
||||||
}
|
}
|
||||||
if (exifInfo['meteringMode']) {
|
if (exifInfo['meteringMode']) {
|
||||||
exifStr += `\r\n测光模式: ${exifInfo['meteringMode']}`
|
exifStr += `${lineBreak}测光模式: ${exifInfo['meteringMode']}`
|
||||||
}
|
}
|
||||||
if (exifInfo['whiteBalanceMode']) {
|
if (exifInfo['whiteBalanceMode']) {
|
||||||
exifStr += `\r\n白平衡: ${exifInfo['whiteBalanceMode']}`
|
exifStr += `${lineBreak}白平衡: ${exifInfo['whiteBalanceMode']}`
|
||||||
}
|
}
|
||||||
if (exifInfo['software']) {
|
if (exifInfo['software']) {
|
||||||
exifStr += `内容创作者: ${exifInfo['software']}`
|
exifStr += `${lineBreak}内容创作者: ${exifInfo['software']}`
|
||||||
}
|
}
|
||||||
if (exifInfo['longitude']) {
|
if (exifInfo['longitude']) {
|
||||||
exifStr += `\r\n经度: ${exifInfo['longitude']}`
|
exifStr += `${lineBreak}经度: ${exifInfo['longitude']}`
|
||||||
}
|
}
|
||||||
if (exifInfo['latitude']) {
|
if (exifInfo['latitude']) {
|
||||||
exifStr += `\r\n纬度: ${exifInfo['latitude']}`
|
exifStr += `${lineBreak}纬度: ${exifInfo['latitude']}`
|
||||||
}
|
}
|
||||||
return exifStr
|
return exifStr
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatVideo(videoInfo) {
|
export function formatVideo(videoInfo, lineBreak) {
|
||||||
if (!videoInfo) {
|
if (!videoInfo) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
|
lineBreak = lineBreak || '\r\n'
|
||||||
let videoStr = ''
|
let videoStr = ''
|
||||||
if (videoInfo['height'] && videoInfo['width']) {
|
if (videoInfo['height'] && videoInfo['width']) {
|
||||||
videoStr += `尺寸: ${videoInfo['width']} x ${videoInfo['height']}`
|
videoStr += `尺寸: ${videoInfo['width']} x ${videoInfo['height']}`
|
||||||
}
|
}
|
||||||
if (videoInfo['bitrate']) {
|
if (videoInfo['bitrate']) {
|
||||||
videoStr += `\r\n码率: ${videoInfo['bitrate']}`
|
videoStr += `${lineBreak}码率: ${videoInfo['bitrate']}`
|
||||||
}
|
}
|
||||||
if (videoInfo['frameRate']) {
|
if (videoInfo['frameRate']) {
|
||||||
videoStr += `\r\n帧率: ${videoInfo['frameRate']} fps`
|
videoStr += `${lineBreak}帧率: ${videoInfo['frameRate']} fps`
|
||||||
}
|
}
|
||||||
if (videoInfo['format']) {
|
if (videoInfo['format']) {
|
||||||
videoStr += `\r\n格式: ${videoInfo['format']}`
|
videoStr += `${lineBreak}格式: ${videoInfo['format']}`
|
||||||
}
|
}
|
||||||
if (videoInfo['duration']) {
|
if (videoInfo['duration']) {
|
||||||
videoStr += `\r\n时长: ${videoInfo['duration']}`
|
videoStr += `${lineBreak}时长: ${videoInfo['duration']}`
|
||||||
}
|
}
|
||||||
if (videoInfo['toBitrate']) {
|
if (videoInfo['toBitrate']) {
|
||||||
videoStr += `\r\n转码后的码率: ${videoInfo['toBitrate']} kbps`
|
videoStr += `${lineBreak}转码后的码率: ${videoInfo['toBitrate']} kbps`
|
||||||
}
|
}
|
||||||
if (videoInfo['toFrameRate']) {
|
if (videoInfo['toFrameRate']) {
|
||||||
videoStr += `\r\n转码后的帧率: ${videoInfo['toFrameRate']} fps`
|
videoStr += `${lineBreak}转码后的帧率: ${videoInfo['toFrameRate']} fps`
|
||||||
}
|
}
|
||||||
if (videoInfo['toHeight']) {
|
if (videoInfo['toHeight']) {
|
||||||
videoStr += `\r\n转码后的高度: ${videoInfo['toHeight']}`
|
videoStr += `${lineBreak}转码后的高度: ${videoInfo['toHeight']}`
|
||||||
}
|
}
|
||||||
return videoStr
|
return videoStr
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,10 +50,12 @@
|
|||||||
<div class="popper-arrow"></div>
|
<div class="popper-arrow"></div>
|
||||||
<ul v-for="item in menus" :key="item.label">
|
<ul v-for="item in menus" :key="item.label">
|
||||||
<li class="menu-option" @click="menusOperations(item.operation)">
|
<li class="menu-option" @click="menusOperations(item.operation)">
|
||||||
<label class="menuitem">
|
<div class="menuitem home-contextmenu">
|
||||||
<svg-icon :icon-class="item.iconClass"/>
|
<svg-icon :icon-class="item.iconClass"/>
|
||||||
<span class="menuitem text">{{ item.label }}</span>
|
<div class="home-contextmenu-title">
|
||||||
</label>
|
<span class="menuitem text">{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</e-vue-contextmenu>
|
</e-vue-contextmenu>
|
||||||
@ -270,8 +272,8 @@
|
|||||||
:sharer="sharerUsername"
|
:sharer="sharerUsername"
|
||||||
:status.sync="iframePreviewVisible"
|
:status.sync="iframePreviewVisible"
|
||||||
></iframe-preview>
|
></iframe-preview>
|
||||||
<el-divider v-if="!linkFailed && !showShareCode" class="grid-divider" content-position="center"><i
|
<el-divider v-if="!linkFailed && !showShareCode" class="grid-divider" content-position="center">
|
||||||
class="el-icon-folder-opened"></i> {{ summaries }}
|
<i class="el-icon-folder-opened"></i> {{ summaries }}
|
||||||
</el-divider>
|
</el-divider>
|
||||||
<el-pagination
|
<el-pagination
|
||||||
background
|
background
|
||||||
@ -1144,7 +1146,8 @@ export default {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "src/styles/home-index";
|
@import 'src/styles/index';
|
||||||
|
@import 'src/styles/home-index';
|
||||||
/*.el-breadcrumb {*/
|
/*.el-breadcrumb {*/
|
||||||
/*margin: 50px;*/
|
/*margin: 50px;*/
|
||||||
.dashboard-container {
|
.dashboard-container {
|
||||||
@ -1228,9 +1231,10 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.grid-divider {
|
.grid-divider {
|
||||||
height: 0;
|
height: 30px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
width: auto;
|
width: auto;
|
||||||
|
background: unset;
|
||||||
|
|
||||||
> > > .el-divider__text {
|
> > > .el-divider__text {
|
||||||
}
|
}
|
||||||
@ -1315,8 +1319,11 @@ export default {
|
|||||||
margin-left: 5px !important;
|
margin-left: 5px !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
>>> .van-grid-item__content .svg-icon{
|
>>> .van-grid-item__content {
|
||||||
font-size: 4.5rem;
|
padding: 8px;
|
||||||
|
.svg-icon{
|
||||||
|
font-size: 4.5rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
>>> .grid-file-text {
|
>>> .grid-file-text {
|
||||||
|
|||||||
@ -562,7 +562,8 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "src/styles/home-index";
|
@import 'src/styles/index';
|
||||||
|
@import 'src/styles/home-index';
|
||||||
.container {
|
.container {
|
||||||
min-width: 1024px;
|
min-width: 1024px;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user