fix: 人员数据治理迁移功能优化6
This commit is contained in:
+64
-4
@@ -6,6 +6,8 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public class PersonApiSyncOptions {
|
public class PersonApiSyncOptions {
|
||||||
@@ -13,11 +15,11 @@ public class PersonApiSyncOptions {
|
|||||||
private String unitConfigId;
|
private String unitConfigId;
|
||||||
private List<FieldRule> rules = new ArrayList<>();
|
private List<FieldRule> rules = new ArrayList<>();
|
||||||
|
|
||||||
public Map<String, String> fieldDefaults() {
|
public Map<String, FieldRule> defaultRulesByTarget() {
|
||||||
Map<String, String> defaults = new HashMap<>();
|
Map<String, FieldRule> defaults = new HashMap<>();
|
||||||
for (FieldRule rule : rules) {
|
for (FieldRule rule : rules) {
|
||||||
if (rule.getDefaultValue() != null && !rule.getDefaultValue().isEmpty()) {
|
if (rule.hasDefault()) {
|
||||||
defaults.put(rule.getTargetField(), rule.getDefaultValue());
|
defaults.put(rule.getTargetField(), rule);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return defaults;
|
return defaults;
|
||||||
@@ -30,5 +32,63 @@ public class PersonApiSyncOptions {
|
|||||||
private Integer length;
|
private Integer length;
|
||||||
private String dictCode;
|
private String dictCode;
|
||||||
private String defaultValue;
|
private String defaultValue;
|
||||||
|
private List<DefaultRule> defaultRules = new ArrayList<>();
|
||||||
|
|
||||||
|
public boolean hasDefault() {
|
||||||
|
return (defaultValue != null && !defaultValue.isEmpty())
|
||||||
|
|| (defaultRules != null && !defaultRules.isEmpty());
|
||||||
|
}
|
||||||
|
|
||||||
|
public String resolveDefault(Function<String, Object> readSource) {
|
||||||
|
if (defaultRules != null) {
|
||||||
|
for (DefaultRule rule : defaultRules) {
|
||||||
|
if (rule.getCondition().matches(readSource)) {
|
||||||
|
return rule.getResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return defaultValue == null || defaultValue.isEmpty() ? null : defaultValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class DefaultRule {
|
||||||
|
private Condition condition;
|
||||||
|
private String result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public static class Condition {
|
||||||
|
private String operator;
|
||||||
|
private String sourceField;
|
||||||
|
private String value;
|
||||||
|
private List<Condition> children;
|
||||||
|
|
||||||
|
public boolean matches(Function<String, Object> readSource) {
|
||||||
|
if ("AND".equals(operator)) {
|
||||||
|
return children.stream().allMatch(child -> child.matches(readSource));
|
||||||
|
}
|
||||||
|
if ("OR".equals(operator)) {
|
||||||
|
return children.stream().anyMatch(child -> child.matches(readSource));
|
||||||
|
}
|
||||||
|
Object actual = readSource.apply(sourceField);
|
||||||
|
String text = actual == null ? null : String.valueOf(actual);
|
||||||
|
return switch (operator) {
|
||||||
|
case "EQ" -> text != null && text.equals(value);
|
||||||
|
case "NE" -> text != null && !text.equals(value);
|
||||||
|
case "CONTAINS" -> text != null && text.contains(value);
|
||||||
|
case "EMPTY" -> text == null || text.isBlank();
|
||||||
|
case "NOT_EMPTY" -> text != null && !text.isBlank();
|
||||||
|
default -> throw new IllegalArgumentException("不支持的默认值条件运算符:" + operator);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public void collectSourceFields(Set<String> fields) {
|
||||||
|
if (children != null) {
|
||||||
|
children.forEach(child -> child.collectSourceFields(fields));
|
||||||
|
} else {
|
||||||
|
fields.add(sourceField);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+36
-4
@@ -49,6 +49,7 @@ import java.util.List;
|
|||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
/** API配置、数据库元数据和只读测试实现。 */
|
/** API配置、数据库元数据和只读测试实现。 */
|
||||||
@@ -134,7 +135,7 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
|||||||
|
|
||||||
private PersonApiConfig saveConfig(PersonApiConfig config, List<PersonApiFieldMapping> mappings,
|
private PersonApiConfig saveConfig(PersonApiConfig config, List<PersonApiFieldMapping> mappings,
|
||||||
List<PersonApiRequestParam> params, PersonApiSyncOptions options) {
|
List<PersonApiRequestParam> params, PersonApiSyncOptions options) {
|
||||||
Map<String, String> defaults = options == null ? java.util.Collections.emptyMap() : options.fieldDefaults();
|
Map<String, PersonApiSyncOptions.FieldRule> defaults = options == null ? Map.of() : options.defaultRulesByTarget();
|
||||||
Assert.notNull(config, "配置不能为空");
|
Assert.notNull(config, "配置不能为空");
|
||||||
Assert.hasText(config.getConfigName(), "请填写配置名称");
|
Assert.hasText(config.getConfigName(), "请填写配置名称");
|
||||||
PersonApiConfig stored = Strings.isBlank(config.getId()) ? null : fetch(config.getId());
|
PersonApiConfig stored = Strings.isBlank(config.getId()) ? null : fetch(config.getId());
|
||||||
@@ -376,7 +377,7 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
|||||||
return "HTTP_API".equals(config.getSourceType()) && "sys_unit".equalsIgnoreCase(config.getTargetTable());
|
return "HTTP_API".equals(config.getSourceType()) && "sys_unit".equalsIgnoreCase(config.getTargetTable());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean hasMappingValue(PersonApiFieldMapping mapping, Map<String, String> defaults) {
|
private boolean hasMappingValue(PersonApiFieldMapping mapping, Map<String, PersonApiSyncOptions.FieldRule> defaults) {
|
||||||
return Strings.isNotBlank(mapping.getSourceField())
|
return Strings.isNotBlank(mapping.getSourceField())
|
||||||
|| (mapping.getTargetField() != null && defaults.containsKey(mapping.getTargetField()));
|
|| (mapping.getTargetField() != null && defaults.containsKey(mapping.getTargetField()));
|
||||||
}
|
}
|
||||||
@@ -390,10 +391,15 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
|||||||
"请选择有效的单位API配置");
|
"请选择有效的单位API配置");
|
||||||
}
|
}
|
||||||
Assert.notNull(options.getRules(), "字段配置不能为空");
|
Assert.notNull(options.getRules(), "字段配置不能为空");
|
||||||
|
Set<String> sourceFields = new LinkedHashSet<>();
|
||||||
|
if (mappings != null) {
|
||||||
|
mappings.stream().map(PersonApiFieldMapping::getSourceField).filter(Strings::isNotBlank)
|
||||||
|
.map(String::trim).forEach(sourceFields::add);
|
||||||
|
}
|
||||||
java.util.Set<List<String>> ruleFields = new java.util.HashSet<>();
|
java.util.Set<List<String>> ruleFields = new java.util.HashSet<>();
|
||||||
for (PersonApiSyncOptions.FieldRule rule : options.getRules()) {
|
for (PersonApiSyncOptions.FieldRule rule : options.getRules()) {
|
||||||
Assert.notNull(rule, "字段配置不能为空");
|
Assert.notNull(rule, "字段配置不能为空");
|
||||||
boolean hasDefault = rule.getDefaultValue() != null && !rule.getDefaultValue().isEmpty();
|
boolean hasDefault = rule.hasDefault();
|
||||||
if (rule.getLength() != null || !hasDefault) {
|
if (rule.getLength() != null || !hasDefault) {
|
||||||
Assert.hasText(rule.getSourceField(), "请填写配置规则的来源字段");
|
Assert.hasText(rule.getSourceField(), "请填写配置规则的来源字段");
|
||||||
}
|
}
|
||||||
@@ -410,6 +416,13 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
|||||||
Assert.isTrue(!List.of("id", "pullTime").contains(rule.getTargetField())
|
Assert.isTrue(!List.of("id", "pullTime").contains(rule.getTargetField())
|
||||||
&& !Boolean.TRUE.equals(mapping.getIdentityFlag()), "主键、批次时间和唯一标识不能设置默认值");
|
&& !Boolean.TRUE.equals(mapping.getIdentityFlag()), "主键、批次时间和唯一标识不能设置默认值");
|
||||||
}
|
}
|
||||||
|
if (rule.getDefaultRules() != null) {
|
||||||
|
for (PersonApiSyncOptions.DefaultRule defaultRule : rule.getDefaultRules()) {
|
||||||
|
Assert.notNull(defaultRule, "默认值规则不能为空");
|
||||||
|
Assert.hasText(defaultRule.getResult(), "请填写默认值规则的结果:" + rule.getTargetField());
|
||||||
|
validateDefaultCondition(defaultRule.getCondition(), sourceFields, rule.getTargetField());
|
||||||
|
}
|
||||||
|
}
|
||||||
if (Strings.isNotBlank(rule.getDictCode())) {
|
if (Strings.isNotBlank(rule.getDictCode())) {
|
||||||
Assert.hasText(rule.getTargetField(), "选择字典前请填写目标字段");
|
Assert.hasText(rule.getTargetField(), "选择字典前请填写目标字段");
|
||||||
Assert.isTrue(!"pullTime".equals(rule.getTargetField())
|
Assert.isTrue(!"pullTime".equals(rule.getTargetField())
|
||||||
@@ -418,12 +431,31 @@ public class PersonApiConfigServiceImpl extends BaseServiceImpl<PersonApiConfig>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (Strings.isNotBlank(options.getUnitConfigId())) {
|
if (Strings.isNotBlank(options.getUnitConfigId())) {
|
||||||
Map<String, String> defaults = options.fieldDefaults();
|
Map<String, PersonApiSyncOptions.FieldRule> defaults = options.defaultRulesByTarget();
|
||||||
Assert.isTrue(mappings != null && mappings.stream().anyMatch(mapping -> "unitId".equals(mapping.getTargetField())
|
Assert.isTrue(mappings != null && mappings.stream().anyMatch(mapping -> "unitId".equals(mapping.getTargetField())
|
||||||
&& hasMappingValue(mapping, defaults)), "自动同步单位需要配置unitId映射");
|
&& hasMappingValue(mapping, defaults)), "自动同步单位需要配置unitId映射");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void validateDefaultCondition(PersonApiSyncOptions.Condition condition,
|
||||||
|
Set<String> sourceFields, String targetField) {
|
||||||
|
Assert.notNull(condition, "请配置默认值条件:" + targetField);
|
||||||
|
String operator = condition.getOperator();
|
||||||
|
if ("AND".equals(operator) || "OR".equals(operator)) {
|
||||||
|
Assert.notEmpty(condition.getChildren(), "默认值条件组至少需要一项:" + targetField);
|
||||||
|
condition.getChildren().forEach(child -> validateDefaultCondition(child, sourceFields, targetField));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Assert.isTrue(operator != null && List.of("EQ", "NE", "CONTAINS", "EMPTY", "NOT_EMPTY").contains(operator),
|
||||||
|
"默认值条件运算符不正确:" + targetField);
|
||||||
|
Assert.isNull(condition.getChildren(), "普通条件不能包含子条件:" + targetField);
|
||||||
|
Assert.isTrue(sourceFields.contains(condition.getSourceField()), "请选择已配置的来源字段:" + targetField);
|
||||||
|
if (List.of("EQ", "NE", "CONTAINS").contains(operator)) {
|
||||||
|
Assert.isTrue(condition.getValue() != null && !condition.getValue().isEmpty(),
|
||||||
|
"请填写默认值条件的比较值:" + targetField);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Aop(TransAop.READ_COMMITTED)
|
@Aop(TransAop.READ_COMMITTED)
|
||||||
public void deleteConfig(String id) {
|
public void deleteConfig(String id) {
|
||||||
|
|||||||
+18
-7
@@ -101,7 +101,13 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
|
|||||||
List<PersonApiFieldMapping> mappings = dao.query(PersonApiFieldMapping.class,
|
List<PersonApiFieldMapping> mappings = dao.query(PersonApiFieldMapping.class,
|
||||||
Cnd.where("configId", "=", configId).asc("sort"));
|
Cnd.where("configId", "=", configId).asc("sort"));
|
||||||
PersonApiSyncOptions options = personApiConfigService.syncOptions(configId);
|
PersonApiSyncOptions options = personApiConfigService.syncOptions(configId);
|
||||||
Map<String, String> fieldDefaults = options.fieldDefaults();
|
Map<String, PersonApiSyncOptions.FieldRule> fieldDefaults = options.defaultRulesByTarget();
|
||||||
|
Set<String> conditionSourceFields = new HashSet<>();
|
||||||
|
for (PersonApiSyncOptions.FieldRule rule : fieldDefaults.values()) {
|
||||||
|
if (rule.getDefaultRules() != null) {
|
||||||
|
rule.getDefaultRules().forEach(item -> item.getCondition().collectSourceFields(conditionSourceFields));
|
||||||
|
}
|
||||||
|
}
|
||||||
boolean unitTarget = "sys_unit".equalsIgnoreCase(config.getTargetTable());
|
boolean unitTarget = "sys_unit".equalsIgnoreCase(config.getTargetTable());
|
||||||
if (unitTarget) {
|
if (unitTarget) {
|
||||||
// 单位写入可能随父批次一起提交,行锁一直持有到外层事务完成。
|
// 单位写入可能随父批次一起提交,行锁一直持有到外层事务完成。
|
||||||
@@ -114,9 +120,11 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
|
|||||||
// 页面会加载整张表的列;批次字段由系统生成,未配置的非必填列交给数据库默认值。
|
// 页面会加载整张表的列;批次字段由系统生成,未配置的非必填列交给数据库默认值。
|
||||||
mappings = mappings.stream()
|
mappings = mappings.stream()
|
||||||
.filter(m -> !"pullTime".equals(m.getTargetField()) && (unitTarget || !"id".equals(m.getTargetField())))
|
.filter(m -> !"pullTime".equals(m.getTargetField()) && (unitTarget || !"id".equals(m.getTargetField())))
|
||||||
.filter(m -> Strings.isNotBlank(m.getTargetField()) || options.getRules().stream().noneMatch(rule ->
|
.filter(m -> Strings.isNotBlank(m.getTargetField())
|
||||||
rule.getLength() != null && Strings.isBlank(rule.getTargetField())
|
|| (!conditionSourceFields.contains(Strings.sNull(m.getSourceField()).trim())
|
||||||
&& Objects.equals(rule.getSourceField(), m.getSourceField())))
|
&& options.getRules().stream().noneMatch(rule ->
|
||||||
|
rule.getLength() != null && Strings.isBlank(rule.getTargetField())
|
||||||
|
&& Objects.equals(rule.getSourceField(), m.getSourceField()))))
|
||||||
.filter(m -> Boolean.TRUE.equals(m.getRequired())
|
.filter(m -> Boolean.TRUE.equals(m.getRequired())
|
||||||
|| (Strings.isNotBlank(m.getTargetField())
|
|| (Strings.isNotBlank(m.getTargetField())
|
||||||
&& (Strings.isNotBlank(m.getSourceField()) || fieldDefaults.containsKey(m.getTargetField()))))
|
&& (Strings.isNotBlank(m.getSourceField()) || fieldDefaults.containsKey(m.getTargetField()))))
|
||||||
@@ -376,7 +384,7 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
|
|||||||
|
|
||||||
/** 校验目标表和业务唯一标识;数据库唯一约束由批量写入检查,冲突时整批回滚。 */
|
/** 校验目标表和业务唯一标识;数据库唯一约束由批量写入检查,冲突时整批回滚。 */
|
||||||
private List<NutMap> validateTarget(PersonApiConfig config, List<PersonApiFieldMapping> mappings,
|
private List<NutMap> validateTarget(PersonApiConfig config, List<PersonApiFieldMapping> mappings,
|
||||||
Map<String, String> fieldDefaults) {
|
Map<String, PersonApiSyncOptions.FieldRule> fieldDefaults) {
|
||||||
Assert.hasText(config.getTargetTable(), "请选择目标人员中间表");
|
Assert.hasText(config.getTargetTable(), "请选择目标人员中间表");
|
||||||
List<NutMap> columns = personApiConfigService.columns(config.getTargetTable());
|
List<NutMap> columns = personApiConfigService.columns(config.getTargetTable());
|
||||||
Map<String, NutMap> fields = new HashMap<>();
|
Map<String, NutMap> fields = new HashMap<>();
|
||||||
@@ -427,7 +435,7 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
|
|||||||
private void insertPage(PersonApiConfig config, List<PersonApiFieldMapping> mappings, List<NutMap> columns,
|
private void insertPage(PersonApiConfig config, List<PersonApiFieldMapping> mappings, List<NutMap> columns,
|
||||||
List<?> rows, PersonSyncBatch batch, String lockKey, String owner,
|
List<?> rows, PersonSyncBatch batch, String lockKey, String owner,
|
||||||
PersonApiSyncOptions options, Map<String, Set<String>> dictionaryValues, Set<String> unitIds,
|
PersonApiSyncOptions options, Map<String, Set<String>> dictionaryValues, Set<String> unitIds,
|
||||||
Map<String, String> fieldDefaults) {
|
Map<String, PersonApiSyncOptions.FieldRule> fieldDefaults) {
|
||||||
Assert.isTrue((long) batch.getSourceCount() + rows.size() <= MAX_ROWS, "超过单批最大行数,已终止同步");
|
Assert.isTrue((long) batch.getSourceCount() + rows.size() <= MAX_ROWS, "超过单批最大行数,已终止同步");
|
||||||
batch.setSourceCount(batch.getSourceCount() + rows.size());
|
batch.setSourceCount(batch.getSourceCount() + rows.size());
|
||||||
Map<String, String> types = new HashMap<>();
|
Map<String, String> types = new HashMap<>();
|
||||||
@@ -470,7 +478,10 @@ public class PersonGovernanceSyncServiceImpl implements PersonGovernanceSyncServ
|
|||||||
Object value = Strings.isBlank(mapping.getSourceField()) ? null
|
Object value = Strings.isBlank(mapping.getSourceField()) ? null
|
||||||
: personApiConfigService.readResponsePath(row, mapping.getSourceField());
|
: personApiConfigService.readResponsePath(row, mapping.getSourceField());
|
||||||
if (value == null) {
|
if (value == null) {
|
||||||
value = fieldDefaults.get(mapping.getTargetField());
|
PersonApiSyncOptions.FieldRule defaultRule = fieldDefaults.get(mapping.getTargetField());
|
||||||
|
if (defaultRule != null) {
|
||||||
|
value = defaultRule.resolveDefault(source -> personApiConfigService.readResponsePath(row, source));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (Boolean.TRUE.equals(mapping.getRequired()) && !(preserveUserNulls && value == null)) {
|
if (Boolean.TRUE.equals(mapping.getRequired()) && !(preserveUserNulls && value == null)) {
|
||||||
Assert.isTrue(value != null && !Strings.isBlank(String.valueOf(value)), "必填来源字段为空:" + mapping.getSourceField());
|
Assert.isTrue(value != null && !Strings.isBlank(String.valueOf(value)), "必填来源字段为空:" + mapping.getSourceField());
|
||||||
|
|||||||
@@ -470,7 +470,10 @@
|
|||||||
<el-input v-model="fieldRuleDraft.length" clearable placeholder="留空不过滤,例如 10"></el-input>
|
<el-input v-model="fieldRuleDraft.length" clearable placeholder="留空不过滤,例如 10"></el-input>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="默认值">
|
<el-form-item label="默认值">
|
||||||
<el-input v-model="fieldRuleDraft.defaultValue" clearable placeholder="未配置来源或来源值为 null 时使用"></el-input>
|
<el-input v-model="fieldRuleDraft.defaultValue" clearable placeholder="未配置来源或来源值为 null 时使用">
|
||||||
|
<el-button slot="append" @click="openDefaultLogic">逻辑设置</el-button>
|
||||||
|
</el-input>
|
||||||
|
<span v-if="fieldRuleDraft.defaultRules.length">已配置 {{fieldRuleDraft.defaultRules.length}} 条规则</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
<el-form-item label="对应字典">
|
<el-form-item label="对应字典">
|
||||||
<el-select v-model="fieldRuleDraft.dictCode" clearable filterable placeholder="请选择字典分类">
|
<el-select v-model="fieldRuleDraft.dictCode" clearable filterable placeholder="请选择字典分类">
|
||||||
@@ -484,6 +487,62 @@
|
|||||||
<el-button type="primary" @click="applyFieldRule">确定</el-button>
|
<el-button type="primary" @click="applyFieldRule">确定</el-button>
|
||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
<el-dialog title="默认值逻辑设置" :visible.sync="defaultLogicVisible" append-to-body
|
||||||
|
width="min(1200px, 94vw)" :close-on-click-modal="false" custom-class="person-governance-dialog">
|
||||||
|
<div style="display: flex; flex-wrap: wrap; align-items: flex-start; gap: 16px; max-height: 65vh; overflow: auto;">
|
||||||
|
<div style="flex: 3 1 530px; min-width: 0;">
|
||||||
|
<div style="max-height: 53vh; overflow: auto;">
|
||||||
|
<el-card v-for="(rule, index) in defaultLogicDraft" :key="index" shadow="never"
|
||||||
|
:style="{ marginTop: index ? '12px' : '0' }">
|
||||||
|
<div slot="header" style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<strong>规则 {{index + 1}}</strong>
|
||||||
|
<div>
|
||||||
|
<el-button type="text" :disabled="index === 0" @click="moveDefaultRule(index, -1)">上移</el-button>
|
||||||
|
<el-button type="text" :disabled="index === defaultLogicDraft.length - 1" @click="moveDefaultRule(index, 1)">下移</el-button>
|
||||||
|
<el-button type="text" class="text-danger" @click="defaultLogicDraft.splice(index, 1)">删除规则</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<default-condition-group :group="rule.condition" :source-fields="defaultLogicSourceFields"></default-condition-group>
|
||||||
|
<el-form label-width="100px" style="margin-top: 12px;">
|
||||||
|
<el-form-item label="设为" style="margin-bottom: 0;">
|
||||||
|
<el-input v-model="rule.result" placeholder="请输入写入系统字段的值" clearable></el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
<el-button size="small" icon="el-icon-plus" style="margin-top: 12px;" @click="addDefaultRule">新增规则</el-button>
|
||||||
|
</div>
|
||||||
|
<el-card shadow="never" style="flex: 2 1 320px; min-width: 0; overflow-wrap: anywhere;">
|
||||||
|
<div slot="header"><strong>逻辑预览</strong></div>
|
||||||
|
<div style="max-height: 48vh; overflow: auto; line-height: 1.6;">
|
||||||
|
<div v-if="fieldRuleRow">
|
||||||
|
写入系统字段 <strong>{{fieldRuleRow.targetField}}</strong>
|
||||||
|
</div>
|
||||||
|
<p style="margin: 8px 0 12px; color: #606266;">
|
||||||
|
<template v-if="fieldRuleRow && (fieldRuleRow.sourceField || '').trim()">
|
||||||
|
优先取来源字段「{{fieldRuleRow.sourceField.trim()}}」的值,值为 null 时才使用默认值逻辑。
|
||||||
|
</template>
|
||||||
|
<template v-else>当前未配置直接来源,使用默认值逻辑。</template>
|
||||||
|
<span v-if="defaultLogicDraft.length">从上往下判断,命中即停止。</span>
|
||||||
|
</p>
|
||||||
|
<div v-for="(rule, index) in defaultLogicDraft" :key="index"
|
||||||
|
style="border-top: 1px solid #ebeef5; padding: 8px 0;">
|
||||||
|
<div><strong>{{index === 0 ? '如果' : '否则,如果'}}</strong> {{formatDefaultCondition(rule.condition)}}</div>
|
||||||
|
<div><strong>则写入</strong> {{rule.result.trim() ? JSON.stringify(rule.result) : '(待填写结果)'}}</div>
|
||||||
|
</div>
|
||||||
|
<div style="border-top: 1px solid #ebeef5; padding-top: 8px;">
|
||||||
|
<strong>{{defaultLogicDraft.length ? '否则' : '固定默认值'}}</strong>
|
||||||
|
<template v-if="fieldRuleDraft.defaultValue">写入 {{JSON.stringify(fieldRuleDraft.defaultValue)}}</template>
|
||||||
|
<template v-else>未设置固定默认值</template>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
<div slot="footer">
|
||||||
|
<el-button @click="defaultLogicVisible=false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="applyDefaultLogic">确定</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
<el-dialog title="快捷映射" :visible.sync="quickMappingVisible" append-to-body
|
<el-dialog title="快捷映射" :visible.sync="quickMappingVisible" append-to-body
|
||||||
width="720px" :close-on-click-modal="false" custom-class="person-governance-dialog">
|
width="720px" :close-on-click-modal="false" custom-class="person-governance-dialog">
|
||||||
<el-alert title="输入 JSON 数组,以目标系统字段名为键,id 填已有来源字段名。确认后回填右侧目标字段。"
|
<el-alert title="输入 JSON 数组,以目标系统字段名为键,id 填已有来源字段名。确认后回填右侧目标字段。"
|
||||||
@@ -521,15 +580,64 @@
|
|||||||
</div>
|
</div>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
|
<script type="text/x-template" id="api-default-condition-group-template">
|
||||||
|
<div>
|
||||||
|
<el-radio-group v-model="group.operator" size="small" aria-label="条件组关系">
|
||||||
|
<el-radio-button label="AND">全部满足(AND)</el-radio-button>
|
||||||
|
<el-radio-button label="OR">任一满足(OR)</el-radio-button>
|
||||||
|
</el-radio-group>
|
||||||
|
<div v-for="(child, index) in group.children" :key="index" style="margin-top: 8px;">
|
||||||
|
<template v-if="child.children">
|
||||||
|
<div style="border-left: 2px solid #dcdfe6; padding-left: 12px;">
|
||||||
|
<default-condition-group :group="child" :source-fields="sourceFields"></default-condition-group>
|
||||||
|
<el-button type="text" class="text-danger" @click="group.children.splice(index, 1)">删除子条件组</el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else style="display: flex; flex-wrap: wrap; align-items: center; gap: 8px;">
|
||||||
|
<el-select v-model="child.sourceField" size="small" filterable placeholder="请选择来源字段">
|
||||||
|
<el-option v-for="field in sourceFields" :key="field" :label="field" :value="field"></el-option>
|
||||||
|
</el-select>
|
||||||
|
<el-select v-model="child.operator" size="small" placeholder="比较方式" style="width: 120px;">
|
||||||
|
<el-option label="等于" value="EQ"></el-option>
|
||||||
|
<el-option label="不等于" value="NE"></el-option>
|
||||||
|
<el-option label="包含" value="CONTAINS"></el-option>
|
||||||
|
<el-option label="为空" value="EMPTY"></el-option>
|
||||||
|
<el-option label="不为空" value="NOT_EMPTY"></el-option>
|
||||||
|
</el-select>
|
||||||
|
<el-input v-if="child.operator !== 'EMPTY' && child.operator !== 'NOT_EMPTY'" v-model="child.value"
|
||||||
|
size="small" placeholder="比较值" style="width: 160px;"></el-input>
|
||||||
|
<el-button type="text" class="text-danger" @click="group.children.splice(index, 1)">删除条件</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<el-button type="text" @click="addCondition">新增条件</el-button>
|
||||||
|
<el-button type="text" @click="addGroup">新增子条件组</el-button>
|
||||||
|
</div>
|
||||||
|
</script>
|
||||||
<script>
|
<script>
|
||||||
// 使用独立作用域承载页面常量和实例,支持表单页通过 PJAX 多次进入而不发生重复声明。
|
// 使用独立作用域承载页面常量和实例,支持表单页通过 PJAX 多次进入而不发生重复声明。
|
||||||
;(() => {
|
;(() => {
|
||||||
const apiBase = '/platform/personGovernance/apiConfig'
|
const apiBase = '/platform/personGovernance/apiConfig'
|
||||||
|
const newDefaultCondition = () => ({ sourceField: '', operator: 'EQ', value: '' })
|
||||||
|
const newDefaultGroup = () => ({ operator: 'AND', children: [newDefaultCondition()] })
|
||||||
|
const defaultConditionLabels = { EQ: '等于', NE: '不等于', CONTAINS: '包含', EMPTY: '为空', NOT_EMPTY: '不为空' }
|
||||||
|
const defaultConditionGroup = {
|
||||||
|
name: 'default-condition-group',
|
||||||
|
template: '#api-default-condition-group-template',
|
||||||
|
props: { group: Object, sourceFields: Array },
|
||||||
|
methods: {
|
||||||
|
addCondition() {
|
||||||
|
this.group.children.push(newDefaultCondition())
|
||||||
|
},
|
||||||
|
addGroup() {
|
||||||
|
this.group.children.push(newDefaultGroup())
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
new Vue({
|
new Vue({
|
||||||
el: '#app',
|
el: '#app',
|
||||||
mixins: [initTableMixins],
|
mixins: [initTableMixins],
|
||||||
// platform布局已加载本地vCrontab资源,页面仅注册组件,不重复引入脚本。
|
// platform布局已加载本地vCrontab资源,页面仅注册组件,不重复引入脚本。
|
||||||
components: { vcron: vcrontab.default },
|
components: { vcron: vcrontab.default, 'default-condition-group': defaultConditionGroup },
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
pageReady: false,
|
pageReady: false,
|
||||||
@@ -553,7 +661,9 @@
|
|||||||
dictChoices: [],
|
dictChoices: [],
|
||||||
fieldRuleVisible: false,
|
fieldRuleVisible: false,
|
||||||
fieldRuleRow: null,
|
fieldRuleRow: null,
|
||||||
fieldRuleDraft: { length: '', dictCode: '', defaultValue: '' },
|
fieldRuleDraft: { length: '', dictCode: '', defaultValue: '', defaultRules: [] },
|
||||||
|
defaultLogicVisible: false,
|
||||||
|
defaultLogicDraft: [],
|
||||||
params: [],
|
params: [],
|
||||||
testCases: [],
|
testCases: [],
|
||||||
identityRow: null,
|
identityRow: null,
|
||||||
@@ -669,7 +779,10 @@
|
|||||||
return this.testResults[this.activeEndpoint]
|
return this.testResults[this.activeEndpoint]
|
||||||
},
|
},
|
||||||
hasMappedField() {
|
hasMappedField() {
|
||||||
return this.mappings.some((v) => Boolean(v.targetField && (v.sourceField || v.defaultValue)))
|
return this.mappings.some((v) => Boolean(v.targetField && (v.sourceField || v.defaultValue || (v.defaultRules || []).length)))
|
||||||
|
},
|
||||||
|
defaultLogicSourceFields() {
|
||||||
|
return [...new Set(this.mappings.map((row) => (row.sourceField || '').trim()).filter(Boolean))]
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -686,12 +799,64 @@
|
|||||||
handleAutoSyncUnit(enabled) {
|
handleAutoSyncUnit(enabled) {
|
||||||
if (!enabled) this.$set(this.syncOptions, 'unitConfigId', '')
|
if (!enabled) this.$set(this.syncOptions, 'unitConfigId', '')
|
||||||
},
|
},
|
||||||
|
openDefaultLogic() {
|
||||||
|
const row = this.fieldRuleRow
|
||||||
|
if (!row.targetField) {
|
||||||
|
this.notifyWarning('设置默认值前请选择目标字段')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (['id', 'pullTime'].includes(row.targetField) || row === this.identityRow) {
|
||||||
|
this.notifyWarning('主键、批次时间和唯一标识不能设置默认值')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.defaultLogicDraft = JSON.parse(JSON.stringify(this.fieldRuleDraft.defaultRules))
|
||||||
|
this.defaultLogicVisible = true
|
||||||
|
},
|
||||||
|
addDefaultRule() {
|
||||||
|
this.defaultLogicDraft.push({ condition: newDefaultGroup(), result: '' })
|
||||||
|
},
|
||||||
|
moveDefaultRule(index, offset) {
|
||||||
|
const [rule] = this.defaultLogicDraft.splice(index, 1)
|
||||||
|
this.defaultLogicDraft.splice(index + offset, 0, rule)
|
||||||
|
},
|
||||||
|
formatDefaultCondition(condition) {
|
||||||
|
if (condition.children) {
|
||||||
|
const conditions = condition.children.map((child) => this.formatDefaultCondition(child))
|
||||||
|
if (conditions.length < 2) {
|
||||||
|
return conditions[0] || '(待添加条件)'
|
||||||
|
}
|
||||||
|
return '(' + conditions.join(condition.operator === 'OR' ? ' 或 ' : ' 且 ') + ')'
|
||||||
|
}
|
||||||
|
const field = condition.sourceField ? '「' + condition.sourceField + '」' : '(待选择来源字段)'
|
||||||
|
const operator = defaultConditionLabels[condition.operator]
|
||||||
|
if (['EMPTY', 'NOT_EMPTY'].includes(condition.operator)) {
|
||||||
|
return field + ' ' + operator
|
||||||
|
}
|
||||||
|
return field + ' ' + operator + ' ' + (condition.value ? JSON.stringify(condition.value) : '(待填写比较值)')
|
||||||
|
},
|
||||||
|
isValidDefaultCondition(condition) {
|
||||||
|
if (condition.children) {
|
||||||
|
return condition.children.length > 0
|
||||||
|
&& condition.children.every((child) => this.isValidDefaultCondition(child))
|
||||||
|
}
|
||||||
|
return this.defaultLogicSourceFields.includes(condition.sourceField)
|
||||||
|
&& (['EMPTY', 'NOT_EMPTY'].includes(condition.operator) || Boolean(condition.value))
|
||||||
|
},
|
||||||
|
applyDefaultLogic() {
|
||||||
|
if (this.defaultLogicDraft.some((rule) => !rule.result.trim() || !this.isValidDefaultCondition(rule.condition))) {
|
||||||
|
this.notifyWarning('请为每条规则填写结果,并补全来源字段、比较值和条件组')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
this.fieldRuleDraft.defaultRules = this.defaultLogicDraft
|
||||||
|
this.defaultLogicVisible = false
|
||||||
|
},
|
||||||
openFieldRule(row) {
|
openFieldRule(row) {
|
||||||
this.$set(this, 'fieldRuleRow', row)
|
this.$set(this, 'fieldRuleRow', row)
|
||||||
this.$set(this, 'fieldRuleDraft', {
|
this.$set(this, 'fieldRuleDraft', {
|
||||||
length: row.filterLength == null ? '' : String(row.filterLength),
|
length: row.filterLength == null ? '' : String(row.filterLength),
|
||||||
dictCode: row.dictCode || '',
|
dictCode: row.dictCode || '',
|
||||||
defaultValue: row.defaultValue || '',
|
defaultValue: row.defaultValue || '',
|
||||||
|
defaultRules: row.defaultRules || [],
|
||||||
})
|
})
|
||||||
this.$set(this, 'fieldRuleVisible', true)
|
this.$set(this, 'fieldRuleVisible', true)
|
||||||
},
|
},
|
||||||
@@ -700,7 +865,9 @@
|
|||||||
const length = this.fieldRuleDraft.length.trim()
|
const length = this.fieldRuleDraft.length.trim()
|
||||||
const dictCode = this.fieldRuleDraft.dictCode
|
const dictCode = this.fieldRuleDraft.dictCode
|
||||||
const defaultValue = this.fieldRuleDraft.defaultValue
|
const defaultValue = this.fieldRuleDraft.defaultValue
|
||||||
if ((length || (dictCode && !defaultValue)) && !(row.sourceField || '').trim()) {
|
const defaultRules = this.fieldRuleDraft.defaultRules
|
||||||
|
const hasDefault = Boolean(defaultValue || defaultRules.length)
|
||||||
|
if ((length || (dictCode && !hasDefault)) && !(row.sourceField || '').trim()) {
|
||||||
this.notifyWarning('请先填写来源字段')
|
this.notifyWarning('请先填写来源字段')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -712,17 +879,18 @@
|
|||||||
this.notifyWarning('选择字典前请填写目标字段')
|
this.notifyWarning('选择字典前请填写目标字段')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (defaultValue && !row.targetField) {
|
if (hasDefault && !row.targetField) {
|
||||||
this.notifyWarning('设置默认值前请选择目标字段')
|
this.notifyWarning('设置默认值前请选择目标字段')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (defaultValue && (['id', 'pullTime'].includes(row.targetField) || row === this.identityRow)) {
|
if (hasDefault && (['id', 'pullTime'].includes(row.targetField) || row === this.identityRow)) {
|
||||||
this.notifyWarning('主键、批次时间和唯一标识不能设置默认值')
|
this.notifyWarning('主键、批次时间和唯一标识不能设置默认值')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
this.$set(row, 'filterLength', length ? Number(length) : null)
|
this.$set(row, 'filterLength', length ? Number(length) : null)
|
||||||
this.$set(row, 'dictCode', dictCode)
|
this.$set(row, 'dictCode', dictCode)
|
||||||
this.$set(row, 'defaultValue', defaultValue)
|
this.$set(row, 'defaultValue', defaultValue)
|
||||||
|
this.$set(row, 'defaultRules', defaultRules)
|
||||||
this.$set(this, 'fieldRuleVisible', false)
|
this.$set(this, 'fieldRuleVisible', false)
|
||||||
},
|
},
|
||||||
openQuickMapping() {
|
openQuickMapping() {
|
||||||
@@ -804,6 +972,7 @@
|
|||||||
this.$set(row, 'targetComment', column ? column.columnComment || '' : '')
|
this.$set(row, 'targetComment', column ? column.columnComment || '' : '')
|
||||||
this.$set(row, 'dictCode', '')
|
this.$set(row, 'dictCode', '')
|
||||||
this.$set(row, 'defaultValue', '')
|
this.$set(row, 'defaultValue', '')
|
||||||
|
this.$set(row, 'defaultRules', [])
|
||||||
this.$set(row, 'identityFlag', false)
|
this.$set(row, 'identityFlag', false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -818,6 +987,7 @@
|
|||||||
return Boolean(row && row.sourceField && row.sourceField.trim()
|
return Boolean(row && row.sourceField && row.sourceField.trim()
|
||||||
&& row.targetField && row.targetField.trim()
|
&& row.targetField && row.targetField.trim()
|
||||||
&& !row.defaultValue
|
&& !row.defaultValue
|
||||||
|
&& !(row.defaultRules || []).length
|
||||||
&& row.targetField !== 'pullTime'
|
&& row.targetField !== 'pullTime'
|
||||||
&& (row.targetField !== 'id' || this.form.targetTable === 'sys_unit'))
|
&& (row.targetField !== 'id' || this.form.targetTable === 'sys_unit'))
|
||||||
},
|
},
|
||||||
@@ -1000,6 +1170,7 @@
|
|||||||
this.$set(row, 'targetComment', '')
|
this.$set(row, 'targetComment', '')
|
||||||
this.$set(row, 'identityFlag', false)
|
this.$set(row, 'identityFlag', false)
|
||||||
this.$set(row, 'defaultValue', '')
|
this.$set(row, 'defaultValue', '')
|
||||||
|
this.$set(row, 'defaultRules', [])
|
||||||
})
|
})
|
||||||
this.$set(this, 'identityRow', null)
|
this.$set(this, 'identityRow', null)
|
||||||
this.$set(this.form, 'enabled', false)
|
this.$set(this.form, 'enabled', false)
|
||||||
@@ -1043,6 +1214,7 @@
|
|||||||
filterLength: old ? old.filterLength : null,
|
filterLength: old ? old.filterLength : null,
|
||||||
dictCode: old ? old.dictCode : '',
|
dictCode: old ? old.dictCode : '',
|
||||||
defaultValue: old ? old.defaultValue : '',
|
defaultValue: old ? old.defaultValue : '',
|
||||||
|
defaultRules: old ? old.defaultRules : [],
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
this.$set(this, 'mappings', rows)
|
this.$set(this, 'mappings', rows)
|
||||||
@@ -1104,6 +1276,7 @@
|
|||||||
targetChanged(row) {
|
targetChanged(row) {
|
||||||
const c = this.targetColumns.find((v) => v.columnName === row.targetField)
|
const c = this.targetColumns.find((v) => v.columnName === row.targetField)
|
||||||
this.$set(row, 'targetComment', c ? c.columnComment : '')
|
this.$set(row, 'targetComment', c ? c.columnComment : '')
|
||||||
|
this.$set(row, 'defaultRules', [])
|
||||||
if (!this.hasMappedField) {
|
if (!this.hasMappedField) {
|
||||||
this.$set(this.form, 'enabled', false)
|
this.$set(this.form, 'enabled', false)
|
||||||
}
|
}
|
||||||
@@ -1177,6 +1350,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
save() {
|
save() {
|
||||||
|
const invalidDefaultRow = this.mappings.find((row) => (row.defaultRules || []).some((rule) =>
|
||||||
|
!this.isValidDefaultCondition(rule.condition)))
|
||||||
|
if (invalidDefaultRow) {
|
||||||
|
this.notifyWarning('请检查默认值逻辑使用的来源字段:' + invalidDefaultRow.targetField)
|
||||||
|
this.openFieldRule(invalidDefaultRow)
|
||||||
|
return
|
||||||
|
}
|
||||||
const unitTarget = this.form.targetTable === 'sys_unit'
|
const unitTarget = this.form.targetTable === 'sys_unit'
|
||||||
if (!unitTarget && this.autoSyncUnit && !this.syncOptions.unitConfigId) {
|
if (!unitTarget && this.autoSyncUnit && !this.syncOptions.unitConfigId) {
|
||||||
this.notifyWarning('请选择单位API配置')
|
this.notifyWarning('请选择单位API配置')
|
||||||
@@ -1209,12 +1389,14 @@
|
|||||||
const syncOptions = {
|
const syncOptions = {
|
||||||
autoCreateDict: this.syncOptions.autoCreateDict,
|
autoCreateDict: this.syncOptions.autoCreateDict,
|
||||||
unitConfigId: !unitTarget && this.autoSyncUnit ? this.syncOptions.unitConfigId : '',
|
unitConfigId: !unitTarget && this.autoSyncUnit ? this.syncOptions.unitConfigId : '',
|
||||||
rules: this.mappings.filter((row) => row.filterLength != null || row.dictCode || row.defaultValue).map((row) => ({
|
rules: this.mappings.filter((row) => row.filterLength != null || row.dictCode || row.defaultValue
|
||||||
|
|| (row.defaultRules || []).length).map((row) => ({
|
||||||
sourceField: row.sourceField,
|
sourceField: row.sourceField,
|
||||||
targetField: row.targetField,
|
targetField: row.targetField,
|
||||||
length: row.filterLength,
|
length: row.filterLength,
|
||||||
dictCode: row.dictCode,
|
dictCode: row.dictCode,
|
||||||
defaultValue: row.defaultValue,
|
defaultValue: row.defaultValue,
|
||||||
|
defaultRules: row.defaultRules,
|
||||||
})),
|
})),
|
||||||
}
|
}
|
||||||
this.$set(this, 'formLoading', true)
|
this.$set(this, 'formLoading', true)
|
||||||
@@ -1354,6 +1536,7 @@
|
|||||||
this.$set(row, 'filterLength', rule.length)
|
this.$set(row, 'filterLength', rule.length)
|
||||||
this.$set(row, 'dictCode', rule.dictCode || '')
|
this.$set(row, 'dictCode', rule.dictCode || '')
|
||||||
this.$set(row, 'defaultValue', rule.defaultValue || '')
|
this.$set(row, 'defaultValue', rule.defaultValue || '')
|
||||||
|
this.$set(row, 'defaultRules', rule.defaultRules || [])
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const params = (res.data.params || []).map((v) => {
|
const params = (res.data.params || []).map((v) => {
|
||||||
|
|||||||
Reference in New Issue
Block a user