feat: 优化web
This commit is contained in:
@@ -0,0 +1,119 @@
|
||||
'use client';
|
||||
|
||||
import { create } from 'zustand';
|
||||
|
||||
export type AppTab = {
|
||||
id: string;
|
||||
title: string;
|
||||
path: string;
|
||||
pinned?: boolean;
|
||||
};
|
||||
|
||||
type TabStoreState = {
|
||||
tabs: AppTab[];
|
||||
activeId: string;
|
||||
open: (tab: Omit<AppTab, 'id'> & { id?: string }) => void;
|
||||
/** 同 path 则仅激活,否则新开 */
|
||||
openOrActivate: (tab: { path: string; title: string }) => void;
|
||||
/** 浏览器地址变化时同步当前页签 */
|
||||
syncFromPath: (path: string) => void;
|
||||
/** 切换租户:保留固定「概览」并重置页签 */
|
||||
resetForTenantSwitch: () => void;
|
||||
close: (id: string) => string | null;
|
||||
activate: (id: string) => void;
|
||||
};
|
||||
|
||||
let seq = 0;
|
||||
|
||||
const overview: AppTab = {
|
||||
id: 'overview',
|
||||
title: '概览',
|
||||
path: '/dashboard',
|
||||
pinned: true,
|
||||
};
|
||||
|
||||
function titleFromPath(path: string): string {
|
||||
const parts = path.split('/').filter(Boolean);
|
||||
const last = parts[parts.length - 1];
|
||||
return last ? decodeURIComponent(last) : path;
|
||||
}
|
||||
|
||||
export const useTabStore = create<TabStoreState>((set, get) => ({
|
||||
tabs: [overview],
|
||||
activeId: 'overview',
|
||||
|
||||
open: (tab) => {
|
||||
const id = tab.id ?? `t-${++seq}`;
|
||||
set((s) => ({
|
||||
tabs: [...s.tabs, { ...tab, id }],
|
||||
activeId: id,
|
||||
}));
|
||||
},
|
||||
|
||||
openOrActivate: ({ path, title }) => {
|
||||
const normalized = path.startsWith('/') ? path : `/${path}`;
|
||||
set((s) => {
|
||||
const hit = s.tabs.find((t) => t.path === normalized);
|
||||
if (hit) {
|
||||
return { activeId: hit.id };
|
||||
}
|
||||
const id = `t-${++seq}`;
|
||||
return {
|
||||
tabs: [...s.tabs, { id, path: normalized, title: title || titleFromPath(normalized) }],
|
||||
activeId: id,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
syncFromPath: (path) => {
|
||||
const normalized = path.startsWith('/') ? path : `/${path}`;
|
||||
if (!normalized.startsWith('/dashboard')) {
|
||||
return;
|
||||
}
|
||||
set((s) => {
|
||||
const hit = s.tabs.find((t) => t.path === normalized);
|
||||
if (hit) {
|
||||
return { activeId: hit.id };
|
||||
}
|
||||
const id = `t-${++seq}`;
|
||||
return {
|
||||
tabs: [
|
||||
...s.tabs,
|
||||
{
|
||||
id,
|
||||
path: normalized,
|
||||
title: titleFromPath(normalized),
|
||||
},
|
||||
],
|
||||
activeId: id,
|
||||
};
|
||||
});
|
||||
},
|
||||
|
||||
resetForTenantSwitch: () => {
|
||||
set({
|
||||
tabs: [overview],
|
||||
activeId: 'overview',
|
||||
});
|
||||
},
|
||||
|
||||
close: (id) => {
|
||||
const { tabs, activeId } = get();
|
||||
const t = tabs.find((x) => x.id === id);
|
||||
if (!t || t.pinned) {
|
||||
return null;
|
||||
}
|
||||
const nextTabs = tabs.filter((x) => x.id !== id);
|
||||
let nextActive = activeId;
|
||||
if (activeId === id) {
|
||||
const idx = tabs.findIndex((x) => x.id === id);
|
||||
const neighbor = tabs[idx - 1] ?? tabs[idx + 1];
|
||||
nextActive = neighbor?.id ?? 'overview';
|
||||
}
|
||||
set({ tabs: nextTabs, activeId: nextActive });
|
||||
const activeTab = get().tabs.find((x) => x.id === nextActive);
|
||||
return activeTab?.path ?? '/dashboard';
|
||||
},
|
||||
|
||||
activate: (id) => set({ activeId: id }),
|
||||
}));
|
||||
Reference in New Issue
Block a user