feat(layout): 简洁模式右上角加换肤 + 界面模式下拉切换

抽出共享 appearance-controls(ThemeSwitcher 按 tone 适配 banner/content
配色,新增图标+下拉的 LayoutModeSwitcher);Header 复用换肤组件,简洁模式
ChatPage 顶栏右上角补上换肤与界面模式切换,与全量模式保持一致。

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
fengxiangdebaoziandClaude Opus 4.8 committed 2026-06-22 11:02:51 +08:00
1 parent 548cfd1719
commit 79f1a004a0
3 files changed
+191 -91

No files matched your search

@@ -1,9 +1,10 @@
import { Bell, ChevronDown, FileText, Shirt, User } from "lucide-react";
import { useEffect, useRef, useState } from "react";
import { Bell, ChevronDown, FileText, User } from "lucide-react";
import { useState } from "react";
import { useLocation, useNavigate } from "react-router-dom";
import { Button } from "@/components/ui/button";
import "@/components/page-sdebar.css";
import { ThemeSwitcher } from "@/components/workspace/appearance-controls";
import { NotificationDialog } from "@/components/workspace/notification-dialog";
import { getStoredAuth, getAccountDisplayName } from "@/core/auth";
import { isWujieEnv } from "@/core/embed/wujie";
@@ -18,7 +19,7 @@ import {
import { useSidebarMenuItems } from "@/core/page-layout/use-sidebar-menu";
import { cn } from "@/lib/utils";
import { useTheme, type ThemeType } from "../contexts/ThemeContext";
import { useTheme } from "../contexts/ThemeContext";
const iconButtonClass = (isDark: boolean) =>
cn(
@@ -32,93 +33,6 @@ const iconButtonClass = (isDark: boolean) =>
const LEFT_MENU_IDS = ["qa-center", "task-management", "light-app-center"];
const RIGHT_MENU_IDS = ["comprehensive-management", "resource-management"];
// 换肤色板:红色=红白主题(light),蓝色=浅蓝主题(lightblue),深蓝色=深色主题(dark)
const THEME_SWATCHES: { id: ThemeType; label: string; color: string }[] = [
{ id: "light", label: "红色", color: "#cf1322" },
{ id: "lightblue", label: "蓝色", color: "#0062d9" },
{ id: "dark", label: "深蓝色", color: "#002466" },
];
function ThemeSwitcher({ isDark }: { isDark: boolean }) {
const { theme, setTheme } = useTheme();
const [open, setOpen] = useState(false);
const closeTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
const cancelClose = () => {
if (closeTimer.current) {
clearTimeout(closeTimer.current);
closeTimer.current = null;
}
};
const scheduleClose = () => {
cancelClose();
closeTimer.current = setTimeout(() => setOpen(false), 120);
};
useEffect(() => () => cancelClose(), []);
// 切换按钮:悬浮只改背景,不改图标颜色(显式锁定 hover 文字色等于常态色)
const triggerClass = cn(
"size-8 shrink-0 rounded-sm",
isDark
? "text-slate-400 hover:bg-slate-800/50 hover:text-slate-400"
: "text-white hover:bg-[var(--brand-950)] hover:text-white active:bg-[#8a0618]",
);
return (
<div
className="relative"
onMouseEnter={() => {
cancelClose();
setOpen(true);
}}
onMouseLeave={scheduleClose}
>
<Button
type="button"
variant="ghost"
size="icon-sm"
className={triggerClass}
title="换肤"
aria-haspopup="true"
aria-expanded={open}
>
<Shirt className="size-4" />
</Button>
{open ? (
<div className="absolute top-full left-1/2 z-50 -translate-x-1/2 pt-2">
<div className="relative flex items-center gap-2 rounded-lg border border-black/5 bg-white px-2.5 py-1.5 shadow-lg">
<div className="absolute -top-1 left-1/2 size-2 -translate-x-1/2 rotate-45 rounded-[1px] border-t border-l border-black/5 bg-white" />
{THEME_SWATCHES.map((swatch) => {
const active = theme === swatch.id;
return (
<button
key={swatch.id}
type="button"
title={swatch.label}
aria-label={swatch.label}
aria-pressed={active}
onClick={() => {
setTheme(swatch.id);
setOpen(false);
}}
className="size-5 rounded-full transition-transform hover:scale-110"
style={{
backgroundColor: swatch.color,
boxShadow: active
? `0 0 0 1.5px #ffffff, 0 0 0 3px ${swatch.color}`
: undefined,
}}
/>
);
})}
</div>
</div>
) : null}
</div>
);
}
type HeaderProps = {
collapsed?: boolean;
};
@@ -210,7 +124,7 @@ const Header = ({ collapsed }: HeaderProps) => {
// 右侧操作区(换肤/通知/用户)—— 深浅两套布局共用
const actionCluster = (
<>
<ThemeSwitcher isDark={isDark} />
<ThemeSwitcher tone={isDark ? "banner-dark" : "banner-light"} />
<Button
type="button"