feat: optimize file icons, UI, share direct links, and upload performance
- Replace CSS-only file icons with SVG icons for each file type (folder, image, video, audio, archive, document, app) - Add navigation icons to left sidebar (Baidu Netdisk style) - Enlarge file cards (108px -> 120px) with smoother transitions - Add direct links section to share page with copy/delete actions - Add Rust bridge commands for fetching and deleting direct links - Optimize local storage put() to use rename-first strategy instead of copyFileSync for instant large file completion - Show "server processing" status during upload finalization instead of appearing stuck Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -244,16 +244,25 @@ class LocalStorageClient {
|
|||||||
const tempPath = `${destPath}.uploading_${Date.now()}`;
|
const tempPath = `${destPath}.uploading_${Date.now()}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 复制到临时文件
|
|
||||||
fs.copyFileSync(localPath, tempPath);
|
|
||||||
|
|
||||||
// 如果目标文件存在,先删除
|
// 如果目标文件存在,先删除
|
||||||
if (fs.existsSync(destPath)) {
|
if (fs.existsSync(destPath)) {
|
||||||
fs.unlinkSync(destPath);
|
fs.unlinkSync(destPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 重命名临时文件为目标文件
|
// 优先尝试 rename(同文件系统下瞬时完成,大文件不再需要逐字节复制)
|
||||||
|
let movedDirectly = false;
|
||||||
|
try {
|
||||||
|
fs.renameSync(localPath, destPath);
|
||||||
|
movedDirectly = true;
|
||||||
|
} catch (renameErr) {
|
||||||
|
if (renameErr.code === 'EXDEV') {
|
||||||
|
// 跨文件系统,回退到 copy + rename
|
||||||
|
fs.copyFileSync(localPath, tempPath);
|
||||||
fs.renameSync(tempPath, destPath);
|
fs.renameSync(tempPath, destPath);
|
||||||
|
} else {
|
||||||
|
throw renameErr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 更新已使用空间(使用净增量)
|
// 更新已使用空间(使用净增量)
|
||||||
if (netIncrease !== 0) {
|
if (netIncrease !== 0) {
|
||||||
@@ -262,7 +271,7 @@ class LocalStorageClient {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
// 清理临时文件
|
// 清理临时文件
|
||||||
if (fs.existsSync(tempPath)) {
|
if (fs.existsSync(tempPath)) {
|
||||||
fs.unlinkSync(tempPath);
|
try { fs.unlinkSync(tempPath); } catch (_) {}
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1032,6 +1032,39 @@ async fn api_get_my_shares(
|
|||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn api_get_my_direct_links(
|
||||||
|
state: tauri::State<'_, ApiState>,
|
||||||
|
base_url: String,
|
||||||
|
) -> Result<BridgeResponse, String> {
|
||||||
|
request_with_optional_csrf(
|
||||||
|
&state.client,
|
||||||
|
Method::GET,
|
||||||
|
&base_url,
|
||||||
|
"/api/direct-link/my",
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tauri::command]
|
||||||
|
async fn api_delete_direct_link(
|
||||||
|
state: tauri::State<'_, ApiState>,
|
||||||
|
base_url: String,
|
||||||
|
link_id: i64,
|
||||||
|
) -> Result<BridgeResponse, String> {
|
||||||
|
request_with_optional_csrf(
|
||||||
|
&state.client,
|
||||||
|
Method::DELETE,
|
||||||
|
&base_url,
|
||||||
|
&format!("/api/direct-link/{}", link_id),
|
||||||
|
None,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
async fn api_create_share(
|
async fn api_create_share(
|
||||||
state: tauri::State<'_, ApiState>,
|
state: tauri::State<'_, ApiState>,
|
||||||
@@ -2074,6 +2107,8 @@ pub fn run() {
|
|||||||
api_delete_file,
|
api_delete_file,
|
||||||
api_get_download_url,
|
api_get_download_url,
|
||||||
api_get_my_shares,
|
api_get_my_shares,
|
||||||
|
api_get_my_direct_links,
|
||||||
|
api_delete_direct_link,
|
||||||
api_create_share,
|
api_create_share,
|
||||||
api_delete_share,
|
api_delete_share,
|
||||||
api_create_direct_link,
|
api_create_direct_link,
|
||||||
|
|||||||
@@ -35,6 +35,17 @@ type ShareItem = {
|
|||||||
storage_type?: string;
|
storage_type?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
type DirectLinkItem = {
|
||||||
|
id: number;
|
||||||
|
link_code: string;
|
||||||
|
direct_url: string;
|
||||||
|
file_path: string;
|
||||||
|
file_name?: string;
|
||||||
|
storage_type?: string;
|
||||||
|
created_at?: string;
|
||||||
|
expires_at?: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
type OnlineDeviceItem = {
|
type OnlineDeviceItem = {
|
||||||
session_id: string;
|
session_id: string;
|
||||||
client_type?: string;
|
client_type?: string;
|
||||||
@@ -125,6 +136,8 @@ const files = ref<FileItem[]>([]);
|
|||||||
const selectedFileName = ref("");
|
const selectedFileName = ref("");
|
||||||
const searchKeyword = ref("");
|
const searchKeyword = ref("");
|
||||||
const shares = ref<ShareItem[]>([]);
|
const shares = ref<ShareItem[]>([]);
|
||||||
|
const directLinks = ref<DirectLinkItem[]>([]);
|
||||||
|
const directLinksLoading = ref(false);
|
||||||
const batchMode = ref(false);
|
const batchMode = ref(false);
|
||||||
const batchSelectedNames = ref<string[]>([]);
|
const batchSelectedNames = ref<string[]>([]);
|
||||||
|
|
||||||
@@ -253,11 +266,11 @@ const toast = reactive({
|
|||||||
let toastTimer: ReturnType<typeof setTimeout> | null = null;
|
let toastTimer: ReturnType<typeof setTimeout> | null = null;
|
||||||
|
|
||||||
const navItems = computed(() => [
|
const navItems = computed(() => [
|
||||||
{ key: "files" as const, label: "全部文件", hint: `${files.value.length} 项` },
|
{ key: "files" as const, label: "全部文件", hint: `${files.value.length} 项`, icon: "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z" },
|
||||||
{ key: "transfers" as const, label: "传输列表", hint: `${transferTasks.value.length} 个任务` },
|
{ key: "transfers" as const, label: "传输列表", hint: `${transferTasks.value.length} 个任务`, icon: "M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-8l-4-4m0 0L8 8m4-4v12" },
|
||||||
{ key: "shares" as const, label: "我的分享", hint: `${shares.value.length} 条` },
|
{ key: "shares" as const, label: "我的分享", hint: `${shares.value.length + directLinks.value.length} 条`, icon: "M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z" },
|
||||||
{ key: "sync" as const, label: "同步盘", hint: syncState.localDir ? "已配置" : "未配置" },
|
{ key: "sync" as const, label: "同步盘", hint: syncState.localDir ? "已配置" : "未配置", icon: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" },
|
||||||
{ key: "settings" as const, label: "设置", hint: updateState.available ? "发现新版本" : "系统与更新" },
|
{ key: "settings" as const, label: "设置", hint: updateState.available ? "发现新版本" : "系统与更新", icon: "M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.066 2.573c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.573 1.066c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.066-2.573c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z M15 12a3 3 0 11-6 0 3 3 0 016 0z" },
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const sortedShares = computed(() => {
|
const sortedShares = computed(() => {
|
||||||
@@ -546,10 +559,10 @@ function applyNativeUploadProgress(payload: NativeUploadProgressEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateTransferTask(taskId, {
|
updateTransferTask(taskId, {
|
||||||
status: "uploading",
|
status: boundedProgress >= 99.5 && !payload?.done ? "processing" : "uploading",
|
||||||
speed: transferSpeed,
|
speed: boundedProgress >= 99.5 && !payload?.done ? "服务器处理中" : transferSpeed,
|
||||||
progress: Number(boundedProgress.toFixed(1)),
|
progress: Number(boundedProgress.toFixed(1)),
|
||||||
note: `${formatBytes(uploadedBytes)} / ${formatBytes(totalBytes)}`,
|
note: boundedProgress >= 99.5 && !payload?.done ? "分片已上传完成,等待服务器处理..." : `${formatBytes(uploadedBytes)} / ${formatBytes(totalBytes)}`,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (taskId === uploadRuntime.taskId) {
|
if (taskId === uploadRuntime.taskId) {
|
||||||
@@ -593,6 +606,20 @@ function fileExtLabel(item: FileItem) {
|
|||||||
return ext.slice(0, 4);
|
return ext.slice(0, 4);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function fileIconSvg(kind: string): string {
|
||||||
|
const icons: Record<string, string> = {
|
||||||
|
folder: "M3 7v10a2 2 0 002 2h14a2 2 0 002-2V9a2 2 0 00-2-2h-6l-2-2H5a2 2 0 00-2 2z",
|
||||||
|
image: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z",
|
||||||
|
video: "M15 10l4.553-2.276A1 1 0 0121 8.618v6.764a1 1 0 01-1.447.894L15 14M5 18h8a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v8a2 2 0 002 2z",
|
||||||
|
audio: "M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zm12-3c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2z",
|
||||||
|
archive: "M5 8h14M5 8a2 2 0 110-4h14a2 2 0 110 4M5 8v10a2 2 0 002 2h10a2 2 0 002-2V8m-9 4h4",
|
||||||
|
document: "M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z M9 13h6 M9 17h4",
|
||||||
|
app: "M14 10l-2 1m0 0l-2-1m2 1v2.5M20 7l-2 1m2-1l-2-1m2 1v2.5M14 4l-2-1-2 1M4 7l2-1M4 7l2 1M4 7v2.5M12 21l-2-1m2 1l2-1m-2 1v-2.5M6 18l-2-1v-2.5M18 18l2-1v-2.5",
|
||||||
|
file: "M7 21h10a2 2 0 002-2V9.414a1 1 0 00-.293-.707l-5.414-5.414A1 1 0 0012.586 3H7a2 2 0 00-2 2v14a2 2 0 002 2z",
|
||||||
|
};
|
||||||
|
return icons[kind] || icons.file;
|
||||||
|
}
|
||||||
|
|
||||||
function matchFileTypeFilter(item: FileItem, type: string) {
|
function matchFileTypeFilter(item: FileItem, type: string) {
|
||||||
if (item.isDirectory || item.type === "directory") return false;
|
if (item.isDirectory || item.type === "directory") return false;
|
||||||
const name = String(item.name || "").toLowerCase();
|
const name = String(item.name || "").toLowerCase();
|
||||||
@@ -670,13 +697,14 @@ function getTaskStatusLabel(status: string) {
|
|||||||
if (status === "queued") return "排队中";
|
if (status === "queued") return "排队中";
|
||||||
if (status === "uploading") return "上传中";
|
if (status === "uploading") return "上传中";
|
||||||
if (status === "downloading") return "下载中";
|
if (status === "downloading") return "下载中";
|
||||||
|
if (status === "processing") return "服务器处理中";
|
||||||
if (status === "done") return "已完成";
|
if (status === "done") return "已完成";
|
||||||
if (status === "failed") return "失败";
|
if (status === "failed") return "失败";
|
||||||
return status;
|
return status;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isTaskRunning(status: string) {
|
function isTaskRunning(status: string) {
|
||||||
return status === "uploading" || status === "downloading";
|
return status === "uploading" || status === "downloading" || status === "processing";
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeTransferTask(taskId: string) {
|
function removeTransferTask(taskId: string) {
|
||||||
@@ -1565,6 +1593,39 @@ async function loadShares(silent = false) {
|
|||||||
if (!silent) sharesLoading.value = false;
|
if (!silent) sharesLoading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadDirectLinks(silent = false) {
|
||||||
|
if (!silent) directLinksLoading.value = true;
|
||||||
|
const response = await invokeBridge("api_get_my_direct_links", {
|
||||||
|
baseUrl: appConfig.baseUrl,
|
||||||
|
});
|
||||||
|
if (response.ok && response.data?.success) {
|
||||||
|
directLinks.value = Array.isArray(response.data.links) ? response.data.links : [];
|
||||||
|
} else if (!silent) {
|
||||||
|
showToast(response.data?.message || "获取直链列表失败", "error");
|
||||||
|
}
|
||||||
|
if (!silent) directLinksLoading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function copyDirectLink(link: DirectLinkItem) {
|
||||||
|
const url = link.direct_url || "";
|
||||||
|
if (url) {
|
||||||
|
await copyText(url, "直链已复制到剪贴板");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deleteDirectLink(link: DirectLinkItem) {
|
||||||
|
const response = await invokeBridge("api_delete_direct_link", {
|
||||||
|
baseUrl: appConfig.baseUrl,
|
||||||
|
linkId: link.id,
|
||||||
|
});
|
||||||
|
if (response.ok && response.data?.success) {
|
||||||
|
showToast("直链已删除", "success");
|
||||||
|
await loadDirectLinks(true);
|
||||||
|
} else {
|
||||||
|
showToast(response.data?.message || "删除直链失败", "error");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function getSignedUrlForItem(item: FileItem, mode: "download" | "preview") {
|
async function getSignedUrlForItem(item: FileItem, mode: "download" | "preview") {
|
||||||
const targetPath = buildItemPath(item);
|
const targetPath = buildItemPath(item);
|
||||||
const response = await invokeBridge("api_get_download_url", {
|
const response = await invokeBridge("api_get_download_url", {
|
||||||
@@ -2524,6 +2585,7 @@ watch(nav, async (next) => {
|
|||||||
}
|
}
|
||||||
if (next === "shares" && authenticated.value) {
|
if (next === "shares" && authenticated.value) {
|
||||||
await loadShares();
|
await loadShares();
|
||||||
|
await loadDirectLinks();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (next === "settings" && authenticated.value) {
|
if (next === "settings" && authenticated.value) {
|
||||||
@@ -2645,7 +2707,10 @@ onBeforeUnmount(() => {
|
|||||||
:class="{ active: nav === item.key }"
|
:class="{ active: nav === item.key }"
|
||||||
@click="nav = item.key"
|
@click="nav = item.key"
|
||||||
>
|
>
|
||||||
|
<div class="nav-btn-row">
|
||||||
|
<svg class="nav-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><path :d="item.icon" /></svg>
|
||||||
<span>{{ item.label }}</span>
|
<span>{{ item.label }}</span>
|
||||||
|
</div>
|
||||||
<small>{{ item.hint }}</small>
|
<small>{{ item.hint }}</small>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -2705,7 +2770,7 @@ onBeforeUnmount(() => {
|
|||||||
<button class="action-btn" @click="clearCompletedTransferTasks">清理已结束</button>
|
<button class="action-btn" @click="clearCompletedTransferTasks">清理已结束</button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="nav === 'shares'">
|
<template v-else-if="nav === 'shares'">
|
||||||
<button class="action-btn" @click="loadShares()">刷新分享</button>
|
<button class="action-btn" @click="loadShares(); loadDirectLinks()">刷新分享</button>
|
||||||
</template>
|
</template>
|
||||||
<template v-else-if="nav === 'sync'">
|
<template v-else-if="nav === 'sync'">
|
||||||
<button class="action-btn" :disabled="syncState.syncing || syncState.scanning" @click="runSyncOnce('manual')">
|
<button class="action-btn" :disabled="syncState.syncing || syncState.scanning" @click="runSyncOnce('manual')">
|
||||||
@@ -2764,13 +2829,8 @@ onBeforeUnmount(() => {
|
|||||||
{{ isBatchSelected(item.name) ? "✓" : "" }}
|
{{ isBatchSelected(item.name) ? "✓" : "" }}
|
||||||
</div>
|
</div>
|
||||||
<div class="file-icon-shell" :class="`kind-${fileVisualKind(item)}`">
|
<div class="file-icon-shell" :class="`kind-${fileVisualKind(item)}`">
|
||||||
<template v-if="item.isDirectory || item.type === 'directory'">
|
<svg class="file-type-svg" viewBox="0 0 24 24" fill="none" stroke="#fff" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"><path :d="fileIconSvg(fileVisualKind(item))" /></svg>
|
||||||
<span class="folder-tab" />
|
<span v-if="!item.isDirectory && item.type !== 'directory'" class="file-ext">{{ fileExtLabel(item) }}</span>
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<span class="file-corner" />
|
|
||||||
<span class="file-ext">{{ fileExtLabel(item) }}</span>
|
|
||||||
</template>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="file-name" :title="item.displayName || item.name">
|
<div class="file-name" :title="item.displayName || item.name">
|
||||||
<template v-if="isInlineRenaming(item)">
|
<template v-if="isInlineRenaming(item)">
|
||||||
@@ -2832,9 +2892,11 @@ onBeforeUnmount(() => {
|
|||||||
<template v-else-if="nav === 'shares'">
|
<template v-else-if="nav === 'shares'">
|
||||||
<div class="panel-head">
|
<div class="panel-head">
|
||||||
<h3>我的分享</h3>
|
<h3>我的分享</h3>
|
||||||
<span>仅展示已分享文件及操作</span>
|
<span>分享链接与直链管理</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="shares-section">
|
||||||
|
<div class="section-label">分享链接</div>
|
||||||
<div v-if="sharesLoading" class="empty-tip">正在加载分享列表...</div>
|
<div v-if="sharesLoading" class="empty-tip">正在加载分享列表...</div>
|
||||||
<div v-else-if="sortedShares.length === 0" class="empty-tip">暂无分享记录</div>
|
<div v-else-if="sortedShares.length === 0" class="empty-tip">暂无分享记录</div>
|
||||||
<div v-else class="share-list">
|
<div v-else class="share-list">
|
||||||
@@ -2860,6 +2922,31 @@ onBeforeUnmount(() => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="shares-section" style="margin-top: 20px;">
|
||||||
|
<div class="section-label">直链列表</div>
|
||||||
|
<div v-if="directLinksLoading" class="empty-tip">正在加载直链列表...</div>
|
||||||
|
<div v-else-if="directLinks.length === 0" class="empty-tip">暂无直链记录</div>
|
||||||
|
<div v-else class="share-list">
|
||||||
|
<div v-for="link in directLinks" :key="link.id" class="share-item direct-link-item">
|
||||||
|
<div class="share-main">
|
||||||
|
<div class="share-title">
|
||||||
|
<strong :title="link.file_path">{{ link.file_name || link.file_path.split('/').pop() || '未命名' }}</strong>
|
||||||
|
<span class="share-badge direct-badge">直链</span>
|
||||||
|
</div>
|
||||||
|
<div class="share-link direct-link-url" :title="link.direct_url">{{ link.direct_url }}</div>
|
||||||
|
<div class="share-meta">
|
||||||
|
<span>到期 {{ getShareExpireLabel(link.expires_at) }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="share-actions">
|
||||||
|
<button class="action-btn" @click="copyDirectLink(link)">复制直链</button>
|
||||||
|
<button class="action-btn danger" @click="deleteDirectLink(link)">删除</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<template v-else-if="nav === 'sync'">
|
<template v-else-if="nav === 'sync'">
|
||||||
@@ -3459,6 +3546,19 @@ select:focus {
|
|||||||
gap: 2px;
|
gap: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.nav-btn-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-icon {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: #4a6381;
|
||||||
|
}
|
||||||
|
|
||||||
.nav-btn span {
|
.nav-btn span {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #223244;
|
color: #223244;
|
||||||
@@ -3478,7 +3578,8 @@ select:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.nav-btn.active span,
|
.nav-btn.active span,
|
||||||
.nav-btn.active small {
|
.nav-btn.active small,
|
||||||
|
.nav-btn.active .nav-icon {
|
||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3742,7 +3843,7 @@ select:focus {
|
|||||||
.icon-grid {
|
.icon-grid {
|
||||||
display: grid;
|
display: grid;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
grid-template-columns: repeat(auto-fill, 108px);
|
grid-template-columns: repeat(auto-fill, 120px);
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
align-content: start;
|
align-content: start;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
@@ -3766,20 +3867,21 @@ select:focus {
|
|||||||
|
|
||||||
.file-card {
|
.file-card {
|
||||||
border: 1px solid transparent;
|
border: 1px solid transparent;
|
||||||
border-radius: 9px;
|
border-radius: 10px;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 7px 6px 6px;
|
padding: 8px 6px 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: flex-start;
|
justify-content: flex-start;
|
||||||
gap: 4px;
|
gap: 6px;
|
||||||
width: 108px;
|
width: 120px;
|
||||||
min-height: 124px;
|
min-height: 130px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
position: relative;
|
position: relative;
|
||||||
|
transition: background 0.15s, border-color 0.15s, box-shadow 0.15s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-card:focus-visible {
|
.file-card:focus-visible {
|
||||||
@@ -3832,23 +3934,32 @@ select:focus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.file-icon-shell {
|
.file-icon-shell {
|
||||||
width: 60px;
|
width: 64px;
|
||||||
height: 50px;
|
height: 56px;
|
||||||
position: relative;
|
position: relative;
|
||||||
margin-top: 1px;
|
margin-top: 1px;
|
||||||
border-radius: 10px;
|
border-radius: 12px;
|
||||||
border: 1px solid #cfdced;
|
border: 1px solid rgba(0, 0, 0, 0.08);
|
||||||
background: linear-gradient(180deg, #7ab5ff 0%, #4689dd 100%);
|
background: linear-gradient(180deg, #7ab5ff 0%, #4689dd 100%);
|
||||||
box-shadow: 0 2px 4px rgba(32, 77, 131, 0.14);
|
box-shadow: 0 3px 8px rgba(32, 77, 131, 0.15);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.file-type-svg {
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
margin-top: -4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-icon-shell.kind-folder {
|
.file-icon-shell.kind-folder {
|
||||||
height: 44px;
|
height: 52px;
|
||||||
margin-top: 8px;
|
margin-top: 4px;
|
||||||
border-radius: 8px;
|
border-radius: 10px;
|
||||||
border-color: #d8b860;
|
border-color: rgba(210, 170, 50, 0.3);
|
||||||
background: linear-gradient(180deg, #f2c85e 0%, #e3b145 100%);
|
background: linear-gradient(180deg, #f2c85e 0%, #e3b145 100%);
|
||||||
box-shadow: 0 2px 4px rgba(152, 106, 14, 0.14);
|
box-shadow: 0 3px 8px rgba(152, 106, 14, 0.15);
|
||||||
}
|
}
|
||||||
|
|
||||||
.folder-tab {
|
.folder-tab {
|
||||||
@@ -3877,13 +3988,14 @@ select:focus {
|
|||||||
.file-ext {
|
.file-ext {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
bottom: 7px;
|
bottom: 4px;
|
||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
color: #fff;
|
color: rgba(255, 255, 255, 0.95);
|
||||||
font-size: 10px;
|
font-size: 9px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.4px;
|
letter-spacing: 0.5px;
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
|
text-transform: uppercase;
|
||||||
}
|
}
|
||||||
|
|
||||||
.file-icon-shell.kind-document {
|
.file-icon-shell.kind-document {
|
||||||
@@ -4679,4 +4791,30 @@ select:focus {
|
|||||||
width: 108px;
|
width: 108px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.section-label {
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #3a5274;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
padding-left: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shares-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.direct-link-item {
|
||||||
|
border-left: 3px solid #06b6d4;
|
||||||
|
}
|
||||||
|
|
||||||
|
.direct-badge {
|
||||||
|
background: #06b6d4 !important;
|
||||||
|
color: #fff !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.direct-link-url {
|
||||||
|
color: #0891b2 !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Reference in New Issue
Block a user