style: 优化样式和布局
This commit is contained in:
parent
dacc585ea3
commit
cdd3c73128
@ -51,6 +51,7 @@
|
||||
"sortablejs": "^1.10.2",
|
||||
"spark-md5": "^3.0.0",
|
||||
"stompjs": "^2.3.3",
|
||||
"tippy.js": "^6.3.7",
|
||||
"v-contextmenu": "^2.9.0",
|
||||
"v-viewer": "^1.5.1",
|
||||
"vditor": "3.10.4",
|
||||
|
||||
@ -529,6 +529,7 @@
|
||||
minHeight: gridMinHeight + 'px',
|
||||
rowGap: '10px',
|
||||
overflow: 'auto',
|
||||
paddingBottom: '12px',
|
||||
'box-shadow':
|
||||
fileListScrollTop > 0
|
||||
? '-1px -1px 4px #00152914'
|
||||
@ -538,26 +539,6 @@
|
||||
<van-grid-item
|
||||
v-for="(item, index) in fileList"
|
||||
: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 + '%'}"
|
||||
>
|
||||
<div
|
||||
@ -575,6 +556,7 @@
|
||||
? 'solid 1px var(--apple-shadow-color)'
|
||||
: '',
|
||||
}"
|
||||
v-tooltip="tooltipConfig(item)"
|
||||
@click="gridItemClick(item, $event)"
|
||||
@dblclick="fileClick(item, $event)"
|
||||
@contextmenu.prevent="rowContextmenu(item)"
|
||||
@ -1470,6 +1452,49 @@ export default {
|
||||
},
|
||||
},
|
||||
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) {
|
||||
return char.charCodeAt(0) > 255
|
||||
@ -2401,7 +2426,7 @@ export default {
|
||||
if (!file.exif && !file.video) {
|
||||
return ''
|
||||
}
|
||||
return "\r\n" + formatExif(file.exif) + formatVideo(file.video)
|
||||
return "\r\n" + formatExif(file.exif) + formatVideo(file.video, '<br>')
|
||||
},
|
||||
upload() {
|
||||
// 打开文件选择框
|
||||
|
||||
@ -5,7 +5,7 @@
|
||||
<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" icon="el-icon-user-solid"></el-avatar>
|
||||
<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">
|
||||
@ -122,8 +122,8 @@ export default {
|
||||
.timeline-icon {
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -140,8 +140,8 @@ export default {
|
||||
|
||||
.timeline-line {
|
||||
position: absolute;
|
||||
left: 16px;
|
||||
top: 32px;
|
||||
left: 20px;
|
||||
top: 40px;
|
||||
bottom: 0;
|
||||
height: 100%;
|
||||
width: 1px;
|
||||
|
||||
@ -81,7 +81,7 @@
|
||||
<span> {{ formatSize(file.size) }}</span>
|
||||
</el-form-item>
|
||||
<el-form-item label="位置:" class="details-position">
|
||||
<span><a :href="pathUrl" title="跳转至文件所在位置">{{ filepath }}</a></span>
|
||||
<span><a :href="pathUrl" v-tooltip="{content: '文件所在位置', placement: 'bottom'}">{{ filepath }}</a></span>
|
||||
</el-form-item>
|
||||
<el-form-item label="上传时间:">
|
||||
<span>{{ file.uploadDate }}</span>
|
||||
@ -95,7 +95,7 @@
|
||||
@show="showOperationHistory"
|
||||
@hide="hideOperationHistory">
|
||||
<timeline-component :show-operation-history="operationHistory" :file-id="file.id"></timeline-component>
|
||||
<span title="查看文件操作历史" slot="reference" class="file-operation-history-btn">{{ file.updateDate }}</span>
|
||||
<span slot="reference" v-tooltip="{content: '文件操作历史', placement: 'bottom'}" class="file-operation-history-btn">{{ file.updateDate }}</span>
|
||||
</el-popover>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="file.exif" label="">
|
||||
@ -114,6 +114,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import tippy from 'tippy.js';
|
||||
import fileApi from '@/api/file-api'
|
||||
import TimelineComponent from '@/components/Timeline'
|
||||
import { onlyOfficeSupportedFormats } from '@/utils/file-type'
|
||||
@ -183,8 +184,6 @@ export default {
|
||||
operationHistory: false
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
},
|
||||
computed: {
|
||||
...mapState(['message']),
|
||||
fleUrl() {
|
||||
@ -226,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: {
|
||||
getLocalPath(path) {
|
||||
if (path === '/') {
|
||||
@ -262,7 +274,7 @@ export default {
|
||||
return formatExif(exifInfo)
|
||||
},
|
||||
formatVideo(videoInfo) {
|
||||
return formatVideo(videoInfo)
|
||||
return formatVideo(videoInfo, '\r\n')
|
||||
},
|
||||
getIsSync() {
|
||||
settingApi.isSync({username: this.$store.state.user.name}).then((res) => {
|
||||
@ -275,7 +287,22 @@ export default {
|
||||
}
|
||||
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) {
|
||||
|
||||
this.setTip()
|
||||
|
||||
if (this.clickSync) {
|
||||
return
|
||||
}
|
||||
@ -514,11 +541,11 @@ $bg-blur: rgba(255, 255, 255, 0.75);
|
||||
background: transparent;
|
||||
}
|
||||
>>> .el-scrollbar__thumb {
|
||||
background: rgba(64,158,255,0.18);
|
||||
background: #7c7c7c2e;
|
||||
border-radius: 4px;
|
||||
transition: background 0.2s;
|
||||
&:hover {
|
||||
background: rgba(64,158,255,0.32);
|
||||
background: #7c7c7c2e;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -44,14 +44,86 @@ body {
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.file-operation-history {
|
||||
.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 {
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
// import Vue from 'vue'
|
||||
import tippy from 'tippy.js';
|
||||
import 'tippy.js/themes/light.css';
|
||||
import 'tippy.js/dist/tippy.css';
|
||||
|
||||
// v-draw-rectangle 画矩形选区
|
||||
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', { // 初始化指令
|
||||
bind(el, binding, vnode) {
|
||||
|
||||
@ -54,34 +54,35 @@ export function formatExif(exifInfo) {
|
||||
return exifStr
|
||||
}
|
||||
|
||||
export function formatVideo(videoInfo) {
|
||||
export function formatVideo(videoInfo, lineBreak) {
|
||||
if (!videoInfo) {
|
||||
return ''
|
||||
}
|
||||
lineBreak = lineBreak || '\r\n'
|
||||
let videoStr = ''
|
||||
if (videoInfo['height'] && videoInfo['width']) {
|
||||
videoStr += `尺寸: ${videoInfo['width']} x ${videoInfo['height']}`
|
||||
}
|
||||
if (videoInfo['bitrate']) {
|
||||
videoStr += `\r\n码率: ${videoInfo['bitrate']}`
|
||||
videoStr += `${lineBreak}码率: ${videoInfo['bitrate']}`
|
||||
}
|
||||
if (videoInfo['frameRate']) {
|
||||
videoStr += `\r\n帧率: ${videoInfo['frameRate']} fps`
|
||||
videoStr += `${lineBreak}帧率: ${videoInfo['frameRate']} fps`
|
||||
}
|
||||
if (videoInfo['format']) {
|
||||
videoStr += `\r\n格式: ${videoInfo['format']}`
|
||||
videoStr += `${lineBreak}格式: ${videoInfo['format']}`
|
||||
}
|
||||
if (videoInfo['duration']) {
|
||||
videoStr += `\r\n时长: ${videoInfo['duration']}`
|
||||
videoStr += `${lineBreak}时长: ${videoInfo['duration']}`
|
||||
}
|
||||
if (videoInfo['toBitrate']) {
|
||||
videoStr += `\r\n转码后的码率: ${videoInfo['toBitrate']} kbps`
|
||||
videoStr += `${lineBreak}转码后的码率: ${videoInfo['toBitrate']} kbps`
|
||||
}
|
||||
if (videoInfo['toFrameRate']) {
|
||||
videoStr += `\r\n转码后的帧率: ${videoInfo['toFrameRate']} fps`
|
||||
videoStr += `${lineBreak}转码后的帧率: ${videoInfo['toFrameRate']} fps`
|
||||
}
|
||||
if (videoInfo['toHeight']) {
|
||||
videoStr += `\r\n转码后的高度: ${videoInfo['toHeight']}`
|
||||
videoStr += `${lineBreak}转码后的高度: ${videoInfo['toHeight']}`
|
||||
}
|
||||
return videoStr
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user