package jnpf.onlinedev.model;
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import jnpf.exception.DataException;
|
import lombok.Getter;
|
import jnpf.util.visiual.JnpfKeyConsts;
|
import java.util.*;
|
|
/** Shared field discovery for document annotation and display-value rendering. */
|
public final class FormDisplaySchema {
|
private static final ObjectMapper JSON = new ObjectMapper();
|
// Use the same constants as OnlineSwapDataUtils; UI labels are not control keys.
|
private static final Set<String> TEXT_TYPES = Set.of("", JnpfKeyConsts.COM_INPUT,
|
JnpfKeyConsts.TEXTAREA, JnpfKeyConsts.NUM_INPUT, JnpfKeyConsts.CALCULATE,
|
JnpfKeyConsts.DATE, JnpfKeyConsts.TIME, JnpfKeyConsts.DATE_CALCULATE,
|
JnpfKeyConsts.SWITCH, JnpfKeyConsts.RATE, JnpfKeyConsts.SLIDER, JnpfKeyConsts.BILLRULE,
|
JnpfKeyConsts.CREATETIME, JnpfKeyConsts.MODIFYTIME,
|
JnpfKeyConsts.RELATIONFORM_ATTR, JnpfKeyConsts.POPUPSELECT_ATTR);
|
private static final Set<String> REFERENCE_TYPES = Set.of(JnpfKeyConsts.DEPSELECT,
|
JnpfKeyConsts.COMSELECT, JnpfKeyConsts.USERSELECT, JnpfKeyConsts.POSSELECT,
|
JnpfKeyConsts.ROLESELECT, JnpfKeyConsts.GROUPSELECT, JnpfKeyConsts.CURRDEPT,
|
JnpfKeyConsts.CURRORGANIZE, JnpfKeyConsts.CURRPOSITION, JnpfKeyConsts.CREATEUSER,
|
JnpfKeyConsts.MODIFYUSER, JnpfKeyConsts.SELECT, JnpfKeyConsts.RADIO,
|
JnpfKeyConsts.CHECKBOX, JnpfKeyConsts.TREESELECT, JnpfKeyConsts.CASCADER,
|
JnpfKeyConsts.RELATIONFORM, JnpfKeyConsts.POPUPSELECT, JnpfKeyConsts.POPUPTABLESELECT,
|
JnpfKeyConsts.ADDRESS);
|
private FormDisplaySchema() { }
|
|
public static List<Field> parse(String source) {
|
try {
|
List<Field> result = new ArrayList<>();
|
collect(JSON.readTree(source), "", "", result);
|
Set<String> seen = new HashSet<>();
|
for (Field field : result) if (!seen.add(field.path))
|
throw new DataException("表单字段路径重复: " + field.path);
|
return result;
|
} catch (DataException e) { throw e; }
|
catch (Exception e) { throw new DataException("无法解析签批页表单配置: " + e.getMessage()); }
|
}
|
|
private static void collect(JsonNode node, String parent, String label, List<Field> result) throws Exception {
|
if (node == null || node.isNull()) return;
|
if (node.isTextual()) { collect(JSON.readTree(node.asText()), parent, label, result); return; }
|
if (node.isArray()) { for (JsonNode child : node) collect(child, parent, label, result); return; }
|
if (!node.isObject()) return;
|
if (node.has("fields")) { collect(node.get("fields"), parent, label, result); return; }
|
JsonNode config = node.path("__config__");
|
String key = node.path("__vModel__").asText(node.path("vModel").asText(""));
|
String type = config.path("jnpfKey").asText("");
|
String title = config.path("label").asText(node.path("label").asText(key));
|
JsonNode children = config.has("children") ? config.get("children") : node.get("children");
|
if (children != null && children.isTextual()) children = JSON.readTree(children.asText());
|
boolean table = "table".equals(type) || (type.isEmpty() && validKey(key) && children != null && !children.isEmpty());
|
if (children != null && !children.isEmpty()) {
|
if (table && (!parent.isEmpty() || !validKey(key))) {
|
result.add(new Field(join(parent, key), joinLabel(label, title), type, node, "暂不支持嵌套子表"));
|
return;
|
}
|
collect(children, table ? key : parent, table ? joinLabel(label, title) : label, result);
|
return;
|
}
|
if (!validKey(key) || "biz_sign".equals(key)) return;
|
result.add(new Field(join(parent, key), joinLabel(label, title), type, node,
|
TEXT_TYPES.contains(type) || REFERENCE_TYPES.contains(type) ? "" : "暂不支持此控件的文本回填"));
|
}
|
public static boolean validKey(String value) { return value != null && value.matches("[A-Za-z_][A-Za-z0-9_]*"); }
|
private static String join(String a, String b) { return a.isEmpty() ? b : a + "." + b; }
|
private static String joinLabel(String a, String b) { return a.isEmpty() ? b : a + "-" + b; }
|
|
@Getter
|
public static final class Field {
|
private final String path;
|
private final String label;
|
private final String type;
|
private final JsonNode configuration;
|
private final String reason;
|
Field(String path, String label, String type, JsonNode configuration, String reason) {
|
this.path = path; this.label = label; this.type = type; this.configuration = configuration; this.reason = reason;
|
}
|
public String displaySignature() {
|
// Ignore presentation/layout, but never merge incompatible lookup/format definitions.
|
return type + "|" + configuration.path("format") + "|" + configuration.path("multiple")
|
+ "|" + configuration.path("options") + "|" + configuration.path("props")
|
+ "|" + configuration.path("__config__").path("dataType")
|
+ "|" + configuration.path("__config__").path("dictionaryType")
|
+ "|" + configuration.path("__config__").path("propsUrl")
|
+ "|" + configuration.path("__config__").path("templateJson");
|
}
|
public boolean isSupported() { return reason.isEmpty(); }
|
public boolean isReference() { return REFERENCE_TYPES.contains(type); }
|
public String getTable() { int dot = path.indexOf('.'); return dot < 0 ? "" : path.substring(0, dot); }
|
public String getColumn() { return path.substring(path.lastIndexOf('.') + 1); }
|
}
|
}
|