修改部份ui

This commit is contained in:
Azmat@qq.com
2026-08-18 18:32:19 +08:00
parent dfe94a923e
commit 18b3927817
19 changed files with 2151 additions and 681 deletions
+5 -1
View File
@@ -28,7 +28,11 @@ const iconPaths: Record<string, string> = {
gauge: '<path d="m12 14 4-4"/><path d="M3.34 19a10 10 0 1 1 17.32 0"/>',
sliders: '<line x1="4" x2="4" y1="21" y2="14"/><line x1="4" x2="4" y1="10" y2="3"/><line x1="12" x2="12" y1="21" y2="12"/><line x1="12" x2="12" y1="8" y2="3"/><line x1="20" x2="20" y1="21" y2="16"/><line x1="20" x2="20" y1="12" y2="3"/><line x1="2" x2="6" y1="14" y2="14"/><line x1="10" x2="14" y1="8" y2="8"/><line x1="18" x2="22" y1="16" y2="16"/>',
logOut: '<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4"/><path d="m16 17 5-5-5-5"/><path d="M21 12H9"/>',
film: '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 3v18"/><path d="M17 3v18"/><path d="M3 7.5h4"/><path d="M3 16.5h4"/><path d="M17 7.5h4"/><path d="M17 16.5h4"/><path d="M3 12h4"/><path d="M17 12h4"/>'
film: '<rect x="3" y="3" width="18" height="18" rx="2"/><path d="M7 3v18"/><path d="M17 3v18"/><path d="M3 7.5h4"/><path d="M3 16.5h4"/><path d="M17 7.5h4"/><path d="M17 16.5h4"/><path d="M3 12h4"/><path d="M17 12h4"/>',
wand: '<path d="m21.64 3-1.28 1.28a5.5 5.5 0 0 0-7.78 7.78l-8.5 8.5a2.12 2.12 0 0 0 3 3l8.5-8.5a5.5 5.5 0 0 0 7.78-7.78Z"/><path d="m14 7 3 3"/>',
scan: '<path d="M3 7V5a2 2 0 0 1 2-2h2"/><path d="M17 3h2a2 2 0 0 1 2 2v2"/><path d="M21 17v2a2 2 0 0 1-2 2h-2"/><path d="M7 21H5a2 2 0 0 1-2-2v-2"/><path d="M7 12h10"/>',
swap: '<path d="M8 3 4 7l4 4"/><path d="M4 7h16"/><path d="m16 21 4-4-4-4"/><path d="M20 17H4"/>',
arrowRight: '<path d="M5 12h14"/><path d="m12 5 7 7-7 7"/>'
};
const iconAliases: Record<string, string> = {
+105 -34
View File
@@ -205,6 +205,35 @@ const NAV: NavDef[] = [
{ id: "trash", page: "trash", label: "垃圾桶", icon: "trash" },
{ id: "settings", page: "settings", label: "设置", icon: "settings" }
];
const RESOURCE_NAV: Array<{ id: string; page: Page; label: string }> = [
{ id: "products", page: "products", label: "商品库" },
{ id: "models", page: "models", label: "模特库" },
{ id: "library", page: "library", label: "资产库" },
// 成品库先隐藏,后续再加回资源中心
{ id: "account", page: "account", label: "消费" },
{ id: "trash", page: "trash", label: "垃圾桶" },
];
export type TopModule = "workbench" | "image" | "video";
const OPEN_PALETTE_EVENT = "airshelf:open-palette";
export function openCommandPalette() {
window.dispatchEvent(new Event(OPEN_PALETTE_EVENT));
}
export function topModuleForPage(page: Page): TopModule | null {
if (page === "dashboard") return "workbench";
if (
page === "assetFactory"
|| page === "imageOptimize"
|| page === "modelPhoto"
|| page === "modelPhotoDemoA"
|| page === "modelPhotoDemoB"
|| page === "platformCover"
) return "image";
if (page === "projects" || page === "projectWizard" || page === "pipeline" || page === "freeCreate") return "video";
return null;
}
const PAGE_TO_NAV: Partial<Record<Page, Page>> = {
dashboard: "dashboard",
@@ -251,6 +280,14 @@ export function Sidebar({ page, navigate, user, team, canManageBilling = true, p
const activeNav = PAGE_TO_NAV[page];
// 子账号:导航里去掉「团队」「消费」两项(命令面板、账户菜单同步过滤)。
const navItems = NAV.filter((item) => canManageBilling || (item.page !== "team" && item.page !== "account"));
const resourceNavItems = RESOURCE_NAV.filter((item) => navItems.some((nav) => nav.page === item.page));
const topModule = topModuleForPage(page);
const activeMajor: "resource" | "team" | "settings" | null =
activeNav === "team" ? "team"
: activeNav === "settings" ? "settings"
: topModule ? null
: resourceNavItems.some((item) => item.page === activeNav) ? "resource"
: null;
// 徽标用后端真实总数(分页 count),不用已加载的页内条数
const badges: Partial<Record<string, number>> = { products: productTotal ?? products.length, projects: projectTotal ?? projects.length };
// YYX#row22:有未读生成任务才显示胶囊(0 不显示)
@@ -275,6 +312,12 @@ export function Sidebar({ page, navigate, user, team, canManageBilling = true, p
document.addEventListener("keydown", onKey);
return () => document.removeEventListener("keydown", onKey);
}, [mobileNavOpen]);
// 二级类目展开/收起:进资源中心时展开;切到顶栏模块(工作台/图片/视频)时收起,避免左边再出现选中。
const [resourceOpen, setResourceOpen] = useState(activeMajor === "resource");
useEffect(() => {
if (activeMajor === "resource") setResourceOpen(true);
else if (topModule) setResourceOpen(false);
}, [activeMajor, topModule]);
// 账户下拉菜单:点侧栏 .user 用其 DOMRect 当锚点展开,再次点击收起(toggle)。
const [accountAnchor, setAccountAnchor] = useState<DOMRect | null>(null);
@@ -290,9 +333,8 @@ export function Sidebar({ page, navigate, user, team, canManageBilling = true, p
window.location.href = "/login";
};
// 命令面板:Ctrl/Cmd K 开关,点搜索框打开
// 命令面板:Ctrl/Cmd K,以及顶栏搜索按钮
const [paletteOpen, setPaletteOpen] = useState(false);
const openCommandPalette = () => setPaletteOpen(true);
useEffect(() => {
const onKey = (event: KeyboardEvent) => {
if ((event.metaKey || event.ctrlKey) && event.key.toLowerCase() === "k") {
@@ -300,8 +342,13 @@ export function Sidebar({ page, navigate, user, team, canManageBilling = true, p
setPaletteOpen((value) => !value);
}
};
const onOpen = () => setPaletteOpen(true);
window.addEventListener("keydown", onKey);
return () => window.removeEventListener("keydown", onKey);
window.addEventListener(OPEN_PALETTE_EVENT, onOpen);
return () => {
window.removeEventListener("keydown", onKey);
window.removeEventListener(OPEN_PALETTE_EVENT, onOpen);
};
}, []);
return (
@@ -328,28 +375,61 @@ export function Sidebar({ page, navigate, user, team, canManageBilling = true, p
<span className="sidebar-toggle-icon sidebar-toggle-icon--collapse"><IconKitSvg name="chevronLeft" size={18} strokeWidth={1.8} /></span>
<span className="sidebar-toggle-icon sidebar-toggle-icon--expand"><IconKitSvg name="chevronRight" size={18} strokeWidth={1.8} /></span>
</button>
<div className="search-box" title="搜索 (Ctrl K)" role="button" tabIndex={0} onClick={openCommandPalette} onKeyDown={(event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); openCommandPalette(); } }}>
<IconKitSvg name="search" />
<input id="global-search" placeholder="搜索" readOnly aria-label="打开全局搜索" />
<span className="kbd">Ctrl K</span>
<div className="yz-side-main">
<button
className={`yz-resource-head${activeMajor === "resource" ? " active" : ""}`}
type="button"
aria-expanded={resourceOpen}
onClick={() => {
// 在资源中心内点击=折叠/展开;在其它大类点击=切回资源中心并展开
if (activeMajor !== "resource") {
setResourceOpen(true);
navigate("products");
return;
}
setResourceOpen((value) => !value);
}}
>
<span className="major-dot" aria-hidden="true" />
<IconKitSvg name="library" />
<span></span>
<span className="chev"><IconKitSvg name="chevronRight" /></span>
</button>
<div className={`yz-resource-sub${resourceOpen ? " open" : " collapsed"}`}>
{resourceNavItems.map((item) => (
<a
key={item.id}
href={`/${item.id}`}
className={activeNav === item.page ? "active" : ""}
title={item.label}
aria-label={item.label}
onClick={(event) => { event.preventDefault(); setMobileNavOpen(false); navigate(item.page); }}
>
<span className="dot" />
<span>{item.label}</span>
{badges[item.id] !== undefined && <span className="pill-mini">{badges[item.id]}</span>}
</a>
))}
</div>
<a
className={`yz-side-link${activeMajor === "team" ? " active" : ""}`}
href="/team"
onClick={(event) => { event.preventDefault(); setMobileNavOpen(false); navigate("team"); }}
>
<span className="major-dot" aria-hidden="true" />
<IconKitSvg name="users" />
<span></span>
</a>
<a
className={`yz-side-link${activeMajor === "settings" ? " active" : ""}`}
href="/settings"
onClick={(event) => { event.preventDefault(); setMobileNavOpen(false); navigate("settings"); }}
>
<span className="major-dot" aria-hidden="true" />
<IconKitSvg name="settings" />
<span></span>
</a>
</div>
<div className="nav-section"></div>
<nav>
{navItems.map((item) => (
<a
key={item.id}
href={`/${item.id}`}
className={activeNav === item.page ? "active" : ""}
title={item.label}
aria-label={item.label}
onClick={(event) => { event.preventDefault(); setMobileNavOpen(false); navigate(item.page); }}
>
<IconKitSvg name={item.icon} />
<span>{item.label}</span>
{badges[item.id] !== undefined && <span className="pill-mini">{badges[item.id]}</span>}
</a>
))}
</nav>
{user.is_platform_admin && onOpenAdmin && (
<>
<div className="nav-section"></div>
@@ -399,16 +479,7 @@ export function Sidebar({ page, navigate, user, team, canManageBilling = true, p
}
export function Decorations() {
// 与设计稿 shell.js 一致:grid-bg 底纹 + 4 个 sq-mark(写死坐标,不压内容)
return (
<>
<div className="grid-bg" />
<span className="sq-mark" style={{ top: 238, left: 478 }} />
<span className="sq-mark" style={{ top: 478, left: 1198 }} />
<span className="sq-mark" style={{ bottom: 300, left: 238 }} />
<span className="sq-mark" style={{ top: 718, right: 240 }} />
</>
);
return <div className="grid-bg" />;
}
export function CornerMarks() {