update FileExplorer

This commit is contained in:
Timi
2026-04-03 14:58:30 +08:00
parent ded231671a
commit 603a503644
9 changed files with 335 additions and 178 deletions

View File

@@ -1,50 +1,5 @@
<template>
<div class="page">
<section class="toolbar glass-white">
<div class="path">
<t-button
size="small"
variant="text"
theme="primary"
:disabled="!currentSegments.length"
@click="openRoot"
>
根目录
</t-button>
<span v-for="(segment, index) in currentSegments" :key="`${index}-${segment}`" class="crumb-wrap">
<span class="sep">/</span>
<t-button
size="small"
variant="text"
theme="primary"
class="crumb"
@click="openByIndex(index)"
>
<span v-text="segment"></span>
</t-button>
</span>
</div>
<div class="actions">
<t-button
size="small"
theme="primary"
:variant="displayMode === 'list' ? 'base' : 'outline'"
@click="setDisplayMode('list')"
>
列表
</t-button>
<t-button
size="small"
theme="primary"
:variant="displayMode === 'grid' ? 'base' : 'outline'"
@click="setDisplayMode('grid')"
>
平铺
</t-button>
</div>
</section>
<section v-if="currentSegments.length" class="go-up" @click="openParent">
<div class="icon dir">
<t-icon name="rollback" />
@@ -55,43 +10,32 @@
</div>
</section>
<section :class="['list', `is-${displayMode}`]">
<article
v-for="item in currentItems"
:key="item.path"
class="item glass-white"
@click="openItem(item)"
>
<div :class="['icon', item.type]">
<t-icon :name="item.type === 'dir' ? 'folder' : item.icon" />
</div>
<div class="meta">
<p class="name" v-text="item.name"></p>
<p class="desc" v-text="formatItemDesc(item)"></p>
</div>
</article>
<section v-if="currentItems.length" class="content">
<file-explorer-list
v-if="displayMode === 'list'"
:items="currentItems"
@open="openItem"
/>
<file-explorer-grid
v-else
:items="currentItems"
@open="openItem"
/>
</section>
<t-empty v-if="!currentItems.length" description="当前目录为空" />
<t-empty v-else description="当前目录为空" />
</div>
</template>
<script setup lang="ts">
type DisplayMode = "list" | "grid";
type FileItemType = "dir" | "file";
interface FileEntry {
name: string;
type: FileItemType;
size?: string;
updatedAt: string;
icon: string;
children?: FileEntry[];
}
interface ExplorerItem extends Omit<FileEntry, "children"> {
path: string;
}
import FileExplorerGrid from "./FileExplorerGrid.vue";
import FileExplorerList from "./FileExplorerList.vue";
import { useNavBarStore } from "@/store/navBarStore";
import {
type DisplayMode,
type ExplorerItem,
type FileEntry
} from "./fileExplorer.shared";
const props = withDefaults(defineProps<{
mode?: DisplayMode;
@@ -112,6 +56,8 @@ const emit = defineEmits<{
const route = useRoute();
const router = useRouter();
const navBarStore = useNavBarStore();
const navBarRightOwner = "file-explorer-page";
const fileTree: FileEntry[] = [
{
@@ -185,7 +131,7 @@ const fileTree: FileEntry[] = [
name: "系统日志.log",
type: "file",
size: "428 KB",
icon: "file-text",
icon: "file",
updatedAt: "今天 07:55"
},
{
@@ -224,6 +170,32 @@ const currentItems = computed<ExplorerItem[]>(() => {
path: buildItemPath(currentSegments.value, item.name)
}));
});
const currentTitle = computed(() => {
if (!currentSegments.value.length) {
return "文件";
}
return currentSegments.value[currentSegments.value.length - 1];
});
const displayModeActionText = computed(() => {
return displayMode.value === "list" ? "平铺" : "列表";
});
const navBarRightRenderer = defineComponent({
name: "file-explorer-nav-right",
setup() {
const buttonComponent = resolveComponent("t-button");
return () => h(buttonComponent, {
size: "small",
variant: "text",
theme: "primary",
class: "nav-switch",
onClick: toggleDisplayMode
}, {
default: () => displayModeActionText.value
});
}
});
watch(
() => props.mode,
@@ -233,6 +205,22 @@ watch(
{ immediate: true }
);
watch(
currentTitle,
(value) => {
navBarStore.setTitle(value);
},
{ immediate: true }
);
watch(
displayModeActionText,
() => {
navBarStore.setRightRenderer(navBarRightRenderer, navBarRightOwner);
},
{ immediate: true }
);
watch(
() => props.path,
(value) => {
@@ -277,14 +265,22 @@ function buildItemPath(pathSegments: string[], name: string): string {
}
function setDisplayMode(mode: DisplayMode): void {
if (displayMode.value === mode) {
return;
}
displayMode.value = mode;
emit("update:mode", mode);
}
async function openRoot(): Promise<void> {
await syncPath([]);
function toggleDisplayMode(): void {
setDisplayMode(displayMode.value === "list" ? "grid" : "list");
}
onUnmounted(() => {
navBarStore.clearRight(navBarRightOwner);
});
async function openParent(): Promise<void> {
if (shouldUseRoutePath.value && route.name === "FileExplorerPage" && currentSegments.value.length === 1) {
await router.back();
@@ -294,10 +290,6 @@ async function openParent(): Promise<void> {
await syncPath(currentSegments.value.slice(0, -1));
}
async function openByIndex(index: number): Promise<void> {
await syncPath(currentSegments.value.slice(0, index + 1));
}
async function openItem(item: ExplorerItem): Promise<void> {
if (item.type === "dir") {
const nextSegments = [...currentSegments.value, item.name];
@@ -328,14 +320,6 @@ async function syncPath(nextSegments: string[]): Promise<void> {
localSegments.value = nextSegments;
emit("update:path", nextSegments);
}
function formatItemDesc(item: ExplorerItem): string {
if (item.type === "dir") {
return `文件夹 · ${item.updatedAt}`;
}
return `${item.size || "--"} · ${item.updatedAt}`;
}
</script>
<style scoped lang="less">
@@ -344,48 +328,20 @@ function formatItemDesc(item: ExplorerItem): string {
height: 100%;
display: flex;
padding: 1rem;
overflow: auto;
overflow: hidden;
flex-direction: column;
.toolbar,
.go-up,
.item {
.nav-switch {
padding: 0;
}
.go-up {
border: 1px solid var(--app-line);
background: var(--app-card);
box-shadow: 0 .35rem 1rem rgba(17, 32, 56, .05);
}
.toolbar {
gap: .75rem;
display: flex;
padding: .875rem;
border-radius: 1rem;
flex-direction: column;
.path,
.actions {
gap: .25rem;
display: flex;
flex-wrap: wrap;
align-items: center;
}
.crumb-wrap {
display: inline-flex;
align-items: center;
}
.sep {
color: var(--app-sub);
}
.crumb {
padding: 0;
}
}
.go-up,
.item {
.go-up {
gap: .875rem;
display: flex;
padding: .875rem 1rem;
@@ -394,23 +350,9 @@ function formatItemDesc(item: ExplorerItem): string {
cursor: pointer;
}
.list {
gap: .75rem;
display: grid;
&.is-grid {
grid-template-columns: repeat(2, minmax(0, 1fr));
.item {
padding: 1rem .875rem;
align-items: flex-start;
flex-direction: column;
.meta {
width: 100%;
}
}
}
.content {
min-height: 0;
flex: 1 1 auto;
}
.icon {
@@ -420,11 +362,11 @@ function formatItemDesc(item: ExplorerItem): string {
border-radius: .9rem;
align-items: center;
justify-content: center;
color: #FFF;
background: linear-gradient(135deg, #4D8DFF, #76A9FF);
color: #fff;
background: linear-gradient(135deg, #4d8dff, #76a9ff);
&.dir {
background: linear-gradient(135deg, #FF9C3D, #FFBF69);
background: linear-gradient(135deg, #ff9c3d, #ffbf69);
}
}
@@ -454,9 +396,7 @@ function formatItemDesc(item: ExplorerItem): string {
}
:global(.theme-dark) .page {
.toolbar,
.go-up,
.item {
.go-up {
box-shadow: 0 .35rem 1rem rgba(0, 0, 0, .22);
}
}