Skip to content

PageSpec 协议规范

状态:Current -- 本文是 PageSpec/表单展示/selector 等 wire 契约的唯一文档出处。权威实现是 internal/dashboard/spec(Go DTO)与 web/src/types/dashboard.ts(前端共享类型),两侧逐项对应;本文与实现不一致时以代码为准并修正本文。

两种不同的 schema

协议所属层用途是否来自注册
JSON SchemaFunctionContract输入、输出、校验和字段候选
PageSpec页面编排页面类型、列表、详情、动作、任务、报表、导航和 binding

JSON Schema 不被当作页面布局树。PageSpec 也不复用 JSON Schema 的组件扩展字段。

FormPresentationSpec

表单展示由以下稳定结构描述(对应 spec/form_presentation.go):

ts
interface FormPresentationSpec {
  jsonSchema: JSONSchema; // 表单校验的事实源
  layout?: "vertical" | "horizontal" | "inline" | "grid";
  groups?: FormGroupSpec[]; // 字段分组(key/title/fields/collapsible)
  fields?: FormFieldSpec[]; // 逐字段展示覆盖
  submitButton?: FormButtonSpec;
  cancelButton?: FormButtonSpec;
}

interface FormFieldSpec {
  key: string; // JSON Schema 字段路径,如 "name"、"address.city"
  label?: LocalizedText; // 覆盖 schema title
  description?: LocalizedText; // 帮助文本
  placeholder?: LocalizedText;
  widget?: FormWidget; // 受控 antd 组件名枚举
  width?: number; // grid 栅格 1-12
  order?: number; // 排序,小者在前
  visible?: boolean;
  visibleWhen?: ConditionSpec; // 只读 form/page state
  disabled?: boolean;
  required?: boolean; // 覆盖 schema required
  defaultValue?: JSONValue;
  enumOptions?: EnumOption[]; // select/radio 选项
  widgetProps?: Record<string, JSONValue>;
  validationRules?: ValidationRule[];
}

widget 是受控枚举,取值为 antd/ProComponents 组件名(PascalCase):InputTextAreaInputNumberPasswordSelectMultiSelectRadioCheckboxSwitchDatePickerTimePickerDateRangeUploadImageUploadFileUploadRichTextCodeCascaderTreeSelectColorSliderRateJSONKeyValueArrayObject。扩展 widget 需要修改 spec 包并同步前端类型,不允许前端私加。

可见性条件是受限表达式,不能读 row/详情数据或调用函数:

ts
interface ConditionSpec {
  kind: "equals" | "notEquals" | "exists" | "all" | "any";
  path?: string; // 当前表单或 page state 的指针
  value?: JSONValue; // equals/notEquals 的比较值
  conditions?: ConditionSpec[]; // all/any 的子条件
}

Server 从 input JSON Schema 生成默认 FormPresentationSpec;管理员只能在 Page Studio 改展示信息,不能改变 FunctionContract payload。前端渲染器固定使用 @rjsf/antd + @rjsf/validator-ajv8 校验 payload。rjsf uiSchema 只能作为 adapter 的内存派生结果,不得成为 SDK/OpenAPI 注册字段或持久页面协议。

历史表单展示数据不提供转换或导入路径;只能在删除旧路径前导出、备份,并由管理员在 Page Studio 按当前 FormPresentationSpec 人工重建。旧数据不得进入新页面发布流程。

PageSpec 视图节点

PageSpec 使用业务节点而非组件名。节点集合固定为:

text
ListViewSpec | DetailViewSpec | ActionSpec | ConfirmActionSpec
| TaskViewSpec | ResultViewSpec | FormPresentationSpec
| DatasetSpec | ChartSpec

各页面类型的编排见 Dashboard Resource/Page 模型。每个节点有版本化、强类型字段和服务端校验器。页面 renderer 根据节点类型选择 ProComponents;PageSpec 不得出现具体 React 组件名或任意组件 props。

数据引用和 mapping(Selector AST)

输入输出 mapping 必须是可校验的 AST(对应 spec/selector_ast.go):

ts
type ValueSource =
  | { kind: "form"; path: JsonPointer }
  | { kind: "row"; path: JsonPointer }
  | { kind: "selection"; path: JsonPointer; transform?: { type: "pick" } }
  | { kind: "detail"; path: JsonPointer }
  | { kind: "page_state"; key: string; path?: JsonPointer }
  | { kind: "literal"; value: JSONValue };

interface InputAssignment {
  target: JsonPointer;
  source: ValueSource;
}

interface OutputAssignment {
  stateKey: string;
  source: JsonPointer;
  shape: "scalar" | "object" | "collection" | "task" | "dataset";
}

transform 是白名单受控变换,当前仅 pick(selection 提取行 identity 数组);新增变换必须扩展 spec 包并同步校验器。

校验器必须确认:

  • target 存在于绑定函数的 input JSON Schema。
  • source 只引用已定义的表单、行、选择、详情或页面状态。
  • source/target 类型可赋值;不允许裸整行对象自动传给函数。
  • ListView 的 collection、分页和 identity 引用可被 output JSON Schema 或 CapabilitySemantics 验证。
  • Report 的 dataset、指标和维度引用可验证。

禁止保存无结构 map[string]any、任意 JSONPath 字符串或组件级临时 mapping 覆盖。

PageBinding

ts
interface PageFunctionBinding {
  id: string;
  functionId: string;
  usage:
    | "query"
    | "detail"
    | "action"
    | "task"
    | "task_status"
    | "task_events"
    | "task_result"
    | "task_cancel"
    | "task_retry"
    | "report";
  selectors?: {
    input: SelectorAST; // InputAssignment 集
    output: OutputAssignment[];
  };
  execution: {
    mode: "sync" | "task";
    requireConfirm?: boolean;
  };
}

task 生命周期能力拆分为独立 usage(task_status/task_events/task_result/task_cancel/task_retry),由 TaskSemantic 的 start/status/events/result/cancel 函数一一映射;不存在 create/update/delete usage——写操作统一为 action,由 ResourcePage 的 CreateForm/UpdateForm/DeleteAction 节点引用。确认行为由 execution.requireConfirm 表达,没有独立的 ConfirmationSpec。

Schema 节点和页面动作只能引用 bindingId。运行时由服务端根据 active PublishedPageSpec 找到 functionId、权限、风险、scope 和 dispatch target;浏览器无权选择这些信息。

导航与多语言

分类、标题、图标与排序是 PageSpec 顶层字段(category{key,labels,order}titleiconorder)。NavigationSpec 仅承载返回导航行为:

ts
interface NavigationSpec {
  title?: LocalizedText;
  breadcrumb?: LocalizedText[];
  showBack?: boolean;
  backPath?: string;
}

PageProposal 根据 resource/page key 提供默认值;PageDraft 保存最终值;PublishedPageSpec 是 Console 动态菜单的唯一来源。静态 locale 与字典都不得成为动态页面事实源。ConditionSpec 只读取当前表单或 page state,禁止通过可见性条件访问 row、详情、外部函数或任意 JSONPath,以保证保存、发布和运行时具有一致语义。

ABI 与版本

rendererSchemaVersion(当前 page-spec:1)、generatorVersion(当前 page-generator:1)和各 snapshot digest 必须单独保存。发布校验必须拒绝未知版本、未知节点、未知字段和非法 mapping。页面运行时不得尝试降级、猜测或转换成其他 layout/schema。

安全边界

  • PageSpec 不存储 route、target service、scope 覆盖、Secret 或任意 HTTP 参数。
  • 未知 JSON 只能存在于 JSON Schema 的标准扩展边界;核心 DTO 使用明确类型和 JSONValue,禁止 any
  • 表单展示变化不允许静默改变已发布页面;发布快照必须包含 FormPresentationSpec。