运营中心收口:四区(驾驶舱/工作流/台账/系统)摆建设中占位;L2「启动活动」改「创建活动」、沙箱区块隐藏、行内终止撤掉收口 L3、负责人小签隐藏;文案定名活动列表/活动号
CI / ci (push) Failing after 12s

This commit is contained in:
shanqin
2026-08-28 10:39:13 +08:00
parent f41bd76b0e
commit 21d41f5859
3 changed files with 47 additions and 48 deletions
+17
View File
@@ -38,6 +38,7 @@ import { TestbedHome } from '@features/gov-testbed';
import { CockpitHome } from '@features/gov-memory'; import { CockpitHome } from '@features/gov-memory';
import { ChanPage, LlmPage, ModelsHome, SystemHome } from '@features/gov-system'; import { ChanPage, LlmPage, ModelsHome, SystemHome } from '@features/gov-system';
import { Btn } from '@web-shared/Btn.tsx'; import { Btn } from '@web-shared/Btn.tsx';
import { Card, Note } from '@web-shared/gov/primitives.tsx';
/* ── URL 编解码:`#/<surface>/<zone>/<tab>[/<k>:<id>…]` ────────────────── */ /* ── URL 编解码:`#/<surface>/<zone>/<tab>[/<k>:<id>…]` ────────────────── */
@@ -292,7 +293,23 @@ export function GovShell({ surface }: { surface: Surface }) {
); );
} }
/** 运营中心四区先摆建设中(2026-08-28 用户定:驾驶舱 / 工作流 / 台账 / 系统)——
真面板组件与其下钻路由原样留着,只换区首页;要做回来,把区从这个集合摘掉即可 */
const WIP_ZONES: ReadonlySet<GovZone> = new Set(['cockpit', 'runs', 'ledgers', 'system']);
/** 建设中占位:如实说还没有 —— 不摆假数据、不摆点不动的钮(与知识库「待建」页同一条规矩) */
function WipHome({ zone }: { zone: GovZone }) {
const zm = ZM[zone];
return (
<Card ico={zm.icon} title={zm.title} extra="建设中">
<Note>这一区正在建设中 —— 运营中心目前开放「活动」与「员工」两区,其余区建好后开放。</Note>
</Card>
);
}
function ZoneHome({ zone, tab, nav }: { zone: GovZone; tab: string; nav: Nav }) { function ZoneHome({ zone, tab, nav }: { zone: GovZone; tab: string; nav: Nav }) {
/* 建设中的区先拦在这里(2026-08-28 用户定):真面板组件仍在下方映射里,摘掉集合即恢复 */
if (WIP_ZONES.has(zone)) return <WipHome zone={zone} />;
const P = { const P = {
// 设计中心 // 设计中心
blueprint: BlueprintHome, blueprint: BlueprintHome,
@@ -227,7 +227,7 @@ const INSTANCE_PAGE_SIZE = 8;
type InstanceFilterKey = 'id' | 'owner' | 'state' | 'time'; type InstanceFilterKey = 'id' | 'owner' | 'state' | 'time';
const INSTANCE_FILTER_LABEL: Readonly<Record<InstanceFilterKey, string>> = { const INSTANCE_FILTER_LABEL: Readonly<Record<InstanceFilterKey, string>> = {
id: '实例', id: '活动号',
owner: '负责人', owner: '负责人',
state: '状态', state: '状态',
time: '启动时间', time: '启动时间',
@@ -240,7 +240,7 @@ type PopAskSpec = { title: string; okLabel: string; danger?: boolean; onOk: () =
const errText = (e: unknown): string => (e as { message?: string } | null)?.message ?? '网络错误,请稍后重试'; const errText = (e: unknown): string => (e as { message?: string } | null)?.message ?? '网络错误,请稍后重试';
/** 气泡式二次确认 —— 锚在页头右下的小泡泡:一句话问清,确认/取消两钮;点气泡外的透明罩即取消; /** 气泡式二次确认 —— 锚在页头右下的小泡泡:一句话问清,确认/取消两钮;点气泡外的透明罩即取消;
`inrow` = 锚在行内操作格(终止实例用,接口文档 §10),定位由 CSS 的 .inrow 变体给。 `inrow` = 锚在行内操作格的变体(行内终止撤掉后暂无处用,2026-08-28;变体与 CSS 保留,要用直接传);
2026-08-28 反馈闭环(用户报:终止点了不知道成没成):确认后气泡**不再立即消失** —— 2026-08-28 反馈闭环(用户报:终止点了不知道成没成):确认后气泡**不再立即消失** ——
在途时留场转忙碌(两钮禁用、罩不可点),成功由调用方 onSuccess 关掉,失败把原因留在气泡里可重试 */ 在途时留场转忙碌(两钮禁用、罩不可点),成功由调用方 onSuccess 关掉,失败把原因留在气泡里可重试 */
function PopAsk({ spec, onCancel, inrow, busy, error }: { spec: PopAskSpec; onCancel: () => void; inrow?: boolean; busy?: boolean; error?: string | null }) { function PopAsk({ spec, onCancel, inrow, busy, error }: { spec: PopAskSpec; onCancel: () => void; inrow?: boolean; busy?: boolean; error?: string | null }) {
@@ -470,7 +470,7 @@ function LaunchDrawer({ activityId, name, onCancel, onLaunch }: { activityId: st
</option> </option>
))} ))}
</select> </select>
<small>这次启动的实例归他 —— 负责人验收、数据工单都落到责任人身上;默认是你自己。</small> <small>这次创建的活动归他 —— 负责人验收、数据工单都落到责任人身上;默认是你自己。</small>
{fieldErr['ownerId'] !== undefined && <small className="warn">{fieldErr['ownerId']}</small>} {fieldErr['ownerId'] !== undefined && <small className="warn">{fieldErr['ownerId']}</small>}
</label> </label>
</section> </section>
@@ -577,8 +577,12 @@ function LaunchDrawer({ activityId, name, onCancel, onLaunch }: { activityId: st
* · 启动活动(即创建实例)—— 弹参数抽屉(LaunchDrawer),参数表按活动下发;已禁用不能启动。 * · 启动活动(即创建实例)—— 弹参数抽屉(LaunchDrawer),参数表按活动下发;已禁用不能启动。
* *
* 2026-08-27 接真接口(docs/API文档-活动模块.md):定义 §2.2 / 清单 §3 / 启动 §4、§5 / 禁用激活终止 §8; * 2026-08-27 接真接口(docs/API文档-活动模块.md):定义 §2.2 / 清单 §3 / 启动 §4、§5 / 禁用激活终止 §8;
* 沙箱区:view.url + 票据装载,高度听 `jcai:view-height`;view 为 null 藏起整个区块。 * 沙箱区:view.url + 票据装载,高度听 `jcai:view-height`;2026-08-28 用户定:沙箱区块先隐藏(SHOW_SANDBOX 开关)。
*/ */
/** 沙箱区块开关(2026-08-28 用户定:先隐藏)—— 置回 true 即恢复;显式标注 boolean 免得被字面量收窄 */
const SHOW_SANDBOX: boolean = false;
export function ActvPage({ id, nav }: { id: string; nav: Nav }) { export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
/* 接口对接(2026-08-27,docs/API文档-活动模块.md):定义 §2.2 / 实例清单 §3(检索分页全在服务端)/ 写动作 §5、§8 */ /* 接口对接(2026-08-27,docs/API文档-活动模块.md):定义 §2.2 / 实例清单 §3(检索分页全在服务端)/ 写动作 §5、§8 */
const actQ = useOpsActivity(id); const actQ = useOpsActivity(id);
@@ -590,9 +594,9 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
const [fFrom, setFFrom] = useState(''); const [fFrom, setFFrom] = useState('');
const [fTo, setFTo] = useState(''); const [fTo, setFTo] = useState('');
const [page, setPage] = useState(0); const [page, setPage] = useState(0);
/* 轻量二次确认(2026-08-26 用户定):头部动作(禁用/激活)走页头气泡;行内「终止活动」走行内气泡(接口文档 §10) */ /* 轻量二次确认(2026-08-26 用户定):头部动作(禁用/激活)走页头气泡;
行内「终止活动」与它的气泡已撤(2026-08-28 用户定:终止只留在 L3) */
const [ask, setAsk] = useState<PopAskSpec | null>(null); const [ask, setAsk] = useState<PopAskSpec | null>(null);
const [rowAsk, setRowAsk] = useState<{ rid: string; no: string } | null>(null);
/* 启动抽屉(2026-08-27 用户定) */ /* 启动抽屉(2026-08-27 用户定) */
const [launch, setLaunch] = useState(false); const [launch, setLaunch] = useState(false);
@@ -632,7 +636,6 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
const qc = useQueryClient(); const qc = useQueryClient();
const disable = useGovAction(() => call(disableOpsActivityRoute, { params: { activityId: id } }), ['activities']); const disable = useGovAction(() => call(disableOpsActivityRoute, { params: { activityId: id } }), ['activities']);
const activate = useGovAction(() => call(activateOpsActivityRoute, { params: { activityId: id } }), ['activities']); const activate = useGovAction(() => call(activateOpsActivityRoute, { params: { activityId: id } }), ['activities']);
const terminate = useGovAction((iid: string) => call(terminateOpsInstanceRoute, { params: { activityId: id, instanceId: iid } }), ['activities']);
/* 页头写动作的在途/失败态 —— 摆进气泡(2026-08-28 用户报:点了没反馈);同一时刻只有一枚气泡,二选一即可 */ /* 页头写动作的在途/失败态 —— 摆进气泡(2026-08-28 用户报:点了没反馈);同一时刻只有一枚气泡,二选一即可 */
const headBusy = disable.isPending || activate.isPending; const headBusy = disable.isPending || activate.isPending;
const headErr = disable.isError ? errText(disable.error) : activate.isError ? errText(activate.error) : null; const headErr = disable.isError ? errText(disable.error) : activate.isError ? errText(activate.error) : null;
@@ -666,7 +669,7 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
<Chip ico="tag">v{a.version}</Chip> <Chip ico="tag">v{a.version}</Chip>
{/* 绑定的 LLM 上头版(2026-08-25 用户定):每个活动一个专属 agent */} {/* 绑定的 LLM 上头版(2026-08-25 用户定):每个活动一个专属 agent */}
<Chip ico="cpu">{a.llm}</Chip> <Chip ico="cpu">{a.llm}</Chip>
<Chip ico="user">{a.owner?.name ?? '—'}</Chip> {/* 负责人小签隐藏(2026-08-28 用户定:活动本身没有负责人;责任人是每次创建的固定参数,在 L3 看) */}
<Chip ico="calendar-days">创建于 {atMinute(a.createdAt).slice(0, 5)}</Chip> <Chip ico="calendar-days">创建于 {atMinute(a.createdAt).slice(0, 5)}</Chip>
</> </>
} }
@@ -703,8 +706,8 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
}); });
}, },
}, },
/* 启动活动(2026-08-27 接):弹参数抽屉填参启动;已禁用不能启动 —— 先激活,再启动 */ /* 创建活动(2026-08-28 用户改文案,原「启动活动」):弹参数抽屉填参创建实例;已禁用不能创建 —— 先激活 */
{ label: '启动活动', ico: 'play', primary: st === 'active', disabled: st !== 'active', onClick: () => setLaunch(true) }, { label: '创建活动', ico: 'play', primary: st === 'active', disabled: st !== 'active', onClick: () => setLaunch(true) },
/* 版本迭代入口先隐藏(2026-08-25 用户定):功能设计回头讨论,要做时把这行加回 acts /* 版本迭代入口先隐藏(2026-08-25 用户定):功能设计回头讨论,要做时把这行加回 acts
{ label: '版本迭代', ico: 'git-branch', disabled: true }, { label: '版本迭代', ico: 'git-branch', disabled: true },
*/ */
@@ -714,7 +717,8 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
{launch && <LaunchDrawer activityId={a.id} name={a.name} onCancel={() => setLaunch(false)} onLaunch={onLaunched} />} {launch && <LaunchDrawer activityId={a.id} name={a.name} onCancel={() => setLaunch(false)} onLaunch={onLaunched} />}
</div> </div>
<Sec ico="repeat" title="历史活动" extra={`共 ${list?.stats.total ?? '…'} 个 · 查看详情进 L3`}> {/* 「历史活动」改「活动列表」(2026-08-28 用户定) */}
<Sec ico="repeat" title="活动列表" extra={`共 ${list?.stats.total ?? '…'} 个 · 查看详情进 L3`}>
{/* 统计带 + 检索条同一行(2026-08-26 用户定:检索放右侧)—— {/* 统计带 + 检索条同一行(2026-08-26 用户定:检索放右侧)——
左:总数 + 五态灰字合计(状态色只进 6px 小圆点);右:条件下拉 + 随条件换控件; 左:总数 + 五态灰字合计(状态色只进 6px 小圆点);右:条件下拉 + 随条件换控件;
统计数是服务端给的全局 stats,不随检索条件动(2026-08-27 接接口) */} 统计数是服务端给的全局 stats,不随检索条件动(2026-08-27 接接口) */}
@@ -751,7 +755,7 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
<input <input
className="g-iselect-input" className="g-iselect-input"
value={fText} value={fText}
placeholder="按实例号模糊搜,如 INS-2417" placeholder="按活动号模糊搜,如 INS-2417"
onChange={(e) => { onChange={(e) => {
setFText(e.target.value); setFText(e.target.value);
setPage(0); setPage(0);
@@ -835,9 +839,10 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
2026-08-27 接接口:摆的是 no(人读实例号),寻址(L3/终止)用 id(任务 uuid) */} 2026-08-27 接接口:摆的是 no(人读实例号),寻址(L3/终止)用 id(任务 uuid) */}
<QueryGate query={instQ}> <QueryGate query={instQ}>
<div className="g-itab"> <div className="g-itab">
{/* 列序(2026-08-26 用户定):实例 / 负责人 / 输入 / 输出 / 启动时间 / 状态 / 操作(操作栏也有标题) */} {/* 列序(2026-08-26 用户定):实例 / 负责人 / 输入 / 输出 / 启动时间 / 状态 / 操作(操作栏也有标题);
表头第一列定名「活动号」(2026-08-28 用户定) */}
<div className="g-itab-h"> <div className="g-itab-h">
<span>实例</span> <span>活动号</span>
<span>负责人</span> <span>负责人</span>
<span>输入</span> <span>输入</span>
<span>输出</span> <span>输出</span>
@@ -862,38 +867,10 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
{INSTANCE_STATE_LABEL[r.state]} {INSTANCE_STATE_LABEL[r.state]}
</span> </span>
<span className="a"> <span className="a">
{r.state === 'running' && ( {/* 行内「终止活动」撤掉(2026-08-28 用户定):终止只留在 L3 详情页头部,清单行只剩查看 */}
<button
type="button"
className="g-rowbtn danger"
onClick={() => {
terminate.reset();
setRowAsk({ rid: r.id, no: r.no });
}}
>
终止活动
</button>
)}
{/* L3 实例详情(2026-08-26 接):路由 id 带活动与实例两段,详情页按活动找回上下文 */}
<button type="button" className="g-rowbtn" onClick={() => nav.push({ k: 'acti', id: `${a.id}:${r.id}` })}> <button type="button" className="g-rowbtn" onClick={() => nav.push({ k: 'acti', id: `${a.id}:${r.id}` })}>
查看详情 查看详情
</button> </button>
{/* 行内终止的二次确认(接口文档 §10):轻气泡锚在操作格里,触发钮左边;
确认后留场等结果 —— 成功自动关 + 行状态翻亮,失败原因摆进气泡(2026-08-28 用户报) */}
{rowAsk?.rid === r.id && (
<PopAsk
inrow
spec={{
title: `确认终止实例 ${rowAsk.no}?`,
okLabel: '确认终止',
danger: true,
onOk: () => terminate.mutate(r.id, { onSuccess: () => setRowAsk(null) }),
}}
busy={terminate.isPending}
error={terminate.isError ? errText(terminate.error) : null}
onCancel={() => setRowAsk(null)}
/>
)}
</span> </span>
</div> </div>
))} ))}
@@ -901,7 +878,7 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
{pageRows.length === 0 && ( {pageRows.length === 0 && (
<div className="g-irow-empty"> <div className="g-irow-empty">
<Ico name="search" s={14} /> <Ico name="search" s={14} />
没有匹配的实例 —— 换个条件,或清空重看全部。 没有匹配的活动 —— 换个条件,或清空重看全部。
</div> </div>
)} )}
</div> </div>
@@ -923,9 +900,10 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
{/* {/*
* 沙箱(2026-08-25 用户定;2026-08-27 接接口,文档 §2.3):加载服务器返回的活动视图 HTML。 * 沙箱(2026-08-25 用户定;2026-08-27 接接口,文档 §2.3):加载服务器返回的活动视图 HTML。
* 隔离:iframe + sandbox="allow-scripts" —— 不透明源,禁同源/表单/弹窗,视图只能渲染与跑自己的脚本; * 隔离:iframe + sandbox="allow-scripts" —— 不透明源,禁同源/表单/弹窗,视图只能渲染与跑自己的脚本;
* src = 活动详情里的 view.url + 装载那一刻现取的票据;view 为 null 时整个区块藏起来。 * src = 活动详情里的 view.url + 装载那一刻现取的票据。
* 2026-08-28 用户定:沙箱区块**先隐藏**(SandboxFrame 组件与 view 取数保留,SHOW_SANDBOX 置回 true 即恢复)。
*/} */}
{a.view !== null && ( {SHOW_SANDBOX && a.view !== null && (
<Sec ico="dashboard" title="活动视图 · 沙箱" extra="服务器返回的 HTML · 隔离渲染"> <Sec ico="dashboard" title="活动视图 · 沙箱" extra="服务器返回的 HTML · 隔离渲染">
<SandboxFrame activityId={a.id} viewUrl={a.view.url} name={a.name} /> <SandboxFrame activityId={a.id} viewUrl={a.view.url} name={a.name} />
</Sec> </Sec>
@@ -1104,7 +1082,7 @@ export function ActiPage({ id }: { id: string }) {
<Chip ico="cpu">{d.llm}</Chip> <Chip ico="cpu">{d.llm}</Chip>
</> </>
} }
desc="一次运行的完整视图:输入 / 过程 / 输出 / 状态 —— 实例不可编辑、不可删除,进行中的运行可终止。" desc="一次运行的完整视图:输入 / 过程 / 输出 / 状态 —— 活动不可编辑、不可删除,进行中的运行可终止。"
acts={[ acts={[
/* 唯一动作(2026-08-26 用户定:实例不许删、不许编):进行中可终止,轻气泡二次确认 */ /* 唯一动作(2026-08-26 用户定:实例不许删、不许编):进行中可终止,轻气泡二次确认 */
...(state === 'running' ...(state === 'running'
@@ -1117,7 +1095,7 @@ export function ActiPage({ id }: { id: string }) {
onClick: () => { onClick: () => {
terminate.reset(); terminate.reset();
setAsk({ setAsk({
title: `确认终止实例 ${d.no}?`, title: `确认终止活动 ${d.no}?`,
okLabel: '确认终止', okLabel: '确认终止',
danger: true, danger: true,
onOk: () => terminate.mutate(undefined, { onSuccess: () => setAsk(null) }), onOk: () => terminate.mutate(undefined, { onSuccess: () => setAsk(null) }),
@@ -98,6 +98,10 @@ L1(模块首页 · 活动清单)需要一个**标签页**切换,因为活
- 2026-08-27 · **启动固定参数「责任人」(用户定)**:每次启动必须指定这次实例的责任人(负责人验收、数据工单都落到他身上)—— 它是**启动契约的固定字段 `ownerId`**,不进按活动下发的参数表。抽屉里已加「固定参数」段:人员下拉来自员工名册真接口(`GET /api/gov/staff`,只列在职),默认当前登录人(标注「我自己」);接口口径见 `docs/API文档-活动模块.md` §5。 - 2026-08-27 · **启动固定参数「责任人」(用户定)**:每次启动必须指定这次实例的责任人(负责人验收、数据工单都落到他身上)—— 它是**启动契约的固定字段 `ownerId`**,不进按活动下发的参数表。抽屉里已加「固定参数」段:人员下拉来自员工名册真接口(`GET /api/gov/staff`,只列在职),默认当前登录人(标注「我自己」);接口口径见 `docs/API文档-活动模块.md` §5。
- 2026-08-28 · **轻气泡确认的反馈闭环(用户报修)**:禁用/激活/终止实例确认后气泡**不再立即消失** —— 请求在途留场转忙碌(两钮禁用、罩不可点),成功自动关(列表/详情由失效重取刷新,状态翻转有弹出高亮),失败把接口原因留在气泡里可重试。此前确认即关 + mutation 无 onError,失败时全哑,点了不知道成没成。 - 2026-08-28 · **轻气泡确认的反馈闭环(用户报修)**:禁用/激活/终止实例确认后气泡**不再立即消失** —— 请求在途留场转忙碌(两钮禁用、罩不可点),成功自动关(列表/详情由失效重取刷新,状态翻转有弹出高亮),失败把接口原因留在气泡里可重试。此前确认即关 + mutation 无 onError,失败时全哑,点了不知道成没成。
- 2026-08-28 · **L3 实例详情 7 秒轮询(用户定)**:`useOpsInstance` 加 `refetchInterval` —— 待执行/进行中每 7 秒重取一次;进终态(完成/驳回/失败)自动停,不白打接口。 - 2026-08-28 · **L3 实例详情 7 秒轮询(用户定)**:`useOpsInstance` 加 `refetchInterval` —— 待执行/进行中每 7 秒重取一次;进终态(完成/驳回/失败)自动停,不白打接口。
- 2026-08-28 · **运营中心四区先摆建设中(用户定)**:驾驶舱 / 工作流 / 台账 / 系统的区首页换建设中占位(gov-shell `WIP_ZONES`),真面板与下钻深链保留,摘掉集合即恢复;目前运营中心只开放「活动」与「员工」。
- 2026-08-28 · **L2 头部「启动活动」改文案为「创建活动」(用户定)**;同页「活动视图 · 沙箱」区块先隐藏(`SHOW_SANDBOX=false`,SandboxFrame 与 view 取数保留)。
- 2026-08-28 · **终止收口到 L3(用户定)**:L2 实例清单行内的「终止活动」按钮连同行内气泡一并撤掉(PopAsk 的 inrow 变体与 CSS 保留),终止只在 L3 详情页头部;**界面文案收口(同日两轮定)**:L1 卡底栏仍叫「实例」;L2 区块「历史活动」改「**活动列表**」,表头第一列与检索条件定名「**活动号**」,L3 描述与终止确认气泡里的「实例」改「活动」。工作流区的「节点实例」是另一概念,未动。
- 2026-08-28 · **L2 头版负责人小签隐藏(用户定)**:活动本身没有负责人——责任人是每次创建的固定参数(`ownerId`),归 L3 实例头版;定义上的 owner 字段保留不渲染。
## 三、功能表(整理稿) ## 三、功能表(整理稿)