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 @@
@@ -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