This commit is contained in:
Rocky Kwok committed 2026-07-27 10:06:33 +08:00
commit fb73ae0471
220 files changed
+30857 -1735

No files matched your search

+10 -3
View File
@@ -37,6 +37,7 @@ import { Welcome } from "@/components/workspace/welcome";
import { CompactWelcome } from "@/components/workspace/compact-welcome";
import { readSidebarEnabled } from "@/core/page-layout/sidebar-enabled";
import { isChatsIframeWorkspacePath } from "@/core/page-layout/chats-iframe-route";
import { isHideChromeActive } from "@/core/embed/embed-session";
import { useSidebar } from "@/components/ui/sidebar";
import { Button } from "@/components/ui/button";
import { PanelLeftOpen } from "lucide-react";
@@ -100,6 +101,11 @@ export default function ChatPage() {
const router = useRouter();
const searchParams = useSearchParams();
const pathname = usePathname();
// `chatMode=full` is used by an embedded standard chat. It intentionally
// overrides the user's compact-layout preference for this route only, so
// the page keeps the full landing composition and its agent/history sidebar.
const fullChatMode =
searchParams.get("chatMode")?.trim().toLowerCase() === "full";
const { open: historySidebarOpen, toggleSidebar: toggleHistorySidebar } =
useSidebar();
const { t } = useI18n();
@@ -451,7 +457,7 @@ export default function ChatPage() {
// 主题底图(workspace-chat-bg)不生效。非紧凑(嵌入/standalone)保持原样。
const layoutMode = effectiveLayoutMode;
const chromeByPreference =
layoutMode === "full"
fullChatMode || layoutMode === "full"
? true
: layoutMode === "compact"
? false
@@ -459,6 +465,7 @@ export default function ChatPage() {
const compactChrome =
chromeByPreference && !isChatsIframeWorkspacePath(pathname);
const compactLanding = isLandingState && compactChrome;
const compactActionsHidden = isHideChromeActive();
return (
<ThreadContext.Provider value={{ thread, isMock }}>
@@ -483,7 +490,7 @@ export default function ChatPage() {
</div>
<div className="flex items-center gap-2">
{/* 仅在收起态显示「展开历史」钮;展开后由侧栏内(新对话右侧)的收起钮负责收起。 */}
{compactChrome && !historySidebarOpen && (
{compactChrome && !historySidebarOpen && !compactActionsHidden && (
<Button
type="button"
variant="ghost"
@@ -507,7 +514,7 @@ export default function ChatPage() {
与全量模式 Header 右上角保持一致。全量模式已有顶栏则不重复展示。 */}
{/* 简洁模式无全局顶栏:把换肤 + 界面模式切换搬到页面级顶栏右上角,
与全量模式 Header 右上角保持一致(content 配色贴浅底顶栏)。 */}
{!compactChrome && (
{!compactChrome && !compactActionsHidden && (
<>
<CompactShortcutButtons tone="content" />
<ThemeSwitcher tone="content" />
@@ -28,7 +28,7 @@ import {
getNodeTitle,
} from "@/components/knowledge-graph/graphUtils";
import "@/components/knowledge-graph/graphStyles.css";
import { useTheme } from "@/contexts/ThemeContext";
import { themeToBaseMode, useTheme } from "@/contexts/ThemeContext";
import {
useGraphBlacklist,
useGraphBlacklistActions,
@@ -799,7 +799,7 @@ export default function KnowledgeGraphPage() {
searchMatchIds={searchMatchIds}
layoutType={layoutType}
showLabels={showLabels}
theme={theme}
theme={themeToBaseMode(theme)}
onSelectNode={handleSelectNode}
onTooltip={setTooltip}
onZoomChange={setZoomLevel}
+11 -1
View File
@@ -21,7 +21,17 @@ export default function LightAppIframeView() {
// 把新主题色传给被嵌套应用(不依赖 <html> 的 dark class,避免时序读旧值)。
// 深色=dark-blue,浅蓝=blue,其余(红白)=red。
const themeColor: AppThemeColor =
theme === "dark" ? "dark-blue" : theme === "lightblue" ? "blue" : "red";
theme === "dark"
? "dark-blue"
: theme === "lightgreen"
? "green"
: theme === "lightblue"
? "blue"
: theme === "iceblue"
? "iceblue"
: theme === "lightyellow"
? "lightyellow"
: "red";
const src = useMemo(
() => (app ? buildLightAppUrl(app, undefined, themeColor) : ""),
[app, themeColor],
+80 -12
View File
@@ -34,7 +34,8 @@ import { toast } from "sonner";
import { Pagination } from "@/components/ui/pagination";
import { cn } from "@/lib/utils";
import { useTheme } from "@/contexts/ThemeContext";
import { useTheme, type ThemeType } from "@/contexts/ThemeContext";
import { isCustomThemeId } from "@/core/theme/custom-theme";
import { useLightApps, type LightApp } from "@/strategy-components/api/light-apps";
import { buildLightAppUrl } from "@/strategy-components/lib/light-app";
@@ -64,12 +65,79 @@ function hashIndex(str: string, mod: number): number {
return mod > 0 ? h % mod : 0;
}
function getLightAccent(theme: ThemeType) {
if (isCustomThemeId(theme)) {
return {
dot: "bg-[var(--primary)]",
badge: "border-[var(--border)] bg-[var(--accent)] text-[var(--accent-foreground)]",
focus: "focus:border-[var(--brand-900)] focus:ring-[var(--ring)]",
text: "text-[var(--accent-foreground)]",
hoverText: "group-hover:text-[var(--accent-foreground)]",
link: "text-[var(--accent-foreground)] hover:text-[var(--brand-950)]",
button: "bg-[var(--primary)] text-[var(--button-foreground,var(--primary-foreground))] shadow-sm hover:bg-[var(--theme-button-hover)]",
};
}
if (theme === "lightgreen") {
return {
dot: "bg-[#07C05F]",
badge: "border-[#07C05F]/15 bg-[#E9F8EC] text-[#049B38]",
focus: "focus:border-[#07C05F]/60 focus:ring-[#07C05F]/20",
text: "text-[#049B38]",
hoverText: "group-hover:text-[#049B38]",
link: "text-[#049B38] hover:text-[#038626]",
button: "bg-[#07C05F] shadow-[0_1px_3px_rgba(7,192,95,0.2)] hover:bg-[#08DD6E]",
};
}
if (theme === "lightblue") {
return {
dot: "bg-[#0062D9]",
badge: "border-[#0062D9]/15 bg-[#EAF4FF] text-[#0062D9]",
focus: "focus:border-[#0062D9]/60 focus:ring-[#0062D9]/20",
text: "text-[#0062D9]",
hoverText: "group-hover:text-[#0062D9]",
link: "text-[#0062D9] hover:text-[#0056C6]",
button: "bg-[#0062D9] shadow-[0_1px_3px_rgba(0,98,217,0.2)] hover:bg-[#0079FE]",
};
}
if (theme === "iceblue") {
return {
dot: "bg-[#9BCEF1]",
badge: "border-[#3D6E96]/15 bg-[#E1F1FB] text-[#2D587A]",
focus: "focus:border-[#3D6E96]/60 focus:ring-[#3D6E96]/20",
text: "text-[#2D587A]",
hoverText: "group-hover:text-[#2D587A]",
link: "text-[#2D587A] hover:text-[#1E405C]",
button: "bg-[#9BCEF1] text-[#18324A] shadow-[0_1px_3px_rgba(155,206,241,0.42)] hover:bg-[#B7DCF5]",
};
}
if (theme === "lightyellow") {
return {
dot: "bg-[#F3FBD9]",
badge: "border-[#4C6330]/15 bg-[#EEF6D4] text-[#4C6330]",
focus: "focus:border-[#4C6330]/60 focus:ring-[#F3FBD9]/40",
text: "text-[#4C6330]",
hoverText: "group-hover:text-[#4C6330]",
link: "text-[#4C6330] hover:text-[#34451F]",
button: "bg-[#F3FBD9] text-[#303D23] shadow-[0_1px_3px_rgba(243,251,217,0.72)] hover:bg-[#E4EFBF]",
};
}
return {
dot: "bg-[#CF1322]",
badge: "border-[#CF1322]/15 bg-[#FFF1F0] text-[#CF1322]",
focus: "focus:border-[#CF1322]/60 focus:ring-[#CF1322]/20",
text: "text-[#CF1322]",
hoverText: "group-hover:text-[#CF1322]",
link: "text-[#CF1322] hover:text-[#A8071A]",
button: "bg-[#CF1322] shadow-[0_1px_3px_rgba(207,19,34,0.2)] hover:bg-[#A8071A]",
};
}
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 lightAccent = getLightAccent(theme);
const { data: apps = [], isLoading } = useLightApps({
appType: "window_open",
@@ -118,13 +186,13 @@ export default function LightAppListPage() {
<div className="space-y-1">
<div className="flex items-center gap-2.5">
<span className="relative flex size-3">
<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 className={cn("absolute inline-flex size-full animate-ping rounded-full opacity-75", lightAccent.dot, "dark:bg-[#1677FF]")} />
<span className={cn("relative inline-flex size-3 rounded-full", lightAccent.dot, "dark:bg-[#1677FF]")} />
</span>
<h1 className="text-2xl font-bold tracking-tight text-zinc-900 dark:text-[#E8EDF5]">
应用列表
</h1>
<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 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]", lightAccent.badge)}>
导航工作台
</span>
</div>
@@ -142,7 +210,7 @@ export default function LightAppListPage() {
value={search}
onChange={(e) => setSearch(e.target.value)}
placeholder="键入应用名称、目标地址、或描述,实时过滤检索…"
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")}
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", lightAccent.focus)}
/>
{search && (
<button
@@ -161,13 +229,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={cn("font-bold dark:text-[#4096FF]", isLightBlue ? "text-[#0062D9]" : "text-[#CF1322]")}>{apps.length}</span> 个关联应用
<span className={cn("font-bold dark:text-[#4096FF]", lightAccent.text)}>{apps.length}</span> 个关联应用
</div>
{search && (
<button
type="button"
onClick={() => setSearch("")}
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]")}
className={cn("text-xs font-medium underline dark:text-[#4096FF] dark:hover:text-[#1677FF]", lightAccent.link)}
>
重置过滤条件
</button>
@@ -239,7 +307,7 @@ function AppCard({
onOpen: () => void;
}) {
const { theme } = useTheme();
const isLightBlue = theme === 'lightblue';
const lightAccent = getLightAccent(theme);
const Icon = ICON_POOL[hashIndex(app.appId, ICON_POOL.length)];
const cardTheme = THEME_POOL[hashIndex(app.appId, THEME_POOL.length)];
@@ -256,7 +324,7 @@ function AppCard({
</div>
</div>
<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]")}>
<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]", lightAccent.hoverText)}>
{app.name}
</h4>
@@ -270,7 +338,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={cn("text-[9px] font-bold uppercase tracking-wider dark:text-[#4096FF]", isLightBlue ? "text-[#0062D9]" : "text-[#CF1322]")}>
<span className={cn("text-[9px] font-bold uppercase tracking-wider dark:text-[#4096FF]", lightAccent.text)}>
自动拼接已激活参
</span>
</div>
@@ -312,7 +380,7 @@ function AppCard({
<button
type="button"
onClick={onOpen}
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]")}
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:text-white dark:shadow-none dark:hover:bg-[#0958D9]", lightAccent.button)}
>
<span>进入应用</span>
<ArrowUpRight className="size-3.5" />
@@ -93,7 +93,7 @@ const newParam = (): LightAppRouteParam => ({
paramType: "login",
source: "access_token",
customValue: "",
themeValue: "red=red;blue=blue;dark-blue=dark-blue",
themeValue: "red=red;green=green;blue=blue;dark-blue=dark-blue",
});
function formFromApp(app: LightApp): FormState {
+19 -5
View File
@@ -9,7 +9,9 @@ import { cn } from "@/lib/utils";
export default function LightAppNoteQAPage() {
const { theme } = useTheme();
const isDark = theme === "dark";
const isLightBlue = theme === 'lightblue';
const isLightBlue = theme === 'lightblue' || theme === 'iceblue';
const isLightGreen = theme === "lightgreen";
const isLightYellow = theme === "lightyellow";
const [question, setQuestion] = useState("");
const handleSubmit = () => {
@@ -30,7 +32,9 @@ export default function LightAppNoteQAPage() {
style={{
background: isDark
? "radial-gradient(ellipse 60% 50% at 50% 46%, rgba(59,130,246,0.10) 0%, rgba(139,92,246,0.06) 50%, transparent 70%)"
: "radial-gradient(ellipse 60% 50% at 50% 46%, rgba(147,197,253,0.38) 0%, rgba(165,180,252,0.18) 50%, transparent 70%)",
: isLightYellow
? "radial-gradient(ellipse 60% 50% at 50% 46%, rgba(243,251,217,0.7) 0%, rgba(220,235,173,0.26) 50%, transparent 70%)"
: "radial-gradient(ellipse 60% 50% at 50% 46%, rgba(147,197,253,0.38) 0%, rgba(165,180,252,0.18) 50%, transparent 70%)",
}}
/>
@@ -42,7 +46,11 @@ export default function LightAppNoteQAPage() {
<div
className={cn(
"flex h-12 w-10 shrink-0 items-center justify-center rounded shadow-sm",
isDark ? "bg-violet-400 text-slate-950" : "bg-[#4d3f74] text-white",
isDark
? "bg-violet-400 text-slate-950"
: isLightYellow
? "bg-[#4C6330] text-white"
: "bg-[#4d3f74] text-white",
)}
>
<BookOpen className="h-6 w-6" />
@@ -65,7 +73,9 @@ export default function LightAppNoteQAPage() {
"flex items-center gap-1.5 rounded-full px-4 py-1 text-sm font-medium transition-colors",
isDark
? "bg-sky-500/20 text-sky-300 hover:bg-sky-500/30"
: "bg-sky-100 text-sky-600 hover:bg-sky-200",
: isLightYellow
? "bg-[#EEF6D4] text-[#4C6330] hover:bg-[#E4EFBF]"
: "bg-sky-100 text-sky-600 hover:bg-sky-200",
)}
>
<Plus className="h-3.5 w-3.5" />
@@ -112,7 +122,11 @@ 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"
: isLightBlue
: isLightGreen
? "bg-[#07C05F] text-white hover:bg-[#08DD6E]"
: isLightYellow
? "bg-[#F3FBD9] text-[#303D23] hover:bg-[#E4EFBF]"
: isLightBlue
? "bg-[#0062D9] text-white hover:bg-[#0079FE]"
: "bg-[#F5222D] text-white hover:bg-[#CF1322]",
)}
+42 -10
View File
@@ -71,6 +71,7 @@ function summarizeStoredUserInfo(): Record<string, unknown> | null {
const DEFAULT_USERNAME = 'guest';
const DEFAULT_REDIRECT_PATH = '/page/workspace/chats/new';
const FULL_CHAT_MODE = 'full';
type LoginStatus = 'loading' | 'success' | 'error';
@@ -91,10 +92,21 @@ export default function LoginPage() {
() => readEmbedParam(searchParams.toString()) === 'on',
[searchParams],
);
const isIframeLogin = useMemo(
() => isChatsIframeLogin(searchParams.toString()),
// Wujie is an application container, not the Brainy iframe protocol. It
// must therefore land on the regular full ChatPage even when an old portal
// URL still carries `from=iframe`. Plain embeds can request the same route
// explicitly with `chatMode=full`.
const fullChatRequested = useMemo(
() =>
isWujieEnv() ||
searchParams.get('chatMode')?.trim().toLowerCase() === FULL_CHAT_MODE,
[searchParams],
);
const isIframeLogin = useMemo(
() =>
!fullChatRequested && isChatsIframeLogin(searchParams.toString()),
[fullChatRequested, searchParams],
);
const resolvedUsername = useMemo(() => {
const raw = username?.trim();
return raw && raw.length > 0 ? raw : DEFAULT_USERNAME;
@@ -103,12 +115,20 @@ export default function LoginPage() {
// ticket/userId/env 仍按既有登录存储策略持久化。
const yUserId = searchParams.get('yUserId')?.trim() ?? '';
const redirectPath = useMemo(
() =>
resolvePostLoginRedirectPath(
() => {
if (yUserId) return GENERAL_QA_ROUTE;
// Keep the mode on the destination URL so PageRoutes can retain the
// full chat workspace sidebar while hideChrome hides only the global
// application chrome.
if (fullChatRequested) {
return `${DEFAULT_REDIRECT_PATH}?chatMode=${FULL_CHAT_MODE}`;
}
return resolvePostLoginRedirectPath(
searchParams.toString(),
yUserId ? GENERAL_QA_ROUTE : DEFAULT_REDIRECT_PATH,
),
[searchParams, yUserId],
DEFAULT_REDIRECT_PATH,
);
},
[fullChatRequested, searchParams, yUserId],
);
const toolToken = searchParams.get('token')?.trim() ?? '';
// 内网登录透传的访问令牌:存入 userInfo.accessToken,供 XK/PG 中任务 iframe 拼参
@@ -131,15 +151,27 @@ export default function LoginPage() {
// taskId,无需把它编进 redirect 值里。目标已自带的同名参数优先(不覆盖);登录鉴权类参数
// (token/authToken/ticket 等)一律**不透传**,避免泄漏到应用页地址栏。
const redirectTo = useMemo(() => {
const raw = searchParams.get('redirect')?.trim() ?? '';
const raw =
searchParams.get('redirect')?.trim() ||
searchParams.get('route')?.trim() ||
'';
if (!raw) return '';
if (/^[a-z][a-z0-9+.-]*:/i.test(raw) || raw.startsWith('//') || raw.includes('\\')) return '';
const normalized = raw.startsWith('/') ? raw : `/${raw}`;
const [pathOnly, ownQuery = ''] = normalized.split('?');
const target = new URLSearchParams(ownQuery);
// Keep the Wujie/full-chat decision on the target URL. The destination
// route uses it to retain the full agent/history workspace sidebar.
if (
fullChatRequested &&
pathOnly === DEFAULT_REDIRECT_PATH &&
!target.has('chatMode')
) {
target.set('chatMode', FULL_CHAT_MODE);
}
// 登录流程自身消费的参数,不透传到目标页(尤以鉴权类为甚)。
const LOGIN_OWNED = new Set([
'redirect', 'ticket', 'userId', 'env', 'theme', 'yUserId',
'redirect', 'route', 'ticket', 'userId', 'env', 'theme', 'yUserId',
'token', 'access_token', 'password', 'authToken', 'goPath', 'from',
]);
new URLSearchParams(searchParams.toString()).forEach((value, key) => {
@@ -149,7 +181,7 @@ export default function LoginPage() {
});
const qs = target.toString();
return qs ? `${pathOnly}?${qs}` : pathOnly;
}, [searchParams]);
}, [fullChatRequested, searchParams]);
const externalParams = useMemo(
() => ({
theme: searchParams.get('theme') ?? '',
@@ -4,7 +4,7 @@ import AIWritingPage from '@/open-canvas/pages/AIWritingPage';
import { getNotebookApiBaseURL } from '@/core/config';
import { useTheme, type ThemeType } from '@/contexts/ThemeContext';
const VALID_THEMES: ThemeType[] = ['dark', 'light', 'lightblue'];
const VALID_THEMES: ThemeType[] = ['dark', 'light', 'lightblue', 'lightgreen', 'iceblue', 'lightyellow'];
interface NotebookTokenData {
doc_ids: number[];
+38 -3
View File
@@ -9,6 +9,7 @@ import { isWujieEnv } from "@/core/embed/wujie";
import {
isEmbedActive,
isChromeHidden,
isHideChromeActive,
readEmbedParam,
readHideChromeParam,
} from "@/core/embed/embed-session";
@@ -16,6 +17,7 @@ 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 { env } from "@/env";
import WorkspaceRoutes from "./WorkspaceRoutes";
import StrategyRoutes from "./StrategyRoutes";
@@ -27,6 +29,7 @@ import { RoundtableJobNotifier } from "@/roundtable-planning/components/Roundtab
function PageLayout() {
const [collapsed, setCollapsed] = useState(false);
const forceCollapsedSidebar = env.FORCE_COLLAPSED_SIDEBAR;
const sidebarEnabled = useMemo(() => readSidebarEnabled(), []);
const location = useLocation();
const { theme } = useTheme();
@@ -42,7 +45,13 @@ function PageLayout() {
// 登录时置位)这一旧默认。这样无论是否走门户登录,「全量↔简洁」都能真正控制顶栏+侧边栏。
const [layoutMode] = useLayoutMode();
const chromeByPreference =
layoutMode === "full" ? true : layoutMode === "compact" ? false : sidebarEnabled;
forceCollapsedSidebar
? false
: layoutMode === "full"
? true
: layoutMode === "compact"
? false
: sidebarEnabled;
// iframe 嵌入模式(外部系统 `&embed=1`,按标签页 sessionStorage):去掉全局导航栏 + 侧边栏,
// 只留问答/会商工作区本体。按 location 重算以跟随页内导航;非嵌入标签页恒为 false,不受影响。
const embedActive = useMemo(
@@ -56,11 +65,29 @@ function PageLayout() {
() => isChromeHidden() || readHideChromeParam(location.search) === "on",
[location.pathname, location.search],
);
const hideChromeActive = useMemo(
() => isHideChromeActive() || readHideChromeParam(location.search) === "on",
[location.pathname, location.search],
);
// `chatMode=full` is the embed-friendly standard chat view: hide the global
// Header/PageSidebar when `hideChrome=1`, but keep WorkspaceSidebar so users
// can still pick an agent and access their common agents/history.
const fullChatMode = useMemo(() => {
const mode = new URLSearchParams(location.search)
.get("chatMode")
?.trim()
.toLowerCase();
return (
mode === "full" &&
location.pathname.startsWith("/page/workspace/chats/")
);
}, [location.pathname, location.search]);
// 「大屏绘制」独立页(/page/strategy/dashboard-agent)本就是「带导航栏 + 侧边栏」的独立页,
// 但它常被深链「上一步」从去 chrome 的工作区/新标签打开——那里 chromeByPreference 可能为 false
// (界面模式非全量、或会话级 sidebar 未置位),导致整页没有顶栏/侧栏。故对该路由**强制全局 chrome**
// (仍尊重 embed=1 嵌入态:外部 iframe 套用时仍隐藏)。
const forceChromeForDashboardAgent = location.pathname.includes("/strategy/dashboard-agent");
const forceChromeForDashboardAgent =
!forceCollapsedSidebar && location.pathname.includes("/strategy/dashboard-agent");
const pageSidebarAllowed =
(forceChromeForDashboardAgent || chromeByPreference) &&
!isChatsIframeWorkspacePath(location.pathname) &&
@@ -111,9 +138,17 @@ function PageLayout() {
<WorkspaceRoutes
pageSidebarCollapsed={collapsed}
pageSidebarHidden={!pageSidebarAllowed}
workspaceSidebarHidden={hideChromeActive && !fullChatMode}
headerHidden={!pageSidebarAllowed}
compactWorkspaceSidebar={pageSidebarAllowed}
// Full-chat embedding has no global PageSidebar, but it
// still needs the full GeneralQaSidebar (common agents and
// conversation history) inside the workspace.
compactWorkspaceSidebar={pageSidebarAllowed || fullChatMode}
compactActionsHidden={hideChromeActive}
pageSidebarDark={isDark}
forceWorkspaceSidebarCollapsed={
forceCollapsedSidebar && !fullChatMode
}
/>
}
/>
@@ -793,7 +793,7 @@ export default function ScheduledTasksPage({
</Button>
<Button
size="icon"
variant="destructive"
variant={theme === "iceblue" || theme === "lightyellow" ? "default" : "destructive"}
title="删除"
onClick={(event) => {
event.stopPropagation();
@@ -1034,7 +1034,7 @@ export default function ScheduledTasksPage({
{selectedTaskCanEditRuns && run.can_edit !== false && (
<Button
size="sm"
variant="destructive"
variant={theme === "iceblue" || theme === "lightyellow" ? "default" : "destructive"}
onClick={() => handleDeleteRun(run.id)}
>
<Trash2 className="size-4" />
@@ -21,6 +21,7 @@ import { ActionAnalyzes } from "@/strategy-components/components/task-components
import KnowledgeBaseResourceManagement from "@/strategy-components/components/resource-management/KnowledgeBaseResourceManagement";
import NetworkActorResourceManagement from "@/strategy-components/components/resource-management/NetworkActorResourceManagement";
import RoundtablePlanningPage from "@/roundtable-planning/pages/RoundtablePlanningPage";
import PositionRoundtablePage from "@/position-roundtable/pages/PositionRoundtablePage";
import DashboardAgentPage from "@/roundtable-planning/pages/DashboardAgentPage";
import BusinessChainListPage from "@/roundtable-planning/pages/BusinessChainListPage";
import BusinessChainEditorPage from "@/roundtable-planning/pages/BusinessChainEditorPage";
@@ -91,6 +92,14 @@ export default function StrategyRoutes() {
</ChatRuntime>
}
/>
<Route
path="qa/position-roundtable"
element={
<ChatRuntime>
<PositionRoundtablePage />
</ChatRuntime>
}
/>
{/* 大屏绘制智能体独立页面(按 taskId 存取;ChatRuntime 提供 ArtifactsProvider 给沙箱预览) */}
<Route
+114 -44
View File
@@ -2,6 +2,7 @@ import type { CSSProperties, ReactNode } from 'react';
import { Navigate, Route, Routes, useLocation, useParams } from 'react-router-dom';
import { getStoredAuth } from '@/core/auth';
import { isWujieEnv } from '@/core/embed/wujie';
import { SidebarInset, SidebarProvider } from '@/components/ui/sidebar';
import { PromptInputProvider } from '@/components/ai-elements/prompt-input';
@@ -54,6 +55,7 @@ import AIWritingPage from '@/open-canvas/pages/AIWritingPage';
import StudioNotebooksPage from './StudioNotebooksPage';
import StudioNotebookDetailPage from './StudioNotebookDetailPage';
import RoundtablePlanningPage from '@/roundtable-planning/pages/RoundtablePlanningPage';
import PositionRoundtablePage from '@/position-roundtable/pages/PositionRoundtablePage';
import BusinessChainEditorPage from '@/roundtable-planning/pages/BusinessChainEditorPage';
import BusinessChainListPage from '@/roundtable-planning/pages/BusinessChainListPage';
import DispatchChainPreviewPage from '@/roundtable-planning/pages/DispatchChainPreviewPage';
@@ -71,14 +73,6 @@ function NewAgentPageWithKey() {
return <NewAgentPage key={thread_id} />;
}
function isInIframe(): boolean {
try {
return window.self !== window.top;
} catch {
return true;
}
}
function shouldShowCompactTopActions(pathname: string): boolean {
return (
pathname === '/page/workspace/agents' ||
@@ -117,30 +111,35 @@ export function WorkspaceLayout({
pageSidebarHidden = false,
workspaceSidebarHidden = false,
headerHidden = false,
compactActionsHidden = false,
compactWorkspaceSidebar = false,
defaultHistorySidebarOpen,
wrapperOverflowHidden = false,
insetOverflowHidden = false,
pageSidebarDark = false,
forceWorkspaceSidebarCollapsed = false,
}: {
children: ReactNode;
pageSidebarCollapsed: boolean;
pageSidebarHidden?: boolean;
workspaceSidebarHidden?: boolean;
headerHidden?: boolean;
compactActionsHidden?: boolean;
compactWorkspaceSidebar?: boolean;
/** 全量模式下通用问答等页:聊天记录侧栏默认展开;未指定时紧凑模式仍默认收起。 */
defaultHistorySidebarOpen?: boolean;
wrapperOverflowHidden?: boolean;
insetOverflowHidden?: boolean;
pageSidebarDark?: boolean;
/** Deployment-level compact mode: keep only the icon menu rail. */
forceWorkspaceSidebarCollapsed?: boolean;
}) {
// 全量模式下通用问答(/page/workspace/chats*)的历史侧栏默认展开;其余紧凑页
// (如智能体对话,为聊天区更宽而默认收起)维持原默认。
const pathname = useLocation().pathname;
const isGeneralQa = pathname.startsWith('/page/workspace/chats');
const showCompactTopActions =
headerHidden && shouldShowCompactTopActions(pathname);
headerHidden && !compactActionsHidden && shouldShowCompactTopActions(pathname);
const pageSidebarWidth = pageSidebarHidden
? '0rem'
@@ -169,11 +168,19 @@ export function WorkspaceLayout({
key={String(compactWorkspaceSidebar)}
data-page-sidebar-collapsed={pageSidebarCollapsed}
defaultOpen={
defaultHistorySidebarOpen ?? (isGeneralQa || !compactWorkspaceSidebar)
forceWorkspaceSidebarCollapsed
? false
: (defaultHistorySidebarOpen ?? (isGeneralQa || !compactWorkspaceSidebar))
}
open={forceWorkspaceSidebarCollapsed ? false : undefined}
onOpenChange={forceWorkspaceSidebarCollapsed ? () => undefined : undefined}
disableToggle={forceWorkspaceSidebarCollapsed}
>
{!workspaceSidebarHidden && (
<WorkspaceSidebar compactChrome={compactWorkspaceSidebar} />
<WorkspaceSidebar
compactChrome={compactWorkspaceSidebar}
forceCollapsed={forceWorkspaceSidebarCollapsed}
/>
)}
<SidebarInset
className={cn('min-w-0', insetOverflowHidden && 'overflow-hidden')}
@@ -224,15 +231,21 @@ function NewChatEntryRoute({
layoutProps: {
pageSidebarCollapsed: boolean;
pageSidebarHidden: boolean;
workspaceSidebarHidden: boolean;
headerHidden: boolean;
compactWorkspaceSidebar: boolean;
compactActionsHidden: boolean;
pageSidebarDark: boolean;
forceWorkspaceSidebarCollapsed: boolean;
};
}) {
if (isInIframe()) {
return <Navigate to="/page/workspace/chats_iframe/new" replace />;
}
// Do not infer the chat experience from window.self !== window.top here.
// A generic `/chats/new` deep link must render the same standard chat both
// top-level and inside an iframe. The dedicated iframe protocol page remains
// available only through its explicit `/chats_iframe/...` route (LoginPage
// selects it for `from=iframe`). Otherwise a normal deep link was silently
// redirected to IframeChatPage, whose new-thread state intentionally has no
// manually selected agent and therefore stopped at "选择 Agent".
return (
<WorkspaceLayout {...layoutProps} defaultHistorySidebarOpen>
<ChatRuntime>
@@ -242,25 +255,94 @@ function NewChatEntryRoute({
);
}
/**
* `chats_iframe` is reserved for the Brainy postMessage protocol. Some legacy
* Wujie links still target it (or carry `from=iframe` during login), but Wujie
* needs the normal full chat workspace instead. Redirect it before mounting
* IframeChatPage; `chatMode=iframe` remains an explicit opt-in escape hatch
* for a Wujie integration that really implements that protocol.
*/
function IframeChatEntryRoute({
layoutProps,
hasThreadParam,
}: {
layoutProps: {
pageSidebarCollapsed: boolean;
pageSidebarHidden: boolean;
workspaceSidebarHidden: boolean;
headerHidden: boolean;
compactWorkspaceSidebar: boolean;
compactActionsHidden: boolean;
pageSidebarDark: boolean;
forceWorkspaceSidebarCollapsed: boolean;
};
hasThreadParam: boolean;
}) {
const location = useLocation();
const { thread_id } = useParams<{ thread_id: string }>();
const requestedMode = new URLSearchParams(location.search)
.get('chatMode')
?.trim()
.toLowerCase();
if (isWujieEnv() && requestedMode !== 'iframe') {
const targetParams = new URLSearchParams(location.search);
targetParams.set('chatMode', 'full');
const targetPath = hasThreadParam && thread_id
? `/page/workspace/chats/${thread_id}`
: '/page/workspace/chats/new';
const targetSearch = targetParams.toString();
return (
<Navigate
to={targetSearch ? `${targetPath}?${targetSearch}` : targetPath}
replace
/>
);
}
return (
<WorkspaceLayout
{...layoutProps}
pageSidebarHidden
workspaceSidebarHidden
wrapperOverflowHidden={hasThreadParam}
insetOverflowHidden={hasThreadParam}
>
<ChatRuntime>
<IframeChatPage />
</ChatRuntime>
</WorkspaceLayout>
);
}
export default function WorkspaceRoutes({
pageSidebarCollapsed = false,
pageSidebarHidden = false,
workspaceSidebarHidden = false,
headerHidden = false,
compactActionsHidden = false,
compactWorkspaceSidebar = false,
pageSidebarDark = false,
forceWorkspaceSidebarCollapsed = false,
}: {
pageSidebarCollapsed?: boolean;
pageSidebarHidden?: boolean;
workspaceSidebarHidden?: boolean;
headerHidden?: boolean;
compactActionsHidden?: boolean;
compactWorkspaceSidebar?: boolean;
pageSidebarDark?: boolean;
forceWorkspaceSidebarCollapsed?: boolean;
}) {
const layoutProps = {
pageSidebarCollapsed,
pageSidebarHidden,
workspaceSidebarHidden,
headerHidden,
compactActionsHidden,
compactWorkspaceSidebar,
pageSidebarDark,
forceWorkspaceSidebarCollapsed,
};
return (
@@ -271,7 +353,7 @@ export default function WorkspaceRoutes({
path="chats"
element={
<WorkspaceLayout {...layoutProps}>
<ChatsPage compactChrome={headerHidden} />
<ChatsPage compactChrome={headerHidden && !compactActionsHidden} />
</WorkspaceLayout>
}
/>
@@ -291,33 +373,11 @@ export default function WorkspaceRoutes({
/>
<Route
path="chats_iframe/new"
element={
<WorkspaceLayout
{...layoutProps}
pageSidebarHidden
workspaceSidebarHidden
>
<ChatRuntime>
<IframeChatPage />
</ChatRuntime>
</WorkspaceLayout>
}
element={<IframeChatEntryRoute layoutProps={layoutProps} hasThreadParam={false} />}
/>
<Route
path="chats_iframe/:thread_id"
element={
<WorkspaceLayout
{...layoutProps}
pageSidebarHidden
workspaceSidebarHidden
wrapperOverflowHidden
insetOverflowHidden
>
<ChatRuntime>
<IframeChatPage />
</ChatRuntime>
</WorkspaceLayout>
}
element={<IframeChatEntryRoute layoutProps={layoutProps} hasThreadParam />}
/>
<Route
path="agents"
@@ -357,7 +417,7 @@ export default function WorkspaceRoutes({
<AgentChatPage
showAgentList
compactAppearanceActions={
headerHidden ? <CompactAppearanceActions /> : undefined
headerHidden && !compactActionsHidden ? <CompactAppearanceActions /> : undefined
}
/>
</ChatRuntime>
@@ -394,7 +454,7 @@ export default function WorkspaceRoutes({
<WorkspaceLayout {...layoutProps}>
<AIWritingPage
hideAdvancedFields
compactChrome={headerHidden}
compactChrome={headerHidden && !compactActionsHidden}
/>
</WorkspaceLayout>
}
@@ -428,7 +488,17 @@ export default function WorkspaceRoutes({
element={
<WorkspaceLayout {...layoutProps}>
<ChatRuntime>
<RoundtablePlanningPage compactChrome={headerHidden} />
<RoundtablePlanningPage compactChrome={headerHidden && !compactActionsHidden} />
</ChatRuntime>
</WorkspaceLayout>
}
/>
<Route
path="roundtable/position"
element={
<WorkspaceLayout {...layoutProps}>
<ChatRuntime>
<PositionRoundtablePage compactChrome={headerHidden && !compactActionsHidden} />
</ChatRuntime>
</WorkspaceLayout>
}
@@ -490,7 +560,7 @@ export default function WorkspaceRoutes({
path="scheduled/tasks"
element={
<WorkspaceLayout {...layoutProps}>
<ScheduledTasksPage compactChrome={headerHidden} />
<ScheduledTasksPage compactChrome={headerHidden && !compactActionsHidden} />
</WorkspaceLayout>
}
/>
+171 -4
View File
@@ -651,15 +651,25 @@
box-shadow: 0 0 0 2px rgba(0, 98, 217, 0.16);
}
/* ── TDesign Select focus / hover ────────────────────────────────────── */
/* ── TDesign Select interaction ──────────────────────────────────────── */
[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);
border-color: #A6A6A6;
}
[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);
/* Opening the options with a mouse is not a validation or selected state. */
border-color: #DBDBDB;
box-shadow: none;
}
/* Keep a visible focus ring for keyboard navigation only. */
@supports selector(.t-select:has(.t-input__inner:focus-visible)) {
[data-theme="lightblue"] .light-app-management-page .t-select.t-is-focused:has(.t-input__inner:focus-visible) .t-input,
[data-theme="lightblue"] .light-app-management-page .t-select.t-is-focused:has(.t-input__inner:focus-visible) .t-input__wrap {
border-color: #0062D9;
box-shadow: 0 0 0 2px rgba(0, 98, 217, 0.16);
}
}
/* ── 启用开关 ───────────────────────────────────────────────────────── */
@@ -679,6 +689,163 @@
background-color: #0056C6;
}
/* Ice blue theme — use #9BCEF1 for surfaces and #3D6E96 for actions so
text on primary controls remains readable. */
[data-theme="iceblue"] .light-app-management-page .lam-primary-btn,
[data-theme="iceblue"] .light-app-management-page .lam-dialog-save,
[data-theme="iceblue"] .light-app-management-page .lam-switch-on {
background-color: #9BCEF1;
}
[data-theme="iceblue"] .light-app-management-page .lam-primary-btn,
[data-theme="iceblue"] .light-app-management-page .lam-dialog-save {
color: #18324A;
box-shadow: 0 2px 6px rgba(155, 206, 241, 0.42);
}
[data-theme="iceblue"] .light-app-management-page .lam-primary-btn:hover,
[data-theme="iceblue"] .light-app-management-page .lam-dialog-save:hover {
background-color: #B7DCF5;
}
[data-theme="iceblue"] .light-app-management-page .lam-primary-btn:active,
[data-theme="iceblue"] .light-app-management-page .lam-dialog-save:active {
background-color: #84B9DE;
}
[data-theme="iceblue"] .light-app-management-page .lam-search-input:focus {
border-color: rgba(61, 110, 150, 0.5);
box-shadow: 0 0 0 4px rgba(155, 206, 241, 0.42);
}
[data-theme="iceblue"] .light-app-management-page .lam-view-toggle-btn.is-active,
[data-theme="iceblue"] .light-app-management-page .lam-cat-toggle-btn.is-active,
[data-theme="iceblue"] .light-app-management-page .lam-count strong,
[data-theme="iceblue"] .light-app-management-page .lam-table tr:hover .lam-row-name,
[data-theme="iceblue"] .light-app-management-page .lam-param-popover-close,
[data-theme="iceblue"] .light-app-management-page .lam-param-key,
[data-theme="iceblue"] .light-app-management-page .lam-card:hover .lam-card-title,
[data-theme="iceblue"] .light-app-management-page .lam-dialog-close:hover,
[data-theme="iceblue"] .light-app-management-page .lam-add-param-btn:hover {
color: #2D587A;
}
[data-theme="iceblue"] .light-app-management-page .lam-add-param-btn:hover,
[data-theme="iceblue"] .light-app-management-page .lam-input:focus,
[data-theme="iceblue"] .light-app-management-page .lam-dialog input[type="text"]:focus {
border-color: #5B91B9;
}
[data-theme="iceblue"] .light-app-management-page .lam-input:focus,
[data-theme="iceblue"] .light-app-management-page .lam-dialog input[type="text"]:focus,
[data-theme="iceblue"] .light-app-management-page .lam-dialog [data-slot="input"]:focus-visible,
[data-theme="iceblue"] .light-app-management-page .lam-dialog textarea:focus-visible,
[data-theme="iceblue"] .light-app-management-page .t-select.t-is-focused .t-input,
[data-theme="iceblue"] .light-app-management-page .t-select.t-is-focused .t-input__wrap {
box-shadow: 0 0 0 2px rgba(155, 206, 241, 0.58);
}
[data-theme="iceblue"] .light-app-management-page .lam-dialog-icon {
background-color: #E1F1FB;
color: #2D587A;
}
/* Pale yellow: #F3FBD9 surfaces pair with dark olive interaction text. */
[data-theme="lightyellow"] .light-app-management-page .lam-primary-btn,
[data-theme="lightyellow"] .light-app-management-page .lam-dialog-save,
[data-theme="lightyellow"] .light-app-management-page .lam-switch-on {
background-color: #F3FBD9;
}
[data-theme="lightyellow"] .light-app-management-page .lam-primary-btn,
[data-theme="lightyellow"] .light-app-management-page .lam-dialog-save {
color: #303D23;
box-shadow: 0 2px 6px rgba(243, 251, 217, 0.72);
}
[data-theme="lightyellow"] .light-app-management-page .lam-primary-btn:hover,
[data-theme="lightyellow"] .light-app-management-page .lam-dialog-save:hover {
background-color: #E4EFBF;
}
[data-theme="lightyellow"] .light-app-management-page .lam-primary-btn:active,
[data-theme="lightyellow"] .light-app-management-page .lam-dialog-save:active {
background-color: #DCEBAD;
}
[data-theme="lightyellow"] .light-app-management-page .lam-search-input:focus {
border-color: rgba(76, 99, 48, 0.5);
box-shadow: 0 0 0 4px rgba(243, 251, 217, 0.72);
}
[data-theme="lightyellow"] .light-app-management-page .lam-view-toggle-btn.is-active,
[data-theme="lightyellow"] .light-app-management-page .lam-cat-toggle-btn.is-active,
[data-theme="lightyellow"] .light-app-management-page .lam-count strong,
[data-theme="lightyellow"] .light-app-management-page .lam-table tr:hover .lam-row-name,
[data-theme="lightyellow"] .light-app-management-page .lam-param-popover-close,
[data-theme="lightyellow"] .light-app-management-page .lam-param-key,
[data-theme="lightyellow"] .light-app-management-page .lam-card:hover .lam-card-title,
[data-theme="lightyellow"] .light-app-management-page .lam-dialog-close:hover,
[data-theme="lightyellow"] .light-app-management-page .lam-add-param-btn:hover {
color: #4C6330;
}
[data-theme="lightyellow"] .light-app-management-page .lam-add-param-btn:hover,
[data-theme="lightyellow"] .light-app-management-page .lam-input:focus,
[data-theme="lightyellow"] .light-app-management-page .lam-dialog input[type="text"]:focus {
border-color: #B4C985;
}
[data-theme="lightyellow"] .light-app-management-page .lam-input:focus,
[data-theme="lightyellow"] .light-app-management-page .lam-dialog input[type="text"]:focus,
[data-theme="lightyellow"] .light-app-management-page .lam-dialog [data-slot="input"]:focus-visible,
[data-theme="lightyellow"] .light-app-management-page .lam-dialog textarea:focus-visible,
[data-theme="lightyellow"] .light-app-management-page .t-select.t-is-focused .t-input,
[data-theme="lightyellow"] .light-app-management-page .t-select.t-is-focused .t-input__wrap {
box-shadow: 0 0 0 2px rgba(243, 251, 217, 0.78);
}
[data-theme="lightyellow"] .light-app-management-page .lam-dialog-icon {
background-color: #EEF6D4;
color: #4C6330;
}
/* WeKnora green theme. */
[data-theme="lightgreen"] .light-app-management-page .lam-primary-btn,
[data-theme="lightgreen"] .light-app-management-page .lam-dialog-save,
[data-theme="lightgreen"] .light-app-management-page .lam-switch-on {
background-color: #07C05F;
}
[data-theme="lightgreen"] .light-app-management-page .lam-primary-btn,
[data-theme="lightgreen"] .light-app-management-page .lam-dialog-save {
box-shadow: 0 2px 6px rgba(7, 192, 95, 0.2);
}
[data-theme="lightgreen"] .light-app-management-page .lam-primary-btn:hover,
[data-theme="lightgreen"] .light-app-management-page .lam-dialog-save:hover {
background-color: #08DD6E;
}
[data-theme="lightgreen"] .light-app-management-page .lam-primary-btn:active,
[data-theme="lightgreen"] .light-app-management-page .lam-dialog-save:active {
background-color: #049B38;
}
[data-theme="lightgreen"] .light-app-management-page .lam-search-input:focus {
border-color: rgba(7, 192, 95, 0.4);
box-shadow: 0 0 0 4px rgba(7, 192, 95, 0.1);
}
[data-theme="lightgreen"] .light-app-management-page .lam-view-toggle-btn.is-active,
[data-theme="lightgreen"] .light-app-management-page .lam-cat-toggle-btn.is-active,
[data-theme="lightgreen"] .light-app-management-page .lam-count strong,
[data-theme="lightgreen"] .light-app-management-page .lam-table tr:hover .lam-row-name,
[data-theme="lightgreen"] .light-app-management-page .lam-param-popover-close,
[data-theme="lightgreen"] .light-app-management-page .lam-param-key,
[data-theme="lightgreen"] .light-app-management-page .lam-card:hover .lam-card-title,
[data-theme="lightgreen"] .light-app-management-page .lam-dialog-close:hover,
[data-theme="lightgreen"] .light-app-management-page .lam-add-param-btn:hover {
color: #049B38;
}
[data-theme="lightgreen"] .light-app-management-page .lam-add-param-btn:hover {
border-color: #07C05F;
}
[data-theme="lightgreen"] .light-app-management-page .lam-dialog-icon {
background-color: #E9F8EC;
color: #049B38;
}
[data-theme="lightgreen"] .light-app-management-page .lam-input:focus,
[data-theme="lightgreen"] .light-app-management-page .lam-dialog input[type="text"]:focus,
[data-theme="lightgreen"] .light-app-management-page .t-select.t-is-focused .t-input,
[data-theme="lightgreen"] .light-app-management-page .t-select.t-is-focused .t-input__wrap {
border-color: #07C05F;
box-shadow: 0 0 0 2px rgba(7, 192, 95, 0.16);
}
[data-theme="lightgreen"] .light-app-management-page .t-select:hover .t-input,
[data-theme="lightgreen"] .light-app-management-page .t-select:hover .t-input__wrap {
border-color: rgba(7, 192, 95, 0.5);
}
/* ── iframe 菜单排序:拖拽行 + 拖拽手柄 ───────────────────────────────── */
.light-app-management-page .lam-reorder-row {
cursor: grab;
@@ -7,6 +7,16 @@
background-color: #F9FCFF;
}
[data-theme="lightgreen"] .light-app-management-page,
[data-theme="lightgreen"] .light-app-list-page {
background-color: #F9FDF9;
}
[data-theme="lightyellow"] .light-app-management-page,
[data-theme="lightyellow"] .light-app-list-page {
background-color: #FCFDF5;
}
.dark .light-app-management-page,
.dark .light-app-list-page {
background-color: var(--color-bg-page);