feat: finish mobile task cards for thin pages
This commit is contained in:
@@ -618,6 +618,17 @@ function hasSessionBackendMismatch(expectedBackendUrl = DEFAULT_BACKEND_URL) {
|
||||
return Boolean(expected && current && expected !== current);
|
||||
}
|
||||
|
||||
function formatBackendDisplayLabel(value = DEFAULT_BACKEND_URL) {
|
||||
const normalized = normalizeBackendUrlValue(value || DEFAULT_BACKEND_URL);
|
||||
if (!normalized) return "未配置后端";
|
||||
try {
|
||||
const parsed = new URL(normalized);
|
||||
return parsed.host || normalized;
|
||||
} catch {
|
||||
return normalized;
|
||||
}
|
||||
}
|
||||
|
||||
function compareDateDesc(leftValue, rightValue) {
|
||||
return new Date(rightValue || 0).getTime() - new Date(leftValue || 0).getTime();
|
||||
}
|
||||
@@ -6930,11 +6941,45 @@ function renderCreditsScreen() {
|
||||
const usage = appState.tenantUsage || quota?.usage || {};
|
||||
const categories = usage?.categories || {};
|
||||
const estimatedVideoUsage = (categories.ai_video?.quantity || 0) + (categories.real_cut?.quantity || 0);
|
||||
const budgetAmount = (quota?.monthly_budget_cents || usage?.total_cost_cents || 0) / 100;
|
||||
const usedAmount = (usage?.total_cost_cents || 0) / 100;
|
||||
const quotaProtectionLabel = quota?.enabled === false ? "额度保护关闭" : "额度保护开启";
|
||||
const riskLabel = quota?.storage_over_limit ? "存储超限" : "当前风险可控";
|
||||
return screenShell(
|
||||
"额度",
|
||||
"在接真实计费前,先按任务量给出运营看板。",
|
||||
`${button("刷新", "refresh-data")}`,
|
||||
`
|
||||
<div class="mobile-only mobile-flow-focus-card">
|
||||
<div class="mobile-flow-focus-head">
|
||||
<strong>当前额度任务</strong>
|
||||
<span class="tag blue">${escapeHtml(quotaProtectionLabel)}</span>
|
||||
</div>
|
||||
<p>${escapeHtml(
|
||||
quota
|
||||
? `先确认预算 ${formatNumber(budgetAmount)} 元和已用 ${formatNumber(usedAmount)} 元,再决定是继续放量还是先收紧高成本动作。`
|
||||
: "先看预算和高成本动作的预估消耗,再决定是否要继续做视频或剪辑任务。"
|
||||
)}</p>
|
||||
<div class="task-meta">
|
||||
${actionTag("刷新额度", "refresh-data")}
|
||||
${actionTag("去生产中心", "goto-production")}
|
||||
${actionTag("交给主 Agent", "handoff-to-main-agent", buildMainAgentHandoffAttrs({
|
||||
sourceScreen: "credits",
|
||||
sourceActionKey: "credits-main-agent-handoff",
|
||||
intentKey: "custom",
|
||||
title: "评估当前额度和预算风险",
|
||||
goal: "评估当前额度和预算风险",
|
||||
summary: "让主 Agent 结合当前预算、已用额度和高成本动作,给出下一步建议。",
|
||||
planSteps: ["读取当前额度看板", "判断预算与高成本动作风险", "给出下一步控制建议"]
|
||||
}))}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-only compact-summary-row" style="margin-bottom:14px;">
|
||||
<span class="tag blue">预算 ${escapeHtml(formatNumber(budgetAmount))} 元</span>
|
||||
<span class="tag ${quota?.storage_over_limit ? "orange" : "green"}">${escapeHtml(riskLabel)}</span>
|
||||
<span class="tag">${escapeHtml(formatNumber(categories.copy?.quantity || jobs.filter((item) => item.line_type === "analysis").length))} 条文案</span>
|
||||
<span class="tag">${escapeHtml(formatNumber(estimatedVideoUsage || jobs.filter((item) => item.line_type === "ai_video" || item.line_type === "real_cut").length))} 次视频</span>
|
||||
</div>
|
||||
<div class="layout-grid grid-3">
|
||||
<div class="stat-card"><small>文案消耗预估</small><strong>${escapeHtml(formatNumber(categories.copy?.quantity || jobs.filter((item) => item.line_type === "analysis").length))}</strong><div class="stat-foot"><span>分析 / 生成链路</span><span class="positive">按任务量估算</span></div></div>
|
||||
<div class="stat-card"><small>本周期预算</small><strong>${escapeHtml(formatNumber((quota?.monthly_budget_cents || usage?.total_cost_cents || 0) / 100))}</strong><div class="stat-foot"><span>元</span><span class="warn">已用 ${escapeHtml(formatNumber((usage?.total_cost_cents || 0) / 100))} 元</span></div></div>
|
||||
@@ -6992,17 +7037,49 @@ function renderSettingsScreen() {
|
||||
{ value: "display", label: "界面与帮助" }
|
||||
];
|
||||
const activeTab = getActiveDetailTab("settingsDetailTab", tabs);
|
||||
const sessionConnected = Boolean(session);
|
||||
const settingsHandoffAttrs = buildMainAgentHandoffAttrs({
|
||||
sourceScreen: "settings",
|
||||
sourceActionKey: "settings-main-agent-handoff",
|
||||
intentKey: "custom",
|
||||
title: "检查当前设置和工作区连接",
|
||||
goal: "检查当前设置和工作区连接",
|
||||
summary: "让主 Agent 读取当前连接状态、工作区和常用入口,再给出下一步建议。",
|
||||
planSteps: ["读取当前连接与项目上下文", "确认当前工作区和入口状态", "生成下一步建议"]
|
||||
});
|
||||
return screenShell(
|
||||
"设置",
|
||||
"这里不放系统治理内容,只处理当前用户需要理解的连接、界面和帮助信息。",
|
||||
`${button("连接状态", "open-auth")} ${isSuperAdmin() ? button("管理员配置台", "goto-admin-workbench", "primary") : button("刷新", "refresh-data", "primary")}`,
|
||||
`
|
||||
<div class="hero-card">
|
||||
<div class="hero-card mobile-secondary-card">
|
||||
<h3>设置与帮助</h3>
|
||||
<p>把连接状态、当前工作区和使用说明放在一起,避免和管理员控制面混在同一页。</p>
|
||||
</div>
|
||||
<div class="panel pad" style="margin-top:18px;">
|
||||
<div class="panel-head"><div><h3>当前设置</h3><div class="panel-subtitle">先看你现在接到了哪里,再决定是否要调整。</div></div></div>
|
||||
<div class="mobile-only mobile-flow-focus-card">
|
||||
<div class="mobile-flow-focus-head">
|
||||
<strong>当前设置任务</strong>
|
||||
<span class="tag ${sessionConnected ? "green" : "red"}">${escapeHtml(sessionConnected ? "已自动连接" : "等待连接")}</span>
|
||||
</div>
|
||||
<p>${escapeHtml(
|
||||
sessionConnected
|
||||
? `当前已经连到 ${session?.account?.display_name || session?.account?.username || "当前工作区"},先确认工作区和常用入口,再决定是否切项目或回到业务页。`
|
||||
: "先确认当前站点是否已自动连接到工作区,再决定是重试连接还是回到业务页。"
|
||||
)}</p>
|
||||
<div class="task-meta">
|
||||
${actionTag("打开连接状态", "open-auth")}
|
||||
${project ? actionTag("去我的项目", "goto-intake") : ""}
|
||||
${actionTag("交给主 Agent", "handoff-to-main-agent", settingsHandoffAttrs)}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mobile-only compact-summary-row" style="margin-bottom:14px;">
|
||||
<span class="tag ${sessionConnected ? "green" : "red"}">${escapeHtml(sessionConnected ? "已自动连接" : "等待连接")}</span>
|
||||
<span class="tag blue">${escapeHtml(project?.name || "未选项目")}</span>
|
||||
<span class="tag">${escapeHtml(formatBackendDisplayLabel(session?.backendUrl || DEFAULT_BACKEND_URL))}</span>
|
||||
<span class="tag">${escapeHtml(activeTab === "workspace" ? "连接与工作区" : "界面与帮助")}</span>
|
||||
</div>
|
||||
${renderDetailTabs("settingsDetailTab", tabs)}
|
||||
${activeTab === "workspace" ? `
|
||||
<div class="layout-grid grid-main">
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover" />
|
||||
<meta name="theme-color" content="#eef4fb" />
|
||||
<title>StoryForge Web V4 Prototype</title>
|
||||
<title>StoryForge Workbench</title>
|
||||
<link rel="icon" href="./assets/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="stylesheet" href="./assets/styles.css" />
|
||||
</head>
|
||||
@@ -1687,7 +1687,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-note">StoryForge Web V4 prototype · UI only · no live data binding</div>
|
||||
<div class="footer-note">StoryForge 工作台 · 项目总台与执行入口会按当前工作区实时展开</div>
|
||||
</section>
|
||||
|
||||
<section class="screen" data-screen="review">
|
||||
@@ -1807,7 +1807,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-note">StoryForge Web V4 prototype · review loop separated from production</div>
|
||||
<div class="footer-note">StoryForge 工作台 · 复盘链路与生产链路分层展示,避免同页堆叠</div>
|
||||
</section>
|
||||
|
||||
<section class="screen" data-screen="credits">
|
||||
@@ -1941,7 +1941,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="footer-note">StoryForge Web V4 prototype · credit system expressed as user-facing quota pools</div>
|
||||
<div class="footer-note">StoryForge 工作台 · 额度以用户可理解的预算与动作池方式表达</div>
|
||||
</section>
|
||||
|
||||
<section class="screen" data-screen="strategy"></section>
|
||||
|
||||
@@ -287,6 +287,8 @@ test("remaining mobile workbench screens expose focus cards and compact summarie
|
||||
const automation = extractBetween(APP, "function renderAutomationScreen()", "function renderOwnedScreen()");
|
||||
const owned = extractBetween(APP, "function renderOwnedScreen()", "function renderPlaybookScreen()");
|
||||
const review = extractBetween(APP, "function renderReviewScreen()", "function renderStrategyScreen()");
|
||||
const credits = extractBetween(APP, "function renderCreditsScreen()", "function renderSettingsScreen()");
|
||||
const settings = extractBetween(APP, "function renderSettingsScreen()", "function renderTopbar()");
|
||||
|
||||
assert.match(projects, /mobile-only mobile-flow-focus-card/);
|
||||
assert.match(projects, /当前项目任务/);
|
||||
@@ -312,6 +314,16 @@ test("remaining mobile workbench screens expose focus cards and compact summarie
|
||||
assert.match(review, /当前复盘任务/);
|
||||
assert.match(review, /mobile-only compact-summary-row/);
|
||||
assert.match(review, /已保存/);
|
||||
|
||||
assert.match(credits, /mobile-only mobile-flow-focus-card/);
|
||||
assert.match(credits, /当前额度任务/);
|
||||
assert.match(credits, /mobile-only compact-summary-row/);
|
||||
assert.match(credits, /预算/);
|
||||
|
||||
assert.match(settings, /mobile-only mobile-flow-focus-card/);
|
||||
assert.match(settings, /当前设置任务/);
|
||||
assert.match(settings, /mobile-only compact-summary-row/);
|
||||
assert.match(settings, /已自动连接/);
|
||||
});
|
||||
|
||||
test("projects screen uses an adaptive project grid instead of a fixed three-column squeeze", () => {
|
||||
|
||||
Reference in New Issue
Block a user