feat: File manager multi-tab start path optimization (#10957)

#10302
This commit is contained in:
2025-11-14 13:40:52 +08:00 committed by GitHub
parent 282e196534
commit e4e2d76f75
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -14,7 +14,6 @@
:label="item.name == '' ? $t('file.root') : item.name" :label="item.name == '' ? $t('file.root') : item.name"
:name="item.id" :name="item.id"
> >
<div>
<div class="flex sm:flex-row flex-col justify-start gap-y-2 items-center gap-x-4" ref="toolRef"> <div class="flex sm:flex-row flex-col justify-start gap-y-2 items-center gap-x-4" ref="toolRef">
<div class="flex-shrink-0 flex sm:w-min w-full items-center justify-start"> <div class="flex-shrink-0 flex sm:w-min w-full items-center justify-start">
<el-tooltip :content="$t('file.back')" placement="top"> <el-tooltip :content="$t('file.back')" placement="top">
@ -54,11 +53,7 @@
<el-icon :size="20"><HomeFilled /></el-icon> <el-icon :size="20"><HomeFilled /></el-icon>
</el-link> </el-link>
</span> </span>
<span <span v-for="(path, index) in paths" :key="path.url" class="inline-flex items-center">
v-for="(path, index) in paths"
:key="path.url"
class="inline-flex items-center"
>
<span class="mr-2 arrow">></span> <span class="mr-2 arrow">></span>
<template v-if="index === 0 && hidePaths.length > 0"> <template v-if="index === 0 && hidePaths.length > 0">
<el-dropdown> <el-dropdown>
@ -151,11 +146,7 @@
<el-icon :size="20"><HomeFilled /></el-icon> <el-icon :size="20"><HomeFilled /></el-icon>
</el-link> </el-link>
</span> </span>
<span <span v-for="(path, index) in paths" :key="path.url" class="inline-flex items-center">
v-for="(path, index) in paths"
:key="path.url"
class="inline-flex items-center"
>
<span class="mr-2 arrow">></span> <span class="mr-2 arrow">></span>
<template v-if="index === 0 && hidePaths.length > 0"> <template v-if="index === 0 && hidePaths.length > 0">
<el-dropdown> <el-dropdown>
@ -187,6 +178,8 @@
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
</template>
<template v-else>
<span class="mr-2 arrow">></span> <span class="mr-2 arrow">></span>
<el-tooltip <el-tooltip
class="box-item" class="box-item"
@ -337,17 +330,11 @@
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<template v-for="(mount, index) in hostMount" :key="mount.path"> <template v-for="(mount, index) in hostMount" :key="mount.path">
<el-dropdown-item <el-dropdown-item v-if="index == 0" @click.stop="jump(mount.path)">
v-if="index == 0"
@click.stop="jump(mount.path)"
>
{{ mount.path }} ({{ $t('file.root') }}) {{ mount.path }} ({{ $t('file.root') }})
{{ formatFileSize(mount.free) }} {{ formatFileSize(mount.free) }}
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item v-if="index != 0" @click.stop="jump(mount.path)">
v-if="index != 0"
@click.stop="jump(mount.path)"
>
{{ mount.path }} ({{ $t('home.mount') }}) {{ mount.path }} ({{ $t('home.mount') }})
{{ formatFileSize(mount.free) }} {{ formatFileSize(mount.free) }}
</el-dropdown-item> </el-dropdown-item>
@ -554,12 +541,7 @@
</el-link> </el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column :label="$t('file.size')" prop="size" min-width="100" :sortable="'custom'">
:label="$t('file.size')"
prop="size"
min-width="100"
:sortable="'custom'"
>
<template #default="{ row }"> <template #default="{ row }">
<el-button <el-button
type="primary" type="primary"
@ -618,9 +600,8 @@
</ComplexTable> </ComplexTable>
</template> </template>
</LayoutContent> </LayoutContent>
</div>
</el-tab-pane> </el-tab-pane>
<el-tab-pane :closable="false" :disabled="editableTabs.length > 6"> <el-tab-pane :name="editableTabsKey" :closable="false" :disabled="editableTabs.length > 6">
<template #label> <template #label>
<el-icon @click="addTab()"><Plus /></el-icon> <el-icon @click="addTab()"><Plus /></el-icon>
</template> </template>
@ -712,6 +693,7 @@ import { CompressExtension, CompressType } from '@/enums/files';
import type { TabPaneName } from 'element-plus'; import type { TabPaneName } from 'element-plus';
import { getComponentInfo } from '@/api/modules/host'; import { getComponentInfo } from '@/api/modules/host';
import { routerToNameWithQuery } from '@/utils/router'; import { routerToNameWithQuery } from '@/utils/router';
import { loadBaseDir } from '@/api/modules/setting';
const globalStore = GlobalStore(); const globalStore = GlobalStore();
@ -747,6 +729,7 @@ const initData = () => ({
}); });
let req = reactive(initData()); let req = reactive(initData());
let loading = ref(false); let loading = ref(false);
const baseDir = ref();
const paths = ref<FilePaths[]>([]); const paths = ref<FilePaths[]>([]);
const hidePaths = ref<FilePaths[]>([]); const hidePaths = ref<FilePaths[]>([]);
let pathWidth = ref(0); let pathWidth = ref(0);
@ -973,14 +956,15 @@ const updateButtons = async () => {
} }
}; };
const handlePath = () => { const handlePath = (depth = 0) => {
if (depth > 10) return;
nextTick(function () { nextTick(function () {
let breadCrumbWidth = breadCrumbRef.value?.offsetWidth; let breadCrumbWidth = breadCrumbRef.value?.offsetWidth;
let pathWidth = toolRef.value?.offsetWidth; let pathWidth = toolRef.value?.offsetWidth;
if (pathWidth - breadCrumbWidth < 50 && paths.value.length > 1) { if (pathWidth - breadCrumbWidth < 50 && paths.value.length > 1) {
const removed = paths.value.shift(); const removed = paths.value.shift();
if (removed) hidePaths.value.push(removed); if (removed) hidePaths.value.push(removed);
handlePath(); handlePath(depth + 1);
} }
}); });
}; };
@ -995,19 +979,22 @@ const btnResizeHandler = debounce(() => {
}, 100); }, 100);
const observeResize = () => { const observeResize = () => {
const el = getCurrentPath() as any; const el = getCurrentPath();
if (!el) return; const ele = getCurrentBtnWrapper();
let resizeObserver = new ResizeObserver(() => { if (!el || !ele) return;
resizeHandler();
const observe = new ResizeObserver((entries) => {
const isElChanged = entries.some((entry) => entry.target === el);
const isEleChanged = entries.some((entry) => entry.target === ele);
if (isElChanged) resizeHandler();
if (isEleChanged) btnResizeHandler();
}); });
const ele = getCurrentBtnWrapper() as any; observe.observe(el);
if (!ele) return; observe.observe(ele);
resizeObserver = new ResizeObserver(() => {
btnResizeHandler(); resizeObserver = observe;
});
resizeObserver.observe(el);
resizeObserver.observe(ele);
}; };
function watchTitleHeight() { function watchTitleHeight() {
@ -1738,18 +1725,6 @@ function hideRightMenu() {
getCurrentTable().closeRightClick(); getCurrentTable().closeRightClick();
} }
onMounted(() => {
initShowHidden();
initTabsAndPaths();
getHostMount();
initHistory();
checkFFmpeg();
nextTick(function () {
handlePath();
observeResize();
});
});
function initShowHidden() { function initShowHidden() {
const showHidden = localStorage.getItem('show-hidden'); const showHidden = localStorage.getItem('show-hidden');
if (showHidden === null) { if (showHidden === null) {
@ -1765,7 +1740,6 @@ function initTabsAndPaths() {
let path = getInitialPath(); let path = getInitialPath();
req.path = path; req.path = path;
getPaths(path); getPaths(path);
editableTabsValue.value = path;
updateTab(path); updateTab(path);
paths.value = buildPaths(path); paths.value = buildPaths(path);
pathWidth.value = getCurrentPath()?.offsetWidth; pathWidth.value = getCurrentPath()?.offsetWidth;
@ -1788,21 +1762,28 @@ function initHistory() {
function getInitialPath(): string { function getInitialPath(): string {
const routePath = router.currentRoute.value.query.path; const routePath = router.currentRoute.value.query.path;
if (routePath) { if (routePath != undefined) {
const p = String(routePath); const p = String(routePath);
globalStore.setLastFilePath(p); globalStore.setLastFilePath(p);
return p; return p;
} else if (globalStore.lastFilePath && globalStore.lastFilePath !== '') { } else if (
typeof globalStore.lastFilePath === 'string' &&
globalStore.lastFilePath.trim() !== '' &&
globalStore.lastFilePath !== 'undefined'
) {
return globalStore.lastFilePath; return globalStore.lastFilePath;
} }
const tab = editableTabs.value.find((t) => t.id === editableTabsKey.value);
if (tab) {
globalStore.setLastFilePath(tab.path);
return tab.path;
}
return '/'; return '/';
} }
const editableTabsKey = ref(''); const editableTabsKey = ref('');
const editableTabsValue = ref('');
const editableTabsName = ref('');
const editableTabs = ref([ const editableTabs = ref([
{ id: '1', name: 'opt', path: '/opt' }, { id: '1', name: getLastPath(baseDir.value), path: baseDir.value },
{ id: '2', name: 'home', path: '/home' }, { id: '2', name: 'home', path: '/home' },
]); ]);
@ -1811,17 +1792,9 @@ function initTabs() {
if (savedTabs) { if (savedTabs) {
editableTabs.value = JSON.parse(savedTabs); editableTabs.value = JSON.parse(savedTabs);
} }
const savedTabsKey = localStorage.getItem('editableTabsKey'); const savedTabsKey = localStorage.getItem('editableTabsKey');
if (savedTabsKey) { if (savedTabsKey) {
editableTabsKey.value = savedTabsKey; editableTabsKey.value = savedTabsKey;
const tab = editableTabs.value.find((t) => t.id === savedTabsKey);
if (tab) {
editableTabsValue.value = tab.path;
editableTabsName.value = tab.name;
} else {
setFirstTab();
}
} else { } else {
setFirstTab(); setFirstTab();
} }
@ -1831,19 +1804,18 @@ function setFirstTab() {
if (editableTabs.value.length > 0) { if (editableTabs.value.length > 0) {
const first = editableTabs.value[0]; const first = editableTabs.value[0];
editableTabsKey.value = first.id; editableTabsKey.value = first.id;
editableTabsValue.value = first.path; } else {
editableTabsName.value = first.name; initTabs();
} }
} }
watch( function saveStorageTabs() {
[editableTabs, editableTabsKey], localStorage.setItem('editableTabs', JSON.stringify(editableTabs.value));
([newTabs, newKey]) => { }
localStorage.setItem('editableTabs', JSON.stringify(newTabs));
localStorage.setItem('editableTabsKey', newKey); function saveStorageTabsKey() {
}, localStorage.setItem('editableTabsKey', editableTabsKey.value);
{ deep: true }, }
);
function getLastPath(path: string): string { function getLastPath(path: string): string {
if (!path) return ''; if (!path) return '';
@ -1852,58 +1824,56 @@ function getLastPath(path: string): string {
} }
function updateTab(newPath?: string) { function updateTab(newPath?: string) {
const tab = editableTabs.value.find((t) => t.id === editableTabsKey.value); editableTabs.value = editableTabs.value.map((tab) => {
if (tab) { if (tab.id === editableTabsKey.value) {
tab.path = newPath; return {
tab.name = getLastPath(newPath); ...tab,
path: newPath,
name: getLastPath(newPath),
};
} }
return tab;
});
saveStorageTabs();
} }
const loadPath = async () => {
const pathRes = await loadBaseDir();
baseDir.value = pathRes.data;
};
const addTab = () => { const addTab = () => {
if (editableTabs.value.length >= 6) { if (editableTabs.value.length >= 6) {
MsgWarning(i18n.global.t('file.notCanTab')); MsgWarning(i18n.global.t('file.notCanTab'));
return; return;
} }
const usedIds = editableTabs.value.map((t) => Number(t.id)); const usedIds = new Set(editableTabs.value.map((t) => Number(t.id)));
let newId = null; const newId = Array.from({ length: 6 }, (_, i) => i + 1).find((id) => !usedIds.has(id));
for (let i = 1; i <= 6; i++) {
if (!usedIds.includes(i)) { if (!newId) {
newId = i;
break;
}
}
if (newId === null) {
MsgWarning(i18n.global.t('file.notCanTab')); MsgWarning(i18n.global.t('file.notCanTab'));
return; return;
} }
editableTabs.value.push({ editableTabs.value.push({
id: String(newId), id: String(newId),
name: 'opt', name: getLastPath(baseDir.value),
path: '/opt', path: baseDir.value,
}); });
editableTabsKey.value = String(newId); editableTabsKey.value = String(newId);
changeTab(String(newId)); changeTab(newId);
}; };
const changeTab = (targetPath: TabPaneName) => { const changeTab = (targetPath: TabPaneName) => {
if (targetPath === 99) { if (targetPath === 99) {
return; return;
} }
editableTabsKey.value = targetPath.toString(); const current = editableTabs.value.find((tab) => tab.id === targetPath.toString());
const current = editableTabs.value.find((tab) => tab.id === editableTabsKey.value); editableTabsKey.value = current.id;
editableTabsName.value = current ? current.name : ''; saveStorageTabs();
editableTabsValue.value = current ? current.path : ''; saveStorageTabsKey();
req.path = editableTabsValue.value; req.path = current ? current.path : '';
paths.value = []; globalStore.setLastFilePath(req.path);
const segments = editableTabsValue.value.split('/').filter(Boolean); getPaths(req.path);
let url = '';
segments.forEach((segment) => {
url += '/' + segment;
paths.value.push({
url,
name: segment,
});
});
search(); search();
}; };
@ -1923,7 +1893,7 @@ const removeTab = (targetId: TabPaneName) => {
} }
editableTabs.value = tabs.filter((t) => String(t.id) !== target); editableTabs.value = tabs.filter((t) => String(t.id) !== target);
editableTabsKey.value = String(nextActive); editableTabsKey.value = String(nextActive);
changeTab(String(nextActive)); changeTab(nextActive);
}; };
const checkFFmpeg = () => { const checkFFmpeg = () => {
@ -1932,6 +1902,19 @@ const checkFFmpeg = () => {
}); });
}; };
onMounted(() => {
loadPath();
initShowHidden();
initTabsAndPaths();
getHostMount();
initHistory();
checkFFmpeg();
nextTick(function () {
handlePath();
observeResize();
});
});
onBeforeUnmount(() => { onBeforeUnmount(() => {
if (resizeObserver) resizeObserver.disconnect(); if (resizeObserver) resizeObserver.disconnect();
window.removeEventListener('resize', watchTitleHeight); window.removeEventListener('resize', watchTitleHeight);