import { useEffect, useMemo, useState } from 'react' import { Card, Row, Col, Form, Button, Table, Space, Tag, Typography, Alert, message, Divider, Empty, } from 'antd' import { PlusOutlined, DeleteOutlined } from '@ant-design/icons' import { api } from '../api' import type { DragComponent, OptionItem, SceneSchema } from '../types' import { FieldControl } from './FieldControl' const { Text } = Typography type Role = 'master' | 'single' | 'table' function roleOf(container: DragComponent): Role { const fb = container.fieldBind || ({} as any) if (fb.bindSourceType === 'aggregate_root') return 'master' if (container.compType === 'table') return 'table' return 'single' } function collectOptionSources(comps: DragComponent[], acc: Set) { for (const c of comps) { const src = c.fieldBind?.optionsSource if (src) acc.add(src.aggregateId) if (c.childComponents) collectOptionSources(c.childComponents, acc) } } /** * 场景通用渲染引擎:读取合成后的场景 Schema,动态生成表单。 * 完全由 M8 模板结构驱动——主表(aggregate_root)渲染为卡片字段, * 子实体(aggregate_entity)按 table/card 渲染为多行表格或单行卡片。改模板即改页面。 */ export function SceneForm({ schema, onExecuted, principal }: { schema: SceneSchema; onExecuted: (r: any) => void; principal: string }) { const template = schema.template! const containers = template.rootComponents || [] const [master, setMaster] = useState>({}) const [singles, setSingles] = useState>>({}) const [tables, setTables] = useState>>>({}) const [optionsMap, setOptionsMap] = useState>({}) const [precheck, setPrecheck] = useState(null) const [submitting, setSubmitting] = useState(false) // 初始化表单状态(据模板结构) useEffect(() => { const s: Record> = {} const t: Record>> = {} for (const c of containers) { const role = roleOf(c) const entity = c.fieldBind?.domainEntityName if (role === 'single' && entity) s[entity] = {} if (role === 'table' && entity) t[entity] = [{}] } setMaster({}) setSingles(s) setTables(t) setPrecheck(null) // 拉取所有引用下拉数据源 const aggs = new Set() collectOptionSources(containers, aggs) Promise.all([...aggs].map((a) => api.options(a).then((opts) => [a, opts] as const))) .then((pairs) => setOptionsMap(Object.fromEntries(pairs))) .catch(() => {}) }, [schema.sceneId]) function buildPayload() { const details: Record = {} for (const c of containers) { const role = roleOf(c) const entity = c.fieldBind?.domainEntityName if (!entity) continue if (role === 'table') details[entity] = tables[entity] || [] if (role === 'single') details[entity] = [singles[entity] || {}] } return { master, details } } async function doPrecheck() { try { const r = await api.precheck(schema.sceneId, buildPayload(), principal) setPrecheck(r) if (r.reject) message.warning('预检未通过,请查看提示') else message.success('预检通过,可提交') } catch (e: any) { message.error('预检失败:' + e.message) } } async function doSubmit() { setSubmitting(true) try { const r = await api.execute(schema.sceneId, buildPayload(), principal) onExecuted(r) if (r.success) { message.success(`${schema.sceneName}成功:${r.rootId ?? ''}`) setPrecheck(null) } else { message.error(`${r.stage} · ${r.message}`) setPrecheck({ reject: true, errors: r.errors || [], rejectRules: r.appliedRules?.filter((x: any) => x.effect === 'REJECT') || [], warnings: r.warnings || [] }) } } catch (e: any) { message.error('提交失败:' + e.message) } finally { setSubmitting(false) } } return (
模板 {template.templateId} {template.templateType} 主聚合 {template.masterAggregate} 命令 API {schema.commandApi} 权限 {schema.permissionBind?.join(',')} {precheck && (precheck.reject || precheck.warnings?.length > 0) && ( {(precheck.errors || []).map((e: string, i: number) =>
❌ {e}
)} {(precheck.rejectRules || []).map((r: any, i: number) =>
⛔ [{r.ruleId}] {r.message}
)} {(precheck.warnings || []).map((w: any, i: number) =>
⚠️ [{w.ruleId}] {w.message}
)}
} /> )}
{containers.map((c) => { const role = roleOf(c) if (role === 'master') return if (role === 'single') return return })} ) } // ---------- 字段标签(展示 M1 绑定溯源,强化"字段强绑定领域模型") ---------- function FieldLabel({ comp }: { comp: DragComponent }) { const fb = comp.fieldBind! const path = `${fb.bindAggregateId}${fb.domainEntityName ? '.' + fb.domainEntityName : ''}.${fb.domainFieldName}` return ( {fb.formLabel} {fb.required && *} [{fb.fieldType}] {fb.masked && 脱敏}
⇠ {path}
) } function MasterCard({ container, values, setValues, optionsMap }: any) { const children: DragComponent[] = container.childComponents || [] return ( {container.fieldBind?.formLabel}} style={{ marginBottom: 12 }}> {children.map((ch) => ( }> setValues((prev: any) => ({ ...prev, [ch.fieldBind!.domainFieldName]: v }))} optionsMap={optionsMap} /> ))} ) } function SingleCard({ container, state, setState, optionsMap }: any) { const entity = container.fieldBind?.domainEntityName const children: DragComponent[] = container.childComponents || [] const values = state[entity] || {} return ( {container.fieldBind?.formLabel}} extra={子实体 {entity}} style={{ marginBottom: 12 }}> {children.map((ch) => ( }> setState((prev: any) => ({ ...prev, [entity]: { ...prev[entity], [ch.fieldBind!.domainFieldName]: v } }))} optionsMap={optionsMap} /> ))} ) } function TableCard({ container, state, setState, optionsMap }: any) { const entity = container.fieldBind?.domainEntityName const children: DragComponent[] = container.childComponents || [] const rows: Array> = state[entity] || [] const setCell = (idx: number, field: string, v: any) => setState((prev: any) => { const next = [...(prev[entity] || [])] next[idx] = { ...next[idx], [field]: v } return { ...prev, [entity]: next } }) const addRow = () => setState((prev: any) => ({ ...prev, [entity]: [...(prev[entity] || []), {}] })) const delRow = (idx: number) => setState((prev: any) => ({ ...prev, [entity]: (prev[entity] || []).filter((_: any, i: number) => i !== idx) })) const columns = [ ...children.map((ch) => ({ title: , key: ch.compId, render: (_: any, __: any, idx: number) => ( setCell(idx, ch.fieldBind!.domainFieldName, v)} optionsMap={optionsMap} /> ), })), { title: '操作', key: '_op', width: 70, render: (_: any, __: any, idx: number) => (