Archived
活动模块二次确认瘦身成轻气泡(2026-08-26 用户定):只问一句+确认/取消,不摆影响三必答;本地 PopAsk 组件替代共享 AskBand,L2 禁用/激活与 L3 终止同用
This commit is contained in:
@@ -283,6 +283,36 @@ function inTimeRange(at: string, from: string, to: string): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 轻量二次确认的规格(2026-08-26 用户定:只问一句 + 确认/取消 —— 影响三必答(AskBand)是治理台重度动作的规格,禁用活动/终止实例是轻动作,不摆) */
|
||||
type PopAskSpec = { title: string; okLabel: string; danger?: boolean; onOk: () => void };
|
||||
|
||||
/** 气泡式二次确认 —— 锚在页头右下的小泡泡:一句话问清,确认/取消两钮;点气泡外的透明罩即取消 */
|
||||
function PopAsk({ spec, onCancel }: { spec: PopAskSpec; onCancel: () => void }) {
|
||||
return (
|
||||
<>
|
||||
<div className="g-popask-veil" onClick={onCancel} />
|
||||
<div className={'g-popask' + (spec.danger === true ? ' t-danger' : '')} role="alertdialog" aria-label={spec.title}>
|
||||
<p>{spec.title}</p>
|
||||
<div className="g-popask-a">
|
||||
<button type="button" className="g-rowbtn" onClick={onCancel}>
|
||||
取消
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
className={'g-rowbtn' + (spec.danger === true ? ' danger' : '')}
|
||||
onClick={() => {
|
||||
spec.onOk();
|
||||
onCancel();
|
||||
}}
|
||||
>
|
||||
{spec.okLabel}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* L2 · 活动详情 —— 两个问题一屏答:「这个活动是什么」(定义,只读)
|
||||
* 与「它跑得怎么样」(小统计 + 实例清单,点行进 L3)。
|
||||
@@ -309,6 +339,8 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
|
||||
const [fState, setFState] = useState<InstanceState | ''>('');
|
||||
const [fFrom, setFFrom] = useState('');
|
||||
const [fTo, setFTo] = useState('');
|
||||
/* 轻量二次确认(2026-08-26 用户定):一句话 + 确认/取消的气泡,不走 AskBand 三必答 */
|
||||
const [ask, setAsk] = useState<PopAskSpec | null>(null);
|
||||
if (a === undefined) return <Note tone="warn">活动不存在 —— 它可能已被调整,返回活动清单看看。</Note>;
|
||||
const st = status ?? a.status;
|
||||
const instances = rows ?? demoInstances(a.owner);
|
||||
@@ -354,64 +386,66 @@ export function ActvPage({ id, nav }: { id: string; nav: Nav }) {
|
||||
|
||||
return (
|
||||
<div className={'g-actvd' + (a.hue === 'blue' ? '' : ` h-${a.hue}`)}>
|
||||
<PageHead
|
||||
ico={a.ico}
|
||||
kicker={`${a.biz} · ${a.id}`}
|
||||
title={a.name}
|
||||
chips={
|
||||
<>
|
||||
<Chip tone={st === 'active' ? 'ok' : 'mute'}>{st === 'active' ? '激活中' : '已禁用'}</Chip>
|
||||
{a.version !== undefined && <Chip ico="tag">v{a.version}</Chip>}
|
||||
{/* 绑定的 LLM 上头版(2026-08-25 用户定):每个活动一个专属 agent */}
|
||||
<Chip ico="cpu">{a.llm}</Chip>
|
||||
<Chip ico="user">{a.owner}</Chip>
|
||||
<Chip ico="calendar-days">创建于 {a.createdAt}</Chip>
|
||||
</>
|
||||
}
|
||||
desc="活动定义只读 —— 输入与输出在创建时定死,演进走版本迭代;验收在工作台,这里只看状态。"
|
||||
acts={[
|
||||
st === 'active'
|
||||
? {
|
||||
label: '禁用活动',
|
||||
ico: 'power',
|
||||
danger: true,
|
||||
ask: {
|
||||
title: '确认禁用这个活动?',
|
||||
note: '禁用后不能再为它创建实例;已在跑的实例不受影响。',
|
||||
impact: { who: '该活动及其后续实例创建', undo: '可恢复 —— 随时重新激活', when: '确认后立即生效(本期前端演示,接口对接后落库)' },
|
||||
okLabel: '确认禁用',
|
||||
onOk: () => {
|
||||
/* 规则(2026-08-25 用户定):有实例时接口会提示无法禁用 —— 本期前端先演出这个拦截 */
|
||||
if (a.instances > 0) {
|
||||
setBlocked(true);
|
||||
} else {
|
||||
setStatus('disabled');
|
||||
setBlocked(false);
|
||||
}
|
||||
},
|
||||
<div className="g-pahead">
|
||||
<PageHead
|
||||
ico={a.ico}
|
||||
kicker={`${a.biz} · ${a.id}`}
|
||||
title={a.name}
|
||||
chips={
|
||||
<>
|
||||
<Chip tone={st === 'active' ? 'ok' : 'mute'}>{st === 'active' ? '激活中' : '已禁用'}</Chip>
|
||||
{a.version !== undefined && <Chip ico="tag">v{a.version}</Chip>}
|
||||
{/* 绑定的 LLM 上头版(2026-08-25 用户定):每个活动一个专属 agent */}
|
||||
<Chip ico="cpu">{a.llm}</Chip>
|
||||
<Chip ico="user">{a.owner}</Chip>
|
||||
<Chip ico="calendar-days">创建于 {a.createdAt}</Chip>
|
||||
</>
|
||||
}
|
||||
desc="活动定义只读 —— 输入与输出在创建时定死,演进走版本迭代;验收在工作台,这里只看状态。"
|
||||
acts={[
|
||||
st === 'active'
|
||||
? {
|
||||
label: '禁用活动',
|
||||
ico: 'power',
|
||||
danger: true,
|
||||
onClick: () =>
|
||||
setAsk({
|
||||
title: '确认禁用这个活动?',
|
||||
okLabel: '确认禁用',
|
||||
danger: true,
|
||||
onOk: () => {
|
||||
/* 规则(2026-08-25 用户定):有实例时接口会提示无法禁用 —— 本期前端先演出这个拦截 */
|
||||
if (a.instances > 0) {
|
||||
setBlocked(true);
|
||||
} else {
|
||||
setStatus('disabled');
|
||||
setBlocked(false);
|
||||
}
|
||||
},
|
||||
}),
|
||||
}
|
||||
: {
|
||||
label: '激活活动',
|
||||
ico: 'zap',
|
||||
primary: true,
|
||||
onClick: () =>
|
||||
setAsk({
|
||||
title: '确认激活这个活动?',
|
||||
okLabel: '确认激活',
|
||||
onOk: () => {
|
||||
setStatus('active');
|
||||
setBlocked(false);
|
||||
},
|
||||
}),
|
||||
},
|
||||
}
|
||||
: {
|
||||
label: '激活活动',
|
||||
ico: 'zap',
|
||||
primary: true,
|
||||
ask: {
|
||||
title: '确认激活这个活动?',
|
||||
note: '激活后可以为它创建实例。',
|
||||
impact: { who: '该活动及其后续实例创建', undo: '可逆 —— 随时可以再次禁用', when: '确认后立即生效(本期前端演示,接口对接后落库)' },
|
||||
okLabel: '确认激活',
|
||||
onOk: () => {
|
||||
setStatus('active');
|
||||
setBlocked(false);
|
||||
},
|
||||
},
|
||||
},
|
||||
{ label: '启动活动', ico: 'plus', disabled: true },
|
||||
/* 版本迭代入口先隐藏(2026-08-25 用户定):功能设计回头讨论,要做时把这行加回 acts
|
||||
{ label: '版本迭代', ico: 'git-branch', disabled: true },
|
||||
*/
|
||||
]}
|
||||
/>
|
||||
{ label: '启动活动', ico: 'plus', disabled: true },
|
||||
/* 版本迭代入口先隐藏(2026-08-25 用户定):功能设计回头讨论,要做时把这行加回 acts
|
||||
{ label: '版本迭代', ico: 'git-branch', disabled: true },
|
||||
*/
|
||||
]}
|
||||
/>
|
||||
{ask !== null && <PopAsk spec={ask} onCancel={() => setAsk(null)} />}
|
||||
</div>
|
||||
{blocked && (
|
||||
<Note tone="warn">
|
||||
无法禁用:该活动还有 {a.instances} 个实例 —— 这条提示将来由接口返回(有实例时接口拒绝禁用),本期为前端占位演示。
|
||||
@@ -735,6 +769,8 @@ export function ActiPage({ id }: { id: string }) {
|
||||
/* 状态就地翻转(本期前端演示):终止暂落「失败」(归态接口语义未定),对接后走 mutation 由服务端重投影;
|
||||
term 记下终止时刻 —— 终止的过程面与「自己跑挂」不同(当前步记「已终止」,不是「附件缺失」那种失败原因) */
|
||||
const [term, setTerm] = useState<string | undefined>(undefined);
|
||||
/* 轻量二次确认(2026-08-26 用户定):一句话 + 确认/取消的气泡 */
|
||||
const [ask, setAsk] = useState<PopAskSpec | null>(null);
|
||||
if (a === undefined || inst === undefined) {
|
||||
return <Note tone="warn">实例不存在 —— 它不在该活动的清单里,从面包屑返回活动详情看看。</Note>;
|
||||
}
|
||||
@@ -754,7 +790,8 @@ export function ActiPage({ id }: { id: string }) {
|
||||
|
||||
return (
|
||||
<div className={'g-actid' + (a.hue === 'blue' ? '' : ` h-${a.hue}`)}>
|
||||
<PageHead
|
||||
<div className="g-pahead">
|
||||
<PageHead
|
||||
ico={a.ico}
|
||||
tone={INSTANCE_ST[state]}
|
||||
kicker={`${a.name} · 一次运行`}
|
||||
@@ -774,25 +811,27 @@ export function ActiPage({ id }: { id: string }) {
|
||||
}
|
||||
desc="一次运行的完整视图:输入 / 过程 / 输出 / 状态 —— 实例不可编辑、不可删除,进行中的运行可终止。"
|
||||
acts={[
|
||||
/* 唯一动作(2026-08-26 用户定:实例不许删、不许编):进行中可终止,二次确认 */
|
||||
/* 唯一动作(2026-08-26 用户定:实例不许删、不许编):进行中可终止,轻气泡二次确认 */
|
||||
...(state === '进行中'
|
||||
? [
|
||||
{
|
||||
label: '终止活动',
|
||||
ico: 'power',
|
||||
danger: true,
|
||||
ask: {
|
||||
title: `确认终止实例 ${inst.id}?`,
|
||||
note: '进行中的步骤就地停下,不再产生后续动作;已经走完的步骤留痕保留。',
|
||||
impact: { who: '该实例当前与后续的全部动作', undo: '不可恢复 —— 只能重新启动一次新运行', when: '确认后立即生效(本期前端演示,接口对接后落库)' },
|
||||
okLabel: '确认终止',
|
||||
onOk: () => setTerm(fmtMinute(Date.now())),
|
||||
},
|
||||
onClick: () =>
|
||||
setAsk({
|
||||
title: `确认终止实例 ${inst.id}?`,
|
||||
okLabel: '确认终止',
|
||||
danger: true,
|
||||
onOk: () => setTerm(fmtMinute(Date.now())),
|
||||
}),
|
||||
} satisfies ActSpec,
|
||||
]
|
||||
: []),
|
||||
]}
|
||||
/>
|
||||
/>
|
||||
{ask !== null && <PopAsk spec={ask} onCancel={() => setAsk(null)} />}
|
||||
</div>
|
||||
{reason !== undefined && <Note tone="warn">{reason}</Note>}
|
||||
{state === '待执行' && <Note>待执行:已排入队列,等待调度 —— 到点或由负责人手动起步。</Note>}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user