刘光辉
8 小时以前 06f314545ac3514d7ce28939920866ae8188d31c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
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); }
    }
}