Archived
三面拆分⑦⑧:触发器一分为二 —— 规则定义入设计·流程包,起单开关入运营·任务
定义面可改名/改目标包/改三类规则/改起单装配;编辑期的装配行走本地状态, 存盘才解析 —— 派生自已解析 mapping 的话,刚添加的空行会被当场滤掉,人打不出第一个字。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KeDBx2PHLavbURXB8LJJ7m
This commit is contained in:
@@ -41,7 +41,7 @@ import { packageToGraph } from '@web-shared/gov/flow/pkg-to-graph.ts';
|
|||||||
import { mergeGraphIntoPackage } from '@web-shared/gov/flow/graph-to-pkg.ts';
|
import { mergeGraphIntoPackage } from '@web-shared/gov/flow/graph-to-pkg.ts';
|
||||||
import type { FlowGraph, FlowNode } from '@web-shared/gov/flow/flow-layout.ts';
|
import type { FlowGraph, FlowNode } from '@web-shared/gov/flow/flow-layout.ts';
|
||||||
import type { SpPosition } from '@web-shared/gov/flow/support.tsx';
|
import type { SpPosition } from '@web-shared/gov/flow/support.tsx';
|
||||||
import { TriggersHome } from '@features/gov-trigger';
|
import { TriggerRulesHome } from '@features/gov-trigger';
|
||||||
|
|
||||||
export type HomeProps = { tab: string; nav: Nav };
|
export type HomeProps = { tab: string; nav: Nav };
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ function edgeCheckOf(pkg: ProcessPackage, report: ClosureReport | undefined) {
|
|||||||
|
|
||||||
export function WorkflowsHome({ tab, nav }: HomeProps) {
|
export function WorkflowsHome({ tab, nav }: HomeProps) {
|
||||||
// 调起规则 = 触发器的**定义面**:谁调起这条流,与流程包一处维护(启停在运营中心)
|
// 调起规则 = 触发器的**定义面**:谁调起这条流,与流程包一处维护(启停在运营中心)
|
||||||
if (tab === 'rules') return <TriggersHome tab={tab} nav={nav} />;
|
if (tab === 'rules') return <TriggerRulesHome nav={nav} />;
|
||||||
return <PackagesHome tab={tab} nav={nav} />;
|
return <PackagesHome tab={tab} nav={nav} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,12 +825,26 @@ function Orchestrator({
|
|||||||
title={t.name}
|
title={t.name}
|
||||||
sub={t.spec.kind === 'event' ? t.spec.rule : t.spec.kind === 'registration' ? TRIGGER_KIND_LABEL.registration : TRIGGER_KIND_LABEL.periodic}
|
sub={t.spec.kind === 'event' ? t.spec.rule : t.spec.kind === 'registration' ? TRIGGER_KIND_LABEL.registration : TRIGGER_KIND_LABEL.periodic}
|
||||||
cols={[{ k: '状态', v: t.enabled ? '已启用' : '已停用' }]}
|
cols={[{ k: '状态', v: t.enabled ? '已启用' : '已停用' }]}
|
||||||
|
right={
|
||||||
|
<button
|
||||||
|
className="g-btn"
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation();
|
||||||
|
nav.jump('workflows', 'rules');
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
改规则
|
||||||
|
</button>
|
||||||
|
}
|
||||||
onClick={() => nav.jump('workflows', 'rules', { k: 'trigger', id: t.id })}
|
onClick={() => nav.jump('workflows', 'rules', { k: 'trigger', id: t.id })}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</NList>
|
</NList>
|
||||||
) : (
|
) : (
|
||||||
<Note>暂无触发器绑定 —— 该版本只能由已绑定的旧版本或登记起单。</Note>
|
<Note>
|
||||||
|
还没有触发器指向这条流 —— 它定义得再完整,也没人调得起来。去
|
||||||
|
<b>调起规则</b>页签配一条(周期 / 事件 / 登记三选一)。
|
||||||
|
</Note>
|
||||||
)}
|
)}
|
||||||
</Sec>
|
</Sec>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { Ava, Card, Chip, Clear, Entry, Facts, Kpi, NList, NRow, Note, PageHead,
|
|||||||
import type { ActSpec } from '@web-shared/gov/primitives.tsx';
|
import type { ActSpec } from '@web-shared/gov/primitives.tsx';
|
||||||
import { APR_ST, PARA, RUNTIME_ST, TERMINAL_ST, WF_ST } from '@web-shared/gov/tone.ts';
|
import { APR_ST, PARA, RUNTIME_ST, TERMINAL_ST, WF_ST } from '@web-shared/gov/tone.ts';
|
||||||
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
||||||
|
import { TriggerOpsTab } from '@features/gov-trigger';
|
||||||
import { FlowFigure } from '@web-shared/gov/flow/FlowFigure.tsx';
|
import { FlowFigure } from '@web-shared/gov/flow/FlowFigure.tsx';
|
||||||
import {
|
import {
|
||||||
useGovApprovals,
|
useGovApprovals,
|
||||||
@@ -35,6 +36,12 @@ import {
|
|||||||
export type HomeProps = { tab: string; nav: Nav };
|
export type HomeProps = { tab: string; nav: Nav };
|
||||||
|
|
||||||
export function TasksHome({ tab, nav }: HomeProps) {
|
export function TasksHome({ tab, nav }: HomeProps) {
|
||||||
|
// 起单开关 = 触发器的**处置面**:今天开不开是当班主管的事,规则怎么写在设计中心改
|
||||||
|
if (tab === 'trg') return <TriggerOpsTab nav={nav} />;
|
||||||
|
return <RuntimeHome tab={tab} nav={nav} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
function RuntimeHome({ tab, nav }: HomeProps) {
|
||||||
const q = useGovTasks();
|
const q = useGovTasks();
|
||||||
const approvals = useGovApprovals();
|
const approvals = useGovApprovals();
|
||||||
const tasks = q.data?.tasks ?? [];
|
const tasks = q.data?.tasks ?? [];
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* feature `gov-trigger` 唯一出口(编码规范 §9.1)。
|
* feature `gov-trigger` 唯一出口(编码规范 §9.1)。
|
||||||
* 触发器配置(周期 / 事件 / 登记三类):与流程包平级,启停不改任何版本
|
* 触发器的两副面孔:规则定义在设计中心(流程包 - 调起规则),启停在运营中心(任务 - 起单开关)。
|
||||||
*/
|
*/
|
||||||
export { TriggersHome, TriggerPage } from './internal/panel.tsx';
|
export { TriggerOpsTab, TriggerPage, TriggerRulesHome } from './internal/panel.tsx';
|
||||||
|
|||||||
@@ -1,62 +1,49 @@
|
|||||||
/**
|
/**
|
||||||
* feature `gov-trigger` —— 触发器配置(周期 / 事件 / 登记三类):与流程包平级,启停不改任何版本
|
* feature `gov-trigger` —— 触发器的两副面孔,各归其台:
|
||||||
|
*
|
||||||
|
* `TriggerRulesHome`(设计中心 · 流程包 - 调起规则):**定义面** —— 谁按什么规则调起这条流,
|
||||||
|
* 起单载荷怎么装配。与流程包一处维护:「谁调起它」本就是流程定义的一部分。
|
||||||
|
* `TriggerOpsTab`(运营中心 · 任务 - 起单开关):**处置面** —— 今天开不开。
|
||||||
|
*
|
||||||
|
* 同一个对象两种动作各归其位:定义是设计者的事,启停是当班主管的事。
|
||||||
|
* 触发器与流程包**平级** —— 不入包结构、不随包版本,增删启停改定义都不动任何版本。
|
||||||
*
|
*
|
||||||
* 自 `~/coding/JCAI_PRD/governance/_components/Governance.tsx` 平移:
|
* 自 `~/coding/JCAI_PRD/governance/_components/Governance.tsx` 平移:
|
||||||
* DOM 结构、类名(`g-*` + 色调 `t-*`)与文案原样保留,mock 常量换成服务端投影(README 平移规则 1/2/4)。
|
* DOM 结构、类名(`g-*` + 色调 `t-*`)与文案原样保留,mock 常量换成服务端投影(README 平移规则 1/2/4)。
|
||||||
* 区首页只负责导航,治理动作一律落在下钻详情页的**就地确认带**(影响预览三必答)。
|
|
||||||
*/
|
*/
|
||||||
|
import { useState } from 'react';
|
||||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||||
|
import type { TriggerConfig } from '@contracts';
|
||||||
import {
|
import {
|
||||||
TRIGGER_KIND_LABEL,
|
TRIGGER_KIND_LABEL,
|
||||||
|
processPackageIdSchema,
|
||||||
toggleTriggerRoute,
|
toggleTriggerRoute,
|
||||||
triggerKindOf,
|
triggerKindOf,
|
||||||
|
upsertTriggerRoute,
|
||||||
} from '@contracts';
|
} from '@contracts';
|
||||||
import { call } from '@web-shared/typed-fetch.ts';
|
import { call } from '@web-shared/typed-fetch.ts';
|
||||||
import { Card, Chip, NList, NRow, Note, PageHead, QueryGate, Rows, St } from '@web-shared/gov/primitives.tsx';
|
|
||||||
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
|
||||||
import {
|
import {
|
||||||
useGovTriggers,
|
Acts,
|
||||||
} from '@web-shared/gov/gov-api.ts';
|
Card,
|
||||||
|
Chip,
|
||||||
|
Clear,
|
||||||
|
Field,
|
||||||
|
ListEdit,
|
||||||
|
NList,
|
||||||
|
NRow,
|
||||||
|
Note,
|
||||||
|
PageHead,
|
||||||
|
Prop,
|
||||||
|
QueryGate,
|
||||||
|
Rows,
|
||||||
|
Seg,
|
||||||
|
St,
|
||||||
|
} from '@web-shared/gov/primitives.tsx';
|
||||||
|
import type { Nav } from '@web-shared/gov/gov-route.ts';
|
||||||
|
import { useGovObjects, useGovPackages, useGovTriggers } from '@web-shared/gov/gov-api.ts';
|
||||||
|
|
||||||
export type HomeProps = { tab: string; nav: Nav };
|
export type HomeProps = { tab: string; nav: Nav };
|
||||||
|
|
||||||
export function TriggersHome({ nav }: HomeProps) {
|
|
||||||
const q = useGovTriggers();
|
|
||||||
const triggers = q.data?.triggers ?? [];
|
|
||||||
return (
|
|
||||||
<QueryGate query={q}>
|
|
||||||
<>
|
|
||||||
<Note>
|
|
||||||
触发器与流程包<b>平级</b>——不入包结构、不随包版本,增删启停不改任何版本。周期 / 事件 / 登记三类。
|
|
||||||
</Note>
|
|
||||||
<NList>
|
|
||||||
{triggers.map((t) => {
|
|
||||||
const kind = triggerKindOf(t.spec);
|
|
||||||
return (
|
|
||||||
<NRow
|
|
||||||
key={t.id}
|
|
||||||
tone={t.enabled ? 'brand' : 'mute'}
|
|
||||||
ico="zap"
|
|
||||||
title={t.name}
|
|
||||||
sub={`调起 ${t.processPackageId} · ${t.mapping.length} 条起单映射`}
|
|
||||||
cols={[{ k: '类别', v: <Chip tone="brand">{TRIGGER_KIND_LABEL[kind]}</Chip> }]}
|
|
||||||
right={
|
|
||||||
<St tone={t.enabled ? 'ok' : 'mute'} ico={t.enabled ? 'circle-check' : 'power'}>
|
|
||||||
{t.enabled ? '已启用' : '已停用'}
|
|
||||||
</St>
|
|
||||||
}
|
|
||||||
onClick={() => nav.push({ k: 'trigger', id: t.id })}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</NList>
|
|
||||||
</>
|
|
||||||
</QueryGate>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ---------------- ⑤ 活动库管理 ---------------- */
|
|
||||||
|
|
||||||
/** 治理写动作统一入口:成功即失效相关查询,画面由服务端重投影 */
|
/** 治理写动作统一入口:成功即失效相关查询,画面由服务端重投影 */
|
||||||
function useGovAction<TInput>(mutate: (input: TInput) => Promise<unknown>, keys: readonly string[]) {
|
function useGovAction<TInput>(mutate: (input: TInput) => Promise<unknown>, keys: readonly string[]) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
@@ -68,7 +55,376 @@ function useGovAction<TInput>(mutate: (input: TInput) => Promise<unknown>, keys:
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 工作流 ---------------- */
|
/** 规则原文:一行说清「什么时候起」 */
|
||||||
|
function ruleText(t: TriggerConfig): string {
|
||||||
|
switch (t.spec.kind) {
|
||||||
|
case 'periodic-calendar':
|
||||||
|
return `按日历 ${t.spec.calendar}`;
|
||||||
|
case 'periodic-data':
|
||||||
|
return `按 ${t.spec.domain}.${t.spec.object} 的 ${t.spec.dateField} ${t.spec.offset}${t.spec.filter === undefined ? '' : ` 且 ${t.spec.filter}`}`;
|
||||||
|
case 'event':
|
||||||
|
return `事件(${t.spec.eventType})命中:${t.spec.rule}`;
|
||||||
|
case 'registration':
|
||||||
|
return '人工登记即起单';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 设计侧:调起规则(定义面) ==================== */
|
||||||
|
|
||||||
|
const SPEC_KINDS = [
|
||||||
|
{ k: 'registration', label: '登记' },
|
||||||
|
{ k: 'event', label: '事件' },
|
||||||
|
{ k: 'periodic-calendar', label: '周期 · 日历' },
|
||||||
|
{ k: 'periodic-data', label: '周期 · 数据日历' },
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
export function TriggerRulesHome({ nav }: { nav: Nav }) {
|
||||||
|
const q = useGovTriggers();
|
||||||
|
const [editing, setEditing] = useState<string | undefined>(undefined);
|
||||||
|
const triggers = q.data?.triggers ?? [];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QueryGate query={q}>
|
||||||
|
<>
|
||||||
|
<Note>
|
||||||
|
调起规则决定<b>什么时候起一单</b>:周期 / 事件 / 登记三类。触发器与流程包平级——不入包结构、
|
||||||
|
不随包版本,改规则不动任何版本。<b>启停不在这里</b>,那是运营中心的事。
|
||||||
|
</Note>
|
||||||
|
{triggers.length === 0 ? (
|
||||||
|
<Clear>还没有调起规则 —— 配一条,这条流就能被自动或人工调起来。</Clear>
|
||||||
|
) : (
|
||||||
|
<NList>
|
||||||
|
{triggers.map((t) =>
|
||||||
|
editing === t.id ? (
|
||||||
|
<TriggerForm
|
||||||
|
key={t.id}
|
||||||
|
trigger={t}
|
||||||
|
onDone={() => setEditing(undefined)}
|
||||||
|
onCancel={() => setEditing(undefined)}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<NRow
|
||||||
|
key={t.id}
|
||||||
|
tone={t.enabled ? 'brand' : 'mute'}
|
||||||
|
ico="zap"
|
||||||
|
title={t.name}
|
||||||
|
sub={ruleText(t)}
|
||||||
|
cols={[
|
||||||
|
{ k: '类别', v: <Chip tone="brand">{TRIGGER_KIND_LABEL[triggerKindOf(t.spec)]}</Chip> },
|
||||||
|
{ k: '调起', v: t.processPackageId },
|
||||||
|
{ k: '装配', v: `${t.mapping.length} 条` },
|
||||||
|
]}
|
||||||
|
right={
|
||||||
|
<button
|
||||||
|
className="g-btn"
|
||||||
|
onClick={(e) => {
|
||||||
|
// 行本身也可点(进详情)—— 按钮要拦住冒泡,否则点「改规则」会跳走
|
||||||
|
e.stopPropagation();
|
||||||
|
setEditing(t.id);
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
改规则
|
||||||
|
</button>
|
||||||
|
}
|
||||||
|
onClick={() => nav.push({ k: 'trigger', id: t.id })}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)}
|
||||||
|
</NList>
|
||||||
|
)}
|
||||||
|
</>
|
||||||
|
</QueryGate>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 行内编辑表单 —— 改名 / 改目标包 / 改规则 / 改起单装配。
|
||||||
|
*
|
||||||
|
* 起单装配这里只编辑 `initiation` 一种来源(起单载荷 → 调用接口字段):
|
||||||
|
* 触发器只知道调用接口、不知流程内部,别的来源(上游输出口、工作流变量)是流程包边映射的事。
|
||||||
|
* 写成 `目标字段 = 载荷路径` 一行一条 —— 与边映射页的读法一致。
|
||||||
|
*/
|
||||||
|
function TriggerForm({
|
||||||
|
trigger,
|
||||||
|
onDone,
|
||||||
|
onCancel,
|
||||||
|
}: {
|
||||||
|
trigger: TriggerConfig;
|
||||||
|
onDone: () => void;
|
||||||
|
onCancel: () => void;
|
||||||
|
}) {
|
||||||
|
const packages = useGovPackages();
|
||||||
|
const objects = useGovObjects();
|
||||||
|
const [draft, setDraft] = useState<TriggerConfig>(trigger);
|
||||||
|
|
||||||
|
const save = useGovAction(
|
||||||
|
(next: TriggerConfig) => call(upsertTriggerRoute, { body: { trigger: next } }),
|
||||||
|
['triggers'],
|
||||||
|
);
|
||||||
|
|
||||||
|
const packageIds = [...new Set((packages.data?.packages ?? []).map((p) => p.id))];
|
||||||
|
const domains = [...new Set((objects.data?.objects ?? []).map((o) => o.domain))];
|
||||||
|
const objectsOfDomain = (domain: string) =>
|
||||||
|
(objects.data?.objects ?? []).filter((o) => o.domain === domain).map((o) => o.object);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 起单装配的行式写法:`目标字段 = 载荷路径`。
|
||||||
|
*
|
||||||
|
* 编辑期这些行是**本地状态**,不从 `draft.mapping` 派生 ——
|
||||||
|
* 派生的话,刚点「添加」的空行会被解析器当场滤掉,人永远打不出第一个字(实测)。
|
||||||
|
* 解析只在存盘那一刻做。非 initiation 来源不在此面编辑,原样带着走。
|
||||||
|
*/
|
||||||
|
const [lines, setLines] = useState<string[]>(() =>
|
||||||
|
trigger.mapping.flatMap((m) => (m.source.from === 'initiation' ? [`${m.target} = ${m.source.path}`] : [])),
|
||||||
|
);
|
||||||
|
const foreignMappings = trigger.mapping.filter((m) => m.source.from !== 'initiation');
|
||||||
|
|
||||||
|
/** 行 → 映射:写不成 `目标 = 路径` 的行直接丢掉,不塞半条进契约 */
|
||||||
|
const parseLines = (): TriggerConfig['mapping'] => [
|
||||||
|
...lines.flatMap((line) => {
|
||||||
|
const at = line.indexOf('=');
|
||||||
|
if (at <= 0) return [];
|
||||||
|
const target = line.slice(0, at).trim();
|
||||||
|
const path = line.slice(at + 1).trim();
|
||||||
|
if (target === '' || path === '') return [];
|
||||||
|
return [{ target, source: { from: 'initiation' as const, path } }];
|
||||||
|
}),
|
||||||
|
...foreignMappings,
|
||||||
|
];
|
||||||
|
|
||||||
|
const setSpecKind = (k: string) => {
|
||||||
|
if (k === draft.spec.kind) return;
|
||||||
|
const spec: TriggerConfig['spec'] =
|
||||||
|
k === 'event'
|
||||||
|
? { kind: 'event', rule: 'true', eventType: 'data' }
|
||||||
|
: k === 'periodic-calendar'
|
||||||
|
? { kind: 'periodic-calendar', calendar: '0 9 * * 1' }
|
||||||
|
: k === 'periodic-data'
|
||||||
|
? {
|
||||||
|
kind: 'periodic-data',
|
||||||
|
domain: domains[0] ?? 'shared',
|
||||||
|
object: objectsOfDomain(domains[0] ?? 'shared')[0] ?? 'record',
|
||||||
|
dateField: 'dueDate',
|
||||||
|
offset: '-P3D',
|
||||||
|
}
|
||||||
|
: { kind: 'registration' };
|
||||||
|
setDraft({ ...draft, spec });
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card ico="zap" title={`改规则:${trigger.name}`}>
|
||||||
|
<Prop ico="tag" title="身份与调起目标">
|
||||||
|
<Field k="名称" hint="人读的一句话,说明它什么时候起">
|
||||||
|
<input className="g-input" value={draft.name} onChange={(e) => setDraft({ ...draft, name: e.target.value })} />
|
||||||
|
</Field>
|
||||||
|
<Field k="调起哪条流" hint="只认调用接口,不知流程内部">
|
||||||
|
<select
|
||||||
|
className="g-input"
|
||||||
|
value={draft.processPackageId}
|
||||||
|
onChange={(e) => setDraft({ ...draft, processPackageId: processPackageIdSchema.parse(e.target.value) })}
|
||||||
|
>
|
||||||
|
{packageIds.map((id) => (
|
||||||
|
<option key={id} value={id}>
|
||||||
|
{id}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</Field>
|
||||||
|
</Prop>
|
||||||
|
|
||||||
|
<Prop ico="settings" title="起单时机">
|
||||||
|
<Field k="类别" hint="周期 / 事件 / 登记三类">
|
||||||
|
<Seg value={draft.spec.kind} options={SPEC_KINDS.map((s) => ({ k: s.k, label: s.label }))} onPick={setSpecKind} />
|
||||||
|
</Field>
|
||||||
|
{draft.spec.kind === 'registration' && (
|
||||||
|
<Note>登记型无参:人在工作台按调用接口的 schema 填表提交,即起一单。</Note>
|
||||||
|
)}
|
||||||
|
{draft.spec.kind === 'event' && (
|
||||||
|
<>
|
||||||
|
<Field k="事件类型">
|
||||||
|
<Seg
|
||||||
|
value={draft.spec.eventType}
|
||||||
|
options={[
|
||||||
|
{ k: 'data', label: '数据' },
|
||||||
|
{ k: 'time', label: '时间' },
|
||||||
|
{ k: 'human', label: '人' },
|
||||||
|
{ k: 'control', label: '控制' },
|
||||||
|
]}
|
||||||
|
onPick={(k) => {
|
||||||
|
if (draft.spec.kind !== 'event') return;
|
||||||
|
if (k !== 'data' && k !== 'time' && k !== 'human' && k !== 'control') return;
|
||||||
|
setDraft({ ...draft, spec: { ...draft.spec, eventType: k } });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field k="命中规则" hint="S-FEEL,对事件信封与载荷字段求值">
|
||||||
|
<input
|
||||||
|
className="g-input"
|
||||||
|
value={draft.spec.rule}
|
||||||
|
onChange={(e) =>
|
||||||
|
draft.spec.kind === 'event' && setDraft({ ...draft, spec: { ...draft.spec, rule: e.target.value } })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{draft.spec.kind === 'periodic-calendar' && (
|
||||||
|
<Field k="日历表达式" hint="cron 风格,如 0 9 * * 1 = 每周一 09:00">
|
||||||
|
<input
|
||||||
|
className="g-input"
|
||||||
|
value={draft.spec.calendar}
|
||||||
|
onChange={(e) =>
|
||||||
|
draft.spec.kind === 'periodic-calendar' &&
|
||||||
|
setDraft({ ...draft, spec: { ...draft.spec, calendar: e.target.value } })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
)}
|
||||||
|
{draft.spec.kind === 'periodic-data' && (
|
||||||
|
<>
|
||||||
|
<Field k="台账域 · 对象" hint="按这张表的日期字段逐行起单">
|
||||||
|
<select
|
||||||
|
className="g-input"
|
||||||
|
value={`${draft.spec.domain}.${draft.spec.object}`}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (draft.spec.kind !== 'periodic-data') return;
|
||||||
|
const [domain = '', object = ''] = e.target.value.split('.');
|
||||||
|
setDraft({ ...draft, spec: { ...draft.spec, domain, object } });
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{(objects.data?.objects ?? []).map((o) => (
|
||||||
|
<option key={`${o.domain}.${o.object}`} value={`${o.domain}.${o.object}`}>
|
||||||
|
{o.domain}.{o.object}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</Field>
|
||||||
|
<Field k="日期字段">
|
||||||
|
<input
|
||||||
|
className="g-input"
|
||||||
|
value={draft.spec.dateField}
|
||||||
|
onChange={(e) =>
|
||||||
|
draft.spec.kind === 'periodic-data' &&
|
||||||
|
setDraft({ ...draft, spec: { ...draft.spec, dateField: e.target.value } })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field k="偏移" hint="ISO 时段,如 -P3D = 日期字段前 3 天">
|
||||||
|
<input
|
||||||
|
className="g-input"
|
||||||
|
value={draft.spec.offset}
|
||||||
|
onChange={(e) =>
|
||||||
|
draft.spec.kind === 'periodic-data' &&
|
||||||
|
setDraft({ ...draft, spec: { ...draft.spec, offset: e.target.value } })
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
<Field k="过滤条件" hint="S-FEEL,留空即全表">
|
||||||
|
<input
|
||||||
|
className="g-input"
|
||||||
|
value={draft.spec.filter ?? ''}
|
||||||
|
onChange={(e) => {
|
||||||
|
if (draft.spec.kind !== 'periodic-data') return;
|
||||||
|
const filter = e.target.value.trim();
|
||||||
|
const { filter: _drop, ...rest } = draft.spec;
|
||||||
|
setDraft({ ...draft, spec: filter === '' ? rest : { ...rest, filter } });
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Field>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Prop>
|
||||||
|
|
||||||
|
<Prop ico="arrow-left-right" title="起单装配" extra={`${lines.length + foreignMappings.length} 条`}>
|
||||||
|
<Field k="起单载荷 → 调用接口字段" hint="一行一条,写作「目标字段 = 载荷路径」;缺必填即起单失败入死信">
|
||||||
|
<ListEdit items={lines} placeholder="customerCode = data.customerCode" onChange={setLines} />
|
||||||
|
</Field>
|
||||||
|
{foreignMappings.length > 0 && (
|
||||||
|
<Note>
|
||||||
|
另有 {foreignMappings.length} 条非起单来源的映射(上游输出口 / 工作流变量),
|
||||||
|
那是流程包边映射的事 —— 在流程包详情的边映射页维护,这里原样保留不动。
|
||||||
|
</Note>
|
||||||
|
)}
|
||||||
|
</Prop>
|
||||||
|
|
||||||
|
<Acts
|
||||||
|
align="end"
|
||||||
|
items={[
|
||||||
|
{ label: '取消', ico: 'x', onClick: onCancel },
|
||||||
|
{
|
||||||
|
label: save.isPending ? '保存中…' : '保存规则',
|
||||||
|
ico: 'circle-check',
|
||||||
|
primary: true,
|
||||||
|
disabled: save.isPending || draft.name.trim() === '',
|
||||||
|
ask: {
|
||||||
|
title: `保存调起规则:${draft.name}`,
|
||||||
|
note: <Note>触发器与流程包平级 —— 改规则不动任何流程包版本,已起的单照常跑完。</Note>,
|
||||||
|
impact: {
|
||||||
|
who: '此后命中新规则才起单;在途工作流不受影响',
|
||||||
|
undo: '可再改;要临时不起单,去运营中心停用',
|
||||||
|
when: '确认后即时生效',
|
||||||
|
},
|
||||||
|
okLabel: '确认保存',
|
||||||
|
onOk: () => save.mutate({ ...draft, mapping: parseLines() }, { onSuccess: onDone }),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 运营侧:起单开关(处置面) ==================== */
|
||||||
|
|
||||||
|
export function TriggerOpsTab({ nav }: { nav: Nav }) {
|
||||||
|
const q = useGovTriggers();
|
||||||
|
const triggers = q.data?.triggers ?? [];
|
||||||
|
const toggle = useGovAction(
|
||||||
|
(input: { id: string; enabled: boolean }) =>
|
||||||
|
call(toggleTriggerRoute, { params: { id: input.id }, body: { enabled: input.enabled } }),
|
||||||
|
['triggers'],
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<QueryGate query={q}>
|
||||||
|
<>
|
||||||
|
<Note>
|
||||||
|
开关管的是<b>今天起不起单</b>,不是规则本身。停用后此后不再起新单,<b>已起的照常跑完</b>;
|
||||||
|
规则怎么写在设计中心 · 流程包 - 调起规则里改。
|
||||||
|
</Note>
|
||||||
|
{triggers.length === 0 ? (
|
||||||
|
<Clear>还没有调起规则 —— 设计中心配好之后,开关会出现在这里。</Clear>
|
||||||
|
) : (
|
||||||
|
<NList>
|
||||||
|
{triggers.map((t) => (
|
||||||
|
<NRow
|
||||||
|
key={t.id}
|
||||||
|
tone={t.enabled ? 'brand' : 'mute'}
|
||||||
|
ico="zap"
|
||||||
|
title={t.name}
|
||||||
|
sub={ruleText(t)}
|
||||||
|
cols={[
|
||||||
|
{ k: '类别', v: <Chip tone="brand">{TRIGGER_KIND_LABEL[triggerKindOf(t.spec)]}</Chip> },
|
||||||
|
{ k: '调起', v: t.processPackageId },
|
||||||
|
]}
|
||||||
|
right={
|
||||||
|
<St tone={t.enabled ? 'ok' : 'mute'} ico={t.enabled ? 'circle-check' : 'power'}>
|
||||||
|
{t.enabled ? '已启用' : '已停用'}
|
||||||
|
</St>
|
||||||
|
}
|
||||||
|
onClick={() => nav.push({ k: 'trigger', id: t.id })}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</NList>
|
||||||
|
)}
|
||||||
|
<Note>逐条启停在触发器详情页做 —— 开关是治理动作,要走影响确认带,不做列表上的顺手一点。</Note>
|
||||||
|
{toggle.isError && <Note tone="warn">切换失败,请重试。</Note>}
|
||||||
|
</>
|
||||||
|
</QueryGate>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ==================== 触发器详情(两面共用) ==================== */
|
||||||
|
|
||||||
export function TriggerPage({ id }: { id: string }) {
|
export function TriggerPage({ id }: { id: string }) {
|
||||||
const q = useGovTriggers();
|
const q = useGovTriggers();
|
||||||
@@ -91,7 +447,7 @@ export function TriggerPage({ id }: { id: string }) {
|
|||||||
<Chip ico="boxes">调起 {t.processPackageId}</Chip>
|
<Chip ico="boxes">调起 {t.processPackageId}</Chip>
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
desc="触发器与流程包平级——不入包结构、不随包版本,增删启停不改任何版本。"
|
desc="触发器与流程包平级——不入包结构、不随包版本,增删启停不改任何版本。规则在设计中心改,开关在运营中心拨。"
|
||||||
acts={[
|
acts={[
|
||||||
{
|
{
|
||||||
label: t.enabled ? '停用' : '启用',
|
label: t.enabled ? '停用' : '启用',
|
||||||
@@ -115,6 +471,7 @@ export function TriggerPage({ id }: { id: string }) {
|
|||||||
<Rows
|
<Rows
|
||||||
items={[
|
items={[
|
||||||
{ k: '类别', v: TRIGGER_KIND_LABEL[triggerKindOf(t.spec)] },
|
{ k: '类别', v: TRIGGER_KIND_LABEL[triggerKindOf(t.spec)] },
|
||||||
|
{ k: '何时起', v: ruleText(t) },
|
||||||
{ k: '规格', v: JSON.stringify(t.spec), mono: true },
|
{ k: '规格', v: JSON.stringify(t.spec), mono: true },
|
||||||
{ k: '流程包', v: t.processPackageId, mono: true },
|
{ k: '流程包', v: t.processPackageId, mono: true },
|
||||||
]}
|
]}
|
||||||
@@ -133,6 +490,3 @@ export function TriggerPage({ id }: { id: string }) {
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ---------------- 活动 / 工具 ---------------- */
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user