spec.md 19 KB

低代码表单设计器顺滑拖拽改造

status: done created: 2026-06-15 complexity: 🔴复杂

1. 背景与目标

当前 /app-center/object/crm_follow_record?suiteCode=IN_OUT 进入“设计对象 → 表单设计”后,中间表单编辑区依赖 FormCreate 通用设计器。它能完成基础拖拽,但体验不符合业务对象设计器的目标:字段资产、布局编排、运行态 CRUD 和发布检查是 Forge 自己的业务链路,FormCreate 画布只理解通用表单组件,导致操作割裂、拖拽反馈弱、布局调整不直观。

本次变更目标是将表单设计器中间画布改造成 Forge 原生设计体验,达到“类似飞书多维表格/表格配置面板”的轻快操作感:

  • 拖拉拽顺滑:拖动过程低延迟、重排无跳动、落点明确。
  • 操作简单:字段、分组、卡片、标签页等常用布局通过少量手势完成。
  • 布局一目了然:字段库、画布、属性面板职责清晰,视觉层级稳定,用户进入页面后能直接理解“从哪里拖、拖到哪里、选中后在哪里改”。
  • 组件细分清楚:画布、节点、拖拽状态、落点提示、属性编辑、字段库和模板入口拆成独立组件/Composable,避免单文件堆叠复杂逻辑。
  • 动效克制但明确:拖动时出现“原位幽灵 + 跟手镜像 + 落点双胞胎阴影”,让用户知道原位置、当前位置和将要放置的位置。
  • 运行态不重做:继续复用现有 AiCrudPageAiForm、动态 CRUD、发布检查和后端运行配置生成链路。
  • 多端演进可扩展:设计协议继续维护 formDesignerSchema,后续可由 PC Renderer 和 Mobile Renderer 使用同一 Schema 渲染。

可验证结果:

  • crm_follow_record 表单设计不再直接展示 FcDesigner 作为主画布。
  • 用户可从字段库拖入字段、在画布内重排字段、调整字段跨度、放入卡片/分组/标签页。
  • 拖拽时有明确的原位幽灵、跟手镜像、目标位阴影和吸附提示。
  • 所有可拖拽对象 hover 后必须出现明确反馈:拖拽手柄图标、可拖 cursor、边框/背景变化和可操作工具条。
  • 页面布局在 1366x768 下仍可直接看清核心工作区,不出现工具栏挤压、按钮换行错乱或属性面板遮挡画布。
  • 保存、预览、发布、打开运行应用结果继续兼容现有动态 CRUD。
  • 构建通过,关键 schema 编译逻辑有单元或组件级验证。

2. 代码现状(Research Findings)

每个结论必须有代码出处(文件路径 + 类名/方法名)

2.1 相关入口与链路

  • 业务对象详情页入口在 forge-admin-ui/src/views/app-center/object.[objectCode].vue

    • template 第 18 行和第 95 行通过 openDesigner('form') 打开设计器。
    • 第 163 行嵌入 BusinessObjectDesignerPage,传入 embedded-object-codeembedded-object-idinitial-panel
  • 业务对象设计器表单面板在 forge-admin-ui/src/views/app-center/object-designer.[objectCode].vue

    • 第 81 行进入 activePanel === 'form' 面板。
    • 第 84 行挂载 BusinessFormDesigner
    • 第 86-87 行以 v-model="draft.pageSchema"v-model:form-designer-schema="draft.formDesignerSchema" 同步设计协议。
    • 第 524 行 saveDesignerDraft() 仍走 saveBusinessObjectDesigner() 保存整体设计草稿。
  • 当前中间画布在 forge-admin-ui/src/views/app-center/components/designer/BusinessFormDesigner.vue

    • 第 51 行挂载 BusinessFormCreateDesigner
    • 第 221 行引入 saveBusinessObjectDesignersaveBusinessObjectFormLayout
    • 第 497 行 syncFormDesignerSchemaToPageSchema()formDesignerSchema 编译进 pageSchema.zones[edit]
  • FormCreate 嵌入点在 forge-admin-ui/src/views/app-center/components/designer/BusinessFormCreateDesigner.vue

    • 第 18 行使用 FcDesigner
    • 第 37 行直接 import @form-create/designer
    • 第 40 行通过 formCreateBridge 安装 FormCreate + Element Plus 适配。
    • 第 140 行调用 forgeSchemaToFormCreate() 将 Forge Schema 转为 FormCreate rule。
    • 第 166 行 flushDesigner() 再用 formCreateToForgeSchema() 转回 Forge Schema。

2.2 现有实现

  • Forge 自研表单 Schema 已存在于 forge-admin-ui/src/views/app-center/components/designer/form-first/formDesignerSchema.js

    • 第 14 行 FIELD_COMPONENT_KEYS 定义字段组件。
    • 第 49 行 LAYOUT_COMPONENT_KEYS 同时支持 elCard/cardelTabs/tabs 等双命名。
    • 第 194 行 createDefaultFormDesignerSchema() 能按字段生成默认表单。
    • 第 221 行 normalizeFormDesignerSchema() 负责归一化。
    • 第 340 行 createComponentFromField() 能从字段资产生成组件节点。
  • 表单设计结果已经能编译到运行态:

    • BusinessFormDesigner.vue 第 497 行 syncFormDesignerSchemaToPageSchema() 读取 formDesignerSchema
    • 第 509 行生成 fieldSettings
    • 第 510 行生成 formLayout
    • 第 518 行仍保留 formCreateRule 兼容旧运行逻辑。
    • 第 533 行写入 formLayout
    • 第 535 行标记 compiledFrom: 'formDesignerSchema'
  • 运行态已经支持布局节点:

    • forge-admin-ui/src/views/ai/crud-page.vue 第 307 行 transformEditFields() 会读取 options.editFormLayout
    • 第 325 行 hydrateRuntimeLayoutNode() 将运行布局节点和字段 schema 合并。
    • 第 389 行将水合后的 editSchema 传入 AiCrudPage
    • forge-admin-ui/src/components/ai-form/AiFormLayoutNodes.vue 第 1 行开始渲染布局节点。
    • AiFormLayoutNodes.vue 第 55 行支持 card
    • 第 80 行支持 tabs
    • 第 112 行支持 collapse
    • 第 143 行支持 divider
  • 后端运行配置生成已经消费 formLayout

    • forge-server/forge-framework/forge-plugin-parent/forge-plugin-generator/src/main/java/com/mdframe/forge/plugin/generator/service/lowcode/LowcodeRuntimeConfigBuilder.java 第 55 行 buildRuntimeConfig() 将页面协议转换为 AiCrudPage 运行配置。
    • 第 156 行 buildOptions() 读取编辑区 props。
    • 第 176 行读取 formLayout
    • 第 178 行写入 options.editFormLayout
  • 前端已有拖拽依赖和基础用例:

    • forge-admin-ui/package.json 已包含 vuedraggable@vueuse/core
    • forge-admin-ui/src/components/lowcode-builder/model/ModelFieldTable.vue 使用 vuedraggable 做字段排序。
    • forge-admin-ui/src/components/lowcode-builder/page/BuilderCanvas.vue 使用 vuedraggable 做区块排序。
    • forge-admin-ui/src/components/ai-form/AiTableFilter.vue 已有拖动中/悬停/列表移动样式和 transition-group

2.3 发现与风险

  • 当前最大体验问题不是运行态 CRUD,而是设计态画布和业务字段资产割裂。
  • FormCreate 设计器需要 Forge Schema ↔ FormCreate Rule 双向转换,任何布局、字段绑定、业务组件都会增加转换损耗。
  • 如果直接拿 AiCrudPage 当设计器画布,会把运行态接口、弹窗、表格和设计态拖拽强绑定,后续维护风险高。
  • 推荐替换 BusinessFormCreateDesigner,保留 BusinessFormDesigner.syncFormDesignerSchemaToPageSchema() 和运行态链路。
  • 拖拽动效必须遵守 prefers-reduced-motion,不能为了“顺滑”引入持续动效或大面积重绘。

3. 功能点

  • [ ] 原生 Forge 表单设计器

    • 输入:formDesignerSchema、字段资产、对象编码、对象名称。
    • 处理:以 Forge Schema 为唯一编辑源,直接增删改排组件树。
    • 输出:更新后的 formDesignerSchema,由现有编译链路生成 pageSchema
  • [ ] 三栏式设计工作台

    • 左侧:字段资产、布局组件、模板片段。
    • 中间:真实业务画布。
    • 右侧:属性面板。
    • 顶部:表单列数、弹窗/抽屉、预览、补齐字段、撤销重做、清理失效字段。
    • 信息密度按后台工具设计,避免营销式大卡片和过度留白;画布区域优先保证宽度。
    • 所有面板标题、分组、状态数量和空状态必须清楚,用户无需阅读说明即可知道下一步操作。
  • [ ] 飞书多维表格式顺滑拖拽

    • 字段从左侧拖入画布。
    • 画布内字段可重排。
    • 字段可拖入卡片、分组、标签页、折叠面板。
    • 可拖拽对象 hover 后显示拖拽手柄图标,手柄区域使用 cursor: grab,按下后切换 cursor: grabbing
    • 可拖拽对象 hover 后出现轻量背景、边框或阴影变化,禁用项要明确显示不可拖状态和原因。
    • 拖动时出现:
    • 原位幽灵:原组件保持淡化轮廓,避免用户失去来源位置。
    • 跟手镜像:拖拽卡片跟随指针,使用 transformopacity
    • 目标位双胞胎阴影:落点位置显示与组件同尺寸的浅色阴影,后方附加一层偏移投影,表达“将复制/移动到这里”的空间感。
    • 吸附线:靠近列边界、容器边界、字段之间时显示落点线。
    • 重排使用 FLIP 思路,非拖拽项以 transform 平滑移动。
  • [ ] 字段操作简化

    • 单击选中字段。
    • 双击字段名可快速改显示标签。
    • 拖动边缘或属性面板调整字段跨度。
    • 快捷操作:复制、删除、隐藏、必填、只读、移到上一行/下一行。
    • 未使用字段可一键补齐。
    • 常用操作必须出现在选中态工具条或右键/更多菜单中,避免用户只依赖右侧属性面板。
    • 危险操作如删除必须有明确按钮样式和二次确认或可撤销策略。
  • [ ] 布局操作简化

    • 支持 title/dividercardtabs/tabPanecollapse/collapseItemrow/col
    • 支持“创建分组并移动选中字段进去”。
    • 支持空容器显示可点击落点,避免空容器难以拖入。
    • 支持从常用模板创建布局:基础信息 + 跟进内容 + 时间负责人。
  • [ ] 属性面板

    • 字段属性:标题、占位符、列跨度、必填、只读、默认值、字典、上传限制、关联对象显示字段。
    • 布局属性:标题、描述、列跨度、折叠状态、标签页标题、容器样式。
    • 表单属性:列数、标签位置、标签宽度、行列间距、尺寸、反馈显示。
    • 属性面板按“基础 / 布局 / 校验 / 高级”分组,默认展开常用项,降低认知负担。
    • 所有输入控件必须有清晰 label,不允许只用 placeholder 解释字段含义。
  • [ ] 代码结构与可维护性

    • 新增组件按职责拆分,单个 .vue 文件原则上不承载拖拽、渲染、属性编辑和 schema 操作全部逻辑。
    • 复杂拖拽、落点计算、组件树变更逻辑必须放入独立 composable 或纯函数。
    • 注释只写关键意图和复杂算法说明,例如 FLIP 重排、落点碰撞判断、schema 路径转换;禁止堆砌“赋值/调用”类无效注释。
    • 组件和函数命名要直接表达业务含义,避免 handleDataprocessItemtempList 这类泛化命名。
  • [ ] 预览与运行态一致性

    • 设计画布使用与 AiFormLayoutNodes 兼容的节点模型。
    • 保存后继续生成 fieldSettingsformLayout
    • 发布后 AiCrudPage 新增/编辑/详情表单排版与设计器一致。
  • [ ] 兼容旧数据

    • 已保存的 formDesignerSchema 直接读取。
    • 仅存在 FormCreate rule 的旧对象,进入时可转换成 Forge Schema。
    • 保留 BusinessFormCreateDesigner 临时回退入口,便于对比和应急。

4. 业务规则

  • 表单设计器以 Forge formDesignerSchema 为主数据源。
  • 字段组件必须绑定业务字段,布局/虚拟组件不得占用业务字段。
  • 系统字段默认不可拖入主表单,除非后续明确允许只读展示。
  • 已拖入字段在字段库中标记“已使用”,默认不允许重复拖入同一字段。
  • 删除字段组件只从表单布局移除,不删除字段资产。
  • 删除字段资产时必须清理或标记表单中失效引用。
  • 运行态保存和发布仍以 pageSchemamodelSchemaformDesignerSchema 为准。
  • 拖拽动效必须尊重 prefers-reduced-motion:用户开启减少动态效果时,保留落点高亮但禁用跟手弹性和重排动画。
  • 可交互元素必须有明确状态:默认、hover、active、selected、disabled、dragging、drop-target、invalid-drop。
  • 可拖拽元素必须有手柄或图标提示;不能让用户猜测整块区域是否可拖。
  • 面板布局必须服务高频操作:字段库可快速搜索/分组,画布始终是最大视觉区域,属性面板只展示当前选中对象相关内容。
  • 代码结构必须让后续接入移动端 Renderer 时能复用 schema 操作和编译逻辑。

5. 数据变更

操作 表名 字段/索引 说明
- - Phase 1 不新增表字段,继续使用现有业务对象设计草稿中的 formDesignerSchemapageSchema

6. 接口变更

操作 接口 方法 变更内容
/ai/business/object/{objectId}/designer GET/PUT 继续读取和保存现有设计器协议
/ai/business/object/{objectId}/layout/preview POST 继续使用现有预览生成
/ai/business/object/{objectId}/publish POST 继续使用现有发布

7. 影响范围

  • 前端:

    • forge-admin-ui/src/views/app-center/components/designer/BusinessFormDesigner.vue
    • forge-admin-ui/src/views/app-center/components/designer/BusinessFormCreateDesigner.vue
    • 新增 forge-admin-ui/src/views/app-center/components/designer/forge-form-designer/
    • forge-admin-ui/src/views/app-center/components/designer/form-first/formDesignerSchema.js
    • forge-admin-ui/src/components/ai-form/AiFormLayoutNodes.vue
  • 后端:

    • Phase 1 原则上不改后端。
    • 如需增强发布校验,再评估 LowcodeRuntimeConfigBuilder 和业务对象发布检查。
  • 运行态:

    • forge-admin-ui/src/views/ai/crud-page.vue
    • forge-admin-ui/src/components/ai-form/AiCrudPage.vue
    • forge-admin-ui/src/components/ai-form/AiForm.vue
  • 典型验证对象:

    • /app-center/object/crm_follow_record?suiteCode=IN_OUT
    • 跟进记录字段:负责人、主题、关联客户、关联合同/商机、跟进方式、跟进内容、跟进时间。

8. 风险与关注点

  • 拖拽体验风险:如果只用 vuedraggable 默认 ghost class,可能达不到飞书多维表格式顺滑感;需要补自定义 drag overlay、drop indicator 和 FLIP 动效。
  • 布局复杂度风险:三栏工作台如果信息层级不清,会让用户看不懂当前应操作哪里;需要先固定信息架构和面板职责,再做视觉细节。
  • 代码复杂度风险:拖拽、节点渲染、属性编辑如果堆在单个组件里会快速失控;必须拆分组件和 composable,并用纯函数处理 schema。
  • 性能风险:画布字段超过 80 个时,拖动中频繁深拷贝 schema 会卡顿;拖动中应只维护轻量 drag state,落下后一次性提交 schema。
  • Schema 兼容风险:旧 FormCreate rule、旧 formDesignerSchema、当前运行态 formLayout 必须共存过渡。
  • 运行态一致性风险:设计态展示若与 AiFormLayoutNodes 差异过大,会造成“设计看到的不是发布后的样子”。
  • 可访问性风险:拖拽不能成为唯一操作方式,至少要提供按钮上移/下移、移入分组、键盘焦点和可见 focus 状态。
  • 动效风险:过多动画会降低后台工具效率;只在拖拽、选中、插入、撤销重做几个关键点使用 150-240ms 动效。
  • 权限/资金/状态流转:本次不涉及资金、权限放开或业务状态流转。

8.5 测试策略

  • 测试范围

    • formDesignerSchema 归一化、字段插入、移动、删除、布局容器嵌套。
    • formDesignerSchema -> pageSchema.editZone.props.fieldSettings/formLayout 编译结果。
    • crm_follow_record 保存、预览、发布、打开运行应用。
    • 可拖拽元素 hover、active、disabled、dragging、drop-target 等状态表现。
    • 三栏布局在 1366x768、1440x900、1920x1080 下核心操作区可见。
    • 拖拽交互在 1366x768、1440x900、移动窄屏降级下无重叠。
    • prefers-reduced-motion 下动效降级。
  • 覆盖率目标

    • Schema 工具函数:覆盖新增移动、插入、删除、路径定位逻辑。
    • 组件交互:覆盖字段拖入、画布内排序、容器内拖入、属性面板修改。
    • 构建验证:pnpm build
  • 独立 Test Spec:是。进入 /test 前创建或补充 test-spec.md,并按 code-copilot/rules/automated-testing-standard.md 追加 execution-log.md

9. 待澄清

  • 第一阶段是否允许完全隐藏 FormCreate 画布,只保留高级回退开关?
  • 是否需要首期支持移动端预览模式,还是只预留 schema 字段后续做 H5 Renderer?
  • “双胞胎阴影”的视觉强度是否以轻量后台风格为准:浅蓝边框 + 白色主体 + 第二层偏移阴影?
  • 是否要把 crm_follow_record 内置一个推荐布局模板:基础信息、跟进对象、跟进内容、时间负责人?
  • 拖拽手柄图标优先使用现有 Iconify/UnoCSS 图标,还是统一使用 @vicons/ionicons5 图标?

10. 技术决策

  • 决策 1:设计态不直接复用 AiCrudPage 页面壳。

    • 原因:AiCrudPage 是运行态组件,包含接口、弹窗、表格、导入导出、流程详情等职责;设计态应只复用字段渲染资产和布局协议。
  • 决策 2:Forge formDesignerSchema 作为设计态唯一事实来源。

    • 原因:现有 schema 已支持字段组件、布局组件、归一化、默认生成和运行态编译,替换画布不需要重写后端和动态 CRUD。
  • 决策 3:Phase 1 不引入新拖拽依赖,优先使用已有 vuedraggable + 自定义 Pointer/Overlay/FLIP。

    • 原因:项目已有 vuedraggable,可覆盖列表和嵌套排序;复杂镜像和吸附提示由自研 composable 控制,减少依赖风险。
  • 决策 4:动效只用 transformopacitybox-shadow,禁止拖拽中修改布局尺寸做动画。

    • 原因:降低重排和卡顿风险,保证后台工具效率。
  • 决策 5:保留 FormCreate 兼容转换,但从主链路下线。

    • 原因:旧数据迁移和应急回退需要一段过渡期。

11. 执行日志

Task 状态 实际改动文件 备注
Proposal done spec.md, tasks.md 已创建改造提案
Native designer skeleton done BusinessFormDesigner.vue, formDesignerSchema.js, forge-form-designer/* 新版三栏画布骨架接入,旧 FormCreate 画布保留回退
Validation done test-spec.md, execution-log.md eslint、pnpm build、git diff --check 通过

12. 审查结论

/review lowcode-form-designer-smooth-dnd 执行。

13. 确认记录(HARD-GATE)

  • 确认时间:待确认
  • 确认人:待确认

归档记录(HARD-GATE)

  • 状态:done
  • 归档时间:2026-06-27
  • 归档人:yaomd(批量归档)
  • 归档路径:code-copilot/changes/archive/2026-06-27-lowcode-form-designer-smooth-dnd/
  • 判定依据:任务清单全部完成,execution-log 验证通过(编译/构建/lint 闭环)。