This commit is contained in:
fengxiangdebaozi committed 2026-06-15 18:20:39 +08:00
commit db50493577
24 files changed
+1833 -181

No files matched your search

-5
View File
@@ -590,11 +590,6 @@ export default function ChatPage() {
)}
/>
)}
{compactLanding && (
<div className="text-muted-foreground/70 w-full -translate-y-2 text-center text-[11px]">
CM Assistant V1.2 · 深度推理可能会涉及数据同步 · 信息由AI自动生成,请审慎参考
</div>
)}
{env.NEXT_PUBLIC_STATIC_WEBSITE_ONLY === "true" && (
<div className="text-muted-foreground/67 w-full translate-y-12 text-center text-xs">
{t.common.notAvailableInDemoMode}
+19 -12
View File
@@ -34,9 +34,12 @@ import { toast } from "sonner";
import { Pagination } from "@/components/ui/pagination";
import { cn } from "@/lib/utils";
import { useTheme } from "@/contexts/ThemeContext";
import { useLightApps, type LightApp } from "@/strategy-components/api/light-apps";
import { buildLightAppUrl } from "@/strategy-components/lib/light-app";
import "./light-app-pages.css";
const PAGE_SIZE = 12;
/** 卡片图标池(按 appId 哈希取一个,仅作视觉区分)。 */
@@ -65,6 +68,8 @@ export default function LightAppListPage() {
const [search, setSearch] = useState("");
const [page, setPage] = useState(1);
const [copiedId, setCopiedId] = useState<string | null>(null);
const { theme } = useTheme();
const isLightBlue = theme === 'lightblue';
const { data: apps = [], isLoading } = useLightApps({
appType: "window_open",
@@ -107,19 +112,19 @@ export default function LightAppListPage() {
};
return (
<div className="h-full overflow-y-auto bg-zinc-50/60 font-sans text-zinc-900 antialiased dark:bg-[#0C1836] dark:text-[#C5CFE0]">
<div className="light-app-list-page h-full overflow-y-auto bg-zinc-50/60 font-sans text-zinc-900 antialiased dark:bg-[#0C1836] dark:text-[#C5CFE0]">
<div className="mx-auto flex min-h-full w-full max-w-7xl flex-col gap-6 px-4 py-7 sm:px-6 lg:px-8">
{/* 标题 */}
<div className="space-y-1">
<div className="flex items-center gap-2.5">
<span className="relative flex size-3">
<span className="absolute inline-flex size-full animate-ping rounded-full bg-[#CF1322] opacity-75 dark:bg-[#1677FF]" />
<span className="relative inline-flex size-3 rounded-full bg-[#CF1322] dark:bg-[#1677FF]" />
<span className={cn("absolute inline-flex size-full animate-ping rounded-full opacity-75", isLightBlue ? "bg-[#0062D9]" : "bg-[#CF1322]", "dark:bg-[#1677FF]")} />
<span className={cn("relative inline-flex size-3 rounded-full", isLightBlue ? "bg-[#0062D9]" : "bg-[#CF1322]", "dark:bg-[#1677FF]")} />
</span>
<h1 className="text-2xl font-bold tracking-tight text-zinc-900 dark:text-[#E8EDF5]">
应用列表
</h1>
<span className="rounded-[5px] border border-[#CF1322]/15 bg-[#FFF1F0] px-2 py-0.5 font-mono text-xs font-medium text-[#CF1322] dark:border-[#1677FF]/30 dark:bg-[#1677FF]/15 dark:text-[#4096FF]">
<span className={cn("rounded-[5px] border px-2 py-0.5 font-mono text-xs font-medium dark:border-[#1677FF]/30 dark:bg-[#1677FF]/15 dark:text-[#4096FF]", isLightBlue ? "border-[#0062D9]/15 bg-[#EAF4FF] text-[#0062D9]" : "border-[#CF1322]/15 bg-[#FFF1F0] text-[#CF1322]")}>
导航工作台
</span>
</div>
@@ -137,7 +142,7 @@ export default function LightAppListPage() {
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="键入应用名称、目标地址、或描述,实时过滤检索…"
className="w-full rounded-[5px] border border-zinc-200 bg-zinc-50/50 py-3 pl-10 pr-10 text-sm transition focus:border-[#CF1322]/60 focus:bg-white focus:outline-none focus:ring-2 focus:ring-[#CF1322]/20 dark:border-[#1A2F5A] dark:bg-[#0C1836] dark:text-[#C5CFE0] dark:placeholder-[#4A6491] dark:focus:border-[#1677FF]/60 dark:focus:bg-[#122347] dark:focus:ring-[#1677FF]/25"
className={cn("w-full rounded-[5px] border border-zinc-200 bg-zinc-50/50 py-3 pl-10 pr-10 text-sm transition focus:bg-white focus:outline-none focus:ring-2 dark:border-[#1A2F5A] dark:bg-[#0C1836] dark:text-[#C5CFE0] dark:placeholder-[#4A6491] dark:focus:border-[#1677FF]/60 dark:focus:bg-[#122347] dark:focus:ring-[#1677FF]/25", isLightBlue ? "focus:border-[#0062D9]/60 focus:ring-[#0062D9]/20" : "focus:border-[#CF1322]/60 focus:ring-[#CF1322]/20")}
/>
{search && (
<button
@@ -156,13 +161,13 @@ export default function LightAppListPage() {
<div className="-mt-2 flex items-center justify-between">
<div className="text-xs font-medium text-zinc-500 dark:text-[#6E88B0]">
检索到最终结果:{" "}
<span className="font-bold text-[#CF1322] dark:text-[#4096FF]">{apps.length}</span> 个关联应用
<span className={cn("font-bold dark:text-[#4096FF]", isLightBlue ? "text-[#0062D9]" : "text-[#CF1322]")}>{apps.length}</span> 个关联应用
</div>
{search && (
<button
type="button"
onClick={() => setSearch("")}
className="text-xs font-medium text-[#CF1322] underline hover:text-[#A8071A] dark:text-[#4096FF] dark:hover:text-[#1677FF]"
className={cn("text-xs font-medium underline dark:text-[#4096FF] dark:hover:text-[#1677FF]", isLightBlue ? "text-[#0062D9] hover:text-[#0056C6]" : "text-[#CF1322] hover:text-[#A8071A]")}
>
重置过滤条件
</button>
@@ -233,8 +238,10 @@ function AppCard({
onCopy: () => void;
onOpen: () => void;
}) {
const { theme } = useTheme();
const isLightBlue = theme === 'lightblue';
const Icon = ICON_POOL[hashIndex(app.appId, ICON_POOL.length)];
const theme = THEME_POOL[hashIndex(app.appId, THEME_POOL.length)];
const cardTheme = THEME_POOL[hashIndex(app.appId, THEME_POOL.length)];
const parsedUrl = buildLightAppUrl(app);
const hasParams = (app.routeParams ?? []).some((p) => p.key) && parsedUrl !== app.url;
@@ -244,12 +251,12 @@ function AppCard({
{/* 上:图标 + 标题 + 描述 */}
<div>
<div className="mb-3.5 flex items-start gap-4">
<div className={cn("rounded-[5px] p-3 shadow-sm", theme.iconBg)}>
<div className={cn("rounded-[5px] p-3 shadow-sm", cardTheme.iconBg)}>
<Icon className="size-5.5" />
</div>
</div>
<h4 className="mb-2 line-clamp-1 text-base font-bold leading-snug text-zinc-900 transition-colors group-hover:text-[#CF1322] dark:text-[#E8EDF5] dark:group-hover:text-[#4096FF]">
<h4 className={cn("mb-2 line-clamp-1 text-base font-bold leading-snug text-zinc-900 transition-colors dark:text-[#E8EDF5] dark:group-hover:text-[#4096FF]", isLightBlue ? "group-hover:text-[#0062D9]" : "group-hover:text-[#CF1322]")}>
{app.name}
</h4>
@@ -263,7 +270,7 @@ function AppCard({
<div className="mb-4 rounded-[5px] bg-zinc-50/70 p-3 dark:bg-[#0C1836]">
<div className="mb-1 flex items-center justify-between text-[10px] font-semibold text-zinc-400 dark:text-[#6E88B0]">
<span>拼接后目标路由终端:</span>
<span className="text-[9px] font-bold uppercase tracking-wider text-[#CF1322] dark:text-[#4096FF]">
<span className={cn("text-[9px] font-bold uppercase tracking-wider dark:text-[#4096FF]", isLightBlue ? "text-[#0062D9]" : "text-[#CF1322]")}>
自动拼接已激活参
</span>
</div>
@@ -305,7 +312,7 @@ function AppCard({
<button
type="button"
onClick={onOpen}
className="flex items-center justify-center gap-1.5 rounded-[5px] bg-[#CF1322] px-3 py-2 text-xs font-semibold text-white shadow-[0_1px_3px_rgba(207,19,34,0.2)] transition hover:bg-[#A8071A] active:scale-[0.98] dark:bg-[#1677FF] dark:shadow-none dark:hover:bg-[#0958D9]"
className={cn("flex items-center justify-center gap-1.5 rounded-[5px] px-3 py-2 text-xs font-semibold text-white transition active:scale-[0.98] dark:bg-[#1677FF] dark:shadow-none dark:hover:bg-[#0958D9]", isLightBlue ? "bg-[#0062D9] shadow-[0_1px_3px_rgba(0,98,217,0.2)] hover:bg-[#0079FE]" : "bg-[#CF1322] shadow-[0_1px_3px_rgba(207,19,34,0.2)] hover:bg-[#A8071A]")}
>
<span>进入应用</span>
<ArrowUpRight className="size-3.5" />
+110 -100
View File
@@ -1,3 +1,8 @@
/**
* 应用管理页 —— 注册 / 编辑轻应用(iframe 与 window.open)。
*
* 深蓝主题适配见 `./light-app-management.css`(《深蓝色 UI 规范》tokens.css)。
*/
import { useMemo, useState } from "react";
import {
AlertCircle,
@@ -43,6 +48,8 @@ import {
stringifyThemeMap,
} from "@/strategy-components/lib/light-app";
import "./light-app-management.css";
interface FormState {
name: string;
description: string;
@@ -63,7 +70,10 @@ const PARAM_TYPE_OPTIONS: { label: string; value: "login" | "theme" }[] = [
{ label: "主题参数", value: "theme" },
];
const DIALOG_SELECT_POPUP = { attach: () => document.body };
const DIALOG_SELECT_POPUP = {
attach: () => document.body,
overlayClassName: "lam-select-popup",
};
const emptyForm = (): FormState => ({
name: "",
@@ -213,14 +223,14 @@ export default function LightAppManagementPage() {
const tableApps = useMemo(() => apps, [apps]);
return (
<div className="h-full overflow-y-auto bg-neutral-50">
<div className="light-app-management-page h-full overflow-y-auto bg-neutral-50 dark:bg-[#0C1836] dark:text-[#C5CFE0]">
<div className="mx-auto flex min-h-full w-full max-w-7xl flex-col space-y-7 px-8 py-8">
<div className="flex flex-col justify-between gap-6 md:flex-row md:items-center">
<div className="space-y-1.5">
<h1 className="flex items-center text-xl font-black tracking-tight text-neutral-900">
<h1 className="lam-title flex items-center text-xl font-black tracking-tight text-neutral-900">
应用管理
</h1>
<p className="max-w-2xl text-xs leading-relaxed text-neutral-500">
<p className="lam-desc max-w-2xl text-xs leading-relaxed text-neutral-500">
注册轻应用。
<code className="mx-1 rounded-[5px] bg-neutral-100 px-1.5 py-0.5 font-mono text-[11px] text-neutral-600">
window.open
@@ -235,14 +245,14 @@ export default function LightAppManagementPage() {
<button
onClick={openCreate}
className="flex cursor-pointer items-center justify-center space-x-1.5 rounded-[5px] bg-[#A8071A] px-5 py-2.5 text-xs font-semibold text-white shadow-md shadow-rose-950/10 transition-all hover:bg-[#BC0D1E] hover:shadow-lg hover:shadow-rose-900/15 active:bg-[#820014]"
className="lam-primary-btn flex cursor-pointer items-center justify-center space-x-1.5 rounded-[5px] bg-[#A8071A] px-5 py-2.5 text-xs font-semibold text-white shadow-md shadow-rose-950/10 transition-all hover:bg-[#BC0D1E] hover:shadow-lg hover:shadow-rose-900/15 active:bg-[#820014]"
>
<Plus className="h-4 w-4" />
<span>注册应用</span>
</button>
</div>
<div className="flex flex-col items-center gap-3.5 rounded-[5px] bg-white p-3 shadow-[0_1px_3px_rgba(0,0,0,0.01)] md:flex-row">
<div className="lam-toolbar flex flex-col items-center gap-3.5 rounded-[5px] bg-white p-3 shadow-[0_1px_3px_rgba(0,0,0,0.01)] md:flex-row">
<div className="relative w-full flex-1">
<Search className="absolute left-3.5 top-1/2 h-4 w-4 -translate-y-1/2 text-neutral-400" />
<input
@@ -250,7 +260,7 @@ export default function LightAppManagementPage() {
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="搜索应用名称 / 描述..."
className="w-full rounded-[5px] border border-neutral-100 bg-neutral-50/60 py-2 pl-10 pr-10 text-xs text-neutral-800 placeholder-neutral-400 transition-all hover:bg-white focus:border-[#F5222D]/40 focus:outline-none focus:ring-4 focus:ring-[#F5222D]/10"
className="lam-search-input w-full rounded-[5px] border border-neutral-100 bg-neutral-50/60 py-2 pl-10 pr-10 text-xs text-neutral-800 placeholder-neutral-400 transition-all hover:bg-white focus:border-[#F5222D]/40 focus:outline-none focus:ring-4 focus:ring-[#F5222D]/10"
/>
{search && (
<button
@@ -262,12 +272,12 @@ export default function LightAppManagementPage() {
)}
</div>
<div className="flex shrink-0 items-center space-x-0.5 self-end rounded-[5px] border border-neutral-200/50 bg-neutral-100/60 p-0.5 md:self-auto">
<div className="lam-view-toggle flex shrink-0 items-center space-x-0.5 self-end rounded-[5px] border border-neutral-200/50 bg-neutral-100/60 p-0.5 md:self-auto">
<button
onClick={() => setViewMode("table")}
className={`cursor-pointer rounded-[5px] px-4 py-1.5 text-xs font-bold transition-all ${
className={`lam-view-toggle-btn cursor-pointer rounded-[5px] px-4 py-1.5 text-xs font-bold transition-all ${
viewMode === "table"
? "bg-white text-[#F5222D] shadow-sm"
? "is-active bg-white text-[#F5222D] shadow-sm"
: "text-neutral-500 hover:text-neutral-700"
}`}
>
@@ -275,9 +285,9 @@ export default function LightAppManagementPage() {
</button>
<button
onClick={() => setViewMode("cards")}
className={`cursor-pointer rounded-[5px] px-4 py-1.5 text-xs font-bold transition-all ${
className={`lam-view-toggle-btn cursor-pointer rounded-[5px] px-4 py-1.5 text-xs font-bold transition-all ${
viewMode === "cards"
? "bg-white text-[#F5222D] shadow-sm"
? "is-active bg-white text-[#F5222D] shadow-sm"
: "text-neutral-500 hover:text-neutral-700"
}`}
>
@@ -287,14 +297,14 @@ export default function LightAppManagementPage() {
</div>
<div className="space-y-4">
<div className="text-xs font-medium text-neutral-500">
搜寻到 <span className="font-bold text-[#F5222D]">{tableApps.length}</span>{" "}
<div className="lam-count text-xs font-medium text-neutral-500">
搜寻到 <strong className="font-bold text-[#F5222D]">{tableApps.length}</strong>{" "}
个注册完成的子模块轻应用
</div>
{viewMode === "table" ? (
<div className="overflow-visible rounded-[5px] bg-white shadow-[0_1px_3px_0_rgba(0,0,0,0.01)]">
<table className="w-full table-fixed border-collapse text-left">
<div className="lam-table-wrap overflow-visible rounded-[5px] bg-white shadow-[0_1px_3px_0_rgba(0,0,0,0.01)]">
<table className="lam-table w-full table-fixed border-collapse text-left">
<thead>
<tr className="select-none border-b border-neutral-100/80 bg-neutral-50/40 text-[12.5px] font-semibold text-neutral-500">
<th className="w-[15%] px-6 py-4">名称</th>
@@ -310,19 +320,19 @@ export default function LightAppManagementPage() {
{tableApps.map((app) => (
<tr key={app.appId} className="group align-middle transition-colors hover:bg-neutral-50/40">
<td className="px-6 py-[18px]">
<span className="text-[14px] font-bold text-neutral-800 transition-colors group-hover:text-[#F5222D]">
<span className="lam-row-name text-[14px] font-bold text-neutral-800 transition-colors group-hover:text-[#F5222D]">
{app.name}
</span>
</td>
<td className="px-4 py-[18px]">
{app.appType === "window_open" ? (
<span className="inline-flex select-none items-center space-x-1.5 rounded-[5px] border border-rose-100 bg-rose-50 px-3 py-1 text-[12px] font-semibold text-rose-800">
<span className="lam-tag-window inline-flex select-none items-center space-x-1.5 rounded-[5px] border border-rose-100 bg-rose-50 px-3 py-1 text-[12px] font-semibold text-rose-800">
<ExternalLink className="h-3.5 w-3.5" />
<span>新页跳转</span>
</span>
) : (
<span className="inline-flex select-none items-center space-x-1.5 rounded-[5px] border border-sky-100 bg-sky-50 px-3 py-1 text-[12px] font-semibold text-sky-800">
<span className="lam-tag-iframe inline-flex select-none items-center space-x-1.5 rounded-[5px] border border-sky-100 bg-sky-50 px-3 py-1 text-[12px] font-semibold text-sky-800">
<Monitor className="h-3.5 w-3.5" />
<span>iframe 嵌套</span>
</span>
@@ -330,7 +340,7 @@ export default function LightAppManagementPage() {
</td>
<td className="overflow-hidden px-4 py-[18px]">
<div className="flex max-w-full items-center space-x-1.5 font-mono text-[12px] text-neutral-600 transition-colors group-hover:text-neutral-800">
<div className="lam-url flex max-w-full items-center space-x-1.5 font-mono text-[12px] text-neutral-600 transition-colors group-hover:text-neutral-800">
<a
href={app.url}
target="_blank"
@@ -342,7 +352,7 @@ export default function LightAppManagementPage() {
</a>
<button
onClick={() => handleCopyUrl(app.url)}
className="shrink-0 rounded-[5px] p-1 text-neutral-400 transition-colors hover:bg-neutral-100 hover:text-neutral-600"
className="lam-icon-btn shrink-0 rounded-[5px] p-1 text-neutral-400 transition-colors hover:bg-neutral-100 hover:text-neutral-600"
title="复制网址"
>
<Copy className="h-3 w-3" />
@@ -352,8 +362,8 @@ export default function LightAppManagementPage() {
<td className="px-4 py-[18px]">
{app.appType === "iframe" && app.mountLocation ? (
<span className="inline-flex max-w-full items-center space-x-1.5 rounded-[5px] bg-sky-50 px-2 py-0.5 text-[11px] font-medium text-sky-700">
<span className="h-1.5 w-1.5 shrink-0 rounded-full bg-sky-500" />
<span className="lam-mount-tag inline-flex max-w-full items-center space-x-1.5 rounded-[5px] bg-sky-50 px-2 py-0.5 text-[11px] font-medium text-sky-700">
<span className="lam-mount-dot h-1.5 w-1.5 shrink-0 rounded-full bg-sky-500" />
<span className="truncate">{mountLabel(app.mountLocation)}</span>
</span>
) : (
@@ -370,7 +380,7 @@ export default function LightAppManagementPage() {
activeParamPopover === app.appId ? null : app.appId
)
}
className="inline-flex cursor-pointer items-center space-x-1.5 rounded-[5px] border border-neutral-200/60 bg-neutral-50/60 px-2.5 py-1 text-xs font-medium text-neutral-500 transition-colors hover:bg-neutral-100"
className="lam-param-btn inline-flex cursor-pointer items-center space-x-1.5 rounded-[5px] border border-neutral-200/60 bg-neutral-50/60 px-2.5 py-1 text-xs font-medium text-neutral-500 transition-colors hover:bg-neutral-100"
>
<span>{app.routeParams.length}个</span>
{activeParamPopover === app.appId ? (
@@ -389,14 +399,14 @@ export default function LightAppManagementPage() {
className="fixed inset-0 z-40"
onClick={() => setActiveParamPopover(null)}
/>
<div className="absolute left-4 top-12 z-50 w-[280px] animate-in fade-in rounded-[5px] border border-neutral-100 bg-white p-3.5 text-[12px] shadow-[0_8px_32px_rgba(34,34,34,0.16)]">
<div className="mb-2.5 flex items-center justify-between border-b border-neutral-100 pb-2">
<div className="lam-param-popover absolute left-4 top-12 z-50 w-[280px] animate-in fade-in rounded-[5px] border border-neutral-100 bg-white p-3.5 text-[12px] shadow-[0_8px_32px_rgba(34,34,34,0.16)]">
<div className="lam-param-popover-head mb-2.5 flex items-center justify-between border-b border-neutral-100 pb-2">
<span className="text-[11px] font-bold uppercase tracking-wider text-neutral-400">
注入的路由参数
</span>
<button
onClick={() => setActiveParamPopover(null)}
className="text-[10px] font-semibold text-rose-700 hover:underline"
className="lam-param-popover-close text-[10px] font-semibold text-rose-700 hover:underline"
>
关闭
</button>
@@ -405,11 +415,11 @@ export default function LightAppManagementPage() {
{app.routeParams.map((p, idx) => (
<div
key={`${app.appId}-${idx}`}
className="rounded-[5px] border border-neutral-100 bg-neutral-50/60 p-2 text-[11px] leading-relaxed"
className="lam-param-item rounded-[5px] border border-neutral-100 bg-neutral-50/60 p-2 text-[11px] leading-relaxed"
>
<div className="flex items-center justify-between gap-1 font-mono">
<span className="truncate font-bold text-rose-700">{p.key}</span>
<span className="truncate text-[10px] text-neutral-500">
<span className="lam-param-key truncate font-bold text-rose-700">{p.key}</span>
<span className="lam-param-val truncate text-[10px] text-neutral-500">
{getParamSourceLabel(p)}
</span>
</div>
@@ -427,15 +437,15 @@ export default function LightAppManagementPage() {
{app.enabled ? (
<>
<span className="relative flex h-2.5 w-2.5 shrink-0">
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-emerald-500" />
<span className="lam-status-on-ping absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-75" />
<span className="lam-status-on-dot relative inline-flex h-2.5 w-2.5 rounded-full bg-emerald-500" />
</span>
<span className="text-[12.5px] font-bold text-emerald-700">已启用</span>
<span className="lam-status-on text-[12.5px] font-bold text-emerald-700">已启用</span>
</>
) : (
<>
<span className="h-2.5 w-2.5 shrink-0 rounded-full bg-neutral-300" />
<span className="text-[12.5px] font-medium text-neutral-400">已停用</span>
<span className="lam-status-off-dot h-2.5 w-2.5 shrink-0 rounded-full bg-neutral-300" />
<span className="lam-status-off text-[12.5px] font-medium text-neutral-400">已停用</span>
</>
)}
</div>
@@ -445,14 +455,14 @@ export default function LightAppManagementPage() {
<div className="flex items-center justify-center space-x-1">
<button
onClick={() => openEdit(app)}
className="cursor-pointer rounded-[5px] p-1.5 text-neutral-500 transition-colors hover:bg-neutral-100 hover:text-emerald-600"
className="lam-icon-btn cursor-pointer rounded-[5px] p-1.5 text-neutral-500 transition-colors hover:bg-neutral-100 hover:text-emerald-600"
title="编辑配置"
>
<Pencil className="h-4 w-4" />
</button>
<button
onClick={() => handleDelete(app)}
className="cursor-pointer rounded-[5px] p-1.5 text-neutral-400 transition-colors hover:bg-rose-50 hover:text-rose-600"
className="lam-icon-btn lam-icon-btn-danger cursor-pointer rounded-[5px] p-1.5 text-neutral-400 transition-colors hover:bg-rose-50 hover:text-rose-600"
title="删除配置"
>
<Trash2 className="h-4 w-4" />
@@ -466,15 +476,15 @@ export default function LightAppManagementPage() {
<tr>
<td colSpan={7} className="py-12 text-center">
<div className="flex flex-col items-center justify-center space-y-3">
<div className="flex h-12 w-12 items-center justify-center rounded-full bg-neutral-100 text-neutral-300">
<div className="lam-empty-icon flex h-12 w-12 items-center justify-center rounded-full bg-neutral-100 text-neutral-300">
<Info className="h-6 w-6" />
</div>
<div className="space-y-1">
<p className="text-[14px] font-semibold text-neutral-400">
<p className="lam-empty-title text-[14px] font-semibold text-neutral-400">
{isLoading ? "加载中..." : "暂无匹配的组件应用"}
</p>
{!isLoading && (
<p className="text-[12px] text-neutral-300">
<p className="lam-empty-sub text-[12px] text-neutral-300">
{search ? "请确认搜索关键词输入是否正确" : "点击右上角“注册应用”添加第一款轻应用"}
</p>
)}
@@ -487,15 +497,15 @@ export default function LightAppManagementPage() {
</table>
</div>
) : tableApps.length === 0 ? (
<div className="rounded-[5px] bg-white py-20 text-center">
<div className="mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-neutral-100 text-neutral-300">
<div className="lam-table-wrap rounded-[5px] bg-white py-20 text-center">
<div className="lam-empty-icon mx-auto mb-3 flex h-12 w-12 items-center justify-center rounded-full bg-neutral-100 text-neutral-300">
<Info className="h-6 w-6" />
</div>
<p className="text-[14px] font-semibold text-neutral-400">
<p className="lam-empty-title text-[14px] font-semibold text-neutral-400">
{isLoading ? "加载中..." : "暂无匹配的组件应用"}
</p>
{!isLoading && (
<p className="mt-1 text-[12px] text-neutral-300">
<p className="lam-empty-sub mt-1 text-[12px] text-neutral-300">
{search ? "请确认搜索关键词输入是否正确" : "点击右上角“注册应用”添加第一款轻应用"}
</p>
)}
@@ -505,7 +515,7 @@ export default function LightAppManagementPage() {
{tableApps.map((app) => (
<div
key={app.appId}
className="group flex flex-col justify-between overflow-hidden rounded-[5px] bg-white shadow-[0_1px_3px_0_rgba(0,0,0,0.01)] transition-all hover:shadow-md"
className="lam-card group flex flex-col justify-between overflow-hidden rounded-[5px] bg-white shadow-[0_1px_3px_0_rgba(0,0,0,0.01)] transition-all hover:shadow-md"
>
<div
className={`h-1.5 w-full bg-gradient-to-r ${
@@ -518,38 +528,38 @@ export default function LightAppManagementPage() {
<div className="flex items-start justify-between gap-3">
<div className="min-w-0 flex-1 space-y-1.5">
{app.appType === "window_open" ? (
<span className="inline-flex items-center space-x-1 rounded-[5px] border border-rose-100 bg-rose-50 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-rose-700">
<span className="lam-tag-window inline-flex items-center space-x-1 rounded-[5px] border border-rose-100 bg-rose-50 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-rose-700">
<ExternalLink className="h-3 w-3" />
<span>新页跳转</span>
</span>
) : (
<span className="inline-flex items-center space-x-1 rounded-[5px] border border-sky-100 bg-sky-50 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-sky-700">
<span className="lam-tag-iframe inline-flex items-center space-x-1 rounded-[5px] border border-sky-100 bg-sky-50 px-2 py-0.5 text-[10px] font-bold uppercase tracking-wider text-sky-700">
<Monitor className="h-3 w-3" />
<span>iframe 嵌套</span>
</span>
)}
<h4 className="line-clamp-1 text-sm font-bold leading-snug text-neutral-800 transition-colors group-hover:text-[#F5222D]">
<h4 className="lam-card-title line-clamp-1 text-sm font-bold leading-snug text-neutral-800 transition-colors group-hover:text-[#F5222D]">
{app.name}
</h4>
</div>
{app.enabled ? (
<span className="inline-flex shrink-0 items-center space-x-1 text-[11px] font-bold text-emerald-700">
<span className="h-2 w-2 rounded-full bg-emerald-500" />
<span className="lam-status-on inline-flex shrink-0 items-center space-x-1 text-[11px] font-bold text-emerald-700">
<span className="lam-status-on-dot h-2 w-2 rounded-full bg-emerald-500" />
<span>已启用</span>
</span>
) : (
<span className="inline-flex shrink-0 items-center space-x-1 text-[11px] font-medium text-neutral-400">
<span className="h-2 w-2 rounded-full bg-neutral-300" />
<span className="lam-status-off inline-flex shrink-0 items-center space-x-1 text-[11px] font-medium text-neutral-400">
<span className="lam-status-off-dot h-2 w-2 rounded-full bg-neutral-300" />
<span>已停用</span>
</span>
)}
</div>
<p className="line-clamp-2 min-h-[2.25rem] text-xs leading-relaxed text-neutral-500">
<p className="lam-card-desc line-clamp-2 min-h-[2.25rem] text-xs leading-relaxed text-neutral-500">
{app.description || "暂无应用详细描述说明。"}
</p>
<div className="flex items-center space-x-1.5 rounded-[5px] bg-neutral-50/60 p-2 font-mono text-[11px] text-neutral-600">
<div className="lam-card-url-box flex items-center space-x-1.5 rounded-[5px] bg-neutral-50/60 p-2 font-mono text-[11px] text-neutral-600">
<Globe className="h-3.5 w-3.5 shrink-0 text-neutral-400" />
<span className="flex-1 truncate" title={app.url}>
{app.url}
@@ -568,7 +578,7 @@ export default function LightAppManagementPage() {
{app.routeParams.map((p, idx) => (
<span
key={`${app.appId}-card-${idx}`}
className="rounded-[5px] border border-neutral-200/40 bg-neutral-100 px-1.5 py-0.5 font-mono text-[10px] text-neutral-500"
className="lam-card-param rounded-[5px] border border-neutral-200/40 bg-neutral-100 px-1.5 py-0.5 font-mono text-[10px] text-neutral-500"
>
{p.key}
</span>
@@ -577,24 +587,24 @@ export default function LightAppManagementPage() {
)}
{app.appType === "iframe" && app.mountLocation && (
<div className="inline-flex items-center gap-1.5 rounded-[5px] bg-sky-50/60 p-2 text-[11px] font-medium leading-none text-sky-700">
<div className="lam-card-mount inline-flex items-center gap-1.5 rounded-[5px] bg-sky-50/60 p-2 text-[11px] font-medium leading-none text-sky-700">
<span>挂载分类:</span>
<code className="font-semibold">{mountLabel(app.mountLocation)}</code>
</div>
)}
</div>
<div className="flex items-center justify-end space-x-1 border-t border-neutral-100/60 bg-neutral-50/30 px-5 py-3">
<div className="lam-card-footer flex items-center justify-end space-x-1 border-t border-neutral-100/60 bg-neutral-50/30 px-5 py-3">
<button
onClick={() => openEdit(app)}
className="cursor-pointer rounded-[5px] p-1.5 text-neutral-500 transition-colors hover:bg-neutral-100 hover:text-emerald-600"
className="lam-icon-btn cursor-pointer rounded-[5px] p-1.5 text-neutral-500 transition-colors hover:bg-neutral-100 hover:text-emerald-600"
title="编辑配置"
>
<Pencil className="h-4 w-4" />
</button>
<button
onClick={() => handleDelete(app)}
className="cursor-pointer rounded-[5px] p-1.5 text-neutral-400 transition-colors hover:bg-rose-50 hover:text-rose-600"
className="lam-icon-btn lam-icon-btn-danger cursor-pointer rounded-[5px] p-1.5 text-neutral-400 transition-colors hover:bg-rose-50 hover:text-rose-600"
title="删除配置"
>
<Trash2 className="h-4 w-4" />
@@ -607,21 +617,21 @@ export default function LightAppManagementPage() {
</div>
{viewMode === "table" && (
<div className="select-none rounded-[5px] bg-amber-50/40 p-5 shadow-[0_1px_2px_rgba(0,0,0,0.005)]">
<div className="flex items-center space-x-2 border-b border-amber-100/50 pb-3 text-amber-800">
<div className="lam-tip-box select-none rounded-[5px] bg-amber-50/40 p-5 shadow-[0_1px_2px_rgba(0,0,0,0.005)]">
<div className="lam-tip-head flex items-center space-x-2 border-b border-amber-100/50 pb-3 text-amber-800">
<AlertCircle className="h-4 w-4 shrink-0 text-amber-600" />
<h4 className="text-[13px] font-bold tracking-tight">智能多维挂载规则:</h4>
</div>
<ul className="mt-3.5 space-y-3 pl-1 text-[12px] leading-relaxed text-neutral-600">
<ul className="lam-tip-list mt-3.5 space-y-3 pl-1 text-[12px] leading-relaxed text-neutral-600">
<li className="flex items-start space-x-2">
<span className="mt-1 shrink-0 text-amber-600">•</span>
<span className="lam-tip-bullet mt-1 shrink-0 text-amber-600">•</span>
<span>
当您注册 <strong className="font-bold text-neutral-800">iframe 嵌套</strong>{" "}
应用时,您需要在菜单一栏指定分类(如“资源管理”),组件将直接作为新导航菜单项加入该分类。
</span>
</li>
<li className="flex items-start space-x-2">
<span className="mt-1 shrink-0 text-amber-600">•</span>
<span className="lam-tip-bullet mt-1 shrink-0 text-amber-600">•</span>
<span>
当路由挂载成功,点击左侧注入的子栏名称可在主区域调起{" "}
<strong className="font-bold text-neutral-800">真实 Iframe 预览器</strong>
@@ -636,23 +646,23 @@ export default function LightAppManagementPage() {
{dialogOpen && (
<div className="fixed inset-0 z-[100] flex items-center justify-center overflow-y-auto select-none">
<div
className="fixed inset-0 bg-[#3D3D3D]/40 backdrop-blur-[1px]"
className="lam-dialog-overlay fixed inset-0 bg-[#3D3D3D]/40 backdrop-blur-[1px]"
onClick={() => setDialogOpen(false)}
/>
<div className="relative z-[110] m-4 flex max-h-[90vh] w-full max-w-[580px] flex-col rounded-[5px] border border-[#E8E8E8] bg-white p-6 shadow-[0_8px_32px_rgba(34,34,34,0.16)]">
<div className="flex flex-shrink-0 items-center justify-between border-b border-[#E8E8E8] pb-4">
<div className="lam-dialog relative z-[110] m-4 flex max-h-[90vh] w-full max-w-[580px] flex-col rounded-[5px] border border-[#E8E8E8] bg-white p-6 shadow-[0_8px_32px_rgba(34,34,34,0.16)]">
<div className="lam-dialog-header flex flex-shrink-0 items-center justify-between border-b border-[#E8E8E8] pb-4">
<div className="flex items-center space-x-2.5">
<span className="rounded-[5px] bg-rose-50 p-1.5 text-[#A8071A]">
<span className="lam-dialog-icon rounded-[5px] bg-rose-50 p-1.5 text-[#A8071A]">
<Layers className="h-5 w-5" />
</span>
<h2 className="text-[17px] font-bold text-[#222222]">
<h2 className="lam-dialog-title text-[17px] font-bold text-[#222222]">
{editing ? "修改轻应用" : "注册应用"}
</h2>
</div>
<button
onClick={() => setDialogOpen(false)}
className="cursor-pointer rounded-[5px] p-1 text-[#A6A6A6] transition-colors hover:bg-[#FAFAFA] hover:text-[#F5222D]"
className="lam-dialog-close cursor-pointer rounded-[5px] p-1 text-[#A6A6A6] transition-colors hover:bg-[#FAFAFA] hover:text-[#F5222D]"
>
<X className="h-5 w-5" />
</button>
@@ -660,8 +670,8 @@ export default function LightAppManagementPage() {
<div className="flex-1 space-y-4 overflow-y-auto py-4 pr-1 text-[#222222]">
<div className="space-y-1.5">
<label className="flex items-center space-x-1.5 text-[14px] font-medium">
<span className="font-bold text-[#F5222D]">*</span>
<label className="lam-form-label flex items-center space-x-1.5 text-[14px] font-medium">
<span className="lam-required font-bold text-[#F5222D]">*</span>
<Layers className="h-4 w-4 text-[#A6A6A6]" />
<span>应用类型</span>
</label>
@@ -672,7 +682,7 @@ export default function LightAppManagementPage() {
popupProps={DIALOG_SELECT_POPUP}
onChange={(v) => patchForm({ appType: v as LightAppType })}
/>
<p className="text-[11.5px] leading-relaxed text-[#A6A6A6]">
<p className="lam-form-hint text-[11.5px] leading-relaxed text-[#A6A6A6]">
{form.appType === "window_open"
? "在新标签页或卡片式路由容器打开。多用于外部高度复杂的系统跳转。"
: "通过标准 iframe 嵌入页面,可直接挂载到左侧导航树相应分类内。"}
@@ -680,8 +690,8 @@ export default function LightAppManagementPage() {
</div>
<div className="space-y-1.5">
<label className="flex items-center space-x-1.5 text-[14px] font-medium">
<span className="font-bold text-[#F5222D]">*</span>
<label className="lam-form-label flex items-center space-x-1.5 text-[14px] font-medium">
<span className="lam-required font-bold text-[#F5222D]">*</span>
<Tag className="h-4 w-4 text-[#A6A6A6]" />
<span>应用名称</span>
</label>
@@ -689,13 +699,13 @@ export default function LightAppManagementPage() {
value={form.name}
onChange={(e) => patchForm({ name: e.target.value })}
placeholder="如: 舆情看板"
className="h-9 rounded-[5px] border-[#DBDBDB] text-[14px]"
className="lam-input h-9 rounded-[5px] border-[#DBDBDB] text-[14px]"
/>
</div>
<div className="space-y-1.5">
<label className="flex items-center space-x-1.5 text-[14px] font-medium">
<span className="font-bold text-[#F5222D]">*</span>
<label className="lam-form-label flex items-center space-x-1.5 text-[14px] font-medium">
<span className="lam-required font-bold text-[#F5222D]">*</span>
<Link2 className="h-4 w-4 text-[#A6A6A6]" />
<span>应用地址</span>
</label>
@@ -703,14 +713,14 @@ export default function LightAppManagementPage() {
value={form.url}
onChange={(e) => patchForm({ url: e.target.value })}
placeholder="https://example.com/page 或 http://47.88.25.99:3000/"
className="h-9 rounded-[5px] border-[#DBDBDB] font-mono text-[14px]"
className="lam-input h-9 rounded-[5px] border-[#DBDBDB] font-mono text-[14px]"
/>
</div>
{form.appType === "iframe" && (
<div className="animate-in fade-in space-y-1.5">
<label className="flex items-center space-x-1.5 text-[14px] font-medium">
<span className="font-bold text-[#F5222D]">*</span>
<label className="lam-form-label flex items-center space-x-1.5 text-[14px] font-medium">
<span className="lam-required font-bold text-[#F5222D]">*</span>
<FolderTree className="h-4 w-4 text-[#A6A6A6]" />
<span>挂载菜单</span>
</label>
@@ -726,7 +736,7 @@ export default function LightAppManagementPage() {
)}
<div className="space-y-1.5">
<label className="flex items-center space-x-1.5 text-[14px] font-medium">
<label className="lam-form-label flex items-center space-x-1.5 text-[14px] font-medium">
<span className="w-2 shrink-0" />
<AlignLeft className="h-4 w-4 text-[#A6A6A6]" />
<span>应用描述 (可选)</span>
@@ -736,13 +746,13 @@ export default function LightAppManagementPage() {
onChange={(e) => patchForm({ description: e.target.value })}
placeholder="简要说明该应用的用途"
rows={2}
className="resize-none rounded-[5px] border-[#DBDBDB] text-[14px]"
className="lam-input resize-none rounded-[5px] border-[#DBDBDB] text-[14px]"
/>
</div>
<div className="space-y-2.5 pt-1.5">
<div className="flex items-center justify-between">
<span className="flex items-center space-x-1.5 text-[14px] font-medium">
<span className="lam-form-label flex items-center space-x-1.5 text-[14px] font-medium">
<Sliders className="h-4 w-4 text-[#A6A6A6]" />
<span>路由参数</span>
<span
@@ -755,7 +765,7 @@ export default function LightAppManagementPage() {
<button
type="button"
onClick={addParam}
className="flex h-7 cursor-pointer items-center space-x-1 rounded-[5px] border border-[#DBDBDB] bg-white px-2.5 text-[12px] font-bold text-[#575757] transition-colors hover:border-[#F5222D] hover:text-[#F5222D]"
className="lam-add-param-btn flex h-7 cursor-pointer items-center space-x-1 rounded-[5px] border border-[#DBDBDB] bg-white px-2.5 text-[12px] font-bold text-[#575757] transition-colors hover:border-[#F5222D] hover:text-[#F5222D]"
>
<Plus className="h-3.5 w-3.5" />
<span>添加路由参数</span>
@@ -763,14 +773,14 @@ export default function LightAppManagementPage() {
</div>
{form.routeParams.length > 0 ? (
<div className="max-h-[220px] space-y-2 overflow-y-auto rounded-[5px] border border-[#E8E8E8] bg-[#FAFAFA] p-1">
<div className="lam-param-panel max-h-[220px] space-y-2 overflow-y-auto rounded-[5px] border border-[#E8E8E8] bg-[#FAFAFA] p-1">
{form.routeParams.map((p, idx) => {
const themeMap =
p.paramType === "theme" ? parseThemeMap(p.themeValue) : {};
return (
<div
key={idx}
className="relative rounded-[5px] border border-[#E8E8E8] bg-white p-2"
className="lam-param-row relative rounded-[5px] border border-[#E8E8E8] bg-white p-2"
>
<div className="flex items-center space-x-2">
<div className="flex-1">
@@ -779,7 +789,7 @@ export default function LightAppManagementPage() {
value={p.key}
onChange={(e) => patchParam(idx, { key: e.target.value })}
placeholder="参数名 key"
className="h-8 w-full rounded-[5px] border border-[#DBDBDB] bg-white px-2 font-mono text-[12px] focus:border-[#F5222D] focus:outline-none focus:ring-px focus:ring-[#F5222D]"
className="lam-input h-8 w-full rounded-[5px] border border-[#DBDBDB] bg-white px-2 font-mono text-[12px] focus:border-[#F5222D] focus:outline-none focus:ring-px focus:ring-[#F5222D]"
/>
</div>
@@ -817,7 +827,7 @@ export default function LightAppManagementPage() {
patchParam(idx, { customValue: e.target.value })
}
placeholder="自定义值"
className="h-8 w-[120px] rounded-[5px] border border-[#DBDBDB] px-2 text-[12px] focus:border-[#F5222D] focus:outline-none"
className="lam-input h-8 w-[120px] rounded-[5px] border border-[#DBDBDB] px-2 text-[12px] focus:border-[#F5222D] focus:outline-none"
/>
)}
</div>
@@ -826,7 +836,7 @@ export default function LightAppManagementPage() {
<button
type="button"
onClick={() => removeParam(idx)}
className="cursor-pointer rounded-[5px] p-1 text-[#A6A6A6] transition-colors hover:bg-[#FEEDED] hover:text-[#F5222D]"
className="lam-icon-btn lam-icon-btn-danger cursor-pointer rounded-[5px] p-1 text-[#A6A6A6] transition-colors hover:bg-[#FEEDED] hover:text-[#F5222D]"
>
<X className="h-4 w-4" />
</button>
@@ -834,8 +844,8 @@ export default function LightAppManagementPage() {
{/* 主题参数:逐主题色配置传给目标应用的值(左=系统主题色,右=目标应用值,可改) */}
{p.paramType === "theme" && (
<div className="mt-2 space-y-1.5 rounded-[5px] bg-[#FAFAFA] p-2">
<p className="text-[11px] leading-relaxed text-[#A6A6A6]">
<div className="lam-param-theme-box mt-2 space-y-1.5 rounded-[5px] bg-[#FAFAFA] p-2">
<p className="lam-form-hint text-[11px] leading-relaxed text-[#A6A6A6]">
各系统主题色跳转/嵌套时传给该应用的值(默认同名,可按对方命名修改)
</p>
{APP_THEME_COLORS.map((c) => (
@@ -862,7 +872,7 @@ export default function LightAppManagementPage() {
})
}
placeholder={c.key}
className="h-7 flex-1 rounded-[5px] border border-[#DBDBDB] bg-white px-2 font-mono text-[12px] focus:border-[#F5222D] focus:outline-none focus:ring-px focus:ring-[#F5222D]"
className="lam-input h-7 flex-1 rounded-[5px] border border-[#DBDBDB] bg-white px-2 font-mono text-[12px] focus:border-[#F5222D] focus:outline-none focus:ring-px focus:ring-[#F5222D]"
/>
</div>
))}
@@ -873,19 +883,19 @@ export default function LightAppManagementPage() {
})}
</div>
) : (
<div className="rounded-[5px] border border-dashed border-[#E8E8E8] bg-[#FAFAFA] p-4 text-center text-[12px] text-[#A6A6A6]">
<div className="lam-param-empty rounded-[5px] border border-dashed border-[#E8E8E8] bg-[#FAFAFA] p-4 text-center text-[12px] text-[#A6A6A6]">
暂无注入的路由参数,加载地址时不传递多余拼接。
</div>
)}
</div>
<div className="flex items-center justify-between border-t border-[#E8E8E8] py-2.5">
<span className="text-[14px] font-medium text-[#222222]">立即启用该应用</span>
<div className="lam-dialog-footer flex items-center justify-between border-t border-[#E8E8E8] py-2.5">
<span className="lam-form-label text-[14px] font-medium text-[#222222]">立即启用该应用</span>
<button
type="button"
onClick={() => patchForm({ enabled: !form.enabled })}
className={`relative h-[22px] w-10 cursor-pointer rounded-full p-0.5 transition-colors duration-200 ${
form.enabled ? "bg-[#F5222D]" : "bg-[#DBDBDB]"
form.enabled ? "lam-switch-on bg-[#F5222D]" : "lam-switch-off bg-[#DBDBDB]"
}`}
>
<div
@@ -897,12 +907,12 @@ export default function LightAppManagementPage() {
</div>
</div>
<div className="flex flex-shrink-0 items-center justify-end space-x-3 border-t border-[#E8E8E8] pt-4">
<div className="lam-dialog-footer flex flex-shrink-0 items-center justify-end space-x-3 border-t border-[#E8E8E8] pt-4">
<Button
variant="outline"
disabled={saving}
onClick={() => setDialogOpen(false)}
className="h-9 rounded-[5px] border-[#DBDBDB] px-[18px] text-[14px] font-semibold"
className="lam-dialog-cancel h-9 rounded-[5px] border-[#DBDBDB] px-[18px] text-[14px] font-semibold"
>
取消
</Button>
@@ -910,7 +920,7 @@ export default function LightAppManagementPage() {
type="button"
disabled={saving}
onClick={handleSave}
className="h-9 cursor-pointer rounded-[5px] bg-[#F5222D] px-[18px] text-[14px] font-bold text-white shadow-[0_2px_8px_rgba(34,34,34,0.08)] transition-colors hover:bg-[#CF1322] active:bg-[#A8071A] disabled:cursor-not-allowed disabled:opacity-70"
className="lam-dialog-save h-9 cursor-pointer rounded-[5px] bg-[#F5222D] px-[18px] text-[14px] font-bold text-white shadow-[0_2px_8px_rgba(34,34,34,0.08)] transition-colors hover:bg-[#CF1322] active:bg-[#A8071A] disabled:cursor-not-allowed disabled:opacity-70"
>
{saving ? "保存中..." : editing ? "保存修改" : "立即注册"}
</button>
@@ -9,6 +9,7 @@ import { cn } from "@/lib/utils";
export default function LightAppNoteQAPage() {
const { theme } = useTheme();
const isDark = theme === "dark";
const isLightBlue = theme === 'lightblue';
const [question, setQuestion] = useState("");
const handleSubmit = () => {
@@ -111,7 +112,9 @@ export default function LightAppNoteQAPage() {
"flex h-7 w-7 shrink-0 items-center justify-center rounded-full transition-colors",
isDark
? "bg-sky-500 text-white hover:bg-sky-400"
: "bg-[#F5222D] text-white hover:bg-[#CF1322]",
: isLightBlue
? "bg-[#0062D9] text-white hover:bg-[#0079FE]"
: "bg-[#F5222D] text-white hover:bg-[#CF1322]",
)}
aria-label="发送"
>
+13 -2
View File
@@ -5,8 +5,10 @@ import { PageSidebar } from "@/components/page-sidebar";
import { PageSidebarDark } from "@/components/page-sidebar-dark";
import { isChatsIframeWorkspacePath } from "@/core/page-layout/chats-iframe-route";
import { readSidebarEnabled } from "@/core/page-layout/sidebar-enabled";
import { isWujieEnv } from "@/core/embed/wujie";
import { useTheme } from "@/contexts/ThemeContext";
import { useLocation } from "react-router-dom";
import { useActiveThirdLevel } from "@/core/page-layout/use-sidebar-menu";
import { useLayoutMode } from "@/core/system-settings/hooks";
import WorkspaceRoutes from "./WorkspaceRoutes";
@@ -23,6 +25,12 @@ function PageLayout() {
const location = useLocation();
const { theme } = useTheme();
const isDark = theme === "dark";
// 浅色非嵌入:侧边栏为新版「二级导航图标卡片」窄栏(96px);wujie 嵌入仍走旧版手风琴(64/256px)。
const inWujie = useMemo(() => isWujieEnv(), []);
const lightNarrowSidebar = !isDark && !inWujie;
// 浅色窄栏在右侧展开「三级列」时整体变宽(96→236px),主内容左内边距随之过渡。
const thirdLevel = useActiveThirdLevel();
const lightExpanded = lightNarrowSidebar && thirdLevel.length > 0;
// 界面模式(外观设置):用户显式选「全量/简洁」时以其为准;否则跟随管理员系统默认
// (default_layout_mode);两者都没有才回退到会话级 sidebarEnabled(外部门户/yUserId
// 登录时置位)这一旧默认。这样无论是否走门户登录,「全量↔简洁」都能真正控制顶栏+侧边栏。
@@ -62,8 +70,11 @@ function PageLayout() {
// 只剩左下角浮动按钮(fixed,叠在 main 上不影响布局)。
// 不加 transition:transition + sidebar 立刻 unmount 不同步会抽搐
? (collapsed ? "pl-0" : "pl-[118px]")
// 浅色仍保留过渡动画(pl-16 ↔ pl-64 平滑收展)
: `transition-[padding] duration-300 ${collapsed ? "pl-16" : "pl-64"}`
: lightNarrowSidebar
// 浅色新版窄栏:展开三级列时 96→236px,主内容左内边距随之过渡
? `transition-[padding] duration-300 ${lightExpanded ? "pl-[236px]" : "pl-[96px]"}`
// 浅色旧版手风琴(wujie 嵌入):pl-16 ↔ pl-64 平滑收展
: `transition-[padding] duration-300 ${collapsed ? "pl-16" : "pl-64"}`
}`}
>
<Routes>
@@ -0,0 +1,675 @@
@import "../../docs/ui规范-深蓝/tokens.css";
@import "./light-app-pages.css";
/**
* 应用管理页 —— 深蓝色主题适配(《深蓝色 UI 规范》)
* 浅色样式仍由组件内 Tailwind 承担;.dark 下本文件覆盖为 ZS-004 / ZS-002 令牌。
*/
.dark .light-app-management-page {
color: var(--color-text);
}
/* ── 页头 / 文案 ─────────────────────────────────────────────────────── */
.dark .light-app-management-page .lam-title {
color: var(--color-text-strong);
}
.dark .light-app-management-page .lam-desc {
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-desc code {
background: rgba(22, 119, 255, 0.12);
color: var(--color-link);
}
.dark .light-app-management-page .lam-primary-btn {
background: var(--color-primary);
box-shadow: none;
}
.dark .light-app-management-page .lam-primary-btn:hover {
background: var(--color-primary-hover);
}
.dark .light-app-management-page .lam-primary-btn:active {
background: var(--color-primary-active);
}
/* ── 工具条 / 搜索 / 视图切换 ─────────────────────────────────────────── */
.dark .light-app-management-page .lam-toolbar {
background: var(--color-bg-card);
border: 1px solid var(--color-border);
box-shadow: var(--shadow-1);
}
.dark .light-app-management-page .lam-search-input {
background: var(--color-bg-page);
border-color: var(--color-border);
color: var(--color-text);
}
.dark .light-app-management-page .lam-search-input::placeholder {
color: var(--color-text-disabled);
}
.dark .light-app-management-page .lam-search-input:hover {
background: var(--color-bg-card);
border-color: var(--color-link);
}
.dark .light-app-management-page .lam-search-input:focus {
background: var(--color-bg-card);
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.16);
outline: none;
}
.dark .light-app-management-page .lam-view-toggle {
background: var(--color-bg-page);
border-color: var(--color-border);
}
.dark .light-app-management-page .lam-view-toggle-btn.is-active {
background: var(--color-bg-card);
color: var(--color-link);
box-shadow: var(--shadow-1);
}
.dark .light-app-management-page .lam-view-toggle-btn:not(.is-active) {
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-view-toggle-btn:not(.is-active):hover {
color: var(--color-text);
}
.dark .light-app-management-page .lam-count {
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-count strong {
color: var(--color-link);
}
/* ── 表格(§28)──────────────────────────────────────────────────────── */
.dark .light-app-management-page .lam-table-wrap {
background: var(--color-bg-page);
border: 1px solid var(--color-border);
box-shadow: var(--shadow-1);
}
.dark .light-app-management-page .lam-table thead tr {
background: var(--color-bg-card);
border-color: var(--color-border);
color: var(--color-text);
}
.dark .light-app-management-page .lam-table tbody {
border-color: var(--color-border);
}
.dark .light-app-management-page .lam-table tbody tr {
border-color: var(--color-border);
}
.dark .light-app-management-page .lam-table tbody tr:hover {
background: rgba(22, 119, 255, 0.08);
}
.dark .light-app-management-page .lam-row-name {
color: var(--color-text);
}
.dark .light-app-management-page .lam-table tbody tr:hover .lam-row-name {
color: var(--color-link);
}
.dark .light-app-management-page .lam-tag-window {
border-color: rgba(245, 34, 45, 0.35);
background: var(--func-error-bg);
color: #ff7875;
}
.dark .light-app-management-page .lam-tag-iframe {
border-color: rgba(22, 119, 255, 0.35);
background: var(--func-info-bg);
color: var(--color-link);
}
.dark .light-app-management-page .lam-url {
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-table tbody tr:hover .lam-url {
color: var(--color-text);
}
.dark .light-app-management-page .lam-url a {
color: inherit;
}
.dark .light-app-management-page .lam-url a:hover {
color: var(--color-link);
}
.dark .light-app-management-page .lam-mount-tag {
background: var(--func-info-bg);
color: var(--color-link);
}
.dark .light-app-management-page .lam-mount-dot {
background: var(--color-primary);
}
.dark .light-app-management-page .lam-param-btn {
border-color: var(--color-border);
background: var(--color-bg-page);
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-param-btn:hover {
background: rgba(22, 119, 255, 0.08);
color: var(--color-text);
}
.dark .light-app-management-page .lam-param-popover {
border-color: var(--color-border);
background: var(--color-bg-card);
box-shadow: var(--shadow-3);
}
.dark .light-app-management-page .lam-param-popover-head {
border-color: var(--color-border);
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-param-popover-close {
color: var(--color-link);
}
.dark .light-app-management-page .lam-param-item {
border-color: var(--color-border);
background: var(--color-bg-page);
}
.dark .light-app-management-page .lam-param-key {
color: #ff7875;
}
.dark .light-app-management-page .lam-param-val {
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-status-on {
color: var(--func-success);
}
.dark .light-app-management-page .lam-status-on-dot {
background: var(--func-success);
}
.dark .light-app-management-page .lam-status-on-ping {
background: var(--func-success);
}
.dark .light-app-management-page .lam-status-off {
color: var(--color-text-disabled);
}
.dark .light-app-management-page .lam-status-off-dot {
background: var(--color-text-disabled);
}
.dark .light-app-management-page .lam-icon-btn {
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-icon-btn:hover {
background: rgba(22, 119, 255, 0.08);
color: var(--color-link);
}
.dark .light-app-management-page .lam-icon-btn-danger:hover {
background: var(--func-error-bg);
color: var(--func-error);
}
.dark .light-app-management-page .lam-empty-icon {
background: var(--color-bg-page);
border: 1px solid var(--color-border);
color: var(--color-text-disabled);
}
.dark .light-app-management-page .lam-empty-title {
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-empty-sub {
color: var(--color-text-disabled);
}
/* ── 卡片视图(§22 / §43)────────────────────────────────────────────── */
.dark .light-app-management-page .lam-card {
background: var(--color-bg-card);
border: 1px solid var(--color-border);
box-shadow: var(--shadow-1);
}
.dark .light-app-management-page .lam-card:hover {
box-shadow: var(--shadow-2);
}
.dark .light-app-management-page .lam-card-title {
color: var(--color-text-strong);
}
.dark .light-app-management-page .lam-card:hover .lam-card-title {
color: var(--color-link);
}
.dark .light-app-management-page .lam-card-desc {
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-card-url-box {
background: var(--color-bg-page);
}
.dark .light-app-management-page .lam-card-param {
border-color: var(--color-border);
background: var(--color-bg-page);
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-card-mount {
background: var(--func-info-bg);
color: var(--color-link);
}
.dark .light-app-management-page .lam-card-footer {
border-color: var(--color-border);
background: rgba(12, 24, 54, 0.5);
}
/* ── 底部提示(§33 警告)────────────────────────────────────────────── */
.dark .light-app-management-page .lam-tip-box {
background: var(--func-warning-bg);
border: 1px solid rgba(250, 173, 20, 0.25);
box-shadow: none;
}
.dark .light-app-management-page .lam-tip-head {
border-color: rgba(250, 173, 20, 0.2);
color: var(--func-warning);
}
.dark .light-app-management-page .lam-tip-list {
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-tip-list strong {
color: var(--color-text);
}
.dark .light-app-management-page .lam-tip-bullet {
color: var(--func-warning);
}
/* ── 弹窗(§21 Modal)────────────────────────────────────────────────── */
.dark .light-app-management-page .lam-dialog-overlay {
background: rgba(6, 14, 36, 0.6);
}
.dark .light-app-management-page .lam-dialog {
border-color: var(--color-border);
background: linear-gradient(180deg, var(--color-bg-card), var(--color-bg-page));
box-shadow: var(--shadow-3);
color: var(--color-text);
}
.dark .light-app-management-page .lam-dialog-header {
border-color: var(--color-border);
border-bottom: none;
padding-bottom: 0;
}
.dark .light-app-management-page .lam-dialog-header::after {
content: "";
display: block;
height: 1px;
margin-top: 16px;
margin-bottom: 0;
background: var(--gradient-title-line);
}
.dark .light-app-management-page .lam-dialog-icon {
background: var(--func-info-bg);
color: var(--color-link);
}
.dark .light-app-management-page .lam-dialog-title {
color: var(--color-text-strong);
}
.dark .light-app-management-page .lam-dialog-close {
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-dialog-close:hover {
background: rgba(22, 119, 255, 0.08);
color: var(--color-link);
}
.dark .light-app-management-page .lam-form-label {
color: var(--color-text);
}
.dark .light-app-management-page .lam-form-label .lam-required {
color: var(--func-error);
}
.dark .light-app-management-page .lam-form-label svg {
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-form-hint {
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-dialog .lam-input,
.dark .light-app-management-page .lam-dialog input[type="text"] {
background: var(--color-bg-page);
border-color: var(--color-border);
color: var(--color-text);
}
.dark .light-app-management-page .lam-dialog .lam-input::placeholder,
.dark .light-app-management-page .lam-dialog input[type="text"]::placeholder {
color: var(--color-text-disabled);
}
.dark .light-app-management-page .lam-dialog .lam-input:focus,
.dark .light-app-management-page .lam-dialog input[type="text"]:focus {
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.16);
outline: none;
}
.dark .light-app-management-page .lam-param-panel {
border-color: var(--color-border);
background: var(--color-bg-page);
}
.dark .light-app-management-page .lam-param-row {
border-color: var(--color-border);
background: var(--color-bg-card);
}
.dark .light-app-management-page .lam-param-theme-box {
background: var(--color-bg-page);
}
.dark .light-app-management-page .lam-param-empty {
border-color: var(--color-border);
background: var(--color-bg-page);
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-add-param-btn {
border-color: var(--color-border-strong);
background: var(--color-bg-card);
color: var(--color-text);
}
.dark .light-app-management-page .lam-add-param-btn:hover {
border-color: var(--color-primary);
color: var(--color-link);
}
.dark .light-app-management-page .lam-dialog-footer {
border-color: var(--color-border);
}
.dark .light-app-management-page .lam-dialog-cancel {
border-color: var(--color-border-strong);
background: transparent;
color: var(--color-text);
}
.dark .light-app-management-page .lam-dialog-cancel:hover {
border-color: var(--color-link);
color: var(--color-link);
}
.dark .light-app-management-page .lam-dialog-save {
background: var(--color-primary);
box-shadow: none;
}
.dark .light-app-management-page .lam-dialog-save:hover {
background: var(--color-primary-hover);
}
.dark .light-app-management-page .lam-dialog-save:active {
background: var(--color-primary-active);
}
.dark .light-app-management-page .lam-switch-on {
background: var(--color-primary);
}
.dark .light-app-management-page .lam-switch-off {
background: var(--color-text-disabled);
}
/* ── TDesign Select(§15)──────────────────────────────────────────── */
.dark .light-app-management-page .t-select .t-input,
.dark .light-app-management-page .t-select .t-input__wrap {
background: var(--color-bg-page);
border-color: var(--color-border);
border-radius: var(--radius-input);
}
.dark .light-app-management-page .t-select .t-input__inner {
color: var(--color-text);
}
.dark .light-app-management-page .t-select .t-input__inner::placeholder {
color: var(--color-text-disabled);
}
.dark .light-app-management-page .t-select:hover .t-input,
.dark .light-app-management-page .t-select:hover .t-input__wrap {
border-color: var(--color-link);
}
.dark .light-app-management-page .t-select.t-is-focused .t-input,
.dark .light-app-management-page .t-select.t-is-focused .t-input__wrap {
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.16);
}
.dark .light-app-management-page .t-select .t-fake-arrow {
color: var(--color-text-secondary);
}
.dark .light-app-management-page .t-select__dropdown {
background: var(--color-bg-card);
border-color: var(--color-border);
box-shadow: var(--shadow-2);
}
.dark .light-app-management-page .t-select-option {
color: var(--color-text);
}
.dark .light-app-management-page .t-select-option:hover {
background: rgba(22, 119, 255, 0.08);
}
.dark .light-app-management-page .t-select-option.t-is-selected {
background: rgba(22, 119, 255, 0.16);
color: var(--color-link);
}
/* shadcn Input / Textarea in dialog */
.dark .light-app-management-page .lam-dialog [data-slot="input"],
.dark .light-app-management-page .lam-dialog textarea {
background: var(--color-bg-page);
border-color: var(--color-border);
color: var(--color-text);
}
.dark .light-app-management-page .lam-dialog [data-slot="input"]:focus-visible,
.dark .light-app-management-page .lam-dialog textarea:focus-visible {
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(22, 119, 255, 0.16);
}
.dark .light-app-management-page .lam-param-theme-box span {
color: var(--color-text-secondary);
}
/* TDesign 下拉挂载到 body 时的深蓝样式 */
.dark .lam-select-popup.t-popup {
background: var(--color-bg-card);
border-color: var(--color-border);
box-shadow: var(--shadow-2);
}
.dark .lam-select-popup .t-select-option {
color: var(--color-text);
}
.dark .lam-select-popup .t-select-option:hover {
background: rgba(22, 119, 255, 0.08);
}
.dark .lam-select-popup .t-select-option.t-is-selected {
background: rgba(22, 119, 255, 0.16);
color: var(--color-link);
}
.dark .light-app-management-page .lam-search-input + button,
.dark .light-app-management-page .lam-toolbar .absolute.text-neutral-400 {
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-table .text-neutral-300 {
color: var(--color-text-disabled);
}
.dark .light-app-management-page .lam-card-url-box button {
color: var(--color-text-secondary);
}
.dark .light-app-management-page .lam-card-url-box button:hover {
background: rgba(22, 119, 255, 0.08);
color: var(--color-link);
}
/* ══════════════════════════════════════════════════════════════════════
浅蓝主题覆盖([data-theme="lightblue"])
══════════════════════════════════════════════════════════════════════ */
/* ── 注册应用按钮 ─────────────────────────────────────────────────────── */
[data-theme="lightblue"] .light-app-management-page .lam-primary-btn {
background-color: #0062D9;
box-shadow: 0 2px 6px rgba(0, 98, 217, 0.2);
}
[data-theme="lightblue"] .light-app-management-page .lam-primary-btn:hover {
background-color: #0079FE;
}
[data-theme="lightblue"] .light-app-management-page .lam-primary-btn:active {
background-color: #0056C6;
}
/* ── 搜索框 focus ────────────────────────────────────────────────────── */
[data-theme="lightblue"] .light-app-management-page .lam-search-input:focus {
border-color: rgba(0, 98, 217, 0.4);
box-shadow: 0 0 0 4px rgba(0, 98, 217, 0.1);
}
/* ── 视图切换激活态 ──────────────────────────────────────────────────── */
[data-theme="lightblue"] .light-app-management-page .lam-view-toggle-btn.is-active {
color: #0062D9;
}
/* ── 结果计数 ───────────────────────────────────────────────────────── */
[data-theme="lightblue"] .light-app-management-page .lam-count strong {
color: #0062D9;
}
/* ── 表格行名 hover ──────────────────────────────────────────────────── */
[data-theme="lightblue"] .light-app-management-page .lam-table tr:hover .lam-row-name {
color: #0062D9;
}
/* ── 参数弹窗 ───────────────────────────────────────────────────────── */
[data-theme="lightblue"] .light-app-management-page .lam-param-popover-close {
color: #0062D9;
}
[data-theme="lightblue"] .light-app-management-page .lam-param-key {
color: #0062D9;
}
/* ── 卡片标题 hover ──────────────────────────────────────────────────── */
[data-theme="lightblue"] .light-app-management-page .lam-card:hover .lam-card-title {
color: #0062D9;
}
/* ── 弹窗图标 / 关闭按钮 ─────────────────────────────────────────────── */
[data-theme="lightblue"] .light-app-management-page .lam-dialog-icon {
background-color: #EAF4FF;
color: #0062D9;
}
[data-theme="lightblue"] .light-app-management-page .lam-dialog-close:hover {
color: #0062D9;
}
/* ── 添加路由参数按钮 hover ───────────────────────────────────────────── */
[data-theme="lightblue"] .light-app-management-page .lam-add-param-btn:hover {
border-color: #0062D9;
color: #0062D9;
}
/* ── 表单输入框 focus ────────────────────────────────────────────────── */
[data-theme="lightblue"] .light-app-management-page .lam-input:focus,
[data-theme="lightblue"] .light-app-management-page .lam-dialog input[type="text"]:focus {
border-color: #0062D9;
box-shadow: 0 0 0 1px #0062D9;
}
[data-theme="lightblue"] .light-app-management-page .lam-dialog [data-slot="input"]:focus-visible,
[data-theme="lightblue"] .light-app-management-page .lam-dialog textarea:focus-visible {
border-color: #0062D9;
box-shadow: 0 0 0 2px rgba(0, 98, 217, 0.16);
}
/* ── TDesign Select focus / hover ────────────────────────────────────── */
[data-theme="lightblue"] .light-app-management-page .t-select:hover .t-input,
[data-theme="lightblue"] .light-app-management-page .t-select:hover .t-input__wrap {
border-color: rgba(0, 98, 217, 0.5);
}
[data-theme="lightblue"] .light-app-management-page .t-select.t-is-focused .t-input,
[data-theme="lightblue"] .light-app-management-page .t-select.t-is-focused .t-input__wrap {
border-color: #0062D9;
box-shadow: 0 0 0 2px rgba(0, 98, 217, 0.16);
}
/* ── 启用开关 ───────────────────────────────────────────────────────── */
[data-theme="lightblue"] .light-app-management-page .lam-switch-on {
background-color: #0062D9;
}
/* ── 保存按钮 ───────────────────────────────────────────────────────── */
[data-theme="lightblue"] .light-app-management-page .lam-dialog-save {
background-color: #0062D9;
box-shadow: 0 2px 8px rgba(0, 98, 217, 0.2);
}
[data-theme="lightblue"] .light-app-management-page .lam-dialog-save:hover {
background-color: #0079FE;
}
[data-theme="lightblue"] .light-app-management-page .lam-dialog-save:active {
background-color: #0056C6;
}
@@ -0,0 +1,18 @@
@import "../../docs/ui规范-深蓝/tokens.css";
/** 轻应用中心列表 / 管理页 —— 深色模式共用页面底图(与 workspace 深蓝主题一致) */
/* ── 浅蓝主题:页面底色轻微蓝染 ──────────────────────────────────────── */
[data-theme="lightblue"] .light-app-management-page,
[data-theme="lightblue"] .light-app-list-page {
background-color: #F9FCFF;
}
.dark .light-app-management-page,
.dark .light-app-list-page {
background-color: var(--color-bg-page);
background-image: url("../assets/img/layout-bg-theme-dark-blue.svg");
background-size: cover;
background-position: center top;
background-repeat: no-repeat;
background-attachment: fixed;
}