fix MainLayout.vue content height
This commit is contained in:
@@ -4,26 +4,34 @@
|
||||
<file-explorer-list
|
||||
v-if="displayMode === 'list'"
|
||||
:items="currentItems"
|
||||
:pending-path="pendingFolderPath"
|
||||
@open="openItem"
|
||||
/>
|
||||
<file-explorer-grid
|
||||
v-else
|
||||
:items="currentItems"
|
||||
:pending-path="pendingFolderPath"
|
||||
@open="openItem"
|
||||
/>
|
||||
</section>
|
||||
<div v-else-if="pageLoading" class="loading-wrap">
|
||||
<t-loading text="加载目录中" />
|
||||
</div>
|
||||
<t-empty v-else description="当前目录为空" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Toast } from "tdesign-mobile-vue";
|
||||
import { listServerFiles, resolveRequestErrorMessage } from "@/api/file";
|
||||
import FileExplorerGrid from "./FileExplorerGrid.vue";
|
||||
import FileExplorerList from "./FileExplorerList.vue";
|
||||
import { useNavBarStore } from "@/store/navBarStore";
|
||||
import {
|
||||
type DisplayMode,
|
||||
type ExplorerItem,
|
||||
type FileEntry
|
||||
mapServerFileToExplorerItem,
|
||||
sortExplorerItems
|
||||
} from "./fileExplorer.shared";
|
||||
|
||||
const props = withDefaults(defineProps<{
|
||||
@@ -48,92 +56,12 @@ const router = useRouter();
|
||||
const navBarStore = useNavBarStore();
|
||||
const navBarRightOwner = `file-explorer-page-${Math.random().toString(36).slice(2)}`;
|
||||
|
||||
const fileTree: FileEntry[] = [
|
||||
{
|
||||
name: "文档",
|
||||
type: "dir",
|
||||
icon: "folder",
|
||||
updatedAt: "今天 09:24",
|
||||
children: [
|
||||
{
|
||||
name: "项目说明.md",
|
||||
type: "file",
|
||||
size: "18 KB",
|
||||
icon: "file-text",
|
||||
updatedAt: "今天 09:20"
|
||||
},
|
||||
{
|
||||
name: "设计稿",
|
||||
type: "dir",
|
||||
icon: "folder",
|
||||
updatedAt: "昨天 18:10",
|
||||
children: [
|
||||
{
|
||||
name: "home.png",
|
||||
type: "file",
|
||||
size: "1.2 MB",
|
||||
icon: "image",
|
||||
updatedAt: "昨天 18:08"
|
||||
},
|
||||
{
|
||||
name: "file.png",
|
||||
type: "file",
|
||||
size: "984 KB",
|
||||
icon: "image",
|
||||
updatedAt: "昨天 18:03"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "媒体",
|
||||
type: "dir",
|
||||
icon: "folder",
|
||||
updatedAt: "今天 08:16",
|
||||
children: [
|
||||
{
|
||||
name: "音乐",
|
||||
type: "dir",
|
||||
icon: "folder",
|
||||
updatedAt: "今天 08:11",
|
||||
children: [
|
||||
{
|
||||
name: "demo.mp3",
|
||||
type: "file",
|
||||
size: "6.8 MB",
|
||||
icon: "music",
|
||||
updatedAt: "今天 08:10"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "封面.jpg",
|
||||
type: "file",
|
||||
size: "2.4 MB",
|
||||
icon: "image",
|
||||
updatedAt: "昨天 22:40"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
name: "系统日志.log",
|
||||
type: "file",
|
||||
size: "428 KB",
|
||||
icon: "file",
|
||||
updatedAt: "今天 07:55"
|
||||
},
|
||||
{
|
||||
name: "backup.zip",
|
||||
type: "file",
|
||||
size: "126 MB",
|
||||
icon: "file-zip",
|
||||
updatedAt: "昨天 23:18"
|
||||
}
|
||||
];
|
||||
|
||||
const displayMode = ref<DisplayMode>(props.mode);
|
||||
const localSegments = ref<string[]>(normalizePath(props.path));
|
||||
const directoryCache = ref<Record<string, ExplorerItem[]>>({});
|
||||
const pageLoading = ref(false);
|
||||
const pendingFolderPath = ref("");
|
||||
let latestLoadToken = 0;
|
||||
|
||||
const shouldUseRoutePath = computed(() => {
|
||||
if (!props.useRoutePath) {
|
||||
@@ -153,12 +81,9 @@ const currentSegments = computed(() => {
|
||||
});
|
||||
|
||||
const currentItems = computed<ExplorerItem[]>(() => {
|
||||
const target = resolveEntries(currentSegments.value);
|
||||
return target.map((item) => ({
|
||||
...item,
|
||||
path: buildItemPath(currentSegments.value, item.name)
|
||||
}));
|
||||
return directoryCache.value[getDirectoryKey(currentSegments.value)] || [];
|
||||
});
|
||||
|
||||
const currentTitle = computed(() => {
|
||||
if (!currentSegments.value.length) {
|
||||
return "文件";
|
||||
@@ -166,9 +91,11 @@ const currentTitle = computed(() => {
|
||||
|
||||
return currentSegments.value[currentSegments.value.length - 1];
|
||||
});
|
||||
|
||||
const displayModeActionText = computed(() => {
|
||||
return displayMode.value === "list" ? "平铺" : "列表";
|
||||
});
|
||||
|
||||
const navBarRightRenderer = defineComponent({
|
||||
name: "file-explorer-nav-right",
|
||||
setup() {
|
||||
@@ -228,6 +155,14 @@ watch(
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
watch(
|
||||
currentSegments,
|
||||
(segments) => {
|
||||
void hydrateCurrentDirectory(segments);
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
function normalizePath(source?: string | string[]): string[] {
|
||||
if (Array.isArray(source)) {
|
||||
return source.filter((segment) => !!segment);
|
||||
@@ -240,25 +175,6 @@ function normalizePath(source?: string | string[]): string[] {
|
||||
return source.split("/").filter((segment) => !!segment);
|
||||
}
|
||||
|
||||
function resolveEntries(pathSegments: string[]): FileEntry[] {
|
||||
let entries = fileTree;
|
||||
|
||||
for (const segment of pathSegments) {
|
||||
const next = entries.find((item) => item.type === "dir" && item.name === segment);
|
||||
if (!next || !next.children) {
|
||||
return [];
|
||||
}
|
||||
|
||||
entries = next.children;
|
||||
}
|
||||
|
||||
return entries;
|
||||
}
|
||||
|
||||
function buildItemPath(pathSegments: string[], name: string): string {
|
||||
return [...pathSegments, name].join("/");
|
||||
}
|
||||
|
||||
function setDisplayMode(mode: DisplayMode): void {
|
||||
if (displayMode.value === mode) {
|
||||
return;
|
||||
@@ -276,20 +192,28 @@ onUnmounted(() => {
|
||||
navBarStore.clearRight(navBarRightOwner);
|
||||
});
|
||||
|
||||
async function openParent(): Promise<void> {
|
||||
if (shouldUseRoutePath.value && route.name === "FileExplorerPage" && currentSegments.value.length === 1) {
|
||||
await router.back();
|
||||
async function openItem(item: ExplorerItem): Promise<void> {
|
||||
if (pendingFolderPath.value) {
|
||||
return;
|
||||
}
|
||||
|
||||
await syncPath(currentSegments.value.slice(0, -1));
|
||||
}
|
||||
|
||||
async function openItem(item: ExplorerItem): Promise<void> {
|
||||
if (item.type === "dir") {
|
||||
const nextSegments = [...currentSegments.value, item.name];
|
||||
emit("open-folder", nextSegments);
|
||||
await syncPath(nextSegments);
|
||||
const nextSegments = normalizePath(item.path);
|
||||
pendingFolderPath.value = item.path;
|
||||
|
||||
try {
|
||||
await ensureDirectoryLoaded(nextSegments, false);
|
||||
emit("open-folder", nextSegments);
|
||||
await syncPath(nextSegments);
|
||||
} catch (error) {
|
||||
Toast({
|
||||
theme: "error",
|
||||
message: resolveRequestErrorMessage(error)
|
||||
});
|
||||
} finally {
|
||||
pendingFolderPath.value = "";
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -315,14 +239,61 @@ async function syncPath(nextSegments: string[]): Promise<void> {
|
||||
localSegments.value = nextSegments;
|
||||
emit("update:path", nextSegments);
|
||||
}
|
||||
|
||||
async function hydrateCurrentDirectory(pathSegments: string[]): Promise<void> {
|
||||
try {
|
||||
await ensureDirectoryLoaded(pathSegments, true);
|
||||
} catch (error) {
|
||||
Toast({
|
||||
theme: "error",
|
||||
message: resolveRequestErrorMessage(error)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureDirectoryLoaded(pathSegments: string[], updatePageLoading: boolean): Promise<ExplorerItem[]> {
|
||||
const cacheKey = getDirectoryKey(pathSegments);
|
||||
const cachedItems = directoryCache.value[cacheKey];
|
||||
|
||||
if (cachedItems) {
|
||||
return cachedItems;
|
||||
}
|
||||
|
||||
const requestToken = latestLoadToken + 1;
|
||||
latestLoadToken = requestToken;
|
||||
|
||||
if (updatePageLoading) {
|
||||
pageLoading.value = true;
|
||||
}
|
||||
|
||||
try {
|
||||
const files = await listServerFiles(pathSegments);
|
||||
const items = sortExplorerItems(files.map((item) => mapServerFileToExplorerItem(item)));
|
||||
directoryCache.value = {
|
||||
...directoryCache.value,
|
||||
[cacheKey]: items
|
||||
};
|
||||
return items;
|
||||
} finally {
|
||||
if (updatePageLoading && requestToken === latestLoadToken) {
|
||||
pageLoading.value = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getDirectoryKey(pathSegments: string[]): string {
|
||||
return pathSegments.join("/");
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.page {
|
||||
--page-top-gap: calc(var(--app-nav-offset, 0px) + 1rem);
|
||||
|
||||
gap: 1rem;
|
||||
height: calc(100% - 2rem);
|
||||
height: 100%;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
padding: 1rem;
|
||||
overflow: hidden;
|
||||
flex-direction: column;
|
||||
|
||||
@@ -347,11 +318,29 @@ async function syncPath(nextSegments: string[]): Promise<void> {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.content {
|
||||
.content,
|
||||
.loading-wrap {
|
||||
min-height: 0;
|
||||
flex: 1 1 auto;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
|
||||
> * {
|
||||
width: 100%;
|
||||
flex: 1 1 auto;
|
||||
min-height: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.loading-wrap {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 2.75rem;
|
||||
height: 2.75rem;
|
||||
|
||||
Reference in New Issue
Block a user