feat: restyle desktop file grid to baidu-style icon layout
This commit is contained in:
@@ -563,17 +563,31 @@ function applyNativeUploadProgress(payload: NativeUploadProgressEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
function fileIcon(item: FileItem) {
|
||||
if (item.isDirectory || item.type === "directory") return "📁";
|
||||
const name = String(item.name || "").toLowerCase();
|
||||
if (/\.(jpg|jpeg|png|webp|gif|bmp|svg)$/.test(name)) return "🖼️";
|
||||
if (/\.(mp4|mkv|mov|avi)$/.test(name)) return "🎬";
|
||||
if (/\.(mp3|wav|flac|aac)$/.test(name)) return "🎵";
|
||||
if (/\.(zip|rar|7z|tar|gz)$/.test(name)) return "🗜️";
|
||||
if (/\.(pdf)$/.test(name)) return "📕";
|
||||
if (/\.(doc|docx)$/.test(name)) return "📘";
|
||||
if (/\.(xls|xlsx)$/.test(name)) return "📗";
|
||||
return "📄";
|
||||
function getFileExt(item: FileItem) {
|
||||
if (item.isDirectory || item.type === "directory") return "";
|
||||
const text = String(item.displayName || item.name || "").trim();
|
||||
const idx = text.lastIndexOf(".");
|
||||
if (idx <= 0 || idx >= text.length - 1) return "";
|
||||
return text.slice(idx + 1).toLowerCase();
|
||||
}
|
||||
|
||||
function fileVisualKind(item: FileItem) {
|
||||
if (item.isDirectory || item.type === "directory") return "folder";
|
||||
const ext = getFileExt(item);
|
||||
if (!ext) return "file";
|
||||
if (["jpg", "jpeg", "png", "webp", "gif", "bmp", "svg", "heic"].includes(ext)) return "image";
|
||||
if (["mp4", "mkv", "mov", "avi", "webm", "flv"].includes(ext)) return "video";
|
||||
if (["mp3", "wav", "flac", "aac", "ogg", "m4a"].includes(ext)) return "audio";
|
||||
if (["zip", "rar", "7z", "tar", "gz", "bz2", "xz"].includes(ext)) return "archive";
|
||||
if (["pdf", "doc", "docx", "xls", "xlsx", "ppt", "pptx", "txt", "md", "csv"].includes(ext)) return "document";
|
||||
if (["apk", "exe", "msi", "dmg", "deb", "rpm"].includes(ext)) return "app";
|
||||
return "file";
|
||||
}
|
||||
|
||||
function fileExtLabel(item: FileItem) {
|
||||
const ext = getFileExt(item).toUpperCase();
|
||||
if (!ext) return "FILE";
|
||||
return ext.slice(0, 4);
|
||||
}
|
||||
|
||||
function matchFileTypeFilter(item: FileItem, type: string) {
|
||||
@@ -2480,6 +2494,13 @@ onBeforeUnmount(() => {
|
||||
<span>{{ pathState.mode === "search" ? "搜索结果" : "当前目录" }} {{ pathState.currentPath }}</span>
|
||||
</div>
|
||||
|
||||
<div class="files-summary-bar">
|
||||
<span class="files-summary-main">
|
||||
{{ pathState.mode === "search" ? `搜索完成,共 ${filteredFiles.length} 个结果` : `已加载完成,共 ${filteredFiles.length} 个` }}
|
||||
</span>
|
||||
<span class="files-summary-sub">双击打开,右键查看更多操作</span>
|
||||
</div>
|
||||
|
||||
<div class="file-drop-surface" :class="{ active: dropState.active }">
|
||||
<div v-if="pathState.loading" class="empty-tip">正在加载目录...</div>
|
||||
<div v-else-if="pathState.error" class="empty-tip error">{{ pathState.error }}</div>
|
||||
@@ -2501,7 +2522,15 @@ onBeforeUnmount(() => {
|
||||
<div v-if="batchMode" class="batch-check" :class="{ active: isBatchSelected(item.name) }">
|
||||
{{ isBatchSelected(item.name) ? "✓" : "" }}
|
||||
</div>
|
||||
<div class="file-icon-glyph">{{ fileIcon(item) }}</div>
|
||||
<div class="file-icon-shell" :class="`kind-${fileVisualKind(item)}`">
|
||||
<template v-if="item.isDirectory || item.type === 'directory'">
|
||||
<span class="folder-tab" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<span class="file-corner" />
|
||||
<span class="file-ext">{{ fileExtLabel(item) }}</span>
|
||||
</template>
|
||||
</div>
|
||||
<div class="file-name" :title="item.displayName || item.name">
|
||||
<template v-if="isInlineRenaming(item)">
|
||||
<input
|
||||
@@ -2520,8 +2549,6 @@ onBeforeUnmount(() => {
|
||||
{{ item.displayName || item.name }}
|
||||
</template>
|
||||
</div>
|
||||
<div class="file-meta">{{ fileTypeLabel(item) }} · {{ item.isDirectory ? "-" : (item.sizeFormatted || formatBytes(item.size)) }}</div>
|
||||
<div class="file-time">{{ formatDate(item.modifiedAt) }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="dropState.active" class="drop-overlay">
|
||||
@@ -3330,7 +3357,7 @@ select:focus {
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
justify-content: flex-start;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
@@ -3447,21 +3474,49 @@ select:focus {
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.files-summary-bar {
|
||||
height: 34px;
|
||||
border: 1px solid #e8eef7;
|
||||
border-radius: 10px;
|
||||
background: #f8fbff;
|
||||
padding: 0 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.files-summary-main {
|
||||
font-size: 12px;
|
||||
color: #385373;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.files-summary-sub {
|
||||
font-size: 12px;
|
||||
color: #6b8097;
|
||||
}
|
||||
|
||||
.icon-grid {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
grid-template-columns: repeat(auto-fill, 144px);
|
||||
gap: 8px 10px;
|
||||
grid-template-columns: repeat(auto-fill, 112px);
|
||||
justify-content: flex-start;
|
||||
align-content: start;
|
||||
min-height: 0;
|
||||
overflow: auto;
|
||||
padding: 2px 4px 2px 2px;
|
||||
padding: 4px;
|
||||
}
|
||||
|
||||
.file-drop-surface {
|
||||
position: relative;
|
||||
min-height: 0;
|
||||
flex: 1;
|
||||
border: 1px solid #e8eef7;
|
||||
border-radius: 12px;
|
||||
background: #fff;
|
||||
padding: 12px 10px 10px;
|
||||
}
|
||||
|
||||
.file-drop-surface.active .icon-grid {
|
||||
@@ -3469,19 +3524,19 @@ select:focus {
|
||||
}
|
||||
|
||||
.file-card {
|
||||
border: 1px solid #d8e1ee;
|
||||
border-radius: 14px;
|
||||
background: #fff;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 10px;
|
||||
background: transparent;
|
||||
text-align: left;
|
||||
padding: 12px;
|
||||
padding: 8px 6px;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
gap: 6px;
|
||||
width: 144px;
|
||||
height: 144px;
|
||||
gap: 4px;
|
||||
width: 112px;
|
||||
min-height: 132px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
}
|
||||
@@ -3492,66 +3547,139 @@ select:focus {
|
||||
}
|
||||
|
||||
.file-card:hover {
|
||||
border-color: #c6d7ee;
|
||||
box-shadow: 0 6px 12px rgba(34, 72, 116, 0.08);
|
||||
border-color: #d7e6fb;
|
||||
background: #f3f8ff;
|
||||
}
|
||||
|
||||
.file-card.selected {
|
||||
border-color: #7baaf8;
|
||||
background: #eef5ff;
|
||||
box-shadow: 0 0 0 2px rgba(29, 111, 255, 0.1);
|
||||
border-color: #a5c7f8;
|
||||
background: #e8f2ff;
|
||||
box-shadow: 0 0 0 1px rgba(73, 128, 216, 0.14) inset;
|
||||
}
|
||||
|
||||
.file-card.batchSelected {
|
||||
border-color: #3f84ec;
|
||||
box-shadow: 0 0 0 2px rgba(63, 132, 236, 0.14);
|
||||
border-color: #82b0f3;
|
||||
background: #eaf3ff;
|
||||
box-shadow: 0 0 0 1px rgba(63, 132, 236, 0.2) inset;
|
||||
}
|
||||
|
||||
.file-card.renaming {
|
||||
border-color: #7baaf8;
|
||||
box-shadow: 0 0 0 2px rgba(29, 111, 255, 0.14);
|
||||
background: #eff6ff;
|
||||
}
|
||||
|
||||
.batch-check {
|
||||
position: absolute;
|
||||
right: 8px;
|
||||
top: 8px;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #c3d3e7;
|
||||
right: 6px;
|
||||
top: 6px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid #bad0eb;
|
||||
background: #fff;
|
||||
color: #2a5da6;
|
||||
color: #1f67c9;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 12px;
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.batch-check.active {
|
||||
border-color: #3f84ec;
|
||||
background: #e6f0ff;
|
||||
border-color: #2e7be8;
|
||||
background: #2e7be8;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.file-icon-glyph {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
.file-icon-shell {
|
||||
width: 62px;
|
||||
height: 52px;
|
||||
position: relative;
|
||||
margin-top: 2px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #d7e4f8;
|
||||
background: #f2f7ff;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-size: 24px;
|
||||
border: 1px solid #cfdced;
|
||||
background: linear-gradient(180deg, #7ab5ff 0%, #4689dd 100%);
|
||||
box-shadow: 0 2px 4px rgba(32, 77, 131, 0.14);
|
||||
}
|
||||
|
||||
.file-icon-shell.kind-folder {
|
||||
height: 44px;
|
||||
margin-top: 8px;
|
||||
border-radius: 8px;
|
||||
border-color: #d8b860;
|
||||
background: linear-gradient(180deg, #f2c85e 0%, #e3b145 100%);
|
||||
box-shadow: 0 2px 4px rgba(152, 106, 14, 0.14);
|
||||
}
|
||||
|
||||
.folder-tab {
|
||||
position: absolute;
|
||||
left: 9px;
|
||||
top: -7px;
|
||||
width: 24px;
|
||||
height: 10px;
|
||||
border-radius: 6px 6px 0 0;
|
||||
background: linear-gradient(180deg, #f8d379 0%, #e8bc53 100%);
|
||||
border: 1px solid #d8b860;
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
.file-corner {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
clip-path: polygon(100% 0, 0 0, 100% 100%);
|
||||
border-top-right-radius: 8px;
|
||||
}
|
||||
|
||||
.file-ext {
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
bottom: 7px;
|
||||
transform: translateX(-50%);
|
||||
color: #fff;
|
||||
font-size: 10px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.4px;
|
||||
line-height: 1;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.file-icon-shell.kind-document {
|
||||
background: linear-gradient(180deg, #79b6ff 0%, #4c8ee2 100%);
|
||||
}
|
||||
|
||||
.file-icon-shell.kind-image {
|
||||
background: linear-gradient(180deg, #6fc7a1 0%, #3fa67b 100%);
|
||||
}
|
||||
|
||||
.file-icon-shell.kind-video {
|
||||
background: linear-gradient(180deg, #88b4ff 0%, #5b7fd9 100%);
|
||||
}
|
||||
|
||||
.file-icon-shell.kind-audio {
|
||||
background: linear-gradient(180deg, #ff9f7b 0%, #eb7059 100%);
|
||||
}
|
||||
|
||||
.file-icon-shell.kind-archive {
|
||||
background: linear-gradient(180deg, #f6bc74 0%, #e58e3a 100%);
|
||||
}
|
||||
|
||||
.file-icon-shell.kind-app {
|
||||
background: linear-gradient(180deg, #9cc36f 0%, #6ea649 100%);
|
||||
}
|
||||
|
||||
.file-icon-shell.kind-file {
|
||||
background: linear-gradient(180deg, #95b0d3 0%, #6684ad 100%);
|
||||
}
|
||||
|
||||
.file-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: #1f2b3a;
|
||||
line-height: 1.35;
|
||||
line-height: 1.4;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
overflow: hidden;
|
||||
white-space: normal;
|
||||
overflow-wrap: anywhere;
|
||||
@@ -3559,13 +3687,13 @@ select:focus {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
min-height: 34px;
|
||||
max-height: 34px;
|
||||
min-height: 33px;
|
||||
max-height: 33px;
|
||||
}
|
||||
|
||||
.inline-rename-input {
|
||||
width: 100%;
|
||||
height: 28px;
|
||||
height: 30px;
|
||||
border: 1px solid #7baaf8;
|
||||
border-radius: 8px;
|
||||
background: #fff;
|
||||
@@ -3581,21 +3709,6 @@ select:focus {
|
||||
box-shadow: 0 0 0 2px rgba(63, 132, 236, 0.16);
|
||||
}
|
||||
|
||||
.file-meta,
|
||||
.file-time {
|
||||
font-size: 11px;
|
||||
color: #60768f;
|
||||
line-height: 1.35;
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.file-time {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
.task-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
Reference in New Issue
Block a user