feat(ui): theme & layout improvements
- Dark-blue theme background assets and styling integration - Appearance settings with layoutMode (full/compact) preference - Settings dialog and auth context updates - AI writing form and panel refinements - Open Canvas context updates - Roundtable planning styling and dashboard enhancements - Strategy components and header updates - Light app management and routing improvements - Global style enhancements for dark theme - Various page layout and workspace routing optimizations
This commit is contained in:
1 parent
da2864f422
commit
56449ed129
33 files changed
+1133
-491
No files matched your search
@@ -46,7 +46,13 @@ import { DASHBOARD_VIEW_STORAGE_PREFIX } from "@/roundtable-planning/pages/Dashb
|
||||
import { useAutoScroll } from "@/roundtable-planning/hooks/useAutoScroll";
|
||||
import { useDialogueSpeedMetrics } from "@/roundtable-planning/hooks/useDialogueSpeedMetrics";
|
||||
import type { UseStep3DashboardReturn } from "@/roundtable-planning/hooks/useStep3Dashboard";
|
||||
import { parseReportJson, stripReportFence } from "@/roundtable-planning/lib/report-template";
|
||||
import {
|
||||
parseReportJson,
|
||||
renderDashboardHtml,
|
||||
stripReportFence,
|
||||
type DashboardTheme,
|
||||
} from "@/roundtable-planning/lib/report-template";
|
||||
import { useTheme } from "@/contexts/ThemeContext";
|
||||
|
||||
interface Step3DashboardModeProps {
|
||||
selectedModel: string;
|
||||
@@ -94,13 +100,32 @@ export function Step3DashboardMode({
|
||||
openDashboardPreview,
|
||||
} = dashboard;
|
||||
|
||||
// 真实数据 → 大屏数据:优先本轮实时生成,其次草稿快照;都没有则 undefined(用 DEMO)。
|
||||
const dashboardData = useMemo(() => {
|
||||
const report = dashboard.data ?? parseReportJson(savedReport?.dashboardJson ?? null);
|
||||
return report ? adaptReportData(report, formatNow()) : undefined;
|
||||
}, [dashboard.data, savedReport?.dashboardJson]);
|
||||
// 真实数据(report-json):优先本轮实时生成,其次草稿快照。应用渲染/网页渲染共用。
|
||||
const report = useMemo(
|
||||
() => dashboard.data ?? parseReportJson(savedReport?.dashboardJson ?? null),
|
||||
[dashboard.data, savedReport?.dashboardJson],
|
||||
);
|
||||
|
||||
const hasResult = !!dashboardData || !!html;
|
||||
// 应用渲染用的大屏数据(不传则 DashboardApp 回落 DEMO)。
|
||||
const dashboardData = useMemo(
|
||||
() => (report ? adaptReportData(report, formatNow()) : undefined),
|
||||
[report],
|
||||
);
|
||||
|
||||
// 网页渲染主题跟随应用主题:renderDashboardHtml 是确定性的,主题切换时按 report
|
||||
// 当场重渲(而非用 hook 在生成时烤死主题的 `html`),从而 iframe 大屏色随父元素变化。
|
||||
// 仅 light/dark 两套(lightblue 归 light,与页面 themeMode 映射一致)。
|
||||
const { theme } = useTheme();
|
||||
const dashThemeMode: DashboardTheme = theme === "dark" ? "dark" : "light";
|
||||
const dashboardHtml = useMemo(
|
||||
() =>
|
||||
report
|
||||
? renderDashboardHtml(report, { generatedAt: formatNow(), theme: dashThemeMode })
|
||||
: html,
|
||||
[report, dashThemeMode, html],
|
||||
);
|
||||
|
||||
const hasResult = !!dashboardData || !!dashboardHtml;
|
||||
|
||||
// 视图:有产物默认看「大屏页面」,否则进「交互区」看抽取过程。
|
||||
const [view, setView] = useState<DashView>(() => (hasResult ? "page" : "chat"));
|
||||
@@ -110,6 +135,27 @@ export function Step3DashboardMode({
|
||||
// - "html" 网页渲染:直接把自包含 HTML 大屏塞进 iframe,与「新窗口打开 / 分享 / 下载」一致。
|
||||
const [renderMode, setRenderMode] = useState<RenderMode>("app");
|
||||
|
||||
// 网页渲染:用 blob URL 作 iframe `src`(而非 `srcDoc`)。
|
||||
// 原因:`srcdoc` 文档的 base URL 继承自父页面,大屏内的纯 CSS `:target` 导航锚点
|
||||
// (#overview / #seat-x)会被解析成「父页面URL#seat-x」,导致点击导航时整个 iframe
|
||||
// 跳去加载主应用并命中 HashRouter 的 404(Page not found)。blob: 文档有自己的 URL,
|
||||
// 片段锚点在 iframe 内部解析,:target 切页正常 —— 与「新窗口打开」一致。
|
||||
//
|
||||
// 必须在 **effect 内部**创建/回收 blob:StrictMode(dev) 会 setup→cleanup→setup 双触发
|
||||
// effect,若用 useMemo 跨两次 render 共享同一个 URL,cleanup 会把它 revoke 掉而 iframe
|
||||
// 仍指向它 → 报「该文件可能已被移至别处」。effect 内每次 run 各自建 URL、cleanup 只回收
|
||||
// 本 run 的,最后提交的那个存活,故 StrictMode-safe。dashboardHtml 随主题变 → blob 重建。
|
||||
const [htmlBlobUrl, setHtmlBlobUrl] = useState<string | null>(null);
|
||||
useEffect(() => {
|
||||
if (typeof window === "undefined" || !dashboardHtml) {
|
||||
setHtmlBlobUrl(null);
|
||||
return;
|
||||
}
|
||||
const url = URL.createObjectURL(new Blob([dashboardHtml], { type: "text/html;charset=utf-8" }));
|
||||
setHtmlBlobUrl(url);
|
||||
return () => URL.revokeObjectURL(url);
|
||||
}, [dashboardHtml]);
|
||||
|
||||
// 绘制完成**不再**自动切到大屏页面:用户正在交互区看抽取过程/继续问答时,强行跳走很打断。
|
||||
// 想看成品由用户手动点顶栏「查看大屏」。没有产物时初始即落交互区(见上 useState 初值)。
|
||||
|
||||
@@ -126,8 +172,8 @@ export function Step3DashboardMode({
|
||||
const handleOpenNewWindow = () => {
|
||||
if (typeof window === "undefined") return;
|
||||
|
||||
if (renderMode === "html" && html) {
|
||||
const blob = new Blob([html], { type: "text/html;charset=utf-8" });
|
||||
if (renderMode === "html" && dashboardHtml) {
|
||||
const blob = new Blob([dashboardHtml], { type: "text/html;charset=utf-8" });
|
||||
const blobUrl = URL.createObjectURL(blob);
|
||||
window.open(blobUrl, "_blank", "noopener,noreferrer");
|
||||
// 延迟回收,给新标签页足够时间加载(过早 revoke 会打不开)。
|
||||
@@ -195,9 +241,9 @@ export function Step3DashboardMode({
|
||||
<button
|
||||
type="button"
|
||||
onClick={() => setRenderMode("html")}
|
||||
disabled={!html}
|
||||
disabled={!dashboardHtml}
|
||||
title={
|
||||
html
|
||||
dashboardHtml
|
||||
? "用自包含 HTML 大屏渲染(与新窗口打开 / 分享 / 下载一致)"
|
||||
: "尚无已生成的 HTML 大屏,先完成一次绘制"
|
||||
}
|
||||
@@ -269,10 +315,10 @@ export function Step3DashboardMode({
|
||||
|
||||
{/* 主体:交互区(聊天 + 抽取沙箱) / 大屏页面(React) */}
|
||||
{view === "page" ? (
|
||||
renderMode === "html" && html ? (
|
||||
renderMode === "html" && htmlBlobUrl ? (
|
||||
<iframe
|
||||
title="方案研讨大屏"
|
||||
srcDoc={html}
|
||||
src={htmlBlobUrl}
|
||||
className="flex-1 min-h-0 w-full border-0 bg-white"
|
||||
sandbox="allow-scripts allow-popups allow-downloads allow-same-origin"
|
||||
/>
|
||||
|
||||
Reference in new issue
Block a user