fix: 人员数据治理迁移功能优化6

This commit is contained in:
2026-11-13 10:53:20 +08:00
parent d474992752
commit 5f79a0c8b4
4 changed files with 309 additions and 23 deletions
@@ -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);
}
}
} }
} }
@@ -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) {
@@ -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())
|| (!conditionSourceFields.contains(Strings.sNull(m.getSourceField()).trim())
&& options.getRules().stream().noneMatch(rule ->
rule.getLength() != null && Strings.isBlank(rule.getTargetField()) rule.getLength() != null && Strings.isBlank(rule.getTargetField())
&& Objects.equals(rule.getSourceField(), m.getSourceField()))) && 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) => {