20260627:监测点可以任意指定子设备

This commit is contained in:
zhugongping 2026-06-27 15:38:09 +08:00
parent d07288b64d
commit 1846f4e717
9 changed files with 295 additions and 56 deletions

View File

@ -8,12 +8,14 @@ TARGET_PORT = 8888 # 请根据实际情况修改
# 元数组 modbus 指令
MODBUS_DATA_HEX = (
"7e80D55746B29F687e",
"010310c07ffffe45217c3d41dfe9964140686641f4"
"7e80D21F6C448E8D7e",
"01031c41b6dcec418b0000402d2148000000004017e0b8400c19efc2ad8a8acb2b",
"02031c41babecc419d8000402be3560000000040137c30bfb2f4d0c2ae9bc1b582",
"03031c41bd1c1941810000402bb38600000000bf4501bdc027dfcac2ae8853eaef",
"04031c41babecc419d8000402be3560000000040137c30bfb2f4d0c2ae9bc1D581",
"05031c41bd1c1941810000402bb38600000000bf4501bdc027dfcac2ae88538AEC"
)
# "01031c4268442f41d30000418b6685000000004026f4cac00d1fd3c2ad2a9d6311",
# "02031c42488fd041c5800041865ab7000000003f364458c03f765ec2add9722da5",
# "03031c42ffdad541c78000421529b8000000003d5f38f94082080dc2abdf4f1f94"
# 01 03 1c 4268 442f 41d3 0000 418b 6685 0000 0000 4026 f4ca c00d 1fd3 c2ad 2a9d 6311
# 从机地址 功能码 字节长度 2个字节 CRC校验
@ -45,7 +47,7 @@ def main():
# 2. 等待0.5秒
print("等待 0.1 秒...")
time.sleep(0.5)
time.sleep(1)
# (可选)接收最终响应
try:

View File

@ -133,7 +133,7 @@ public class CRC16Utils {
public static void main(String[] args) throws Exception {
String hex = "0103028000";
String hex = "006000000000";
byte[] bytes = ByteUtils.hexToByte(hex);
String crc = getCRC(bytes);
System.out.println(crc);

View File

@ -303,9 +303,9 @@ public class ByteUtils {
return data.getBytes(charset);
}
/*
/**
* 把16进制字符串转换成字节数组
* @param hex
* @param hexStr 16进制字符
* @return
*/
public static byte[] hexToByte(String hexStr) {

View File

@ -19,6 +19,7 @@ import com.fastbee.iot.platform.flow.FlowContext;
import com.fastbee.iot.platform.flow.PlatformFlowExecutor;
import com.fastbee.iot.platform.mapper.PlatformFlowMapper;
import com.fastbee.iot.platform.mapper.PlatformInterfaceMapper;
import com.fastbee.iot.platform.service.IPlatformDeviceSubMappingService;
import com.fastbee.iot.platform.service.IPlatformInstanceService;
import com.fastbee.iot.platform.service.IPlatformPushLogService;
import lombok.extern.slf4j.Slf4j;
@ -34,6 +35,7 @@ import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@ -66,6 +68,8 @@ public class DcpPlatformAdapter {
private StaticLevelBufferService bufferService;
@Autowired
private PlatformInterfaceMapper interfaceMapper;
@Autowired
private IPlatformDeviceSubMappingService subMappingService;
/** 传感器类型 → 推送接口路径 */
private static final Map<String, String> PUSH_PATH = new HashMap<>();
@ -184,10 +188,12 @@ public class DcpPlatformAdapter {
JSONObject currentBodyFields = buildObjectBySchema(platform, mapping, requestSchema, reportData, fieldMappings);
if (subMapping.isReference()) {
// 基准点上报:仅缓存,不推送
// 基准点上报:缓存当前基准点,并补推此前已缓存的监测点
bufferService.saveReferenceData(
platform.getId(), fieldName, reportSerialNumber, currentItem, reportIntervalSec);
log.info("[DCP] 基准点 {} 已缓存,等待监测点上报", reportSerialNumber);
int replayCount = replayPendingMonitorData(platform, mapping, fieldName, currentItem,
reportIntervalSec, baseUrl, interfaceCode);
log.info("[DCP] 基准点 {} 已缓存,补推监测点 {} 条", reportSerialNumber, replayCount);
return;
}
@ -198,7 +204,7 @@ public class DcpPlatformAdapter {
return;
}
// 获取基准点序列号(从 onboard_config 中取 refSensorId 对应的原始序列号)
// 获取基准点序列号,优先使用子映射中当前标记的 reference 行
String refSerialNumber = resolveRefSerialNumber(subMapping, mapping);
JSONObject refData = bufferService.getReferenceDataIfFresh(
@ -243,12 +249,18 @@ public class DcpPlatformAdapter {
return false;
}
/**
* 从子映射的 onboard_config 解析基准点序列号
*/
/** 解析基准点序列号 */
private String resolveRefSerialNumber(PlatformDeviceSubMapping subMapping, PlatformDeviceMapping mapping) {
// 从 onboard_config 取 refSensorId 对应的子设备序列号
// 通常基准点的 sensorId == 基准点的 serialNumber
try {
PlatformDeviceSubMapping reference = subMappingService.findReference(mapping.getId());
if (reference != null && StringUtils.hasText(reference.getSubSerialNumber())) {
return reference.getSubSerialNumber();
}
} catch (Exception e) {
log.warn("[DCP] 查询静力水准基准点失败 mappingId={}, error={}", mapping.getId(), e.getMessage());
}
// 兼容历史配置:从 onboard_config 取基准点原始序列号
if (StringUtils.hasText(subMapping.getOnboardConfig())) {
try {
JSONObject config = JSON.parseObject(subMapping.getOnboardConfig());
@ -262,6 +274,67 @@ public class DcpPlatformAdapter {
return mapping.getSerialNumber();
}
/**
* 基准点后到时,补推此前缓存的监测点数据。
*/
private int replayPendingMonitorData(PlatformInstance platform, PlatformDeviceMapping mapping,
String fieldName, JSONObject referenceItem,
int reportIntervalSec, String baseUrl, String interfaceCode) {
List<PlatformDeviceSubMapping> subMappings = subMappingService.selectByParentId(mapping.getId());
if (subMappings == null || subMappings.isEmpty()) {
return 0;
}
int replayCount = 0;
for (PlatformDeviceSubMapping monitor : subMappings) {
if (monitor == null || !monitor.isMonitor()
|| !StringUtils.hasText(monitor.getSubSerialNumber())) {
continue;
}
JSONObject monitorData = bufferService.getMonitorDataIfFresh(
platform.getId(), fieldName, monitor.getSubSerialNumber(), reportIntervalSec);
if (monitorData == null) {
continue;
}
if (!StringUtils.hasText(monitor.getPlatformDeviceId())) {
log.warn("[DCP] 监测点 {} 有缓存但未分配平台设备ID,暂不补推",
monitor.getSubSerialNumber());
continue;
}
JSONObject monitorItem = monitorData.getJSONObject("itemData");
if (monitorItem == null || monitorItem.isEmpty()) {
bufferService.removeMonitorData(platform.getId(), fieldName, monitor.getSubSerialNumber());
continue;
}
JSONObject bodyFields = monitorData.getJSONObject("bodyFields");
if (bodyFields == null) {
bodyFields = new JSONObject();
}
JSONArray sensorData = new JSONArray();
sensorData.add(referenceItem);
sensorData.add(monitorItem);
JSONArray pushBody = buildArrayPushBody(mapping, bodyFields, fieldName,
sensorData, monitor.getPlatformDeviceId());
PlatformPushLog logEntry = createLogEntry(platform, mapping, interfaceCode,
monitor.getSubSerialNumber());
doHttpPost(platform, baseUrl, JSON.toJSONString(pushBody), logEntry);
pushLogService.asyncSave(logEntry);
if (logEntry.getStatus() != null && logEntry.getStatus() == 1) {
bufferService.removeMonitorData(platform.getId(), fieldName, monitor.getSubSerialNumber());
replayCount++;
log.info("[DCP] 基准点后到,已补推监测点 {} deviceId={}",
monitor.getSubSerialNumber(), monitor.getPlatformDeviceId());
} else {
log.warn("[DCP] 基准点后到,补推监测点 {} 失败,保留缓存等待下次基准点上报",
monitor.getSubSerialNumber());
}
}
return replayCount;
}
// ==================== 普通推送 ====================
private void handleNormalPush(PlatformInstance platform, PlatformDeviceMapping mapping,

View File

@ -21,6 +21,10 @@ public class FlowContext {
private String currentMonitorSerialNumber;
/** 网关子设备入网时,当前子映射行的 onboard_config JSON(平台相关参数) */
private String subMappingOnboardConfig;
/** 网关子设备入网时,当前基准点序列号 */
private String currentReferenceSerialNumber;
/** 网关子设备入网时,基准点子映射行的 onboard_config JSON */
private String referenceSubMappingOnboardConfig;
public FlowContext(PlatformInstance platform, PlatformDeviceMapping deviceMapping, String payload) {
this.platform = platform;
@ -64,6 +68,22 @@ public class FlowContext {
this.subMappingOnboardConfig = subMappingOnboardConfig;
}
public String getCurrentReferenceSerialNumber() {
return currentReferenceSerialNumber;
}
public void setCurrentReferenceSerialNumber(String currentReferenceSerialNumber) {
this.currentReferenceSerialNumber = currentReferenceSerialNumber;
}
public String getReferenceSubMappingOnboardConfig() {
return referenceSubMappingOnboardConfig;
}
public void setReferenceSubMappingOnboardConfig(String referenceSubMappingOnboardConfig) {
this.referenceSubMappingOnboardConfig = referenceSubMappingOnboardConfig;
}
@SuppressWarnings("unchecked")
public Object getFromContext(String contextKey) {
// contextKey 格式: "step1.access_token" 或 "step1"

View File

@ -429,6 +429,20 @@ public class PlatformFlowExecutor {
}
private List<String> collectSubDeviceSensorIds(FlowContext ctx) {
if (StringUtils.hasText(ctx.getCurrentMonitorSerialNumber())
&& StringUtils.hasText(ctx.getCurrentReferenceSerialNumber())) {
String refSensorId = resolveOnboardSensorId(
ctx.getReferenceSubMappingOnboardConfig(), ctx.getCurrentReferenceSerialNumber());
String monitorSensorId = resolveOnboardSensorId(
ctx.getSubMappingOnboardConfig(), ctx.getCurrentMonitorSerialNumber());
List<String> ids = new ArrayList<>();
if (StringUtils.hasText(refSensorId)) ids.add(refSensorId);
if (StringUtils.hasText(monitorSensorId)) ids.add(monitorSensorId);
if (!ids.isEmpty()) {
return ids.stream().distinct().collect(Collectors.toList());
}
}
// 网关入网时,从子表 onboard_config 获取 sensorId
if (StringUtils.hasText(ctx.getSubMappingOnboardConfig())) {
try {
@ -450,10 +464,32 @@ public class PlatformFlowExecutor {
}
/**
* 网关入网时:从当前子映射的 onboard_config 取 [refSensorId, sensorId] 一对。
* 网关入网时:优先按子表中当前 reference 行取 [基准点sensorId, 监测点sensorId] 一对。
* 直连设备/旧逻辑:从 extra_mapping 解析所有配对。
*/
private List<List<String>> collectReferenceSubDeviceSensorIdPairs(FlowContext ctx) {
if (StringUtils.hasText(ctx.getCurrentMonitorSerialNumber())) {
String sensorId = resolveOnboardSensorId(
ctx.getSubMappingOnboardConfig(), ctx.getCurrentMonitorSerialNumber());
String refSensorId = resolveOnboardSensorId(
ctx.getReferenceSubMappingOnboardConfig(), ctx.getCurrentReferenceSerialNumber());
if (!StringUtils.hasText(refSensorId)) {
refSensorId = resolveOnboardConfigText(ctx.getSubMappingOnboardConfig(), "refSensorId");
}
if (StringUtils.hasText(sensorId) && StringUtils.hasText(refSensorId)) {
if (sameText(refSensorId, sensorId)) {
throw new RuntimeException("静力水准 sensorIds 配置无效:基准点和监测点 sensorId 相同");
}
List<List<String>> pairs = new ArrayList<>();
pairs.add(Arrays.asList(refSensorId, sensorId));
return pairs;
}
if (isStaticLevel(ctx.getDeviceMapping())) {
throw new RuntimeException("静力水准监测点 " + ctx.getCurrentMonitorSerialNumber()
+ " 缺少基准点或监测点 sensorId,请检查子设备映射");
}
}
// 网关入网:onboardConfig 已指定当前监测点
if (StringUtils.hasText(ctx.getSubMappingOnboardConfig())) {
try {
@ -494,6 +530,27 @@ public class PlatformFlowExecutor {
return pairs;
}
private String resolveOnboardSensorId(String onboardConfig, String fallbackSerialNumber) {
String sensorId = resolveOnboardConfigText(onboardConfig, "sensorId");
return StringUtils.hasText(sensorId) ? sensorId : normalizeText(fallbackSerialNumber);
}
private String resolveOnboardConfigText(String onboardConfig, String field) {
if (!StringUtils.hasText(onboardConfig) || !StringUtils.hasText(field)) {
return null;
}
try {
JSONObject config = JSON.parseObject(onboardConfig);
return normalizeText(config.getString(field));
} catch (Exception ignored) {
return null;
}
}
private boolean isStaticLevel(PlatformDeviceMapping mapping) {
return mapping != null && Objects.equals("static_level", mapping.getSensorType());
}
private List<SubDeviceSensorBinding> collectSubDeviceSensorBindings(PlatformDeviceMapping mapping) {
if (mapping == null) {
return Collections.emptyList();

View File

@ -21,6 +21,7 @@ import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@Slf4j
@ -105,6 +106,7 @@ public class PlatformDeviceMappingServiceImpl extends ServiceImpl<PlatformDevice
*/
private void onboardGateway(PlatformDeviceMapping mapping, PlatformInstance platform, PlatformFlow onboardFlow) {
List<PlatformDeviceSubMapping> subMappings = subMappingService.selectByParentId(mapping.getId());
PlatformDeviceSubMapping reference = resolveGatewayReference(mapping, subMappings);
List<PlatformDeviceSubMapping> monitors = subMappings.stream()
.filter(s -> s.isMonitor() && (s.getNetStatus() == null || s.getNetStatus() == 0))
.collect(Collectors.toList());
@ -120,6 +122,10 @@ public class PlatformDeviceMappingServiceImpl extends ServiceImpl<PlatformDevice
FlowContext ctx = new FlowContext(platform, mapping, null);
ctx.setCurrentMonitorSerialNumber(monitor.getSubSerialNumber());
ctx.setSubMappingOnboardConfig(monitor.getOnboardConfig());
if (reference != null) {
ctx.setCurrentReferenceSerialNumber(reference.getSubSerialNumber());
ctx.setReferenceSubMappingOnboardConfig(reference.getOnboardConfig());
}
try {
flowExecutor.execute(onboardFlow, ctx);
@ -165,4 +171,22 @@ public class PlatformDeviceMappingServiceImpl extends ServiceImpl<PlatformDevice
+ String.join("\n", failedMessages));
}
}
private PlatformDeviceSubMapping resolveGatewayReference(PlatformDeviceMapping mapping,
List<PlatformDeviceSubMapping> subMappings) {
List<PlatformDeviceSubMapping> references = subMappings.stream()
.filter(PlatformDeviceSubMapping::isReference)
.collect(Collectors.toList());
boolean staticLevel = Objects.equals("static_level", mapping.getSensorType());
if (references.size() > 1) {
throw new RuntimeException("子表中只能指定一个基准点,请检查子设备映射");
}
if (references.isEmpty()) {
if (staticLevel) {
throw new RuntimeException("静力水准网关入网需在子设备映射中指定一个基准点");
}
return null;
}
return references.get(0);
}
}

View File

@ -5,7 +5,7 @@
<select id="selectByParentId" resultType="com.fastbee.iot.platform.domain.PlatformDeviceSubMapping">
SELECT * FROM platform_device_sub_mapping
WHERE parent_mapping_id = #{parentMappingId}
ORDER BY device_role DESC, id ASC
ORDER BY id ASC
</select>
<select id="selectByParentAndSerial" resultType="com.fastbee.iot.platform.domain.PlatformDeviceSubMapping">

View File

@ -170,7 +170,7 @@
</el-table-column>
<el-table-column label="角色" width="120" align="center">
<template slot-scope="scope">
<el-select v-model="scope.row.deviceRole" size="mini" style="width:100%">
<el-select v-model="scope.row.deviceRole" size="mini" style="width:100%" @change="onSubRoleChange(scope.row)">
<el-option label="基准点" value="reference" />
<el-option label="监测点" value="monitor" />
</el-select>
@ -178,7 +178,7 @@
</el-table-column>
<el-table-column label="入网参数 (JSON)" min-width="220">
<template slot-scope="scope">
<el-input v-model="scope.row.onboardConfig" size="mini" :placeholder="scope.row.deviceRole === 'reference' ? '{\"sensorId\":\"...\"}' : '{\"sensorId\":\"...\",\"refSensorId\":\"...\",\"refSerialNumber\":\"...\"}'" />
<el-input v-model="scope.row.onboardConfig" size="mini" :placeholder="getOnboardConfigPlaceholder(scope.row)" />
</template>
</el-table-column>
<el-table-column label="平台设备ID" width="140" v-if="form.netStatus===1">
@ -323,11 +323,14 @@ export default {
listPlatformDeviceMapping(this.queryParams).then(res => {
this.list = res.rows; this.total = res.total; this.loading = false
// 加载设备名称映射
const ids = [...new Set(res.rows.map(r => r.deviceId))]
const ids = [...new Set(res.rows.map(r => r.deviceId).filter(Boolean))]
if (ids.length) {
listDevice({ pageNum: 1, pageSize: 200 }).then(dr => {
Promise.all(ids.map(id => getDevice(id).catch(() => null))).then(results => {
const map = {}
dr.rows.forEach(d => { map[d.deviceId] = d.deviceName })
results.forEach(res => {
const d = res && (res.data || res)
if (d && d.deviceId) map[d.deviceId] = d.deviceName
})
this.deviceMap = map
})
}
@ -349,6 +352,11 @@ export default {
getSubDevicePairs(row) {
return this.subMappingCache[row.id] || []
},
getOnboardConfigPlaceholder(row) {
return row.deviceRole === 'reference'
? '{"sensorId":"..."}'
: '{"sensorId":"...","refSensorId":"...","refSerialNumber":"..."}'
},
handleQuery() { this.queryParams.pageNum = 1; this.getList() },
resetQuery() { this.resetForm('queryForm'); this.handleQuery() },
handleAdd() {
@ -409,16 +417,11 @@ export default {
onDeviceTypeChange(val) {
if (val === 1 && this.subMappingRows.length === 0 && this.subDeviceOptions.length > 0) {
// 切换为网关设备时,自动用已加载的子设备列表预填
const refSn = this.subDeviceOptions[0].serialNumber
this.subMappingRows = this.subDeviceOptions.map((device, idx) => {
const role = idx === 0 ? 'reference' : 'monitor'
const config = role === 'reference'
? JSON.stringify({ sensorId: device.serialNumber })
: JSON.stringify({ sensorId: device.serialNumber, refSensorId: refSn, refSerialNumber: refSn })
this.subMappingRows = this.subDeviceOptions.map(device => {
return {
subSerialNumber: device.serialNumber,
deviceRole: role,
onboardConfig: config,
deviceRole: 'monitor',
onboardConfig: JSON.stringify({ sensorId: device.serialNumber }),
platformDeviceId: '',
platformPointId: ''
}
@ -475,12 +478,20 @@ export default {
this.loadThingsModel(deviceId)
},
searchDevice(query) {
if (!query) return
const keyword = (query || '').trim()
if (!keyword) return
this.deviceSearchLoading = true
listDevice({ pageNum: 1, pageSize: 50, deviceName: query }).then(res => {
listDevice({ pageNum: 1, pageSize: 50, ...this.buildDeviceSearchParams(keyword) }).then(res => {
this.deviceOptions = res.rows; this.deviceSearchLoading = false
})
},
buildDeviceSearchParams(query) {
const keyword = (query || '').trim()
if (keyword.length === 12) {
return { serialNumber: keyword }
}
return { deviceName: keyword }
},
loadDeviceContext(deviceId) {
if (!deviceId) return
getDevice(deviceId).then(res => {
@ -524,18 +535,13 @@ export default {
if (!this.form.id) {
this.form.deviceType = this.subDeviceOptions.length > 0 ? 1 : 0
}
// 网关设备且子映射为空时,自动预填所有子设备,第一个为基准点
// 网关设备且子映射为空时,自动预填所有子设备;基准点由用户明确指定
if (this.form.deviceType === 1 && this.subMappingRows.length === 0 && this.subDeviceOptions.length > 0) {
this.subMappingRows = this.subDeviceOptions.map((device, idx) => {
const role = idx === 0 ? 'reference' : 'monitor'
const refSn = this.subDeviceOptions[0].serialNumber
const config = role === 'reference'
? JSON.stringify({ sensorId: device.serialNumber })
: JSON.stringify({ sensorId: device.serialNumber, refSensorId: refSn, refSerialNumber: refSn })
this.subMappingRows = this.subDeviceOptions.map(device => {
return {
subSerialNumber: device.serialNumber,
deviceRole: role,
onboardConfig: config,
deviceRole: 'monitor',
onboardConfig: JSON.stringify({ sensorId: device.serialNumber }),
platformDeviceId: '',
platformPointId: ''
}
@ -553,18 +559,69 @@ export default {
addSubMapping() {
this.subMappingRows.push({ subSerialNumber: '', deviceRole: 'monitor', onboardConfig: '', platformDeviceId: '', platformPointId: '' })
},
onSubSerialChange(row) {
// 自动填充 onboardConfig 中的 sensorId
if (!row.onboardConfig && row.subSerialNumber) {
const refRow = this.subMappingRows.find(r => r.deviceRole === 'reference')
onSubRoleChange(row) {
if (row.deviceRole === 'reference') {
this.subMappingRows.forEach(item => {
if (item !== row && item.deviceRole === 'reference') {
item.deviceRole = 'monitor'
}
})
}
this.refreshSubMappingOnboardConfigs()
},
onSubSerialChange(row) {
if (!row.subSerialNumber) return
row.onboardConfig = JSON.stringify({ sensorId: row.subSerialNumber })
} else if (refRow) {
row.onboardConfig = JSON.stringify({ sensorId: row.subSerialNumber, refSensorId: refRow.subSerialNumber, refSerialNumber: refRow.subSerialNumber })
this.refreshSubMappingOnboardConfigs()
},
findReferenceRow(rows = this.subMappingRows) {
return (rows || []).find(r => r.deviceRole === 'reference' && r.subSerialNumber)
},
buildOnboardConfig(row, refRow) {
const config = this.safeParseJson(row.onboardConfig, {})
const next = { ...config, sensorId: config.sensorId || row.subSerialNumber }
if (row.deviceRole === 'reference') {
delete next.refSensorId
delete next.refSerialNumber
} else if (refRow && refRow.subSerialNumber) {
const refConfig = this.safeParseJson(refRow.onboardConfig, {})
next.refSensorId = refConfig.sensorId || refRow.subSerialNumber
next.refSerialNumber = refRow.subSerialNumber
} else {
row.onboardConfig = JSON.stringify({ sensorId: row.subSerialNumber })
delete next.refSensorId
delete next.refSerialNumber
}
return JSON.stringify(next)
},
refreshSubMappingOnboardConfigs() {
const refRow = this.findReferenceRow()
if (refRow && refRow.subSerialNumber) {
refRow.onboardConfig = this.buildOnboardConfig(refRow, null)
}
this.subMappingRows.forEach(row => {
if (!row.subSerialNumber || row === refRow) return
row.onboardConfig = this.buildOnboardConfig(row, refRow)
})
},
normalizeSubMappingsForSubmit() {
const rows = (this.subMappingRows || [])
.filter(r => r.subSerialNumber)
.map(r => ({ ...r, deviceRole: r.deviceRole || 'monitor' }))
const references = rows.filter(r => r.deviceRole === 'reference')
const monitors = rows.filter(r => r.deviceRole === 'monitor')
if (references.length > 1) {
this.$modal.msgError('只能指定一个基准点')
return null
}
if (this.form.sensorType === 'static_level' && (references.length !== 1 || monitors.length === 0)) {
this.$modal.msgError('静力水准需配置 1 个基准点和至少 1 个监测点')
return null
}
const refRow = references[0] || null
return rows.map(row => ({
...row,
onboardConfig: this.buildOnboardConfig(row, refRow)
}))
},
safeParseJson(value, defaultValue) {
if (!value) return defaultValue
@ -576,6 +633,13 @@ export default {
if (!valid) return
this.form.fieldMapping = this.fieldMappingRows.length > 0
? JSON.stringify(this.fieldMappingRows) : null
const normalizedSubRows = this.form.deviceType === 1
? this.normalizeSubMappingsForSubmit()
: []
if (this.form.deviceType === 1 && normalizedSubRows === null) return
if (this.form.deviceType === 1) {
this.subMappingRows = normalizedSubRows
}
// 网关设备:extra_mapping 只存数据映射配置
if (this.form.deviceType === 1) {
this.form.extraMapping = JSON.stringify({
@ -591,8 +655,7 @@ export default {
const mappingId = this.form.id || (res.data && res.data.id) || res.data
// 网关设备:保存后同步子映射
if (this.form.deviceType === 1 && mappingId) {
const subData = this.subMappingRows
.filter(r => r.subSerialNumber)
const subData = normalizedSubRows
.map(r => ({
subSerialNumber: r.subSerialNumber,
deviceRole: r.deviceRole || 'monitor',