feat(*): 功能开放与页面布局 - 新增运维开关并优化后台及创作界面空间利用

This commit is contained in:
ddCat
2026-08-04 16:24:18 +08:00
parent 8f7c492b70
commit f439d600ba
36 changed files with 723 additions and 86 deletions

View File

@@ -6,11 +6,15 @@ import { ModelSetupGuide } from "@/components/layout/model-setup-guide";
import { WorkspaceSidebarFooter } from "@/components/layout/workspace-sidebar-footer";
import { navigationTools, type NavigationToolSlug } from "@/constant/navigation-tools";
import { cn } from "@/lib/utils";
import { refreshFeatureAvailability } from "@/lib/user-session";
import { isSpatialWorkbenchPath } from "@/lib/workspace-routes";
import { useUserStore } from "@/stores/use-user-store";
export function AppWorkspaceShell({ children }: { children: ReactNode }) {
const { pathname } = useLocation();
const navigate = useNavigate();
const user = useUserStore((state) => state.user);
const features = useUserStore((state) => state.features);
const [mobileSidebarExpanded, setMobileSidebarExpanded] = useState(false);
const scrollRef = useRef<HTMLElement>(null);
const [scrollState, setScrollState] = useState({
@@ -22,7 +26,13 @@ export function AppWorkspaceShell({ children }: { children: ReactNode }) {
const creationWorkspace = pathname === "/create";
const slug = pathname.split("/").filter(Boolean)[0];
const activeToolSlug = navigationTools.some((tool) => tool.slug === slug) ? (slug as NavigationToolSlug) : undefined;
const visibleNavigationTools = spatialWorkbench ? navigationTools : navigationTools.filter((tool) => tool.section === "创作空间");
const visibleNavigationTools = (spatialWorkbench ? navigationTools : navigationTools.filter((tool) => tool.section === "创作空间"))
.filter((tool) => {
if (tool.slug === "projects") return features.shortDramaEnabled;
if (tool.slug === "tasks") return features.taskCenterEnabled;
if (tool.slug === "wallet") return features.creditsEnabled;
return true;
});
const handleScroll = () => {
const element = scrollRef.current;
@@ -49,6 +59,17 @@ export function AppWorkspaceShell({ children }: { children: ReactNode }) {
handleScroll();
}, [visibleNavigationTools.length, mobileSidebarExpanded]);
useEffect(() => {
if (!user) return;
const refresh = () => void refreshFeatureAvailability().catch((error) => console.warn("功能开放状态刷新失败", error));
const timer = window.setInterval(refresh, 30_000);
window.addEventListener("focus", refresh);
return () => {
window.clearInterval(timer);
window.removeEventListener("focus", refresh);
};
}, [user]);
return (
<>
<div className={cn("app-workspace-shell flex h-dvh min-h-0 w-full overflow-hidden", spatialWorkbench && "is-spatial", creationWorkspace && "is-creation-workspace")}>

View File

@@ -24,10 +24,11 @@ export function WorkspaceSidebarFooter({ expandedClassName, collapsedClassName,
const setTheme = useThemeStore((state) => state.setTheme);
const user = useUserStore((state) => state.user);
const hydrated = useUserStore((state) => state.hydrated);
const creditsEnabled = useUserStore((state) => state.features.creditsEnabled);
const navigate = useNavigate();
const { message } = App.useApp();
const [menuOpen, setMenuOpen] = useState(false);
const { availableMicrocredits } = useWalletBalance(user?.id, true);
const { availableMicrocredits } = useWalletBalance(user?.id, creditsEnabled);
const balance = availableMicrocredits === null
? "--"
: (availableMicrocredits / 1_000_000).toLocaleString("zh-CN", { maximumFractionDigits: 2 });
@@ -69,7 +70,7 @@ export function WorkspaceSidebarFooter({ expandedClassName, collapsedClassName,
<UserAvatar user={user} className="size-8" />
<div className="min-w-0 flex-1">
<div className="flex min-w-0 items-center gap-1.5"><span className="truncate text-sm font-medium">{user.displayName || user.username}</span><IdentityProviderBadge user={user} /></div>
<div className="mt-0.5 truncate text-[var(--fs-label)] tabular-nums text-foreground/45">可用 {balance} 积分</div>
{creditsEnabled ? <div className="mt-0.5 truncate text-[var(--fs-label)] tabular-nums text-foreground/45">可用 {balance} 积分</div> : null}
</div>
</div>
@@ -92,11 +93,11 @@ export function WorkspaceSidebarFooter({ expandedClassName, collapsedClassName,
</div>
)}
>
<button type="button" className={cn("flex min-h-10 w-full min-w-0 items-center overflow-hidden rounded-md text-left transition-colors hover:bg-foreground/[.045]", accountClassName)} title={`${user.displayName || user.username} · ${balance} 积分`}>
<button type="button" className={cn("flex min-h-10 w-full min-w-0 items-center overflow-hidden rounded-md text-left transition-colors hover:bg-foreground/[.045]", accountClassName)} title={creditsEnabled ? `${user.displayName || user.username} · ${balance} 积分` : user.displayName || user.username}>
<UserAvatar user={user} className="size-7" />
<span className={cn("min-w-0 flex-1 flex-col", expandedClassName)}>
<span className="truncate text-xs font-medium">{user.displayName || user.username}</span>
<span className="mt-0.5 block truncate text-[var(--fs-micro)] tabular-nums text-foreground/42">{balance} 积分</span>
{creditsEnabled ? <span className="mt-0.5 block truncate text-[var(--fs-micro)] tabular-nums text-foreground/42">{balance} 积分</span> : null}
</span>
<ChevronRight className={cn("size-3.5 shrink-0 text-foreground/30", expandedClassName)} />
</button>