Archived
合并 origin/mvp-foundation:运营中心收口(四区建设中占位 · L2 文案与收口)+ 治理台告警钮暂隐(shanqin 两条)
CI / ci (push) Failing after 10s
CI / ci (push) Failing after 10s
主支这边一条:实例时间线第 9 步的结果跟第 8 步走(已放行的单不再永远停在 pending); 远端两条是队友的前端:运营中心四区(驾驶舱 / 工作流 / 台账 / 系统)摆建设中占位、 L2「启动活动」改「创建活动」、沙箱区块隐藏、行内终止撤掉收口 L3、负责人小签隐藏、 文案定名活动列表 / 活动号;治理台顶栏告警钮暂隐(SHOW_ALERTS 开关,计数逻辑保留)。 两边文件零重叠(后端 activity-ops + API 文档 vs 前端 gov-shell / panel + 需求文档),自动合并无冲突。 五道门禁绿:2260 测试通过、23 跳过(无模型密钥 / 无真信箱的惯常项)。 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0167qWjeyBwvfdGovd7FWbCu
This commit is contained in:
@@ -38,6 +38,7 @@ import { TestbedHome } from '@features/gov-testbed';
|
||||
import { CockpitHome } from '@features/gov-memory';
|
||||
import { ChanPage, LlmPage, ModelsHome, SystemHome } from '@features/gov-system';
|
||||
import { Btn } from '@web-shared/Btn.tsx';
|
||||
import { Card, Note } from '@web-shared/gov/primitives.tsx';
|
||||
|
||||
/* ── URL 编解码:`#/<surface>/<zone>/<tab>[/<k>:<id>…]` ────────────────── */
|
||||
|
||||
@@ -192,7 +193,8 @@ export function GovShell({ surface }: { surface: Surface }) {
|
||||
<kbd>⌘K</kbd>
|
||||
</label>
|
||||
<div className="g-top-r">
|
||||
{surface === 'ops' && (
|
||||
{/* 告警钮暂隐(2026-08-28 用户定):计数逻辑(alerts)保留,SHOW_ALERTS 置回 true 即恢复 */}
|
||||
{SHOW_ALERTS && surface === 'ops' && (
|
||||
<button className={'g-tbtn' + (alerts ? ' alert' : '')} onClick={() => jump('tasks', 'todo')}>
|
||||
<Ico name="alert-triangle" s={13} />
|
||||
告警<b>{alerts}</b>
|
||||
@@ -292,7 +294,26 @@ export function GovShell({ surface }: { surface: Surface }) {
|
||||
);
|
||||
}
|
||||
|
||||
/** 运营中心四区先摆建设中(2026-08-28 用户定:驾驶舱 / 工作流 / 台账 / 系统)——
|
||||
真面板组件与其下钻路由原样留着,只换区首页;要做回来,把区从这个集合摘掉即可 */
|
||||
const WIP_ZONES: ReadonlySet<GovZone> = new Set(['cockpit', 'runs', 'ledgers', 'system']);
|
||||
|
||||
/** 顶栏告警钮开关(2026-08-28 用户定:暂时隐藏)—— 置回 true 即恢复 */
|
||||
const SHOW_ALERTS: boolean = false;
|
||||
|
||||
/** 建设中占位:如实说还没有 —— 不摆假数据、不摆点不动的钮(与知识库「待建」页同一条规矩) */
|
||||
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 }) {
|
||||
/* 建设中的区先拦在这里(2026-08-28 用户定):真面板组件仍在下方映射里,摘掉集合即恢复 */
|
||||
if (WIP_ZONES.has(zone)) return <WipHome zone={zone} />;
|
||||
const P = {
|
||||
// 设计中心
|
||||
blueprint: BlueprintHome,
|
||||
|
||||
@@ -227,7 +227,7 @@ const INSTANCE_PAGE_SIZE = 8;
|
||||
type InstanceFilterKey = 'id' | 'owner' | 'state' | 'time';
|
||||
|
||||
const INSTANCE_FILTER_LABEL: Readonly<Record<InstanceFilterKey, string>> = {
|
||||
id: '实例',
|
||||
id: '活动号',
|
||||
owner: '负责人',
|
||||
state: '状态',
|
||||
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 ?? '网络错误,请稍后重试';
|
||||
|
||||
/** 气泡式二次确认 —— 锚在页头右下的小泡泡:一句话问清,确认/取消两钮;点气泡外的透明罩即取消;
|
||||
`inrow` = 锚在行内操作格(终止实例用,接口文档 §10),定位由 CSS 的 .inrow 变体给。
|
||||
`inrow` = 锚在行内操作格的变体(行内终止撤掉后暂无处用,2026-08-28;变体与 CSS 保留,要用直接传);
|
||||
2026-08-28 反馈闭环(用户报:终止点了不知道成没成):确认后气泡**不再立即消失** ——
|
||||
在途时留场转忙碌(两钮禁用、罩不可点),成功由调用方 onSuccess 关掉,失败把原因留在气泡里可重试 */
|
||||
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>
|
||||
))}
|
||||
</select>
|
||||
<small>这次启动的实例归他 —— 负责人验收、数据工单都落到责任人身上;默认是你自己。</small>
|
||||
<small>这次创建的活动归他 —— 负责人验收、数据工单都落到责任人身上;默认是你自己。</small>
|
||||
{fieldErr['ownerId'] !== undefined && <small className="warn">{fieldErr['ownerId']}</small>}
|
||||
</label>
|
||||
</section>
|
||||
@@ -577,8 +577,12 @@ function LaunchDrawer({ activityId, name, onCancel, onLaunch }: { activityId: st
|
||||
* · 启动活动(即创建实例)—— 弹参数抽屉(LaunchDrawer),参数表按活动下发;已禁用不能启动。
|
||||
*
|
||||
* 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 }) {
|
||||
/* 接口对接(2026-08-27,docs/API文档-活动模块.md):定义 §2.2 / 实例清单 §3(检索分页全在服务端)/ 写动作 §5、§8 */
|
||||
const actQ = useOpsActivity(id);
|
||||
@@ -590,9 +594,9 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
|
||||
const [fFrom, setFFrom] = useState('');
|
||||
const [fTo, setFTo] = useState('');
|
||||
const [page, setPage] = useState(0);
|
||||
/* 轻量二次确认(2026-08-26 用户定):头部动作(禁用/激活)走页头气泡;行内「终止活动」走行内气泡(接口文档 §10) */
|
||||
/* 轻量二次确认(2026-08-26 用户定):头部动作(禁用/激活)走页头气泡;
|
||||
行内「终止活动」与它的气泡已撤(2026-08-28 用户定:终止只留在 L3) */
|
||||
const [ask, setAsk] = useState<PopAskSpec | null>(null);
|
||||
const [rowAsk, setRowAsk] = useState<{ rid: string; no: string } | null>(null);
|
||||
/* 启动抽屉(2026-08-27 用户定) */
|
||||
const [launch, setLaunch] = useState(false);
|
||||
|
||||
@@ -632,7 +636,6 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
|
||||
const qc = useQueryClient();
|
||||
const disable = useGovAction(() => call(disableOpsActivityRoute, { 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 用户报:点了没反馈);同一时刻只有一枚气泡,二选一即可 */
|
||||
const headBusy = disable.isPending || activate.isPending;
|
||||
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>
|
||||
{/* 绑定的 LLM 上头版(2026-08-25 用户定):每个活动一个专属 agent */}
|
||||
<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>
|
||||
</>
|
||||
}
|
||||
@@ -703,8 +706,8 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
|
||||
});
|
||||
},
|
||||
},
|
||||
/* 启动活动(2026-08-27 接):弹参数抽屉填参启动;已禁用不能启动 —— 先激活,再启动 */
|
||||
{ label: '启动活动', ico: 'play', primary: st === 'active', disabled: st !== 'active', onClick: () => setLaunch(true) },
|
||||
/* 创建活动(2026-08-28 用户改文案,原「启动活动」):弹参数抽屉填参创建实例;已禁用不能创建 —— 先激活 */
|
||||
{ label: '创建活动', ico: 'play', primary: st === 'active', disabled: st !== 'active', onClick: () => setLaunch(true) },
|
||||
/* 版本迭代入口先隐藏(2026-08-25 用户定):功能设计回头讨论,要做时把这行加回 acts
|
||||
{ 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} />}
|
||||
</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 用户定:检索放右侧)——
|
||||
左:总数 + 五态灰字合计(状态色只进 6px 小圆点);右:条件下拉 + 随条件换控件;
|
||||
统计数是服务端给的全局 stats,不随检索条件动(2026-08-27 接接口) */}
|
||||
@@ -751,7 +755,7 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
|
||||
<input
|
||||
className="g-iselect-input"
|
||||
value={fText}
|
||||
placeholder="按实例号模糊搜,如 INS-2417"
|
||||
placeholder="按活动号模糊搜,如 INS-2417"
|
||||
onChange={(e) => {
|
||||
setFText(e.target.value);
|
||||
setPage(0);
|
||||
@@ -835,9 +839,10 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
|
||||
2026-08-27 接接口:摆的是 no(人读实例号),寻址(L3/终止)用 id(任务 uuid) */}
|
||||
<QueryGate query={instQ}>
|
||||
<div className="g-itab">
|
||||
{/* 列序(2026-08-26 用户定):实例 / 负责人 / 输入 / 输出 / 启动时间 / 状态 / 操作(操作栏也有标题) */}
|
||||
{/* 列序(2026-08-26 用户定):实例 / 负责人 / 输入 / 输出 / 启动时间 / 状态 / 操作(操作栏也有标题);
|
||||
表头第一列定名「活动号」(2026-08-28 用户定) */}
|
||||
<div className="g-itab-h">
|
||||
<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]}
|
||||
</span>
|
||||
<span className="a">
|
||||
{r.state === 'running' && (
|
||||
<button
|
||||
type="button"
|
||||
className="g-rowbtn danger"
|
||||
onClick={() => {
|
||||
terminate.reset();
|
||||
setRowAsk({ rid: r.id, no: r.no });
|
||||
}}
|
||||
>
|
||||
终止活动
|
||||
</button>
|
||||
)}
|
||||
{/* L3 实例详情(2026-08-26 接):路由 id 带活动与实例两段,详情页按活动找回上下文 */}
|
||||
{/* 行内「终止活动」撤掉(2026-08-28 用户定):终止只留在 L3 详情页头部,清单行只剩查看 */}
|
||||
<button type="button" className="g-rowbtn" onClick={() => nav.push({ k: 'acti', id: `${a.id}:${r.id}` })}>
|
||||
查看详情
|
||||
</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>
|
||||
</div>
|
||||
))}
|
||||
@@ -901,7 +878,7 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
|
||||
{pageRows.length === 0 && (
|
||||
<div className="g-irow-empty">
|
||||
<Ico name="search" s={14} />
|
||||
没有匹配的实例 —— 换个条件,或清空重看全部。
|
||||
没有匹配的活动 —— 换个条件,或清空重看全部。
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -923,9 +900,10 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
|
||||
{/*
|
||||
* 沙箱(2026-08-25 用户定;2026-08-27 接接口,文档 §2.3):加载服务器返回的活动视图 HTML。
|
||||
* 隔离: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 · 隔离渲染">
|
||||
<SandboxFrame activityId={a.id} viewUrl={a.view.url} name={a.name} />
|
||||
</Sec>
|
||||
@@ -1104,7 +1082,7 @@ export function ActiPage({ id }: { id: string }) {
|
||||
<Chip ico="cpu">{d.llm}</Chip>
|
||||
</>
|
||||
}
|
||||
desc="一次运行的完整视图:输入 / 过程 / 输出 / 状态 —— 实例不可编辑、不可删除,进行中的运行可终止。"
|
||||
desc="一次运行的完整视图:输入 / 过程 / 输出 / 状态 —— 活动不可编辑、不可删除,进行中的运行可终止。"
|
||||
acts={[
|
||||
/* 唯一动作(2026-08-26 用户定:实例不许删、不许编):进行中可终止,轻气泡二次确认 */
|
||||
...(state === 'running'
|
||||
@@ -1117,7 +1095,7 @@ export function ActiPage({ id }: { id: string }) {
|
||||
onClick: () => {
|
||||
terminate.reset();
|
||||
setAsk({
|
||||
title: `确认终止实例 ${d.no}?`,
|
||||
title: `确认终止活动 ${d.no}?`,
|
||||
okLabel: '确认终止',
|
||||
danger: true,
|
||||
onOk: () => terminate.mutate(undefined, { onSuccess: () => setAsk(null) }),
|
||||
|
||||
Reference in New Issue
Block a user