From 4d1fe87b8f63028212085b91ffffd443dd1f99fe Mon Sep 17 00:00:00 2001 From: Rou <76741805+Rou325@users.noreply.github.com> Date: Tue, 1 Sep 2026 17:51:18 +0800 Subject: [PATCH] =?UTF-8?q?fix(create):=20=E4=BF=AE=E5=A4=8D=E5=88=9B?= =?UTF-8?q?=E4=BD=9C=E9=A1=B5=E5=B8=83=E5=B1=80=E4=B8=8E=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E4=BB=B7=E6=A0=BC=E7=AD=9B=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/components/analytics-panel.tsx | 13 ++++++++--- web/src/styles/globals.css | 22 ++++++++++++++----- web/test/admin-ui-regressions.test.ts | 5 ++++- web/test/create-empty-layout.test.ts | 18 +++++++++++++++ 4 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 web/test/create-empty-layout.test.ts diff --git a/web/src/pages/admin/components/analytics-panel.tsx b/web/src/pages/admin/components/analytics-panel.tsx index 33e969df0..591446597 100644 --- a/web/src/pages/admin/components/analytics-panel.tsx +++ b/web/src/pages/admin/components/analytics-panel.tsx @@ -77,6 +77,7 @@ export default function AnalyticsPanel({ users, channels }: Props) { const [pricingWorkspaceOpen, setPricingWorkspaceOpen] = useState(false); const [pendingPricing, setPendingPricing] = useState(undefined); const [form] = Form.useForm(); + const pricingChannelId = Form.useWatch("channelId", form); const analyticsPageSize = 20; const filters = useMemo( @@ -148,11 +149,17 @@ export default function AnalyticsPanel({ users, channels }: Props) { const pricingModelOptions = useMemo(() => { const names = new Set(); - const sourceChannels = channels.filter((channel) => channel.enabled !== false); + const sourceChannels = channels.filter( + (channel) => + channel.enabled !== false && + (!pricingChannelId || channel.id === pricingChannelId), + ); sourceChannels.forEach((channel) => channel.models?.forEach((name) => names.add(name))); - if (editingPricing?.model) names.add(editingPricing.model); + if (editingPricing?.model && (!pricingChannelId || editingPricing.channelId === pricingChannelId)) { + names.add(editingPricing.model); + } return [...names].sort().map((name) => ({ label: name, value: name })); - }, [channels, editingPricing?.model]); + }, [channels, editingPricing?.channelId, editingPricing?.model, pricingChannelId]); const preparePricingForm = (pricing: ModelPricing | null) => { setEditingPricing(pricing); diff --git a/web/src/styles/globals.css b/web/src/styles/globals.css index 4a8f3669d..5f70371ce 100644 --- a/web/src/styles/globals.css +++ b/web/src/styles/globals.css @@ -15598,28 +15598,38 @@ html:not(.dark) .creation-home .storyboard-workbench-bar-action:hover { backgrou .creation-home .creation-empty-suggest { grid-template-columns: 1fr; } } -/* 空态布局收敛:banner 与下方对话列同宽,整组垂直居中;用首/末子项 auto margin 居中, - 内容超高时自动 margin 归零、回到顶部对齐,避免 justify-content:center 在滚动容器里的裁切问题。 */ +/* 空态布局收敛:保持图片区、文案和输入区的文档流顺序,内容超高时由主区滚动承载。 */ +.creation-home .creation-empty-workspace { + justify-content: flex-start; +} + +.creation-home .creation-empty-workspace > .creation-empty-art, +.creation-home .creation-empty-workspace > .creation-chat-intro, +.creation-home .creation-empty-workspace > .creation-empty-composer, +.creation-home .creation-empty-workspace > .creation-empty-suggest { + flex: 0 0 auto; +} + .creation-empty-workspace > .creation-empty-art { width: min(820px, calc(100% - 28px)); height: 200px; - margin: auto auto 20px; + margin: 0 auto 20px; } .creation-empty-workspace .creation-empty-art-frame.is-back { top: 18px; height: 146px; } .creation-empty-workspace .creation-empty-art-frame.is-main { top: 0; height: 182px; } .creation-empty-workspace .creation-empty-art-frame.is-front { top: 24px; height: 146px; } -.creation-empty-workspace .creation-chat-intro { margin-top: 24px; margin-bottom: 18px; } +.creation-empty-workspace .creation-chat-intro { margin: 0 auto 18px; } -.creation-empty-workspace > .creation-empty-suggest { margin: 26px auto auto; } +.creation-empty-workspace > .creation-empty-suggest { margin: 26px auto 0; } @media (max-width: 800px) { .creation-empty-workspace > .creation-empty-art { height: 168px; margin-bottom: 16px; } .creation-empty-workspace .creation-empty-art-frame.is-back { top: 14px; height: 122px; } .creation-empty-workspace .creation-empty-art-frame.is-main { top: 0; height: 152px; } .creation-empty-workspace .creation-empty-art-frame.is-front { top: 18px; height: 122px; } - .creation-empty-workspace .creation-chat-intro { margin-top: 18px; margin-bottom: 14px; } + .creation-empty-workspace .creation-chat-intro { margin-top: 0; margin-bottom: 14px; } .creation-empty-workspace > .creation-empty-suggest { margin-top: 20px; } } diff --git a/web/test/admin-ui-regressions.test.ts b/web/test/admin-ui-regressions.test.ts index 36ee072ec..cf7d38e2b 100644 --- a/web/test/admin-ui-regressions.test.ts +++ b/web/test/admin-ui-regressions.test.ts @@ -45,7 +45,10 @@ test("analytics keeps fixed range presets distinct and uses enabled channel mode expect(source).toContain("onChange={handlePricingModelChange}"); expect(source).toContain('hasOwnProperty.call(changedValues, "model")'); expect(source).toContain('if (matchingChannels.length) form.setFieldValue("channelId", matchingChannels[0].id)'); - expect(source).toContain("const sourceChannels = channels.filter((channel) => channel.enabled !== false)"); + expect(source).toContain("const sourceChannels = channels.filter("); + expect(source).toContain('Form.useWatch("channelId", form)'); + expect(source).toContain("pricingChannelId"); + expect(source).toContain("channel.id === pricingChannelId"); expect(source).toContain('inputMode="decimal"'); expect(source).toContain('className="admin-analytics-price-input"'); expect(source).toContain('className="admin-analytics-price-field"'); diff --git a/web/test/create-empty-layout.test.ts b/web/test/create-empty-layout.test.ts new file mode 100644 index 000000000..b063287c2 --- /dev/null +++ b/web/test/create-empty-layout.test.ts @@ -0,0 +1,18 @@ +import { expect, test } from "bun:test"; +import { readFileSync } from "node:fs"; +import { resolve } from "node:path"; + +test("create empty state keeps the banner and composer in document flow", () => { + const styles = readFileSync(resolve(import.meta.dir, "../src/styles/globals.css"), "utf8"); + const layoutStart = styles.indexOf("/* 空态布局收敛:保持图片区、文案和输入区的文档流顺序"); + const layoutEnd = styles.indexOf("/* ===== 素材库", layoutStart); + const layout = styles.slice(layoutStart, layoutEnd); + + expect(layoutStart).toBeGreaterThanOrEqual(0); + expect(layoutEnd).toBeGreaterThan(layoutStart); + expect(layout).toContain("justify-content: flex-start;"); + expect(layout).toContain("flex: 0 0 auto;"); + expect(layout).toContain("margin: 0 auto 20px;"); + expect(layout).not.toContain("margin: auto auto 20px;"); + expect(layout).not.toContain("margin: 26px auto auto;"); +});