跳转至

新 Excel 导入工具设计

前端组件

Excel 导入可视化模板设计器(Vite + Vue3 + Pinia)整体设计方案。

工程路径web/obpm-designer-excelimp
当前版本:0.1.0
构建命令npm run dev(开发) / npm run build(ESM/UMD 库)

方案概述

项目定位

打造一款可拖拽、可配置、可撤销重做、可独立 ESM 导出的 Excel 导入模板可视化设计组件。支持业务人员可视化配置 Excel 表头映射、数据校验规则、字段转换规则,无需编码即可生成标准导入模板;组件支持独立打包为 ESM 模块,可被任意 Vue3 项目、Vite 项目、第三方工程直接引入使用。

核心能力

  • 可视化拖拽:拖拽配置 Excel 导入字段、排序、表头映射关系
  • 模板定义:支持设置必填、数据类型、校验规则、填写方式、格式、默认值、字段转换
  • 导出目标:Sheet 级支持导出到表单或数据库表,并绑定模块/表单或数据源/目标表
  • 多级表头:列标签支持多行叠加、横向合并(colspan)、纵向合并(rowspan)及完整样式配置
  • 主从结构:支持多 Sheet 分组、主/从 Sheet 配置;从表字段通过 relation 与主表字段建立一对多关联
  • 操作回溯:撤销/重做(SVG 图标按钮)、清空当前 Sheet 画布(橡皮擦图标)
  • 模板能力:弹窗查看/编辑 JSON 模板;配置变更通过 change 事件通知宿主持久化;Excel 导入模板文件由**服务端**按 TemplateJson 生成(见「Excel 导出接口(后端)」)
  • 开箱即用:ESM 独立组件打包,无侵入,支持第三方项目按需引入
  • 状态统一:Pinia 全局管理设计器状态,数据可持久化、可外部监听

技术栈选型

类别 选型 说明
构建工具 Vite 8 极速构建、原生 ESM 支持
核心框架 Vue 3 + Composition API + <script setup>
状态管理 Pinia 简洁轻量化、支持状态快照,适配撤销重做
拖拽引擎 vue3-dnd + react-dnd-html5-backend 标准 HTML5 拖拽
样式方案 SCSS + scoped 组件样式 画布表格样式见 preview-table.scss
打包方案 Vite Library Mode ESM + UMD 双格式,npm run build

页面布局

整体采用「顶部工具栏 + 左 / 中 / 右三栏」的经典设计器布局:左侧取字段、中间排模板、右侧配属性。

顶部工具栏

┌──────────────────────────────────────────────────────────────────────┐
│ [模板名] [模板描述] │ [撤销][重做][清空] │ JSON模板 │
└──────────────────────────────────────────────────────────────────────┘
  • 模板名称 / 描述:当前导入模板的名称与说明,可就地编辑。
  • 操作回溯(SVG 图标按钮,title 悬停提示):
  • 撤销:回退上一步操作
  • 重做:恢复已撤销的操作
  • 清空(橡皮擦图标):清空**当前 Sheet** 的字段与列标签,保留 Sheet 绑定配置及其他 Sheet
  • JSON 模板:打开弹窗,在多行文本框中查看/编辑完整 TemplateJson;**取消**关闭不保存,**应用**校验 JSON 后加载到画布。

页签 / 字段库

每个 Sheet 页签对应一个导入分组(主 / 从),其左侧面板包含页签配置与可用字段列表:

┌─────────────────────────────────────┐
│  左 · 页签 / 字段库                 │
│  ┌ 页签配置 ────────────────────┐   │
│  │ 页签名称:[ 主表            ] │   │
│  │ 导 出 到:[ 表单         ▾ ] │   │  ← 表单 | 数据库表
│  │ ── 表单模式 ──────────────── │   │
│  │ 所属模块:[ 销售模块     ▾ ] │   │
│  │ 表    单:[ 订单表单     ▾ ] │   │
│  │ ── 数据库表模式 ──────────── │   │
│  │ 数 据 源:[ 主数据库     ▾ ] │   │
│  │ 目 标 表:[ orders       ▾ ] │   │
│  └──────────────────────────────┘   │
│  ┌ 可用字段列表 ────────────────┐   │
│  │ ☑ ITEM_订单号   订单号      │   │
│  │ ☑ ITEM_客户     客户名称    │   │
│  │ ☐ ITEM_金额     订单金额    │   │
│  │ ☐ ITEM_日期     下单日期    │   │
│  │ (勾选 / 拖拽到画布)        │   │
│  └──────────────────────────────┘   │
└─────────────────────────────────────┘
  • 页签名称:Sheet 页签名(对应导出 Excel 的 Sheet 名,最长 31 字符),可自定义修改。
  • 导出到:下拉选择导入目标类型,form(表单)或 database(数据库表),存储于 ExcelSheet.exportTarget,默认 form
  • 表单模式exportTarget === 'form'):
  • 所属模块:下拉选择,数据来源 hostRequestBridge.getModules()
  • 表单:下拉选择,数据来源 hostRequestBridge.getModuleForms(moduleId);选定后通过 getFormSchema() 拉取可绑定字段。
  • 数据库表模式exportTarget === 'database'):
  • 数据源:下拉选择,数据来源 hostRequestBridge.getAppDataSources(appId)
  • 目标表:下拉选择,数据来源 hostRequestBridge.getDataSourceTables(appId, dataSourceId)
  • 字段库可选通过 getDataSourceTableSchema(appId, dataSourceId, tableId) 拉取表字段(FakeHostApi 已提供 Mock)。
  • 可用字段列表:来自所选表单或目标表的可绑定字段(name = 绑定 Key,label = 显示名),支持勾选或拖拽到中间画布。
  • 切换表单 / 目标表且 Sheet 已有绑定字段时,弹出确认并清空画布;切换「导出到」且已有绑定字段时不允许切换。
  • 组件 Prop appId 用于拉取数据源与目标表;未传时 Mock 环境使用 demo_app
  • 未注入 hostRequestBridge 时使用内置 FakeHostApiutils/hostApi.ts)。

模板画布

画布表格分为 设计辅助行(仅设计器内展示,不导出 Excel)与 导出内容行

┌───────────────────────────────────────────────┐
│  中 · 设计画布                                │
│  ┌ Sheet 页签─────────────────────────┐       │
│  │ 主表 │ 订单明细 │ + 新增            │       │
│  ├────────────────────────────────────┤       │
│  │ thead(不导出)                    │       │
│  │   行1 · 字段名  │订单号│客户名称│…  │  ← 可拖拽排序 │
│  │   行2 · 列字母  │  A  │  B   │ C │       │
│  ├────────────────────────────────────┤       │
│  │ tbody(导出内容)                  │       │
│  │   L1 列标签行 │ 订单号 │ 客户名称(合并)│   │
│  │   L2 列标签行 │ (纵向) │ 订单金额 │…  │   │
│  │   数据行      │        │          │   │   │
│  └────────────────────────────────────┘       │
│  [ + 列标签行 ]                               │
└───────────────────────────────────────────────┘
  • 字段名行(thead 第 1 行):绿色表头,展示各列字段名称,支持拖拽排序(CanvasColumn.vue)。
  • 列字母行(thead 第 2 行):展示 A / B / C… 列标识,辅助对齐。
  • 列标签行(tbody):多级表头,可点击选中并在右侧配置属性;支持 + 列标签行 新增层级。
  • 数据行(tbody 最后一行):占位 / 示例数据预览,可配置行高(pt,默认 15)与是否填充默认值。

列名 / 列标签 / 数据行属性

画布中的单元格按类型分为 列名 / 列标签 / 数据行 / Sheet 四类,选中不同类型时右侧属性面板自动切换:

选中类型 属性面板 可配置项
列名(Column) FieldSetting.vue · 列信息 字段名称、绑定 Key、主从关联(从 Sheet 字段)、必填、数据类型、校验规则、填写方式、格式、默认值
列标签(Label) LabelSetting.vue · 列标签属性 标签文本、所在行级、字体大小(pt,默认 11)、字重、斜体、文字装饰、文字/背景颜色、对齐方式、横向合并跨度(colspan)、纵向合并跨度(rowspan)
数据行(Data Row) SheetSetting.vue · 数据行属性 行高(pt,默认 15)、是否示例数据行
Sheet / 未选中 SheetSetting.vue · Sheet 属性 Sheet 类型(主/从)

列标签支持 多行(多级表头)、横向合并纵向合并,可组合使用:

              A列        B列         C列
  L1  ┌──────── 订单号 ──┬──── 客户名称 ────┐   ← A 纵向合并 2 行;B+C 横向合并
  L2  │   (纵向合并)     │ 订单金额 │ (C列标签) │
数据行│                  │          │          │   ← 导出时填充默认值(可选)
      └──────────────────┴──────────┴──────────┘

列标签默认行为

  • 拖入 / 勾选字段时,自动在 L1 创建列标签,文本与字段名称相同。
  • 点击「+ 列标签行」时,为当前 Sheet 所有列在该层级创建默认标签(文本 = 各字段名)。
  • 字段排序、增删列时,labelRow.ts 同步维护 columnIndex
  • 新建标签的默认样式见 DEFAULT_LABEL_STYLElabelRow.ts):
属性 默认值
fontSize 11(pt)
fontWeight normal
fontStyle normal
textDecoration none
color #2e7d32
backgroundColor #e8f5e9

数据行默认行为

  • 新建 Sheet 时 dataRow.height 默认为 15(pt),与全局 config.excelRowHeight 一致。
  • 设计器画布预览使用 pt 作为行高单位;服务端导出 Excel 时写入 POI Row.setHeightInPoints(与 config.excelRowHeight / dataRow.height 同构,默认 15 pt)。

区域与组件对应

区域 组件 说明
顶部工具栏 Toolbar.vue 模板信息编辑、操作回溯(SVG 图标)、JSON 模板弹窗
页签 / 字段库 DragFieldList.vue 页签配置 + 导出到(表单/数据库表)+ 模块/表单或数据源/目标表 + 可用字段列表
Sheet 页签 SheetTabs.vue 多 Sheet 切换、新增 / 删除(主从模式)
模板画布 DragCanvas.vue 表格网格、列标签行、数据行
列拖拽 CanvasColumn.vue 字段名列拖拽排序
字段项 FieldItem.vue 左侧字段列表项
右侧 · 列信息 FieldSetting.vue 字段属性配置(含主从关联)
右侧 · 列标签 LabelSetting.vue 列标签样式与合并配置
右侧 · Sheet/数据行 SheetSetting.vue Sheet 主从属性、数据行配置

整体架构设计

架构分层

  1. 视图层:设计器主页面、拖拽画布、字段/标签配置面板、操作工具栏
  2. 组件层:可复用拖拽组件、表单配置组件、按钮操作组件
  3. 状态层:Pinia Store 统一管理模板数据、选中状态、操作快照
  4. 工具层:标签行渲染、模板序列化、撤销重做快照、宿主 API 桥接

核心数据流

用户拖拽/配置操作 → 触发组件事件 → Pinia 更新状态 + recordSnapshot()
→ 视图响应更新 → serializeTemplate() 生成 TemplateJson → 触发 change 事件 / 支持 JSON 导入导出

模块拆分

模块 文件 职责
拖拽核心 CanvasColumn.vueFieldItem.vue 字段拖拽、排序、放置
模板配置 FieldSetting.vueLabelSetting.vueSheetSetting.vue 字段/标签/Sheet 属性(主从关联在 FieldSetting
操作回溯 snapshot.tsdeepClone.ts 状态快照、撤销重做
标签行逻辑 labelRow.ts 合并覆盖判断、渲染单元格构建、列索引同步
模板序列化 serialize.ts TemplateJson 序列化/反序列化、旧版 relationField 迁移、detailKey 同步
字段工具 excel.ts 从表单字段创建 ExcelFieldItemcreateFieldFromFormField
打包集成 index.tsvite.config.ts ESM 组件打包、对外 API

核心功能详细设计

可视化拖拽设计

  • 从字段库拖拽或勾选新增字段到模板画布
  • 画布内字段名行自由拖拽排序(vue3-dnd),实时更新列顺序并同步标签 columnIndex
  • 支持单个字段删除(同步删除该列全部标签)
  • 支持清空当前 Sheet 画布(字段 + 标签)

模板字段配置规则

每个模板字段(ExcelFieldItem)支持完整配置项,可序列化存储为模板 JSON:

配置项 字段 说明
字段 ID id 前端唯一标识(UUID)
字段名称 name Excel 展示表头名称
绑定字段 Key key 后端映射字段唯一标识(对应表单 / 表字段 name
是否必填 required 导入校验时非空判断;服务端导出 Excel 时影响数据验证 allowBlank
数据类型 dataType 见「列信息配置」
校验规则 validationRule none / numberRange / dateRange / textLength / regex,默认 none
文本长度 / 数值范围 lengthLimit [min, max],用于文本长度或数值范围校验
日期范围 dateRange [start, end] ISO 日期,用于 date / datetime
时间范围 timeRange [start, end] HH:mm,用于 time
正则校验 pattern / patternTip 自定义正则及错误提示
填写方式 fillMode input(输入)/ select(下拉选择),默认 input
下拉选项 selectOptions fillMode === 'select' 时的候选项列表
数值格式 numberFormat 数值类型 Excel 格式串
日期格式 dateFormat 日期类型 Excel 格式串
时间格式 timeFormat 时间类型 Excel 格式串
日期时间格式 datetimeFormat 日期时间类型 Excel 格式串
默认值 defaultValue 示例数据行填充值
字段转换规则 transform Record<string, string>
主从关联 relation MasterDetailRelation,仅从 Sheet 字段有效,表示子表字段与主表字段的对应关系

列信息配置(数据类型 / 校验 / 填写方式 / 格式)

列信息(FieldSetting.vue)按分组展示:主从关联(从 Sheet 且 enableMasterDetail 时)、数据类型校验规则填写方式格式其他(默认值)。表单项采用上下结构(.form-group 标签在上、控件在下)。

数据类型dataType

类型 说明
文本 string 通用字符串
数值 number 配合数值范围、数值格式
日期 date 配合日期范围、日期格式
时间 time 配合时间范围、时间格式
日期时间 datetime 配合日期时间范围、日期时间格式
手机号 phone 可选正则校验
邮箱 email 可选正则校验
身份证 idCard 可选正则校验

校验规则validationRule,非必填,默认 none

校验类型 适用类型 配置字段
none 全部 服务端导出时不写入 Excel 数据验证(下拉填写方式除外)
数值范围 numberRange 数值 lengthLimit = [min, max]
日期范围 dateRange 日期 dateRange = [start, end]
时间范围 dateRange 时间 timeRange = [start, end](HH:mm)
日期时间范围 dateRange 日期时间 dateRange = [start, end](含时间)
文本长度 textLength 文本 lengthLimit = [min, max]
正则 regex 文本 / 手机号 / 邮箱 / 身份证 pattern + patternTip;后三者选中正则时可使用内置默认公式

填写方式fillMode

方式 说明
输入 input 单元格自由输入(默认)
下拉选择 select 单元格以下拉框填写,需配置 selectOptions(每行一项)

格式(按数据类型分别配置;服务端导出 Excel 时写入单元格 DataFormat

数值格式(numberFormat):

格式串 含义
#,##0;-#,##0 千分位整数(正数 ; 负数)
#,##0;[Red]-#,##0 千分位整数,负数显示红色
#,##0.00;-#,##0.00 千分位两位小数
#,##0.00;[Red]-#,##0.00 千分位两位小数,负数显示红色

日期格式(dateFormat):

格式串 含义
yyyy/m/d 年/月/日
[DBNum1][$-804]yyyy"年"m"月"d"日" 中文数字年月日
[DBNum1][$-804]yyyy"年"m"月" 中文数字年月

时间格式(timeFormat):

格式串 含义
mm:ss.0 分:秒.十分位秒
h:mm 时:分
h:mm:ss 时:分:秒

日期时间格式(datetimeFormat):

格式串 含义
yyyy/m/d h:mm 年/月/日 时:分
yyyy-mm-dd hh:mm:ss 年-月-日 时:分:秒

[DBNum1] 为中文小写数字格式,$-804 为简体中文区域代码。

选项常量定义于 components/ExcelDesigner/fieldOptions.ts

列标签配置规则

列标签存储于 ExcelSheet.labelRows: ColumnLabelCell[]

配置项 字段 说明
标签 ID id UUID
列索引 columnIndex 对应 fieldList 中的列位置(0-based)
行级 rowLevel L1、L2…(1-based)
标签文本 text 显示内容
对齐 align left / center / right
横向合并 colspan 默认 1,最大不超过剩余列数
纵向合并 rowspan 默认 1,最大不超过当前行级到底部标签行
样式 style LabelCellStyle 对象:见下表;服务端映射为 JsonCellStyle

LabelCellStyle 字段(前端 → 服务端 JsonCellStyle / ExcelTemplateStyleHelper):

前端字段 类型 说明
fontSize number 字号(pt),默认 11
fontWeight string | number 字重,如 "700" / "bold"
fontStyle string 斜体:"italic"(优先于 fontItalic
fontItalic boolean 斜体(兼容字段)
textDecoration string "underline" / "line-through"
color string 前景色,如 "#2e7d32"
backgroundColor string 背景色,如 "#e8f5e9"

合并渲染规则labelRow.ts):

  • isLabelPositionCovered():判断某 (rowLevel, columnIndex) 是否被上方单元格的 rowspan/colspan 覆盖。
  • buildLabelRowRenderCells():构建单行可见单元格列表,跳过被覆盖位置,保留 columnIndex 供导出定位。
  • normalizeLabelCell():写入时校验 colspan/rowY 不越界。

主从结构导入设计

  • 导入字段按 Sheet 分组,设计器以多 Sheet 页签组织
  • 主 SheetsheetType: 'master'):业务主体,一行一条主记录
  • 从 SheetsheetType: 'detail'):业务明细,一行一条从记录,通过字段级 relation 与主 Sheet 字段建立关联
  • 同一模板仅允许一个主 Sheet;切换某 Sheet 为主时,其余自动降为从 Sheet
  • 主从模式通过 Prop enableMasterDetail 开启,默认关闭

关联字段(MasterDetailRelation

主从关联配置在 列信息FieldSetting.vue)中完成,而非 Sheet 属性。每个从 Sheet 字段可独立配置与主表字段的映射:

字段 说明
masterKey 主表字段 key(下拉选项来自主 Sheet 的 fieldList
detailKey 子表字段 key(即当前列字段的 key,保存时自动同步)

配置界面:

  • 子表字段:只读展示当前列名称与 Key
  • 主表字段:下拉选择主 Sheet 中要对齐的字段;清空表示取消关联

显示条件:enableMasterDetail === true 且当前 Sheet 为从 Sheet 且主 Sheet 已有字段。

序列化示例(从 Sheet 某一列):

{
  "key": "order_id",
  "name": "订单ID",
  "relation": {
    "masterKey": "id",
    "detailKey": "order_id"
  }
}

兼容迁移serialize.ts):

  • 旧版模板在从 Sheet 上存 relationField(仅主表字段 Key),加载 / 序列化时自动迁移为字段级 relation
  • 迁移策略:写入该从 Sheet 第一个尚未配置 relation 的字段(若全部已配置则写入第一个字段)
  • 序列化时同步 relation.detailKey 与字段 key,保证双向映射一致

从 Sheet 绑定继承

  • 从 Sheet 的「导出到」「数据源」继承自主 Sheet(DragFieldList.vue 只读展示)
  • 各从 Sheet 可独立配置目标表 / 表单字段列表,主从关联在各列的 relation 上分别维护

表单 / 数据库字段获取(设计阶段)

表单模式exportTarget === 'form'):

  • hostRequestBridge.getModules() → 模块列表
  • hostRequestBridge.getModuleForms(moduleId) → 表单列表
  • hostRequestBridge.getFormSchema(moduleId, formId) → 可绑定字段(name / label

数据库表模式exportTarget === 'database'):

  • hostRequestBridge.getAppDataSources(appId) → 数据源列表
  • hostRequestBridge.getDataSourceTables(appId, dataSourceId) → 目标表列表
  • hostRequestBridge.getDataSourceTableSchema?(appId, dataSourceId, tableId) → 表字段(可选,用于字段库)

  • 组件 Prop hostRequestBridge 可选;本地开发 / 演示使用 FakeHostApiutils/hostApi.ts

  • 组件 Prop appId 传入应用 ID;Mock 默认 demo_app
  • 主从结构下,各 Sheet 独立配置绑定关系并拉取字段

撤销 / 重做功能设计

实现方案(Pinia 状态快照 + deepClone):

  • 撤销栈 undoStack、重做栈 redoStack,默认最大 20 步(Prop maxStep 可配置)
  • 每次增删改配置后调用 recordSnapshot(),快照包含 templateNametemplateDescsheetsactiveSheetId
  • 使用 deepClone(JSON 序列化)替代 structuredClone,兼容 Pinia Proxy

可回溯操作:字段拖拽排序、新增/删除字段、属性修改、标签增删改、Sheet 操作、模板加载、清空画布。

JSON 模板弹窗

工具栏「JSON模板」按钮打开 JsonTemplateModal.vue

说明
展示 多行 textarea,等宽字体,打开时自动填入当前模板的格式化 JSON(exportTemplateJson()
取消 关闭弹窗,不修改画布
应用 校验 JSON 格式 → loadTemplate()(自动 ensurePrimaryLabelRow()、迁移旧版 relationField)→ 触发 change 事件;格式错误时在弹窗内提示
用途 查看/复制当前模板、粘贴外部 JSON 批量导入、手工微调模板结构

模板能力总览

能力 实现 说明
查看/编辑 JSON ToolbarJsonTemplateModal 弹窗内多行文本框展示与编辑 TemplateJson
导出 Excel 由宿主调用服务端 export-excel 接口生成(见「Excel 导出接口(后端)」)
持久化 change 事件 配置变更时返回 TemplateJson,由宿主写入 jsonTemplate 等后端字段

Excel 模板生成规则(服务端)

前端设计器仅维护 TemplateJson.xlsx 文件由后端 ExcelTemplateBuilder 生成,规则如下。HTTP 入口见「方式二:服务端按 jsonTemplate 生成」。

导出范围(每个 Sheet):

  • ✅ 列标签行(L1 ~ Ln)
  • ✅ 数据行区域(自数据行起向下 **500 行**预置格式单元格,TEMPLATE_FORMAT_ROW_COUNT
  • ❌ 字段名行、列字母行(仅设计器辅助,不写入 Excel)

样式ExcelTemplateStyleHelper + Apache POI):

  • 列标签:来自 LabelSetting 配置的字体、颜色、背景、对齐、边框;字号写入 POI Font.setFontHeightInPointspt
  • 数据列:按字段类型写入 numberFormat / dateFormat / timeFormat / datetimeFormat(通过 resolveNumFmt
  • 数据行行高取自 sheet.dataRow.heightpt)或全局 config.excelRowHeight(默认 15);预置格式行沿用数据行行高
  • 空白单元格:补全边框

单元格格式与值ExcelTemplateBuilder):

  • 数值 / 日期 / 时间 / 日期时间类型写入 Excel 数字单元格,并设置对应 DataFormat
  • 示例默认值按类型解析为 Excel 序列值(日期序列、时间小数等)
  • 空白数据单元格保留数值型以便 Excel 应用格式

数据验证ExcelTemplateBuilder):

  • 由 POI 直接写入 <dataValidations>
  • validationRule === 'none' 时不写入校验(填写方式为下拉选择时仍写入 list 验证
  • 支持:数值范围、日期/时间/日期时间范围、文本长度、正则(含手机号/邮箱/身份证内置公式)、下拉列表
  • 日期范围:使用 =AND(A1>=DATE(y,m,d), A1<=DATE(...)) 公式,兼容 yyyy-MM-ddyyyy/MM/dd 等输入格式
  • 验证范围:数据行至 Excel 最大行(1048576)

合并处理ExcelTemplateBuilder):

  • 根据各标签的 colspan / rowspan 写入合并区域
  • 使用 LabelRenderCell.columnIndex 定位单元格,避免纵向合并导致列索引偏移

数据行内容sheet.dataRow.isExample === true 时,首行数据单元格填充各字段 defaultValue(按类型解析),其余预置行为空。

状态管理设计(Pinia)

Store

单一 Store useExcelDesignStorestore/excelDesign.ts)。

核心 State

// 模板基础信息
templateName: string
templateDesc: string
sheets: ExcelSheet[]

// 拖拽 / 选中
activeSheetId: string | null
dragCurrentItem: ExcelFieldItem | null
dragPlaceholderIndex: number
selection: SelectionState          // type: column | label | dataRow | sheet | null

// 操作快照
undoStack: ExcelStateSnapshot[]
redoStack: ExcelStateSnapshot[]
maxSnapshot: number                // 默认 20

// 全局配置
config: DesignerConfig

// 字段库(当前 Sheet 表单 schema)
availableFields: FormField[]

核心类型定义

interface MasterDetailRelation {
  masterKey: string               // 主表字段 key
  detailKey: string               // 子表字段 key
}

interface ExcelSheet {
  id: string
  name: string
  exportTarget?: ExportTarget     // 'form' | 'database',默认 'form'
  moduleId: string
  formId: string
  dataSourceId?: string           // exportTarget === 'database'
  targetTableId?: string          // exportTarget === 'database'
  sheetType: 'master' | 'detail'
  fieldList: ExcelFieldItem[]
  labelRows: ColumnLabelCell[]
  dataRow: DataRowConfig
}

interface ExcelFieldItem {
  id: string
  key: string
  name: string
  required: boolean
  dataType: DataType              // string | number | date | time | datetime | phone | email | idCard
  validationRule?: ValidationRule // none | numberRange | dateRange | textLength | regex,默认 none
  lengthLimit?: [number, number]
  dateRange?: [string, string]
  timeRange?: [string, string]
  pattern?: string
  patternTip?: string
  fillMode?: FillMode             // input | select,默认 input
  selectOptions?: string[]
  numberFormat?: NumberFormat
  dateFormat?: DateOnlyFormat
  timeFormat?: TimeFormat
  datetimeFormat?: DateTimeFormat
  defaultValue?: string
  transform?: Record<string, string>
  relation?: MasterDetailRelation // 主从关联,仅从 Sheet 字段有效
}

interface LabelCellStyle {
  fontSize?: number
  fontWeight?: string | number
  fontStyle?: string              // 'italic'
  fontItalic?: boolean
  textDecoration?: string         // 'underline' | 'line-through'
  color?: string
  backgroundColor?: string
}

interface ColumnLabelCell {
  id: string
  columnIndex: number
  rowLevel: number
  text: string
  align: 'left' | 'center' | 'right'
  colspan: number
  rowspan: number
  style?: LabelCellStyle
}

interface DataRowConfig {
  height: number                  // 行高(pt),默认 15
  isExample: boolean
  style?: LabelCellStyle | string  // 对象或 JSON 字符串;服务端解析为 JsonCellStyle
}

interface DesignerConfig {
  showRequiredMark: boolean       // 默认 true
  openValidateTip: boolean        // 默认 true
  excelRowHeight: number          // 全局行高(pt),默认 15;非数据行及 dataRow.height 为空时的兜底
}

interface TemplateJson {
  templateName: string
  templateDesc: string
  sheets: ExcelSheet[]
  config: DesignerConfig
}

interface ExcelStateSnapshot {
  templateName: string
  templateDesc: string
  sheets: ExcelSheet[]
  activeSheetId: string | null
  timestamp: number
}

interface HostRequestBridge {
  getModules(): Promise<ModuleItem[]>
  getModuleForms(moduleId: string): Promise<FormItem[]>
  getFormSchema(moduleId: string, formId: string): Promise<FormField[]>
  getAppDataSources(appId: string): Promise<DataSourceItem[]>
  getDataSourceTables(appId: string, dataSourceId: string): Promise<DataSourceTableItem[]>
  getDataSourceTableSchema?(appId: string, dataSourceId: string, tableId: string): Promise<FormField[]>
}

核心 Actions

分类 Actions
Sheet addSheetdeleteSheetupdateSheetsetActiveSheet
主从 setSheetType
字段 addFieldaddFieldFromFormdeleteFieldupdateField(含 relation.detailKey 同步)、sortField
标签 updateLabelCelladdLabelRow
数据行 updateDataRow
选中 setSelectionsetAvailableFields
快照 recordSnapshotundoredoclearSnapshot
模板 loadTemplatesaveTemplateexportTemplateJsonclearCanvas

Getters

  • activeSheet:当前激活 Sheet
  • canUndo / canRedo
  • templateJson:实时序列化结果
  • masterSheet:主 Sheet 引用

项目目录结构

web/obpm-designer-excelimp/
├── src/
│   ├── components/
│   │   └── ExcelDesigner/
│   │       ├── index.vue           # 入口组件(DndProvider 包裹)
│   │       ├── Toolbar.vue         # 工具栏(SVG 图标 + JSON 模板弹窗)
│   │       ├── JsonTemplateModal.vue  # JSON 模板查看/编辑弹窗
│   │       ├── ConfirmModal.vue    # 通用确认弹窗
│   │       ├── DragFieldList.vue
│   │       ├── DragCanvas.vue
│   │       ├── CanvasColumn.vue    # 字段名列拖拽
│   │       ├── FieldItem.vue
│   │       ├── FieldSetting.vue
│   │       ├── fieldOptions.ts     # 列信息选项常量
│   │       ├── LabelSetting.vue
│   │       ├── SheetSetting.vue
│   │       ├── SheetTabs.vue
│   │       └── constants.ts
│   ├── store/
│   │   └── excelDesign.ts
│   ├── utils/
│   │   ├── excel.ts                # 从表单字段创建 ExcelFieldItem
│   │   ├── labelRow.ts             # 标签行渲染 / 合并 / 列索引同步
│   │   ├── preview.ts              # columnLetter 等
│   │   ├── snapshot.ts
│   │   ├── serialize.ts
│   │   ├── validate.ts
│   │   ├── deepClone.ts
│   │   └── hostApi.ts              # FakeHostApi
│   ├── types/
│   │   └── index.ts
│   ├── style/
│   │   ├── index.scss
│   │   └── preview-table.scss
│   ├── index.ts                    # 库模式入口
│   ├── App.vue
│   └── main.ts
├── vite.config.ts
└── package.json

Vite 打包 & ESM 组件导出

打包配置

// vite.config.ts(mode === 'lib' 时生效)
build: {
  lib: {
    entry: 'src/index.ts',
    name: 'ExcelImportDesigner',
    fileName: (format) => `excel-import-designer.${format}.js`,
    formats: ['es', 'umd'],
  },
  rollupOptions: {
    external: ['vue', 'pinia', 'vue3-dnd', 'react-dnd-html5-backend'],
  },
}

package.json 导出

{
  "name": "obpm-designer-excelimp",
  "main": "./dist/excel-import-designer.umd.js",
  "module": "./dist/excel-import-designer.es.js",
  "types": "./dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/excel-import-designer.es.js",
      "require": "./dist/excel-import-designer.umd.js",
      "types": "./dist/index.d.ts"
    }
  }
}

对外导出(src/index.ts

export { ExcelImportDesigner, useExcelDesignStore }
export * from './types'
export * from './utils/excel'
export * from './utils/serialize'
export * from './utils/validate'
export * from './utils/hostApi'
export default ExcelImportDesigner

第三方引用示例

<script setup lang="ts">
import ExcelImportDesigner, { useExcelDesignStore, FakeHostApi } from 'obpm-designer-excelimp'
import type { TemplateJson } from 'obpm-designer-excelimp'

const bridge = new FakeHostApi() // 或宿主实现的 HostRequestBridge

function handleChange(template: TemplateJson) {
  // 配置变更时持久化 template(如 debounce 后 PUT excelconfigs)
}
</script>

<template>
  <ExcelImportDesigner
    :host-request-bridge="bridge"
    app-id="my_app_id"
    :enable-master-detail="true"
    @change="handleChange"
  />
</template>
const store = useExcelDesignStore()
store.undo()
store.exportTemplateJson()
// Excel 模板导出:宿主调用服务端 export-excel 接口

组件属性与事件(对外 API)

Props

Prop 类型 默认 说明
templateJson TemplateJson 初始模板,支持回显
disabled boolean false 禁用编辑
maxStep number 20 最大撤销步数
showToolbar boolean true 是否展示工具栏
defaultFields FormField[] 预留:预设字段(当前未使用)
enableMasterDetail boolean false 启用主从多 Sheet 模式
hostRequestBridge HostRequestBridge FakeHostApi 宿主模块/表单/数据源/字段 API
appId string 应用 ID,拉取数据源与目标表;Mock 默认 demo_app

Emits

事件 payload 说明
change TemplateJson 配置变更(含编辑、撤销/重做、JSON 模板应用等)
undo 撤销后触发
redo 重做后触发

样式与兼容性

  • 组件使用 scoped SCSS,画布表格复用 preview-table.scss
  • .form-group 表单项为上下结构(标签在上、控件在下,index.scss
  • 兼容 Vue 3.3+、Pinia 2+/3+、Vite 8+
  • Peer Dependencies:vuepiniavue3-dnd

已知限制与后续规划

状态 说明
模板预览弹窗 已移除 早期 TemplatePreview 组件已删除,以画布实时预览为准
必填标识 配置已有 config.showRequiredMark 由服务端导出时追加 *
校验提示 部分实现 服务端导出已写入数据验证;config.openValidateTip 全局开关待统一

核心亮点

  1. 完全解耦可复用:独立 ESM/UMD 组件,宿主 API 桥接
  2. 多级表头完备:colspan + rowspan 组合合并,画布预览与服务端导出规则一致
  3. 完整操作回溯:Pinia 快照 + deepClone,覆盖标签与字段全部操作
  4. 模板能力完备:JSON 模板弹窗编辑 + 服务端 Excel 模板生成,Excel 仅含业务表头与数据行
  5. TypeScript 全量支持:类型完整导出
  6. FakeHostApi 开箱调试:无需宿主即可本地开发验证

设计时态后端实现(Java)

Excel 导入后端分 设计时态obpm-designer)与 运行时态obpm-runtime),领域模型与持久化逻辑在 obpm-core。前端组件库(obpm-designer-excelimp)通过 hostRequestBridge 桥接,不直接访问后端obpm-designer-vue3 负责实现桥接层,将 TypeScript 接口映射为下方 HTTP 端点。

模块职责

模块 职责
obpm-designer 设计时 REST 控制器:Excel 导入配置 CRUD、导入模板导出、模块/表单/数据源元数据
obpm-core IMPMappingConfigDesignTimeService:配置持久化;ExcelMappingDiagram / ImpExcelToDoc:运行时 XML 解析与导入执行;ExcelImportRuntimeService / JsonImportProvider / ImportPlanBuilder:JSON 模板运行态导入;ExcelTemplateExportService / ExcelTemplateBuilder / ExcelTemplateStyleHelper:按 jsonTemplate 生成导入模板
obpm-common HttpDownloadHelper:导出响应头(中文文件名 RFC 5987 编码)
obpm-runtime 视图 Excel 导入/校验动作入口;视图数据导出 Excel导入样例模板下载ExcelImportRuntimeController:运行时动态导入模板导出
obpm-designer-vue3 旧版 iframe 画布(public/excelHtml)及高级工具列表页(ExcelConf.vue);新版设计器桥接(ObpmExcelHostBridge)待接入

存储模型

复用现有 IMPMappingConfigVOcn.myapps.core.common.model.excelimport.IMPMappingConfigVO),文件系统持久化,挂在应用(parentId = applicationId)下。

字段 用途
id 配置 ID,对应视图动作 impmappingconfigid
name 配置名称(应用内唯一)
description 配置说明
applicationid / parentId 所属应用 ID
templateType 模板类型:空 / EXCEL_XML 为旧画布;EXCEL_JSON 为新版 JSON
xml 旧版画布映射 XML(ExcelMappingDiagram 序列化,CDATA 存储)
jsonTemplate 新版设计器完整 JSON(TemplateJson,CDATA 存储)
templatePath 上传的 Excel 样例模板路径,前台导入时可下载参考

持久化路径遵循 IMPMappingConfigVO.getPath(),保存在 storage/workspace/{applicationId}/excelconfig/ 下(ModelSuffix.EXCEL_IMPORT_CFG_*)。

// obpm-core/.../IMPMappingConfigVO.java
public static final String TYPE_EXCEL_XML = "EXCEL_XML";
public static final String TYPE_EXCEL_JSON = "EXCEL_JSON";

@XmlElement
private String templateType;

@XmlJavaTypeAdapter(CDataAdapter.class)
private String xml;

@XmlJavaTypeAdapter(CDataAdapter.class)
private String jsonTemplate;

@XmlElement
private String templatePath;

public boolean isExcelJsonTemplate() { ... }
public boolean isExcelXmlTemplate() { ... }

读取兼容getXml() 自动将历史包名 cn.myapps.core.dynaform.dts.excelimport 替换为 cn.myapps.runtime.dynaform.dts.excelimport,保证升级后旧配置仍可解析。

统一约定

Base URL

服务 端口 基础路径
设计器 8082 http://{host}:8082{contextPath}/api/designtime

contextPathmyapps.context-path.designer 配置,默认可为空(如 /designer)。

响应格式

与现有设计器 API 一致,返回 Resource 对象:

{
  "errcode": 0,
  "errmsg": "ok",
  "data": { }
}

旧 ExcelImp 实现(XML 画布)

状态:已实现
Controller:cn.myapps.designtime.dts.excelimport.config.controller.ExcelConfigsController
Service:cn.myapps.core.designtime.dts.excelimport.config.service.IMPMappingConfigDesignTimeService / IMPMappingConfigDesignTimeServiceImpl
DAO:cn.myapps.core.common.dao.excelimport.FileSystemIMPMappingConfigDAO
基础路径:/api/designtime/applications/{applicationId}/excelconfigs

IMPMappingConfigDesignTimeService 继承标准 DesignTimeService<IMPMappingConfigVO>,通过 DesignTimeServiceManager.impMappingConfigDesignTimeService() 获取实例,并注册于 DesignTimeServiceFactory

类结构

classDiagram
    class ExcelConfigsController {
        +doGetExcelList()
        +doGetExcelDetailed()
        +doCreateForm()
        +doUpdateForm()
        +doDeleteForm()
        +exportExcelByConfigId()
        +exportExcelByBody()
    }
    class IMPMappingConfigDesignTimeService {
        +query()
        +findById()
        +save()
        +update()
        +delete()
        +mergeOnUpdate()
        +normalizeBeforeSave()
        +validateBeforeSave()
    }
    class IMPMappingConfigVO {
        +templateType
        +xml
        +jsonTemplate
        +templatePath
        +isExcelJsonTemplate()
        +isExcelXmlTemplate()
    }
    class ExcelMappingDiagram {
        +MasterSheet
        +DetailSheet
        +Column
        +Relation
    }
    ExcelConfigsController --> IMPMappingConfigDesignTimeService
    IMPMappingConfigDesignTimeService --> IMPMappingConfigVO
    ExcelMappingDiagram --> IMPMappingConfigVO : xml 序列化

旧版 XML 映射结构

设计器 iframe(obpm-designer-vue3/public/excelHtml)将画布导出为 ExcelMappingDiagram XML,运行时由 Factory.trnsXML2Dgrm(xml) 反序列化:

XML 元素 说明
ExcelMappingDiagram 根节点,含 descriptionneedRollback
MasterSheet 主工作表,绑定主表单
DetailSheet 子工作表,绑定子表单
Column 列映射:fieldNameprimaryKeyvalueScriptvalidateRule
Relation 工作表与列的关联线

接口清单

GET /{applicationId}/excelconfigs

获取当前应用下的 Excel 导入配置列表。

查询参数

参数 类型 必填 说明
name string 按名称模糊查询
pageNo int 页码,默认 1
linesPerPage int 每页条数,默认 10

响应 dataDataPackage<IMPMappingConfigVO>


GET /{applicationId}/excelconfigs/{excelConfigId}

获取配置详情。旧版配置返回 xml + templatePath;新版配置(templateType = EXCEL_JSON)返回 jsonTemplate + templatePathxml 为空。

实现IMPMappingConfigDesignTimeService.findById


POST /{applicationId}/excelconfigs

新建 Excel 导入配置。

请求体IMPMappingConfigVO JSON。

字段 类型 必填 说明
name string 配置名称(应用内唯一)
description string 配置说明
templateType string 空 / EXCEL_XML 为旧版;EXCEL_JSON 为新版
xml string 旧版画布 XML(templateTypeEXCEL_JSON 时使用)
jsonTemplate string 条件 templateType = EXCEL_JSON 时必填,完整 TemplateJson 字符串
templatePath string 样例 Excel 模板路径

服务端分配 idSequence.getDesignTimeSequence()),强制 applicationid / parentId 为路径中的应用 ID。

保存流程normalizeBeforeSavedoSaveValidate(名称唯一)→ validateBeforeSave(JSON 格式校验)。

响应 data{ "id": "..." }


PUT /{applicationId}/excelconfigs/{excelConfigId}

更新配置,请求体字段同 POST,id 取自路径。

更新合并mergeOnUpdate 在请求体未携带 jsonTemplate / xml / templatePath / description 时,自动保留已有值,避免部分更新清空模板内容。

保存流程mergeOnUpdatenormalizeBeforeSavedoSaveValidatevalidateBeforeSaveupdate


DELETE /{applicationId}/excelconfigs

批量删除,请求体为 ID 数组 string[]


前端接入(旧版)

场景 实现
高级工具列表 obpm-designer-vue3/src/components/AdvancedTool/ExcelConf.vue
画布设计 iframe 加载 public/excelHtml/excelHtmlTem.html
API 封装 AdvancedToolAPI.jsgetExcelConfList / updataExcelConf / deleteExcelConfList
视图动作绑定 ViewApi.js 拉取配置列表,视图 Activity.impmappingconfigid 关联配置

新 ExcelImp 兼容设计(JSON 模板)

状态:已实现(设计时态)
在不破坏旧版 xml 配置的前提下,为新版 obpm-designer-excelimpTemplateJson 提供独立存储字段,模式参考打印设计器 Report.printTemplate + templateType

IMPMappingConfigVO 扩展

/** 旧版 iframe 画布 XML 映射 */
public static final String TYPE_EXCEL_XML = "EXCEL_XML";

/** 新版 JSON Excel 导入设计器模板 */
public static final String TYPE_EXCEL_JSON = "EXCEL_JSON";

/** 模板类型:空或 EXCEL_XML 表示旧画布 XML;EXCEL_JSON 表示新版 JSON */
@XmlElement
private String templateType;

/** 新版设计器完整 JSON(templateName + sheets + config),CDATA 存储 */
@XmlJavaTypeAdapter(CDataAdapter.class)
private String jsonTemplate;

public boolean isExcelJsonTemplate() { return TYPE_EXCEL_JSON.equals(templateType); }
public boolean isExcelXmlTemplate() { return templateType 为空或 TYPE_EXCEL_XML; }
templateType 读写字段 设计器
空 / EXCEL_XML xml + templatePath 旧 iframe 画布
EXCEL_JSON jsonTemplate + templatePath(可选,由服务端 export-excel 生成后上传) obpm-designer-excelimp

IMPMappingConfigDesignTimeService 扩展方法

方法 说明
mergeOnUpdate(existing, incoming) 更新时合并已有配置;请求体未携带的 jsonTemplate / xml / templatePath / description 保留原值
normalizeBeforeSave(vo) EXCEL_JSON 时清空 xmlEXCEL_XML(或空)时清空 jsonTemplate 并补全 templateType
validateBeforeSave(vo) EXCEL_JSON 时校验 jsonTemplate 非空且为合法 JSON;不支持的 templateType 抛出校验异常

兼容策略

  1. 旧配置无 templateType,运行时继续走 vo.getXml()Factory.trnsXML2Dgrm
  2. 新配置 templateType = EXCEL_JSON 时,xml 留空;保存/加载仅读写 jsonTemplate
  3. ExcelConfigsController 的 POST/PUT 路径不变,请求体增加 templateType / jsonTemplate 即可;doSaveValidate 仍校验 name 唯一。
  4. 应用概览 PDF(ExcelImportConfigOverview)已区分 JSON / XML 模板类型展示。
  5. 运行时 JSON 解析器已实现:TemplateJson.sheetsImportPlan,与旧 ImpExcelToDoc 并列,在 ActivityRunTimeServiceImpl 内按 templateType 分支。

TemplateJson 与旧 XML 概念映射

新版 TemplateJson 旧版 XML
sheets[].sheetType: 'master' MasterSheet
sheets[].sheetType: 'detail' + fieldList[].relationmasterKeydetailKey DetailSheet + Relation(工作表与列关联线)
sheets[].fieldList[]name / key / 校验 / 默认值) ColumnfieldName / validateRule / valueScript
sheets[].labelRows[](多级表头) 旧版无等价结构,运行时按列序读取
sheets[].exportTargetform / database 旧版仅支持表单绑定

hostRequestBridge 映射(设计态)

新版组件 HostRequestBridge 所需元数据 API 大部分复用现有端点,无需为 Excel 导入单独新建模块/数据源接口:

前端方法 HTTP 端点 说明
getModules() GET /api/designtime/applications/{appId}/modules?parentId={appId} 复用 ModuleDesignTimeController
getModuleForms(moduleId) GET /api/designtime/applications/{appId}/modules/{moduleId}/forms 复用 FormController
getFormSchema(moduleId, formId) GET /api/designtime/applications/{appId}/print/form-schema?moduleId=&formId= 复用 PrintDesignTimeController.getFormSchema(返回 name / label 字段列表)
getAppDataSources(appId) GET /api/designtime/applications/{appId}/datasources 复用 DataSourceController
getDataSourceTables(appId, dsId) GET /api/designtime/applications/{appId}/datasources/metadatas?datasourceId={dsId}&subNodes=isTables 复用元数据树接口,取表节点
getDataSourceTableSchema(appId, dsId, tableId) GET /api/designtime/applications/{appId}/datasources/{dsId}/forms/{tableId}/info 复用 DataSourceController.getTable(列名 / 类型)
加载配置 GET /api/designtime/applications/{appId}/excelconfigs/{id} 回显 jsonTemplate(新)或 xml(旧)
保存配置 PUT /api/designtime/applications/{appId}/excelconfigs/{id} 写入 templateType=EXCEL_JSON + jsonTemplate
新建配置 POST /api/designtime/applications/{appId}/excelconfigs 同上
导出 Excel 模板(服务端,已保存配置) GET /api/designtime/applications/{appId}/excelconfigs/{id}/export-excel 下载文件名取 IMPMappingConfigVO.name;见「Excel 导出接口(后端)」
预览导出 Excel 模板(服务端) POST /api/designtime/applications/{appId}/excelconfigs/export-excel 请求体含 name + jsonTemplate 或完整 TemplateJson;文件名优先 name,否则 templateName
上传样例模板 POST /api/designtime/applications/{appId}/uploads?path=... 回写 templatePath

obpm-designer-vue3 中计划新增 ObpmExcelHostBridge(命名参考 ObpmPrintHostBridge),实现 HostRequestBridge 并注入 @myapps/obpm-designer-excelimpExcelImportDesigner

保存时请求体示例(新版)

{
  "name": "订单导入模板",
  "description": "主从订单导入",
  "templateType": "EXCEL_JSON",
  "jsonTemplate": "{\"templateName\":\"订单导入模板\",\"templateDesc\":\"...\",\"sheets\":[...],\"config\":{...}}",
  "templatePath": "/uploads/excel/order-template.xlsx"
}

运行时态

Excel 导入运行态由 obpm-runtimeActivityController 提供 HTTP 入口,核心导入逻辑在 obpm-coreImpExcelToDoc / AbstractImportProvider)。前台视图「导入 Excel」动作(ActivityType.EXCEL_IMPORT = 27)通过 Activity.impmappingconfigid 关联设计态配置,上传的 Excel 文件路径由前台传入。

统一约定

Base URL

服务 端口 基础路径
运行时 8083 http://{host}:8083{contextPath}/api/runtime

contextPathmyapps.context-path.runtime 配置,默认可为空(如 /obpm)。

响应格式

与现有运行时 API 一致,返回 Resource 对象。导入成功时 data 为国际化结果字符串;失败时 errcode = 4001data 为错误信息数组($$ 分隔的多条校验错误)。


前台调用流程

sequenceDiagram
    participant V as excel_upload.vue
    participant U as 文件上传
    participant A as ActivityController
    participant S as ActivityRunTimeServiceImpl

    V->>V: 下载样例模板(Activity.excelTemplate)
    V->>U: 上传 .xls/.xlsx,获得 path
    opt 校验
        V->>A: POST validationExcel
        A->>S: validationExcel()
    end
    V->>A: POST importExcel(轮询 readProcess)
    A->>S: improtExcel()
    S-->>V: 导入结果 / 进度
  1. 视图加载时,ViewControllerActivity.excelTemplate(来自 IMPMappingConfigVO.templatePath)加密后下发,供前台下载样例模板。
  2. 用户选择 Excel 文件,经 FrontFileAndImageUploadServletPOST /api/runtime/uploadactionType=excelImport)上传,得到服务器相对路径 path
  3. 可选先调用 校验 接口;校验通过后再调用 导入 接口。
  4. 导入过程中前台每 500ms 轮询 readProcess 获取进度;完成后删除临时 Excel 文件。

已实现(XML 配置)

状态:已实现
Controller:cn.myapps.runtime.activity.controller.ActivityController
Service:cn.myapps.runtime.activity.service.ActivityRunTimeServiceImpl
基础路径:/api/runtime

类结构

classDiagram
    class ActivityController {
        +importExcel()
        +validationExcel()
        +readProcess()
        +readValidateProcess()
    }
    class ActivityRunTimeServiceImpl {
        +improtExcel()
        +validationExcel()
    }
    class ExcelImportRuntimeServiceImpl {
        +importExcel()
        +validationExcel()
    }
    class JsonImportProvider {
        +creatDocument()
        +validationDocument()
    }
    class ImpExcelToDoc {
        +creatDocument()
        +validationDocument()
    }
    class AbstractImportProvider {
        +creatDocument()
        +validationDocument()
    }
    class Factory {
        +trnsXML2Dgrm(xml)
    }
    ActivityController --> ActivityRunTimeServiceImpl
    ActivityRunTimeServiceImpl --> ExcelImportRuntimeServiceImpl : EXCEL_JSON
    ActivityRunTimeServiceImpl --> Factory : EXCEL_XML
    ActivityRunTimeServiceImpl --> ImpExcelToDoc : EXCEL_XML
    ExcelImportRuntimeServiceImpl --> JsonImportProvider
    ImpExcelToDoc --> AbstractImportProvider

接口清单

POST /{applicationId}/views/{viewId}/activities/importExcel

执行 Excel 数据导入。

查询参数

参数 类型 必填 说明
excelImportTime string 导入批次时间戳,用于进度缓存键

请求体Content-Type: application/json

字段 类型 必填 说明
impmappingconfigid string Excel 导入配置 ID
path string 已上传 Excel 的服务器相对路径
actId string 视图动作 ID
parentId string 主从导入时的父文档 ID
isRelate string 是否关联导入("true" / "false"
exparams object 扩展参数(appIdformIddocid 等)

请求体示例

{
  "impmappingconfigid": "__impConfig001",
  "path": "/uploads/excel/20260710/order.xlsx",
  "actId": "__actImport001",
  "parentId": "",
  "exparams": {
    "appId": "sOZu9kthmxyP8qQfq0e",
    "formId": "form_order",
    "docid": "",
    "parentId": "",
    "isRelate": false
  }
}

实现ActivityController.importExcel → 初始化进度缓存 → ActivityRunTimeServiceImpl.improtExcelIMPMappingConfigDesignTimeService.doView → 按 templateType 分支: - EXCEL_JSONExcelImportRuntimeServiceImpl.importExcelJsonImportProvider.creatDocument - EXCEL_XML / 空Factory.trnsXML2Dgrm(vo.getXml())ImpExcelToDoc.creatDocumentDocumentProcess.doCreateOrUpdate4ExcelImport

成功响应errcode = 0datacn.myapps.runtime.dynaform.dts.excelimport.success.total.imported 国际化键及导入行数。

失败响应errcode = 4001errmsg = "导入出错"data$$ 分隔的错误行数组。


POST /{applicationId}/views/{viewId}/activities/validationExcel

导入前校验 Excel 数据(不写入文档)。

查询参数

参数 类型 必填 说明
excelValidateTime string 校验批次时间戳

请求体:与 importExcel 相同。

实现ActivityRunTimeServiceImpl.validationExcel → 按 templateType 分支: - EXCEL_JSONExcelImportRuntimeServiceImpl.validationExcelJsonImportProvider.validationDocument - EXCEL_XML / 空ImpExcelToDoc.validationDocumentValidationDocumentImprotProvider


GET /importExcel/readProcess

轮询导入进度。

查询参数excelImportTime

响应 data

字段 说明
excelImportCount 已处理行数
excelRowCount 总行数
importExcelResult 最终结果(导入完成后写入)

GET /validateExcel/readProcess

轮询校验进度,字段同上(缓存键前缀为 excelValidate*)。


Service 层处理

ActivityRunTimeServiceImplimprotExcel / validationExcel 内按 vo.isExcelJsonTemplate() 分支:

JSON 路径EXCEL_JSON):

ExcelImportRuntimeService jsonService = new ExcelImportRuntimeServiceImpl();
result = jsonService.importExcel(vo, excelPath, user, params, applicationId, viewId);
// 或 validationExcel(...)

XML 路径EXCEL_XMLtemplateType 为空,保持不变):

ExcelMappingDiagram em = Factory.trnsXML2Dgrm(vo.getXml());
ImpExcelToDoc imp = new ImpExcelToDoc(excelPath, em);
result = imp.creatDocument(em, excelPath, user, params, applicationId);
// finally: 删除临时 Excel 文件
职责
JsonImportProvider JSON 模板:读 Excel、校验、主从写入、进度上报
JsonExcelReader / JsonFieldValidator / JsonFieldValueConverter JSON 路径解析与字段处理
JsonFormImporter / JsonDatabaseImporter 表单 / 数据库表写入
XLSDocumentImprotProvider / XLSXDocumentImprotProvider XML 路径:按 .xls / .xlsx 解析工作簿
AbstractImportProvider XML 路径:遍历行、执行值脚本/校验脚本、主从写入、进度上报
ValidationDocumentImprotProvider XML 路径:仅校验、不写库

前台封装

文件 方法 说明
obpm-runtime-web/portal/vue3/src/api.js importExcel type=excelimport → importExcel;否则 → validationExcel
同上 importExcelProgress 轮询 /runtime/importExcel/readProcess
excel_upload.vue performExport 组装 impmappingconfigid / path / actId / exparams

新版 JSON 配置(已实现)

状态:已实现(设计态 jsonTemplate 存储 + 运行态 JSON 导入 + 服务端模板导出)
策略:复用现有 ActivityController 三端点,前台 excel_upload.vueapi.js 无需改动;在 ActivityRunTimeServiceImpl 内按 IMPMappingConfigVO.templateType 分支到 ExcelImportRuntimeService

命名约定:前端 TypeScript 接口为 TemplateJson;后端 Java 模型类为 JsonTemplatecn.myapps.core.runtime.dynaform.dts.excelimport.json.model),二者结构同构。

类结构

classDiagram
    class ActivityController {
        +importExcel()
        +validationExcel()
    }
    class ActivityRunTimeServiceImpl {
        +improtExcel()
        +validationExcel()
    }
    class ExcelImportRuntimeService {
        +importExcel(vo, excelPath, user, params)
        +validationExcel(vo, excelPath, user, params)
    }
    class ExcelImportRuntimeServiceImpl {
        +createProvider(vo) JsonImportProvider
    }
    class JsonImportProvider {
        +creatDocument()
        +validationDocument()
    }
    class JsonExcelReader {
        +readAllSheets(excelPath, plan)
    }
    class JsonFormImporter {
        +importRow()
    }
    class JsonDatabaseImporter {
        +importRow()
    }
    class ImpExcelToDoc {
        +creatDocument()
    }
    ActivityController --> ActivityRunTimeServiceImpl
    ActivityRunTimeServiceImpl --> ExcelImportRuntimeService : EXCEL_JSON
    ActivityRunTimeServiceImpl --> ImpExcelToDoc : EXCEL_XML
    ExcelImportRuntimeServiceImpl ..|> ExcelImportRuntimeService
    ExcelImportRuntimeServiceImpl --> JsonImportProvider
    JsonImportProvider --> JsonExcelReader
    JsonImportProvider --> JsonFormImporter
    JsonImportProvider --> JsonDatabaseImporter
模块 职责
ExcelImportRuntimeService obpm-core 接口:importExcel / validationExcel
ExcelImportRuntimeServiceImpl obpm-core 解析 jsonTemplate、构建 ImportPlan、创建 JsonImportProvider
JsonImportProvider obpm-core 实现 ImportProvider;复用 AbstractImportProvider 进度缓存键;调度读 Excel 与写入
TemplateJsonParser obpm-core jsonTemplate 字符串 → JsonTemplate;解析 labelRows[].styledataRow.style
ImportPlanBuilder obpm-core JsonTemplateImportPlan(Sheet 绑定、列索引、主从关联、数据起始行)
ImportPlan obpm-core 运行时中间结构
JsonExcelReader obpm-core POI 读取 Excel,跳过标签行,按列序提取数据行
JsonFieldValidator obpm-core 必填、数据类型、下拉、数值/日期/文本长度/正则校验
JsonFieldValueConverter obpm-core 字段类型转换(number/date/time/datetime 等)
JsonFormImporter obpm-core exportTarget=form:校验后调用 DocumentProcess.doCreateOrUpdate4ExcelImport
JsonDatabaseImporter obpm-core exportTarget=database:JDBC insert/update(主键存在则更新)

ActivityRunTimeServiceImpl 分支改造

IMPMappingConfigVO vo = process.doView(applicationId, impmappingconfigid);

if (vo.isExcelJsonTemplate()) {
    ExcelImportRuntimeService jsonService = new ExcelImportRuntimeServiceImpl();
    if (isValidate) {
        return jsonService.validationExcel(vo, excelPath, user, params, applicationId, viewId);
    }
    return jsonService.importExcel(vo, excelPath, user, params, applicationId, viewId);
}

// 旧版 XML 路径(保持不变)
ExcelMappingDiagram em = Factory.trnsXML2Dgrm(vo.getXml());
ImpExcelToDoc imp = new ImpExcelToDoc(excelPath, em);
return imp.creatDocument(em, excelPath, user, params, applicationId);

JSON 导入处理流程

  1. 加载配置doView 读取 IMPMappingConfigVOisExcelJsonTemplate() 为真时取 jsonTemplate
  2. 解析模板TemplateJsonParser 反序列化为 JsonTemplatesheets + config)。
  3. 构建导入计划
  4. sheets[].exportTarget 区分表单(form)与数据库表(database)写入目标;
  5. sheetType: 'master' 对应主记录;'detail' 通过 fieldList[].relationmasterKeydetailKey)关联主 Sheet;
  6. 列定位:跳过 labelRows 标签行,从首个数据行起按 fieldList 列序读取(支持多级表头后的固定数据起始行)。
  7. 逐行处理(复用 AbstractImportProvider 进度上报):
  8. 字段类型转换(fieldType / numberFormat / dateFormat 等);
  9. 校验规则(validationRulevalidationMin/MaxregexPattern、下拉列表);
  10. 默认值与 transform 字段转换;
  11. 主键唯一性(isPrimaryKey)→ 决定 create 或 update。
  12. 写入:表单模式调用 DocumentProcess.doCreateOrUpdate4ExcelImport;数据库模式直接 JDBC 写入目标表(新增)。
  13. 清理:与 XML 路径相同,finally 删除临时 Excel 文件。

XML 与 JSON 运行时差异

能力 XML 旧版 JSON 新版
配置来源 vo.getXml()ExcelMappingDiagram vo.getJsonTemplate()JsonTemplate
表头定位 Relation 关联工作表与 Column labelRows 行级 + fieldList 列索引
主从结构 MasterSheet / DetailSheet sheetType + relation 字段
字段校验 validateRule iScript validationRule 内置规则 + 可扩展脚本
值处理 valueScript iScript transform 映射 + 可扩展脚本
数据库表导入 不支持 exportTarget = 'database'
多级表头 不支持 labelRows colspan/rowspan 定位数据行
HTTP 端点 ActivityController 现有三端点 相同,无需新 Controller

错误与进度

JSON 路径**复用** AbstractImportProviderMemoryCacheUtil 键(EXCELIMPORTCOUNTEXCELIMPORTROWCOUNTEXCELIMPORTRESULT 等),保证 readProcess 轮询逻辑不变。错误消息格式与 XML 路径一致(工作表名 {Row}[行号]: 错误详情),多条以 $$ 拼接返回。


Excel 导出接口(后端)

平台中与 Excel 相关的「导出」分两类,本章节说明后端 HTTP 入口(设计时态 + 运行时态):

类型 场景 产出物 典型调用方
导入模板导出 设计器配置完成 / 前台导入前 带表头、校验、示例行的**空白**导入模板 服务端 export-excelexcel_upload.vue
视图数据导出 视图列表页「导出 Excel」按钮 当前视图查询结果的 .xlsx 数据文件 excel_export.vue

导入模板导出与视图数据导出**职责不同**:前者服务「填表再导入」流程;后者服务「把已有数据导出备份/分析」流程(ActivityType.EXPTOEXCEL = 16)。


设计时态 · 导入模板导出

方式一:上传样例模板(已实现)

宿主将服务端生成的 Excel(或用户自备模板)上传至服务器,路径写入 IMPMappingConfigVO.templatePath,供运行时前台下载参考。推荐在宿主监听 change 并持久化时,先调用 export-excel 获取二进制流,再上传至 uploadsObpmExcelHostBridge 联动方案见方式二末尾)。

说明
Controller cn.myapps.designtime.common.controller.UploadDesignTimeController
端点 POST /api/designtime/applications/{applicationId}/uploads
表单字段 fileMultipartFile[]
查询参数 path:存储子目录(旧版 Excel 配置 iframe 传 path=/exceltemplate 等)

响应 dataJSONArray):

[
  {
    "fileName": "订单导入模板.xlsx",
    "filePath": "/resources/2026/uuid.xlsx"
  }
]

保存 Excel 配置时,将 filePath 写入 templatePath 字段(PUT/POST excelconfigs)。

旧版 iframe 上传页:obpm-designer-vue3/public/excelHtml/upload.html/{contextPath}/designtime/applications/{appId}/uploads


方式二:服务端按 jsonTemplate 生成(已实现)

状态:已实现
Controller:cn.myapps.designtime.dts.excelimport.config.controller.ExcelConfigsController
Service:cn.myapps.core.runtime.dynaform.dts.excelimport.json.ExcelTemplateExportService / ExcelTemplateExportServiceImpl
Builder:cn.myapps.core.runtime.dynaform.dts.excelimport.json.ExcelTemplateBuilder(Apache POI XSSFWorkbook
Style:cn.myapps.core.runtime.dynaform.dts.excelimport.json.ExcelTemplateStyleHelper
响应头:cn.myapps.common.util.HttpDownloadHelperobpm-common
目标:设计器保存、批量生成、服务端预览、自动同步 templatePath 等场景,由 Java 按 TemplateJson 生成 .xlsx(规则见上文「Excel 模板生成规则(服务端)」)。

端点

方法 路径 说明
GET /api/designtime/applications/{applicationId}/excelconfigs/{excelConfigId}/export-excel 按已保存配置导出(exportExcelByConfigId
POST /api/designtime/applications/{applicationId}/excelconfigs/export-excel 按请求体预览导出(exportExcelByBody,无需先保存)

下载文件名(无 filename 查询参数;Controller 内 toXlsxFilename() 自动补 .xlsx):

端点 文件名来源
GET 按配置导出 IMPMappingConfigVO.name
POST 预览导出 请求体 name → 否则 TemplateJson.templateName → 否则 excel-template.xlsx
运行时动态导出 IMPMappingConfigVO.name

POST 请求体:完整 TemplateJson JSON 对象;亦支持 { "name": "...", "jsonTemplate": "..." } 包装形式。

响应Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheetContent-Disposition 通过 HttpDownloadHelper 设置(filename + filename*=UTF-8'' 百分号编码,支持中文);响应体为二进制流( Resource JSON)。

实现

模块 职责
ExcelConfigsController.exportExcelByConfigId / exportExcelByBody obpm-designer HTTP 入口,解析下载名、写流
ExcelImportRuntimeController.exportExcel obpm-runtime 运行时动态导出入口
ExcelTemplateExportService obpm-core 接口:exportFromConfig / exportFromTemplateJson
ExcelTemplateExportServiceImpl obpm-core 加载配置、解析 JSON、调用 Builder
ExcelTemplateBuilder obpm-core 构建工作簿、合并、数据验证
ExcelTemplateStyleHelper obpm-core 标签/数据单元格样式、字段 numFmt
TemplateJsonParser obpm-core jsonTemplateJsonTemplate(含 JsonCellStyle
HttpDownloadHelper obpm-common 附件响应头与中文文件名编码

生成规则(与上文「Excel 模板生成规则(服务端)」一致):

  • 每个 sheets[] 对应一个 POI Sheet,名称取 sheet.name(最长 31 字符);
  • 写入 labelRows 标签行 + 合并区域(colspan/rowspan);无 labelRows 时用字段 name 生成默认表头(showRequiredMark 时追加 *);
  • 标签样式:读取 labelRows[].styleLabelCellStyle),经 ExcelTemplateStyleHelper.createLabelStyle 写入字体/颜色/背景/对齐/边框;
  • 数据行样式:读取 dataRow.style(对象或 JSON 字符串),与字段 numberFormat / dateFormat 等合并;
  • 自数据行起预置 **500 行**格式单元格(ExcelTemplateBuilder.TEMPLATE_FORMAT_ROW_COUNT);
  • 数据行行高(pt,默认 15)写入 POI Row.setHeightInPoints
  • 标签字号(pt,默认 11)写入 POI 字体 setFontHeightInPoints
  • 数据起始行与导入一致:sheet.getDisplayLabelLevelCount()(见 ImportPlanBuilder);
  • 数据类型:number/date/time/datetime 空白单元格保留数值型(setBlank())并设置对应 DataFormat;示例行 defaultValue 按类型解析为序列值;
  • 数据验证validationRule === 'none' 时不写入,fillMode === 'select' 下拉除外):
  • 下拉:selectOptions(超长或含逗号时写入隐藏 sheet _validation_lists);
  • 数值范围 / 文本长度:lengthLimit
  • 日期/日期时间范围:dateRange,使用 Excel 公式 DATE() 比较(兼容 yyyy-MM-dd / yyyy/MM/dd 等输入格式);
  • 时间范围:timeRange,使用 TIME() 公式;
  • 正则 / 内置:regex 规则下 phone/email/idCard 使用自定义公式;
  • 必填:required=trueallowBlank=falseconfig.openValidateTip 时使用 patternTip 作为错误提示;
  • 不写入设计器辅助行(字段名行、列字母行)。

与持久化联动(可选)ObpmExcelHostBridge 在收到 change 并写入配置时,可先调用 export-excel,再将生成文件上传至 uploads,自动回写 templatePath

classDiagram
    class ExcelConfigsController {
        +exportExcelByConfigId()
        +exportExcelByBody()
    }
    class ExcelTemplateExportService {
        +exportFromConfig(appId, configId)
        +exportFromTemplateJson(jsonTemplate)
    }
    class ExcelTemplateStyleHelper {
        +createLabelStyle()
        +createDataStyle()
        +resolveNumFmt(field)
    }
    class HttpDownloadHelper {
        +setExcelAttachmentHeaders()
    }
    class ExcelTemplateBuilder {
        +buildWorkbook(template) XSSFWorkbook
        +TEMPLATE_FORMAT_ROW_COUNT
    }
    class ExcelImportRuntimeController {
        +exportExcel()
    }
    ExcelConfigsController --> ExcelTemplateExportService
    ExcelImportRuntimeController --> ExcelTemplateExportService
    ExcelConfigsController --> HttpDownloadHelper
    ExcelImportRuntimeController --> HttpDownloadHelper
    ExcelTemplateExportService --> ExcelTemplateBuilder
    ExcelTemplateBuilder --> ExcelTemplateStyleHelper

运行时态 · 导出相关接口

运行时态包含两条独立链路:导入样例模板下载(服务导入流程)与**视图数据导出**(EXPTOEXCEL 动作)。


导入样例模板下载(已实现)

前台「下载模板」默认下载设计态已保存的 templatePath 静态文件;当 templatePath 为空且配置为 EXCEL_JSON 时,可改调 GET .../excelimport/{configId}/export-excel 动态生成(见下文)。

端点 说明
DownloadController GET/POST /api/runtime/file/isFileExisted 校验模板文件是否存在
DownloadController GET/POST /api/runtime/file/download 下载文件流

查询参数

参数 类型 必填 说明
filepath string 文件相对路径(运行时加密传输)
filename string 下载显示名
applicationId string 应用 ID(/resources 路径时需解析 workspace)

调用链ViewController 下发 Activity.excelTemplateIMPMappingConfigVO.templatePath,按用户加密)→ excel_upload.vuegetFileExistedtemplateDownloadapi.js)→ DownloadController.doFileDownload

POST /obpm/api/runtime/file/download?filename=订单导入模板.xlsx&filepath={encryptedPath}&applicationId={appId}

响应为二进制文件流。


视图数据导出 Excel(已实现)

状态:已实现
Controller:cn.myapps.runtime.activity.controller.ActivityController
Service:ActivityRunTimeServiceImpl.exportExcel
核心构建:cn.myapps.core.common.model.view.ExcelFileBuilder(Apache POI SXSSFWorkbook
动作类型:ActivityType.EXPTOEXCEL(16)

与 Excel **导入**配置无直接耦合;按视图列定义与查询条件导出当前数据。

POST /{applicationId}/views/{viewId}/activities/exportExcel

查询参数

参数 类型 必填 说明
actId string 导出按钮动作 ID
filename string 文件名(无扩展名;优先使用视图 description
excelExportTime string 导出批次时间戳,用于进度缓存
isExpSub string 是否导出子表
parentId string 主从视图父文档 ID
isRelate string 是否关联导出

请求体

字段 类型 说明
selectColumns string 逗号分隔的列 ID,空则导出全部可见列
selectDocIds string[] 勾选导出的文档 ID;空数组表示按查询条件导出全部
items object 查询条件键值(常用查询 / 高级查询表单值)
fixedColumn string 冻结列 ID(可选)

请求体示例

{
  "selectColumns": "col_orderNo,col_customer,col_amount",
  "selectDocIds": ["doc001", "doc002"],
  "items": {
    "ITEM_状态": "已审核"
  }
}

响应:直接写入 HttpServletResponse 输出流,Content-Type: application/x-download,附件名 {filename}.xlsx JSON Resource)。

实现ActivityController.exportExcel → 初始化 ExcelFileBuilder 进度缓存 → ActivityRunTimeServiceImpl.exportExcelexpDocToExcelExcelFileBuilder.buildSheet(view, params, ids)toExcelFile() → 流式输出后删除临时文件。

ExcelFileBuilder 能力摘要:按视图列配置导出字段值;支持图片字段内嵌图、下拉选项脚本列、按 Column.excelVisible 过滤、主子表关联导出、导出过程进度写入 MemoryCacheUtil


GET /exportExcel/readProcess

轮询视图数据导出进度。

查询参数excelExportTime

响应 data

字段 说明
excelExportCount 已导出条数
excelRowCount 总条数(为 0 时前台按 100% 处理)
exportExcelResult 完成后写入的结果路径

前台封装api.jsexportExcel / exportExcelProgressexcel_export.vue 每 500ms 轮询。


运行时按 jsonTemplate 动态生成导入模板(已实现)

状态:已实现
Controller:cn.myapps.runtime.dts.excelimport.controller.ExcelImportRuntimeController
Service:复用 cn.myapps.core.runtime.dynaform.dts.excelimport.json.ExcelTemplateExportServiceobpm-core 共享)
当配置为 EXCEL_JSONtemplatePath 为空或需强制刷新时,运行态按 jsonTemplate 即时生成导入模板,避免依赖设计态上传文件。

端点

方法 路径 说明
GET /api/runtime/{applicationId}/excelimport/{configId}/export-excel impmappingconfigid 生成并下载;applicationId / configId 支持运行时加密 ID

下载文件名IMPMappingConfigVO.name + .xlsx(无查询参数)。

响应:与设计态导出相同,为 .xlsx 二进制流( JSON Resource);响应头由 HttpDownloadHelper 设置。

实现ExcelImportRuntimeController.exportExcelDesUtil.decryptTextByUserId 解密路径参数 → IMPMappingConfigDesignTimeService.findById 加载配置 → 校验 isExcelJsonTemplate()jsonTemplate 非空 → toXlsxFilename(config.getName())ExcelTemplateExportService.exportFromTemplateJson 写流。

错误:配置不存在、jsonTemplate 为空或非 EXCEL_JSON 时抛出 OBPMValidateException(400)。

与样例下载的关系

条件 行为
templatePath 非空 优先 file/download 下载静态文件(现有逻辑)
templatePath 为空且 jsonTemplate 非空 调用 export-excel 动态生成
均为空 返回 400,提示未配置模板

导出接口对照总表

端点 时态 状态 产出 主要类
POST .../uploads 设计时 已实现 上传样例模板 → templatePath UploadDesignTimeController
GET .../excelconfigs/{id}/export-excel 设计时 已实现 由已保存 jsonTemplate 生成 .xlsx ExcelConfigsController.exportExcelByConfigId
POST .../excelconfigs/export-excel 设计时 已实现 由请求体 TemplateJson 预览生成 .xlsx ExcelConfigsController.exportExcelByBody
POST .../file/download 运行时 已实现 下载 templatePath 样例 DownloadController
GET .../excelimport/{id}/export-excel 运行时 已实现 动态生成导入模板 ExcelImportRuntimeController
POST .../activities/exportExcel 运行时 已实现 视图查询结果 .xlsx ActivityController + ExcelFileBuilder
GET .../exportExcel/readProcess 运行时 已实现 视图导出进度 ActivityController