动态插件配置表单(技术高峰)
这是整个前端最精华的一讲。43 个插件各有不同配置项,前端不可能为每个插件手写表单——它从后端返回的 JSON Schema 动态生成表单,还能和 YAML 源码双向同步。今天拆透这套机制。
问题:插件配置千变万化
{type:"object", properties:{qps:{type:"integer", title:"每秒请求数"}}} → 前端 generateFields 看到 integer → 渲染出一个"每秒请求数"数字输入框。你填 100 → 存回 qps: 100。插件端点
services/plugin.ts(88 行):
GET /v1/wasm-plugins/{name}/config :31-33 // ← 返回配置 Schema(动态表单数据源)
GET /v1/wasm-plugins/{name}/readme :84-88
// 三种作用域的实例 CRUD:
/v1/global/plugin-instances[/{name}] :36-48
/v1/routes/{name}/plugin-instances/{pluginName} :51-82
/v1/domains/{name}/plugin-instances/{pluginName}
/config 就是 Day 12 后端从 spec.yaml 加载的那个 openAPIV3Schema。前端拿它渲染表单。三种作用域对应后端 Day 09 的 ROUTE/DOMAIN/GLOBAL。抽屉分发
pages/plugin/components/PluginDrawer/index.tsx:12-17 有个 PLUGIN_COMP_MAP:rewrite/headerModify/cors/retries 四个内置策略用专用组件,其余(普通 wasm 插件)走 schema 驱动的通用表单 GlobalPluginDetail(:70-90)。
数据流
GlobalPluginDetail.tsx(767 行,最核心)的数据流:
(rawConfigurations YAML)→ getConfig拉 Schema
setSchema→ yamlToFormValues
YAML 反解成表单值→ generateFields
递归渲染表单
getConfig(:173-198):若 schema 没有 jsonSchema.properties 则强制切到 YAML 编辑 tab(:178-180)——即无 schema 的插件只能裸写 YAML;取 x-example-raw 作示例默认值。
递归渲染 generateFields
👨🏫:配置能层层嵌套——一个 object 里还有 object,里面可能又有 object。就像俄罗斯套娃,你不知道套了几层。
👶:那怎么全部拆开?
👨🏫:让函数调用它自己:
generateFields 遇到 object 类型,就再调一次自己去处理它的子字段,无论套多深都能一层层展开。这就是递归——处理"树状结构"的标准手法,动态表单能应对任意复杂 schema 全靠它。generateFields(scm, prefix)(:230-341)遍历 schema 的 properties,按类型映射组件:
string → Input
integer → Input[number] + parseInt
number → Input[number] step=any + parseFloat
boolean → Select(true/false)
object 且有子 properties → 递归(用 Card 包成嵌套子表单) :244-258
array → (先 validateArrayTypes 校验元素类型) :302-323
getLocalizedText(:225-228)读 x-{title/description}-i18n[语言]——正是 Day 12/16 那套多语言约定。
generateFields 处理它的子字段,无论嵌套多深都能展开。这是处理"树状结构"的标准手法,动态表单能应对任意复杂的 schema 全靠它。表单 ↔ YAML 双向同步
用户可以在"表单 tab"和"YAML 源码 tab"间切换,两边数据必须实时同步。这套转换是本讲的技术核心:
yamlToFormValues :426-462 // YAML → 扁平化成 a.b.c 点号键,数组包成 {uid,data}
formValuesToSchema :343-380 // 点号键 → 还原成嵌套对象
schemaToYaml :382-416 // 对象 → 手写 YAML 序列化(跳过 enabled,字符串加引号)
fieldChange :594-603 // 表单任意字段变化 → 实时转回 YAML 存 rawConfigurations
fieldChange 保证切 tab 不丢数据——这是最容易出 bug 的地方,作者用一套严谨的扁平化/还原逻辑搞定。ArrayForm 与 Monaco
- ArrayForm(
ArrayForm/index.tsx,302 行):可编辑表格(antd Table + 行内 Form),每行{uid,data},可增删,用于渲染 schema 里 type=array 的字段。 - CodeEditor(
components/CodeEditor/index.tsx:26-58):封装 Monaco,loader.config({monaco})用本地 monaco(配合 Day 16 拷贝的 vs 目录),autoHeight时用 ResizeObserver 自适应高度。
渲染骨架(:614-763):enabled 开关 + 三个 Tab:form(动态表单)/ yaml(Monaco)/ readme(ReactMarkdown + remarkGfm,支持全屏)。提交 onSubmit(:515-528)拼 {...pluginData, enabled, rawConfigurations}。
今日小结 + 动手
🧠 今天你应该能回答
- 为什么不能给每个插件手写表单?schema 驱动怎么解决?
- 数据流四步(getData→getConfig→yamlToFormValues→generateFields)?
- 递归渲染怎么处理嵌套 object?各类型映射成什么组件?
- 表单↔YAML 双向同步为什么必要?谁保证切 tab 不丢数据?
✋ 动手
cd /Users/bitmart/work/codes/github/higress-group/higress-console/frontend/src/pages/plugin
cat ../../services/plugin.ts
sed -n '12,17p' components/PluginDrawer/index.tsx
sed -n '230,341p' components/PluginDrawer/GlobalPluginDetail.tsx
sed -n '426,462p' components/PluginDrawer/GlobalPluginDetail.tsx