Browse Source
- 提取字典ID解析逻辑到WidgetJsonUtil.extractDictIdSet方法 - 移除OnlineFormController中的extractDictIdSetFromWidgetJson私有方法 - 提取字段名收集逻辑到WidgetJsonUtil.collectBindColumnNames方法 - 移除OnlineFormController中的collectAllBindColumnNames私有方法 - 提取移动端字段名收集逻辑到WidgetJsonUtil.buildMobileColumnNameWidgetMap方法 - 移除OnlineFormController中的collectMobileBindColumnNames私有方法 - 提取子表属性收集逻辑到WidgetJsonUtil.collectSubTableProps方法 - 移除OnlineFormController中的collectSubTableAndPropMap私有方法 - 提取字段名到控件映射构建逻辑到多个WidgetJsonUtil静态方法 - 移除OnlineFormController中相关的私有方法实现 - 添加字段路径步骤解析功能支持PC和移动端不同结构 - 实现统一的控件树遍历和递归处理机制feature/2026-07-ccc-dev
2 changed files with 470 additions and 337 deletions
@ -0,0 +1,457 @@ |
|||||||
|
package apelet.common.online.util; |
||||||
|
|
||||||
|
import apelet.common.core.constant.AppDeviceType; |
||||||
|
import cn.hutool.core.collection.CollUtil; |
||||||
|
import cn.hutool.core.map.MapUtil; |
||||||
|
import cn.hutool.core.util.StrUtil; |
||||||
|
import com.alibaba.fastjson.JSON; |
||||||
|
import com.alibaba.fastjson.JSONArray; |
||||||
|
import com.alibaba.fastjson.JSONObject; |
||||||
|
|
||||||
|
import java.util.HashMap; |
||||||
|
import java.util.HashSet; |
||||||
|
import java.util.Map; |
||||||
|
import java.util.Set; |
||||||
|
import java.util.function.Consumer; |
||||||
|
|
||||||
|
/** |
||||||
|
* widgetJson 数据操作工具类。 |
||||||
|
* 提供从在线表单 widgetJson 中提取字典、绑定字段、子表控件等信息的纯数据操作方法。 |
||||||
|
* |
||||||
|
* @author chenchuchuan |
||||||
|
* @date 2026-08-06 |
||||||
|
*/ |
||||||
|
public class WidgetJsonUtil { |
||||||
|
|
||||||
|
private WidgetJsonUtil() { |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 从 widgetJson 中提取所有控件绑定的字典Id。 |
||||||
|
* 仅遍历当前设备端(widgetList 及 childWidgetList)控件树中 props.dictInfo.dictId。 |
||||||
|
* |
||||||
|
* @param widgetJson 在线表单的 widgetJson。 |
||||||
|
* @return 字典Id集合。 |
||||||
|
*/ |
||||||
|
public static Set<Long> extractDictIdSet(String widgetJson) { |
||||||
|
Set<Long> dictIdSet = new HashSet<>(); |
||||||
|
if (StrUtil.isBlank(widgetJson)) { |
||||||
|
return dictIdSet; |
||||||
|
} |
||||||
|
JSONObject deviceData = JSON.parseObject(widgetJson).getJSONObject(AppDeviceType.getDeviceType()); |
||||||
|
if (MapUtil.isEmpty(deviceData)) { |
||||||
|
return dictIdSet; |
||||||
|
} |
||||||
|
JSONArray widgetListArray = deviceData.getJSONArray("widgetList"); |
||||||
|
if (CollUtil.isEmpty(widgetListArray)) { |
||||||
|
return dictIdSet; |
||||||
|
} |
||||||
|
for (int i = 0; i < widgetListArray.size(); i++) { |
||||||
|
extractDictIdRecursively(widgetListArray.getJSONObject(i), dictIdSet); |
||||||
|
} |
||||||
|
return dictIdSet; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 递归提取单个控件及其子控件中的字典Id。 |
||||||
|
*/ |
||||||
|
private static void extractDictIdRecursively(JSONObject widgetData, Set<Long> dictIdSet) { |
||||||
|
JSONObject propsData = widgetData.getJSONObject("props"); |
||||||
|
if (MapUtil.isNotEmpty(propsData)) { |
||||||
|
JSONObject dictInfoData = propsData.getJSONObject("dictInfo"); |
||||||
|
if (MapUtil.isNotEmpty(dictInfoData)) { |
||||||
|
Long dictId = dictInfoData.getLong("dictId"); |
||||||
|
if (dictId != null) { |
||||||
|
dictIdSet.add(dictId); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
JSONArray childWidgetArray = widgetData.getJSONArray("childWidgetList"); |
||||||
|
if (CollUtil.isNotEmpty(childWidgetArray)) { |
||||||
|
for (int i = 0; i < childWidgetArray.size(); i++) { |
||||||
|
extractDictIdRecursively(childWidgetArray.getJSONObject(i), dictIdSet); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 遍历 pc/mobile 控件树,收集所有控件 bindData.columnName 值。 |
||||||
|
* <p> |
||||||
|
* 每个读取源用"设备端 + 字段步骤链"描述,步骤支持对象字段 obj() 和数组字段 arr(): |
||||||
|
* <pre> |
||||||
|
* pc: widgetList -> bindData -> columnName |
||||||
|
* mobile: tableWidget -> childWidgetList -> childWidgetList -> bindData -> columnName |
||||||
|
* mobile: widgetList -> childWidgetList -> bindData -> columnName |
||||||
|
* </pre> |
||||||
|
* 后续新增读取源(如 otherWidgetList)时,只需在下方追加一行 |
||||||
|
* {@code collectColumnNamesByPath(widgetJson, names, "other", arr("otherWidgetList"))}。 |
||||||
|
* |
||||||
|
* @param widgetJson 在线表单的 widgetJson。 |
||||||
|
* @return 绑定的字段名列集合。 |
||||||
|
*/ |
||||||
|
public static Set<String> collectBindColumnNames(JSONObject widgetJson) { |
||||||
|
Set<String> names = new HashSet<>(); |
||||||
|
if (widgetJson == null) { |
||||||
|
return names; |
||||||
|
} |
||||||
|
// pc 端: widgetList -> bindData -> columnName
|
||||||
|
collectColumnNamesByPath(widgetJson, names, "pc", arr("widgetList")); |
||||||
|
// mobile 端(原 tableWidget 结构): tableWidget -> childWidgetList -> childWidgetList -> bindData -> columnName
|
||||||
|
collectColumnNamesByPath(widgetJson, names, "mobile", obj("tableWidget"), arr("childWidgetList"), arr("childWidgetList")); |
||||||
|
// mobile 端(兜底 widgetList 结构): widgetList -> childWidgetList -> bindData -> columnName
|
||||||
|
collectColumnNamesByPath(widgetJson, names, "mobile", arr("widgetList"), arr("childWidgetList")); |
||||||
|
return names; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 按"设备端 + 字段步骤链"读取 columnName。 |
||||||
|
* 从 widgetJson 的 mode 节点开始,依次下钻 steps 中的每个字段步骤, |
||||||
|
* 最后一个步骤的节点即为控件,取其 bindData.columnName。 |
||||||
|
* |
||||||
|
* @param widgetJson widgetJson 根对象。 |
||||||
|
* @param names 收集结果。 |
||||||
|
* @param mode 设备端 key,如 pc/mobile。 |
||||||
|
* @param steps 字段步骤链,支持对象字段 obj() 与数组字段 arr()。 |
||||||
|
*/ |
||||||
|
private static void collectColumnNamesByPath(JSONObject widgetJson, Set<String> names, String mode, PathStep... steps) { |
||||||
|
JSONObject modeObj = widgetJson.getJSONObject(mode); |
||||||
|
if (modeObj == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
collectColumnNamesByStep(modeObj, 0, steps, names); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 逐层下钻字段步骤链,到达末尾时读取控件的 bindData.columnName。 |
||||||
|
*/ |
||||||
|
private static void collectColumnNamesByStep(JSONObject node, int index, PathStep[] steps, Set<String> names) { |
||||||
|
if (index >= steps.length) { |
||||||
|
addColumnName(node, names); |
||||||
|
return; |
||||||
|
} |
||||||
|
PathStep step = steps[index]; |
||||||
|
if (step.isArray) { |
||||||
|
JSONArray array = node.getJSONArray(step.field); |
||||||
|
if (CollUtil.isEmpty(array)) { |
||||||
|
return; |
||||||
|
} |
||||||
|
for (int i = 0; i < array.size(); i++) { |
||||||
|
collectColumnNamesByStep(array.getJSONObject(i), index + 1, steps, names); |
||||||
|
} |
||||||
|
} else { |
||||||
|
JSONObject child = node.getJSONObject(step.field); |
||||||
|
if (child == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
collectColumnNamesByStep(child, index + 1, steps, names); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 读取路径步骤:对象字段(obj) 或 数组字段(arr)。 |
||||||
|
*/ |
||||||
|
private static class PathStep { |
||||||
|
final boolean isArray; |
||||||
|
final String field; |
||||||
|
|
||||||
|
private PathStep(boolean isArray, String field) { |
||||||
|
this.isArray = isArray; |
||||||
|
this.field = field; |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** 对象字段步骤,如 tableWidget。 */ |
||||||
|
private static PathStep obj(String field) { |
||||||
|
return new PathStep(false, field); |
||||||
|
} |
||||||
|
|
||||||
|
/** 数组字段步骤,如 widgetList、childWidgetList。 */ |
||||||
|
private static PathStep arr(String field) { |
||||||
|
return new PathStep(true, field); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 从控件对象中提取 bindData.columnName。 |
||||||
|
*/ |
||||||
|
private static void addColumnName(JSONObject widget, Set<String> names) { |
||||||
|
JSONObject bindData = widget.getJSONObject("bindData"); |
||||||
|
if (bindData != null) { |
||||||
|
String columnName = bindData.getString("columnName"); |
||||||
|
if (StrUtil.isNotEmpty(columnName)) { |
||||||
|
names.add(columnName); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 收集子表控件(widgetType=100)的 bindData.tableId → props 映射。 |
||||||
|
* |
||||||
|
* @param widgetJson 在线表单的 widgetJson。 |
||||||
|
* @return tableId → 子表控件 props 的映射。 |
||||||
|
*/ |
||||||
|
public static Map<String, JSONObject> collectSubTableProps(JSONObject widgetJson) { |
||||||
|
Map<String, JSONObject> resultMap = new HashMap<>(); |
||||||
|
walkAllModes(widgetJson, widget -> { |
||||||
|
JSONArray widgetList = widget.getJSONArray("widgetList"); |
||||||
|
if (widgetList == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
for (int i = 0; i < widgetList.size(); i++) { |
||||||
|
JSONObject childWidget = widgetList.getJSONObject(i); |
||||||
|
// 只处理 widgetType=100 的子表控件
|
||||||
|
Integer widgetType = childWidget.getInteger("widgetType"); |
||||||
|
if (widgetType == null || widgetType != 100) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
JSONObject bindData = childWidget.getJSONObject("bindData"); |
||||||
|
if (bindData == null) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
String tableId = bindData.getString("tableId"); |
||||||
|
if (StrUtil.isBlank(tableId)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
JSONObject props = childWidget.getJSONObject("props"); |
||||||
|
if (props != null) { |
||||||
|
resultMap.put(tableId, props); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
return resultMap; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 收集子表控件(widgetType=100) bindData.tableName 的名称集合。 |
||||||
|
* |
||||||
|
* @param widgetJson 在线表单的 widgetJson。 |
||||||
|
* @return 子表名称集合。 |
||||||
|
*/ |
||||||
|
public static Set<String> collectSubTableNames(JSONObject widgetJson) { |
||||||
|
Set<String> tableNameSet = new HashSet<>(); |
||||||
|
walkAllModes(widgetJson, widget -> { |
||||||
|
JSONArray widgetList = widget.getJSONArray("widgetList"); |
||||||
|
if (widgetList == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
for (int i = 0; i < widgetList.size(); i++) { |
||||||
|
JSONObject childWidget = widgetList.getJSONObject(i); |
||||||
|
// 只处理 widgetType=100 的子表控件
|
||||||
|
Integer widgetType = childWidget.getInteger("widgetType"); |
||||||
|
if (widgetType == null || widgetType != 100) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
JSONObject bindData = childWidget.getJSONObject("bindData"); |
||||||
|
if (bindData == null) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
if (bindData.containsKey("tableName")) { |
||||||
|
tableNameSet.add(bindData.getString("tableName")); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
return tableNameSet; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 构建 columnName → 控件 widget 的映射。 |
||||||
|
* |
||||||
|
* @param widgetJson 在线表单的 widgetJson。 |
||||||
|
* @return 字段名 → widget 的映射。 |
||||||
|
*/ |
||||||
|
public static Map<String, JSONObject> buildColumnNameWidgetMap(JSONObject widgetJson) { |
||||||
|
Map<String, JSONObject> map = new HashMap<>(); |
||||||
|
walkAllModes(widgetJson, widget -> { |
||||||
|
JSONObject bindData = widget.getJSONObject("bindData"); |
||||||
|
if (bindData != null && bindData.containsKey("columnName")) { |
||||||
|
String name = bindData.getString("columnName"); |
||||||
|
if (StrUtil.isNotEmpty(name)) { |
||||||
|
map.put(name, widget); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
return map; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 构建 fieldName → widget 的映射(用于主表已有字段匹配)。 |
||||||
|
* key 优先取 bindData.columnFieldName,兼容 bindData.columnName。 |
||||||
|
* |
||||||
|
* @param widgetJson 在线表单的 widgetJson。 |
||||||
|
* @return 字段名 → widget 的映射。 |
||||||
|
*/ |
||||||
|
public static Map<String, JSONObject> buildFieldNameWidgetMap(JSONObject widgetJson) { |
||||||
|
Map<String, JSONObject> map = new HashMap<>(); |
||||||
|
walkAllModes(widgetJson, widget -> { |
||||||
|
JSONObject bindData = widget.getJSONObject("bindData"); |
||||||
|
if (bindData == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
String fieldName = bindData.getString("columnFieldName"); |
||||||
|
if (StrUtil.isEmpty(fieldName)) { |
||||||
|
fieldName = bindData.getString("columnName"); |
||||||
|
} |
||||||
|
if (StrUtil.isNotEmpty(fieldName)) { |
||||||
|
map.put(fieldName, widget); |
||||||
|
} |
||||||
|
}); |
||||||
|
return map; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 构建子表控件 tableColumnList 的 columnId → 列对象 映射。 |
||||||
|
* 过滤掉 fieldType=1 的列(如序号、合计等非真实字段)。 |
||||||
|
* |
||||||
|
* @param widgetJson 在线表单的 widgetJson。 |
||||||
|
* @return columnId → 子表列对象 的映射。 |
||||||
|
*/ |
||||||
|
public static Map<String, JSONObject> buildSubTableColumnWidgetMap(JSONObject widgetJson) { |
||||||
|
Map<String, JSONObject> resultMap = new HashMap<>(); |
||||||
|
walkAllModes(widgetJson, widget -> { |
||||||
|
JSONArray widgetList = widget.getJSONArray("widgetList"); |
||||||
|
if (widgetList == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
for (int i = 0; i < widgetList.size(); i++) { |
||||||
|
JSONObject childWidget = widgetList.getJSONObject(i); |
||||||
|
// 只处理 widgetType=100 的子表控件
|
||||||
|
Integer widgetType = childWidget.getInteger("widgetType"); |
||||||
|
if (widgetType == null || widgetType != 100) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
JSONObject bindData = childWidget.getJSONObject("bindData"); |
||||||
|
if (bindData == null) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
String tableId = bindData.getString("tableId"); |
||||||
|
if (StrUtil.isBlank(tableId)) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
JSONObject props = childWidget.getJSONObject("props"); |
||||||
|
if (props == null) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
JSONArray tableColumnList = props.getJSONArray("tableColumnList"); |
||||||
|
for (int j = 0; j < tableColumnList.size(); j++) { |
||||||
|
JSONObject tableColumn = tableColumnList.getJSONObject(j); |
||||||
|
// 过滤掉 fieldType=1 的列(如序号、合计等非真实字段),避免被识别为需要新增的字段
|
||||||
|
Integer fieldType = tableColumn.getInteger("fieldType"); |
||||||
|
if (fieldType != null && fieldType == 1) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
resultMap.put(tableColumn.getString("columnId"), tableColumn); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
return resultMap; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 构建 mobile 端 columnName → widget 的映射。 |
||||||
|
* 兼容两种结构: |
||||||
|
* 1. mobile -> tableWidget -> childWidgetList -> childWidgetList -> bindData -> columnName |
||||||
|
* 2. mobile -> widgetList -> childWidgetList -> bindData -> columnName (缺少 tableWidget 时) |
||||||
|
* |
||||||
|
* @param widgetJson 在线表单的 widgetJson。 |
||||||
|
* @return mobile 端字段名 → widget 的映射。 |
||||||
|
*/ |
||||||
|
public static Map<String, JSONObject> buildMobileColumnNameWidgetMap(JSONObject widgetJson) { |
||||||
|
Map<String, JSONObject> map = new HashMap<>(); |
||||||
|
JSONObject mobile = widgetJson.getJSONObject("mobile"); |
||||||
|
if (mobile == null) { |
||||||
|
return map; |
||||||
|
} |
||||||
|
// 优先走原有结构: mobile.tableWidget
|
||||||
|
JSONObject tableWidget = mobile.getJSONObject("tableWidget"); |
||||||
|
if (tableWidget != null) { |
||||||
|
JSONArray cardList = tableWidget.getJSONArray("childWidgetList"); |
||||||
|
if (cardList != null) { |
||||||
|
for (int i = 0; i < cardList.size(); i++) { |
||||||
|
JSONObject cardWidget = cardList.getJSONObject(i); |
||||||
|
JSONArray fieldList = cardWidget.getJSONArray("childWidgetList"); |
||||||
|
if (fieldList == null) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
for (int j = 0; j < fieldList.size(); j++) { |
||||||
|
JSONObject fieldWidget = fieldList.getJSONObject(j); |
||||||
|
JSONObject bindData = fieldWidget.getJSONObject("bindData"); |
||||||
|
if (bindData != null && bindData.containsKey("columnName")) { |
||||||
|
String name = bindData.getString("columnName"); |
||||||
|
if (StrUtil.isNotEmpty(name)) { |
||||||
|
map.put(name, fieldWidget); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return map; |
||||||
|
} |
||||||
|
// 兜底走新结构: mobile.widgetList -> childWidgetList -> bindData.columnName
|
||||||
|
JSONArray widgetList = mobile.getJSONArray("widgetList"); |
||||||
|
if (widgetList != null) { |
||||||
|
for (int i = 0; i < widgetList.size(); i++) { |
||||||
|
JSONObject containerWidget = widgetList.getJSONObject(i); |
||||||
|
JSONArray childWidgetList = containerWidget.getJSONArray("childWidgetList"); |
||||||
|
if (childWidgetList == null) { |
||||||
|
continue; |
||||||
|
} |
||||||
|
for (int j = 0; j < childWidgetList.size(); j++) { |
||||||
|
JSONObject fieldWidget = childWidgetList.getJSONObject(j); |
||||||
|
JSONObject bindData = fieldWidget.getJSONObject("bindData"); |
||||||
|
if (bindData != null && bindData.containsKey("columnName")) { |
||||||
|
String name = bindData.getString("columnName"); |
||||||
|
if (StrUtil.isNotEmpty(name)) { |
||||||
|
map.put(name, fieldWidget); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return map; |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 在 widgetJson 中查找匹配 tableName 的子表控件(widgetType=100),回填 bindData.tableId。 |
||||||
|
* |
||||||
|
* @param widgetJson 在线表单的 widgetJson。 |
||||||
|
* @param tableName 子表名称。 |
||||||
|
* @param tableId 子表Id。 |
||||||
|
*/ |
||||||
|
public static void backfillTableId(JSONObject widgetJson, String tableName, Long tableId) { |
||||||
|
walkAllModes(widgetJson, widget -> { |
||||||
|
if (widget.getInteger("widgetType") != null && 100 == widget.getInteger("widgetType")) { |
||||||
|
JSONObject bindData = widget.getJSONObject("bindData"); |
||||||
|
if (bindData != null && tableName.equals(bindData.getString("tableName"))) { |
||||||
|
bindData.put("tableId", tableId); |
||||||
|
} |
||||||
|
} |
||||||
|
}); |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 遍历 "pc" 和 "mobile" 两个模式的控件树。 |
||||||
|
*/ |
||||||
|
private static void walkAllModes(JSONObject widgetJson, Consumer<JSONObject> visitor) { |
||||||
|
for (String mode : new String[]{"pc", "mobile"}) { |
||||||
|
JSONObject modeObj = widgetJson.getJSONObject(mode); |
||||||
|
if (modeObj != null) { |
||||||
|
walkWidgets(modeObj, visitor); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/** |
||||||
|
* 递归遍历控件树,访问每个控件及其子控件(widgetList)。 |
||||||
|
*/ |
||||||
|
private static void walkWidgets(JSONObject node, Consumer<JSONObject> visitor) { |
||||||
|
if (node == null) { |
||||||
|
return; |
||||||
|
} |
||||||
|
visitor.accept(node); |
||||||
|
JSONArray children = node.getJSONArray("widgetList"); |
||||||
|
if (children != null) { |
||||||
|
for (int i = 0; i < children.size(); i++) { |
||||||
|
walkWidgets(children.getJSONObject(i), visitor); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
Loading…
Reference in new issue