diff --git a/common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java b/common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java index 1f2ee63..d04a798 100644 --- a/common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java +++ b/common/common-online/src/main/java/apelet/common/online/controller/OnlineFormController.java @@ -29,6 +29,7 @@ import apelet.common.online.model.constant.FormType; import apelet.common.online.service.*; import apelet.common.online.util.OnlineRedisKeyUtil; import apelet.common.online.util.WidgetFieldTypeMapping; +import apelet.common.online.util.WidgetJsonUtil; import apelet.common.online.vo.OnlineFormVo; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.map.MapUtil; @@ -56,7 +57,6 @@ import org.springframework.web.bind.annotation.*; import javax.annotation.Resource; import javax.validation.groups.Default; import java.util.*; -import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -519,7 +519,7 @@ public class OnlineFormController { jsonObject.put("onlineVirtualColumnList", virtualColumnList); Set dictIdSet = onlineColumnList.stream() .filter(c -> c.getDictId() != null).map(OnlineColumn::getDictId).collect(Collectors.toSet()); - Set widgetDictIdSet = this.extractDictIdSetFromWidgetJson(onlineForm.getWidgetJson()); + Set widgetDictIdSet = WidgetJsonUtil.extractDictIdSet(onlineForm.getWidgetJson()); CollUtil.addAll(dictIdSet, widgetDictIdSet); if (CollUtil.isNotEmpty(dictIdSet)) { List onlineDictList = onlineDictService.getOnlineDictListFromCache(dictIdSet); @@ -660,46 +660,6 @@ public class OnlineFormController { return ResponseResult.success(datasourceIdSet); } - private Set extractDictIdSetFromWidgetJson(String widgetJson) { - Set dictIdSet = new HashSet<>(); - if (StrUtil.isBlank(widgetJson)) { - return dictIdSet; - } - JSONObject allData = JSON.parseObject(widgetJson); - JSONObject pcData = allData.getJSONObject(AppDeviceType.getDeviceType()); - if (MapUtil.isEmpty(pcData)) { - return dictIdSet; - } - JSONArray widgetListArray = pcData.getJSONArray("widgetList"); - if (CollUtil.isEmpty(widgetListArray)) { - return dictIdSet; - } - for (int i = 0; i < widgetListArray.size(); i++) { - this.recursiveExtractDictId(widgetListArray.getJSONObject(i), dictIdSet); - } - return dictIdSet; - } - - private void recursiveExtractDictId(JSONObject widgetData, Set 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++) { - this.recursiveExtractDictId(childWidgetArray.getJSONObject(i), dictIdSet); - } - } - } - - /** * 获取流程对应的在线表单 * @param bosType @@ -761,19 +721,16 @@ public class OnlineFormController { boolean changed = false; // === Diff 新增字段 === - // 这里是取pc 的 还要取一下 moblie的 - Set widgetColumnNames = collectAllBindColumnNames(widgetJson); - // 收集 mobile 端的列名(遍历 childWidgetList 路径) - widgetColumnNames.addAll(collectMobileBindColumnNames(widgetJson)); + // 收集 pc/mobile 端所有绑定的字段列名 + Set widgetColumnNames = WidgetJsonUtil.collectBindColumnNames(widgetJson); - List existingFields = onlFormFieldService.list( - new LambdaQueryWrapper().eq(OnlFormField::getHeadId, onlFormHead.getId())); + List existingFields = onlFormFieldService.list(new LambdaQueryWrapper().eq(OnlFormField::getHeadId, onlFormHead.getId())); Set existingFieldNames = existingFields.stream() .map(OnlFormField::getFieldName).collect(Collectors.toSet()); Set newColumnNames = new HashSet<>(widgetColumnNames); newColumnNames.removeAll(existingFieldNames); // 主表/单表已有字段:如果绑定了从表(slaveTableId),同步更新 refPropert - Map fieldNameToWidget = mapFieldNameToWidget(widgetJson); + Map fieldNameToWidget = WidgetJsonUtil.buildFieldNameWidgetMap(widgetJson); for (OnlFormField existingField : existingFields) { JSONObject widget = fieldNameToWidget.get(existingField.getFieldName()); if (widget == null) { @@ -790,9 +747,9 @@ public class OnlineFormController { } } if (!newColumnNames.isEmpty()) { - Map nameToWidget = mapColumnNameToWidget(widgetJson); + Map nameToWidget = WidgetJsonUtil.buildColumnNameWidgetMap(widgetJson); // 收集 mobile 端的 widget 映射(遍历 childWidgetList 路径) - Map mobileNameToWidget = mapMobileColumnNameToWidget(widgetJson); + Map mobileNameToWidget = WidgetJsonUtil.buildMobileColumnNameWidgetMap(widgetJson); OnlineDblink dblink = onlineDblinkService.getById(datasource.getDblinkId()); // 预校验:过滤掉 widgetType 不在映射中的控件,避免 getFieldTypeId 抛异常导致部分数据已入库 @@ -876,7 +833,7 @@ public class OnlineFormController { // === Diff 新增子表 === // 取 widgetList->bindData -> tableId 在去 zz_online_table 查询,这样就可以判断哪些是新增,哪些是 已有的表 // 如果存在附表 - Map tableIdAndPropMap = collectSubTableAndPropMap(widgetJson); + Map tableIdAndPropMap = WidgetJsonUtil.collectSubTableProps(widgetJson); if (MapUtil.isNotEmpty(tableIdAndPropMap)) { //搜集的所有tableId, 要么是 tableId(已存在),要么是 tableName(不存在) @@ -900,7 +857,7 @@ public class OnlineFormController { List onlFormHeadList = onlFormHeadService.list(new LambdaQueryWrapper().in(OnlFormHead::getTableName, tableNames)); Map tableNameAndHeadIdMap = onlFormHeadList.stream().collect(Collectors.toMap(OnlFormHead::getTableName, Function.identity())); //因为这里是zz_online_column.column_id 的id和 列名的混合,我只要列名 - Map nameToWidget = mapColumnIdAndWidget(widgetJson); + Map nameToWidget = WidgetJsonUtil.buildSubTableColumnWidgetMap(widgetJson); Set newSubColumnNameSet = nameToWidget.keySet().stream().filter(s -> !s.matches("\\d+")).collect(Collectors.toSet()); //这里要走 新增逻辑 if (!newSubColumnNameSet.isEmpty()) { @@ -1016,7 +973,7 @@ public class OnlineFormController { private boolean createNewSubTables(JSONObject widgetJson, OnlFormHead onlFormHead, OnlineDatasource datasource, Set newSubTableNames) { if (newSubTableNames.isEmpty()) { // 如果他为空,说明我们走tableId取值没取到,我们就去一下 tableName; - newSubTableNames = this.collectSubTableName(widgetJson); + newSubTableNames = WidgetJsonUtil.collectSubTableNames(widgetJson); } if (newSubTableNames.isEmpty()) { return false; @@ -1036,7 +993,7 @@ public class OnlineFormController { onlineDatasourceRelationService.addOnlineDatasourceRelation(onlFormHead, datasource, null, dblink); if (subOnlineTable != null) { - backfillRelationId(widgetJson, subTableName, subOnlineTable.getTableId()); + WidgetJsonUtil.backfillTableId(widgetJson, subTableName, subOnlineTable.getTableId()); } } @@ -1077,210 +1034,6 @@ public class OnlineFormController { onlFormFieldService.save(field); } - /** - * 遍历 pc/mobile 的 widget 树,收集所有 bindData.columnName 值。 - */ - private Set collectAllBindColumnNames(JSONObject widgetJson) { - Set names = new HashSet<>(); - walkAllModes(widgetJson, widget -> { - JSONObject bindData = widget.getJSONObject("bindData"); - if (bindData != null && bindData.containsKey("columnName")) { - String name = bindData.getString("columnName"); - if (StrUtil.isNotEmpty(name)) { - names.add(name); - } - } - }); - return names; - } - - /** - * 遍历 pc/mobile 的 widget 树,收集 widgetType=100 (TableContainer) 的 bindData 下的 tableId - */ - private Map collectSubTableAndPropMap(JSONObject widgetJson) { - Map resultMap = new HashMap<>(); - walkAllModes(widgetJson, widget -> { - JSONArray widgetList = widget.getJSONArray("widgetList"); - if (widgetList != null) { - 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; - } - - private Set collectSubTableName(JSONObject widgetJson) { - Set tableNameSet = new HashSet<>(); - walkAllModes(widgetJson, widget -> { - JSONArray widgetList = widget.getJSONArray("widgetList"); - if (widgetList != null) { - 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 的映射。 - */ - private Map mapColumnNameToWidget(JSONObject widgetJson) { - Map 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。 - */ - private Map mapFieldNameToWidget(JSONObject widgetJson) { - Map 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; - } - - private Map mapColumnIdAndWidget(JSONObject widgetJson) { - Map resultMap = new HashMap<>(); - walkAllModes(widgetJson, widget -> { - JSONArray widgetList = widget.getJSONArray("widgetList"); - if (widgetList != null) { - 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; - } - /** - * 在 widgetJson 中查找匹配 tableName 的 TableContainer 控件,回填 tableId。 - */ - private void backfillRelationId(JSONObject widgetJson, String tableName, Long tableId) { - walkAllModes(widgetJson, widget -> { - if (widget.getInteger("widgetType") != null && 100 == widget.getInteger("widgetType")) { - JSONObject props = widget.getJSONObject("bindData"); - if (props != null && tableName.equals(props.getString("tableName"))) { - props.put("tableId", tableId); - } - } - }); - } - - /** - * 遍历 "pc" 和 "mobile" 两个模式的控件树。 - */ - private void walkAllModes(JSONObject widgetJson, Consumer visitor) { - for (String mode : new String[]{"pc", "mobile"}) { - JSONObject modeObj = widgetJson.getJSONObject(mode); - if (modeObj != null) { - walkWidgets(modeObj, visitor); - } - } - } - - /** - * 递归遍历控件树,访问每个控件及其子控件(widgetList)。 - */ - private void walkWidgets(JSONObject node, Consumer 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); - } - } - } - // ============ mobile 端子表同步相关方法 ============ /** @@ -1355,7 +1108,7 @@ public class OnlineFormController { if (StrUtil.isEmpty(tableName) || !newSubTableNames.contains(tableName)) { continue; } - // createNewSubTables 已通过 backfillRelationId 回写了 tableId,这里只需补 masterColumnName + // createNewSubTables 已通过 WidgetJsonUtil.backfillTableId 回写了 tableId,这里只需补 masterColumnName if (!bindData.containsKey("tableId")) { OnlineTable subTable = onlineTableService.getOne(new LambdaQueryWrapper().eq(OnlineTable::getTableName, tableName.toLowerCase())); if (subTable != null) { @@ -1473,83 +1226,6 @@ public class OnlineFormController { } /** - * 遍历 mobile 端 widget 树,收集所有 bindData.columnName 值。 - * 路径: mobile -> tableWidget -> childWidgetList -> childWidgetList -> bindData -> columnName - */ - private Set collectMobileBindColumnNames(JSONObject widgetJson) { - Set names = new HashSet<>(); - JSONObject mobile = widgetJson.getJSONObject("mobile"); - if (mobile == null) { - return names; - } - JSONObject tableWidget = mobile.getJSONObject("tableWidget"); - if (tableWidget == null) { - return names; - } - JSONArray cardList = tableWidget.getJSONArray("childWidgetList"); - if (cardList == null) { - return names; - } - 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)) { - names.add(name); - } - } - } - } - return names; - } - - /** - * 构建 mobile 端 columnName → widget 的映射。 - * 路径: mobile -> tableWidget -> childWidgetList -> childWidgetList -> bindData -> columnName - */ - private Map mapMobileColumnNameToWidget(JSONObject widgetJson) { - Map map = new HashMap<>(); - JSONObject mobile = widgetJson.getJSONObject("mobile"); - if (mobile == null) { - return map; - } - JSONObject tableWidget = mobile.getJSONObject("tableWidget"); - if (tableWidget == null) { - return map; - } - JSONArray cardList = tableWidget.getJSONArray("childWidgetList"); - if (cardList == null) { - return map; - } - 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; - } - - - /** * 查询指定表名的列表表单 * * @param tableName 表名 diff --git a/common/common-online/src/main/java/apelet/common/online/util/WidgetJsonUtil.java b/common/common-online/src/main/java/apelet/common/online/util/WidgetJsonUtil.java new file mode 100644 index 0000000..c9daa36 --- /dev/null +++ b/common/common-online/src/main/java/apelet/common/online/util/WidgetJsonUtil.java @@ -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 extractDictIdSet(String widgetJson) { + Set 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 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 值。 + *

+ * 每个读取源用"设备端 + 字段步骤链"描述,步骤支持对象字段 obj() 和数组字段 arr(): + *

+     * pc:     widgetList -> bindData -> columnName
+     * mobile: tableWidget -> childWidgetList -> childWidgetList -> bindData -> columnName
+     * mobile: widgetList -> childWidgetList -> bindData -> columnName
+     * 
+ * 后续新增读取源(如 otherWidgetList)时,只需在下方追加一行 + * {@code collectColumnNamesByPath(widgetJson, names, "other", arr("otherWidgetList"))}。 + * + * @param widgetJson 在线表单的 widgetJson。 + * @return 绑定的字段名列集合。 + */ + public static Set collectBindColumnNames(JSONObject widgetJson) { + Set 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 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 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 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 collectSubTableProps(JSONObject widgetJson) { + Map 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 collectSubTableNames(JSONObject widgetJson) { + Set 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 buildColumnNameWidgetMap(JSONObject widgetJson) { + Map 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 buildFieldNameWidgetMap(JSONObject widgetJson) { + Map 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 buildSubTableColumnWidgetMap(JSONObject widgetJson) { + Map 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 buildMobileColumnNameWidgetMap(JSONObject widgetJson) { + Map 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 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 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); + } + } + } +}