1008 lines
49 KiB
Vue
1008 lines
49 KiB
Vue
<template>
|
||
<div class="platform-device-mapping">
|
||
<!-- 搜索 -->
|
||
<el-card v-show="showSearch" style="margin-bottom:10px">
|
||
<el-form :model="queryParams" ref="queryForm" size="mini" :inline="true" style="margin-bottom:-18px">
|
||
<el-form-item label="平台" prop="platformId">
|
||
<el-select v-model="queryParams.platformId" placeholder="全部平台" clearable style="width:140px" @change="handleQuery">
|
||
<el-option v-for="p in platformList" :key="p.id" :label="p.name" :value="p.id" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="设备编号" prop="serialNumber">
|
||
<el-input v-model="queryParams.serialNumber" placeholder="主设备/子设备编号" clearable style="width:170px" @keyup.enter.native="handleQuery" />
|
||
</el-form-item>
|
||
<el-form-item label="设备类型" prop="sensorType">
|
||
<el-select v-model="queryParams.sensorType" placeholder="全部" clearable filterable allow-create default-first-option style="width:140px">
|
||
<el-option v-for="s in sensorTypeOptions" :key="s.value" :label="s.label" :value="s.value" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="入网状态" prop="netStatus">
|
||
<el-select v-model="queryParams.netStatus" placeholder="全部" clearable style="width:100px">
|
||
<el-option label="已入网" :value="1" />
|
||
<el-option label="未入网" :value="0" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item style="float:right">
|
||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||
</el-form-item>
|
||
</el-form>
|
||
</el-card>
|
||
|
||
<el-card>
|
||
<el-row :gutter="10" style="margin-bottom:15px">
|
||
<el-col :span="1.5">
|
||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['platform:deviceMapping:add']">新增绑定</el-button>
|
||
</el-col>
|
||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList" />
|
||
</el-row>
|
||
|
||
<el-table v-loading="loading" :data="list" :row-key="row => row.id">
|
||
<el-table-column type="expand" width="30">
|
||
<template slot-scope="scope">
|
||
<div v-if="scope.row.deviceType === 1 && getSubDevicePairs(scope.row).length > 0" style="padding:10px 50px">
|
||
<el-table :data="getSubDevicePairs(scope.row)" size="mini" border>
|
||
<el-table-column label="子设备序列号" prop="subSerialNumber" min-width="140" />
|
||
<el-table-column label="子设备名称" prop="subDeviceName" min-width="140" show-overflow-tooltip />
|
||
<el-table-column label="角色" prop="deviceRole" width="80">
|
||
<template slot-scope="s">
|
||
<el-tag size="mini" :type="s.row.deviceRole === 'reference' ? 'warning' : ''">{{ s.row.deviceRole === 'reference' ? '基准' : '监测' }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="平台设备ID" prop="platformDeviceId" min-width="200" show-overflow-tooltip />
|
||
<el-table-column label="平台测点ID" prop="platformPointId" min-width="200" show-overflow-tooltip />
|
||
<el-table-column label="入网" prop="netStatus" width="70" align="center">
|
||
<template slot-scope="s">
|
||
<el-tag size="mini" :type="s.row.netStatus === 1 ? 'success' : 'info'">{{ s.row.netStatus === 1 ? '是' : '否' }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</div>
|
||
<div v-else style="padding:10px 50px;color:#909399;font-size:12px">无子设备映射</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="平台" width="120">
|
||
<template slot-scope="scope">{{ platformName(scope.row.platformId) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="项目" min-width="120" show-overflow-tooltip>
|
||
<template slot-scope="scope">
|
||
<span v-if="scope.row.platformProjectName">{{ scope.row.platformProjectName }}</span>
|
||
<span v-else style="color:#909399">-</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="设备名称" min-width="140">
|
||
<template slot-scope="scope">{{ scope.row.deviceName || deviceName(scope.row.deviceId) }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="序列号" prop="serialNumber" width="140" />
|
||
<el-table-column label="传感器类型" width="110" align="center">
|
||
<template slot-scope="scope">
|
||
<el-tag size="small">{{ sensorLabel(scope.row.sensorType) }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="设备类型" width="90" align="center">
|
||
<template slot-scope="scope">
|
||
<el-tag size="small" :type="scope.row.deviceType === 1 ? 'warning' : ''">{{ scope.row.deviceType === 1 ? '网关' : '直连' }}</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="平台设备ID" width="200" show-overflow-tooltip>
|
||
<template slot-scope="scope">
|
||
<span v-if="scope.row.deviceType === 1">
|
||
<el-tag size="small" type="warning">子表({{ getSubDevicePairs(scope.row).length }})</el-tag>
|
||
</span>
|
||
<span v-else>{{ scope.row.platformDeviceId }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="入网状态" width="90" align="center">
|
||
<template slot-scope="scope">
|
||
<el-tag size="small" type="success" v-if="scope.row.netStatus===1">已入网</el-tag>
|
||
<el-tag size="small" type="info" v-else>未入网</el-tag>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="启用" width="70" align="center">
|
||
<template slot-scope="scope">
|
||
<el-switch v-model="scope.row.enable" active-value="1" inactive-value="0" @change="handleEnableChange(scope.row)" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="推送模式" width="100" align="center">
|
||
<template slot-scope="scope">
|
||
<el-tag size="small" :type="scope.row.pushMode === 2 ? 'warning' : 'success'">
|
||
{{ scope.row.pushMode === 2 ? '定时' : '实时' }}
|
||
</el-tag>
|
||
<div v-if="scope.row.pushMode === 2 && scope.row.pushInterval" style="font-size:12px;color:#909399;margin-top:2px">
|
||
{{ scope.row.pushInterval }}分钟
|
||
</div>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" fixed="right" width="200" align="center">
|
||
<template slot-scope="scope">
|
||
<el-button size="mini" type="text" icon="el-icon-connection" @click="handleOnboard(scope.row)" v-hasPermi="['platform:deviceMapping:edit']"
|
||
:disabled="scope.row.netStatus===1">入网</el-button>
|
||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['platform:deviceMapping:edit']">编辑</el-button>
|
||
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" v-hasPermi="['platform:deviceMapping:remove']">删除</el-button>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
|
||
<pagination v-show="total>0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
|
||
</el-card>
|
||
|
||
<!-- 新增/编辑弹窗 -->
|
||
<el-dialog :title="title" :visible.sync="open" width="820px" append-to-body :close-on-click-modal="false">
|
||
<el-form ref="form" :model="form" :rules="rules" label-width="110px">
|
||
<el-form-item label="所属平台" prop="platformId">
|
||
<el-select v-model="form.platformId" placeholder="请选择平台" style="width:100%" @change="onPlatformChange">
|
||
<el-option v-for="p in platformList" :key="p.id" :label="p.name" :value="p.id" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="所属用户" prop="platformUserAuthId">
|
||
<el-select v-model="form.platformUserAuthId" placeholder="请选择已初始化的用户" style="width:100%" @change="onUserAuthChange" :disabled="!form.platformId || form.netStatus === 1" clearable>
|
||
<el-option v-for="u in initializedUserList" :key="u.id" :label="u.platformUserName || u.platformUserId" :value="u.id" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="所属项目" prop="platformProjectId">
|
||
<el-select v-model="form.platformProjectId" placeholder="请选择项目" style="width:100%" @change="onProjectChange" :disabled="!form.platformUserAuthId || form.netStatus === 1" clearable>
|
||
<el-option v-for="proj in projectOptions" :key="proj.id" :label="proj.name" :value="String(proj.id)" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="系统设备" prop="deviceId">
|
||
<el-select v-model="form.deviceId" placeholder="选择直连或网关设备,支持名称/编号搜索" filterable remote clearable
|
||
:remote-method="searchDevice" :loading="deviceSearchLoading" style="width:100%"
|
||
@visible-change="onDeviceSelectVisible"
|
||
@change="onDeviceChange">
|
||
<el-option v-for="d in deviceOptions" :key="d.deviceId" :label="deviceOptionLabel(d)" :value="d.deviceId" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="序列号">
|
||
<el-input v-model="form.serialNumber" disabled />
|
||
</el-form-item>
|
||
<el-form-item label="设备名称">
|
||
<el-input v-model="form.deviceName" />
|
||
</el-form-item>
|
||
<el-form-item label="传感器类型" prop="sensorType">
|
||
<el-select v-model="form.sensorType" placeholder="选择或输入设备类型" filterable allow-create default-first-option style="width:100%">
|
||
<el-option v-for="s in sensorTypeOptions" :key="s.value" :label="s.label" :value="s.value" />
|
||
</el-select>
|
||
</el-form-item>
|
||
<el-form-item label="设备类型" prop="deviceType">
|
||
<el-radio-group v-model="form.deviceType" @change="onDeviceTypeChange">
|
||
<el-radio :label="0">直连设备</el-radio>
|
||
<el-radio :label="1">网关设备(多子设备)</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
<el-form-item label="推送接口" prop="pushInterfaceId">
|
||
<el-select v-model="form.pushInterfaceId" placeholder="选择该平台的推送接口" style="width:100%" @change="onPushInterfaceChange" clearable>
|
||
<el-option v-for="iface in interfaceOptions" :key="iface.id" :label="`${iface.name}(${iface.method} ${iface.path})`" :value="iface.id" />
|
||
</el-select>
|
||
</el-form-item>
|
||
|
||
<!-- 推送配置 -->
|
||
<el-divider content-position="left">推送配置</el-divider>
|
||
<el-row :gutter="16">
|
||
<el-col :span="12">
|
||
<el-form-item label="推送模式" prop="pushMode">
|
||
<el-radio-group v-model="form.pushMode">
|
||
<el-radio :label="1">
|
||
实时推送
|
||
<el-tooltip content="设备上报即刻推送" placement="top">
|
||
<i class="el-icon-question" style="color:#909399;margin-left:5px;cursor:help;"></i>
|
||
</el-tooltip>
|
||
</el-radio>
|
||
<el-radio :label="2">
|
||
定时推送
|
||
<el-tooltip content="按间隔推送最新数据" placement="top">
|
||
<i class="el-icon-question" style="color:#909399;margin-left:5px;cursor:help;"></i>
|
||
</el-tooltip>
|
||
</el-radio>
|
||
</el-radio-group>
|
||
</el-form-item>
|
||
</el-col>
|
||
<el-col :span="12" v-if="form.pushMode === 2">
|
||
<el-form-item label="推送间隔" prop="pushInterval">
|
||
<el-input-number v-model="form.pushInterval" :min="1" :max="1440" :step="5" style="width:160px" />
|
||
<span style="margin-left:8px;color:#606266">分钟</span>
|
||
<div style="font-size:12px;color:#909399;margin-top:4px">建议 5~60 分钟</div>
|
||
</el-form-item>
|
||
</el-col>
|
||
</el-row>
|
||
|
||
<!-- 直连设备:平台ID 字段 -->
|
||
<template v-if="form.deviceType !== 1">
|
||
<el-form-item label="平台设备ID">
|
||
<el-input v-model="form.platformDeviceId" placeholder="入网后自动回填,也可手动填写" />
|
||
</el-form-item>
|
||
<el-form-item label="平台测点ID">
|
||
<el-input v-model="form.platformPointId" placeholder="入网后自动回填,也可手动填写" />
|
||
</el-form-item>
|
||
</template>
|
||
|
||
<!-- 网关设备:子设备映射配置 -->
|
||
<div v-if="form.deviceType === 1" class="array-fields-section">
|
||
<div class="section-label">子设备映射</div>
|
||
<div class="schema-tip" style="margin-bottom:8px">
|
||
配置网关下参与上报的子设备,指定基准点(不会单独推送,与基准点一起推送)和监测点角色。入网时每个监测点独立创建测点和绑定。
|
||
</div>
|
||
<el-table :data="subMappingRows" size="mini" border style="width:100%">
|
||
<el-table-column label="子设备编号" min-width="160">
|
||
<template slot-scope="scope">
|
||
<el-select v-if="subDeviceOptions.length > 0"
|
||
v-model="scope.row.subSerialNumber" size="mini" placeholder="选择子设备" filterable style="width:100%"
|
||
@change="onSubSerialChange(scope.row)">
|
||
<el-option v-for="device in subDeviceOptions" :key="device.serialNumber" :label="subDeviceOptionLabel(device)" :value="device.serialNumber" />
|
||
</el-select>
|
||
<el-input v-else v-model="scope.row.subSerialNumber" size="mini" placeholder="填写子设备编号" @change="onSubSerialChange(scope.row)" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="子设备名称" min-width="140">
|
||
<template slot-scope="scope">
|
||
<el-input v-model="scope.row.subDeviceName" size="mini" placeholder="默认同子设备编号" />
|
||
</template>
|
||
</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%" @change="onSubRoleChange(scope.row)">
|
||
<el-option label="基准点" value="reference" />
|
||
<el-option label="监测点" value="monitor" />
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
<template v-if="form.sensorType === 'deep_inclino'">
|
||
<el-table-column label="传感器深度(m)" min-width="140">
|
||
<template slot-scope="scope">
|
||
<el-input-number v-model="scope.row.sensorDepth" :min="0" :precision="2" size="mini" style="width:100%" @change="refreshOnboardConfig" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="孔深(m)" min-width="120">
|
||
<template slot-scope="scope">
|
||
<el-input-number v-model="scope.row.holeDepth" :min="0" :precision="2" size="mini" style="width:100%" @change="syncHoleDepthToAllMonitors" />
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="孔口高程(m)" min-width="140">
|
||
<template slot-scope="scope">
|
||
<el-input-number v-model="scope.row.holeElevation" :min="0" :precision="2" size="mini" style="width:100%" @change="syncHoleElevationToAllMonitors" />
|
||
</template>
|
||
</el-table-column>
|
||
</template>
|
||
<el-table-column label="入网参数 (JSON)" min-width="220">
|
||
<template slot-scope="scope">
|
||
<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">
|
||
<template slot-scope="scope">
|
||
<span style="font-size:12px;color:#606266">{{ scope.row.platformDeviceId || '—' }}</span>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="50" align="center">
|
||
<template slot-scope="scope">
|
||
<el-button size="mini" type="text" icon="el-icon-delete" style="color:#f56c6c" @click="subMappingRows.splice(scope.$index, 1)" />
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-button size="mini" icon="el-icon-plus" style="margin-top:8px" @click="addSubMapping">添加子设备</el-button>
|
||
|
||
<!-- 数据映射配置 -->
|
||
<el-form-item label="上报间隔" label-width="100px" style="margin-top:12px;margin-bottom:0">
|
||
<el-input-number v-model="form.reportIntervalSec" :min="10" :max="3600" :step="10" style="width:160px" /> 秒
|
||
</el-form-item>
|
||
<el-form-item v-if="form.sensorType === 'static_level'" label="跨网关基准点" label-width="100px" style="margin-top:12px;margin-bottom:0">
|
||
<el-select v-model="form.crossGatewayRefSerial" clearable filterable
|
||
placeholder="选择其他网关已配置的基准点" style="width:100%;max-width:420px" @change="onCrossGatewayRefChange">
|
||
<el-option v-for="refItem in referenceOptions" :key="refItem.referenceSerialNumber"
|
||
:label="`${refItem.referenceSerialNumber}(网关:${refItem.gatewayDeviceName})`"
|
||
:value="refItem.referenceSerialNumber" />
|
||
</el-select>
|
||
<div style="font-size:12px;color:#909399;margin-top:4px">
|
||
本网关子设备全部为监测点时使用:选择其他网关已配置的基准点后,本网关子设备无需再指定基准点。
|
||
</div>
|
||
</el-form-item>
|
||
<el-form-item v-if="itemFieldMappingRows.length > 0" label="数组项映射" label-width="100px" style="margin-top:12px;margin-bottom:0">
|
||
<el-table :data="itemFieldMappingRows" size="mini" border style="width:100%">
|
||
<el-table-column label="平台字段" width="140">
|
||
<template slot-scope="scope">{{ scope.row.platformField }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="物模型标识" min-width="200">
|
||
<template slot-scope="scope">
|
||
<el-select v-model="scope.row.identifier" size="mini" placeholder="选择物模型字段" filterable style="width:100%">
|
||
<el-option v-for="tm in uniqueThingsModelOptions" :key="tm.identifier" :label="`${tm.modelName}(${tm.identifier})`" :value="tm.identifier" />
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
</el-form-item>
|
||
</div>
|
||
|
||
<!-- 标量字段映射 -->
|
||
<el-form-item v-if="fieldMappingRows.length > 0" label="字段映射">
|
||
<el-table :data="fieldMappingRows" size="mini" border style="width:100%">
|
||
<el-table-column label="平台字段" min-width="160">
|
||
<template slot-scope="scope">{{ scope.row.platformField }}</template>
|
||
</el-table-column>
|
||
<el-table-column label="物模型标识" min-width="180">
|
||
<template slot-scope="scope">
|
||
<el-select v-model="scope.row.identifier" size="mini" placeholder="选择物模型字段" filterable style="width:100%">
|
||
<el-option v-for="tm in uniqueThingsModelOptions" :key="tm.identifier" :label="`${tm.modelName}(${tm.identifier})`" :value="tm.identifier" />
|
||
</el-select>
|
||
</template>
|
||
</el-table-column>
|
||
<el-table-column label="操作" width="60" align="center">
|
||
<template slot-scope="scope">
|
||
<el-button size="mini" type="text" icon="el-icon-delete" @click="fieldMappingRows.splice(scope.$index,1)" />
|
||
</template>
|
||
</el-table-column>
|
||
</el-table>
|
||
<el-button size="mini" icon="el-icon-plus" style="margin-top:8px" @click="addFieldMapping">添加字段</el-button>
|
||
</el-form-item>
|
||
|
||
<el-form-item label="备注">
|
||
<el-input v-model="form.remark" type="textarea" :rows="2" />
|
||
</el-form-item>
|
||
</el-form>
|
||
<div slot="footer">
|
||
<el-button @click="cancel">取消</el-button>
|
||
<el-button type="primary" @click="submitForm" :loading="submitLoading">确定</el-button>
|
||
</div>
|
||
</el-dialog>
|
||
</div>
|
||
</template>
|
||
|
||
<script>
|
||
import { listPlatformDeviceMapping, addPlatformDeviceMapping, updatePlatformDeviceMapping, delPlatformDeviceMapping, onboardDevice, listSubMappings, listSubMappingsBatch, syncSubMappings, listStaticLevelReferencePoints } from '@/api/platform/deviceMapping'
|
||
import { listPlatformInstance } from '@/api/platform/instance'
|
||
import { getInitializedUsers, getProjectsByUserAuthId } from '@/api/platform/userAuth'
|
||
import { listPlatformInterface } from '@/api/platform/interface'
|
||
import { listPlatformFlow, getPlatformFlow } from '@/api/platform/flow'
|
||
import { listDevice, listThingsModel, getDevice, listDeviceShort } from '@/api/iot/device'
|
||
import { listGateway } from '@/api/iot/gateway'
|
||
import { getDicts } from '@/api/system/dict/data'
|
||
|
||
const SENSOR_TYPE_DICT = 'platform_sensor_type'
|
||
|
||
export default {
|
||
name: 'PlatformDeviceMapping',
|
||
data() {
|
||
return {
|
||
loading: false, submitLoading: false, showSearch: true,
|
||
total: 0, list: [], platformList: [], deviceOptions: [], deviceMap: {},
|
||
subDeviceOptions: [],
|
||
deviceSearchLoading: false, thingsModelOptions: [],
|
||
interfaceOptions: [],
|
||
projectOptions: [],
|
||
initializedUserList: [],
|
||
open: false, title: '',
|
||
fieldMappingRows: [],
|
||
// 网关子设备映射行
|
||
subMappingRows: [],
|
||
// 网关数据映射
|
||
itemFieldMappingRows: [],
|
||
// 跨网关基准点下拉选项(static_level 专用)
|
||
referenceOptions: [],
|
||
// 子表缓存 (列表展开行使用)
|
||
subMappingCache: {},
|
||
sensorTypeDictOptions: [],
|
||
queryParams: { pageNum: 1, pageSize: 20, platformId: null, serialNumber: '', sensorType: '', netStatus: null },
|
||
form: {
|
||
enable: '1', deviceType: 0, reportIntervalSec: 300, deviceName: '',
|
||
platformId: null, platformUserAuthId: null, platformProjectId: null, platformProjectName: '',
|
||
pushInterfaceId: null, pushMode: 1, pushInterval: 5,
|
||
deviceId: null, serialNumber: '', sensorType: '', remark: ''
|
||
},
|
||
rules: {
|
||
platformId: [{ required: true, message: '请选择平台', trigger: 'change' }],
|
||
platformUserAuthId: [{ required: true, message: '请选择用户', trigger: 'change' }],
|
||
platformProjectId: [{ required: true, message: '请选择所属项目', trigger: 'change' }],
|
||
deviceId: [{ required: true, message: '请选择设备', trigger: 'change' }],
|
||
sensorType: [{ required: true, message: '请选择或输入设备类型', trigger: 'change' }],
|
||
pushInterfaceId: [{ required: true, message: '请选择推送接口', trigger: 'change' }]
|
||
}
|
||
}
|
||
},
|
||
computed: {
|
||
uniqueThingsModelOptions() {
|
||
const deduped = []
|
||
const seen = new Set()
|
||
;(this.thingsModelOptions || []).forEach(item => {
|
||
if (!item || !item.identifier || seen.has(item.identifier)) return
|
||
seen.add(item.identifier)
|
||
deduped.push(item)
|
||
})
|
||
return deduped
|
||
},
|
||
sensorTypeOptions() {
|
||
const optionMap = new Map()
|
||
this.sensorTypeDictOptions.forEach(item => optionMap.set(item.value, { ...item }))
|
||
;(this.list || []).forEach(item => {
|
||
if (item && item.sensorType && !optionMap.has(item.sensorType)) {
|
||
optionMap.set(item.sensorType, { value: item.sensorType, label: item.sensorType })
|
||
}
|
||
})
|
||
return Array.from(optionMap.values())
|
||
}
|
||
},
|
||
created() {
|
||
this.loadSensorTypeOptions()
|
||
listPlatformInstance({ pageNum: 1, pageSize: 100 }).then(res => { this.platformList = res.rows || [] })
|
||
this.getList()
|
||
},
|
||
methods: {
|
||
loadSensorTypeOptions() {
|
||
getDicts(SENSOR_TYPE_DICT).then(res => {
|
||
this.sensorTypeDictOptions = ((res && res.data) || []).map(item => ({
|
||
value: item.dictValue,
|
||
label: item.dictLabel || item.dictValue
|
||
})).filter(item => item.value)
|
||
}).catch(() => {
|
||
this.sensorTypeDictOptions = []
|
||
})
|
||
},
|
||
getList() {
|
||
this.loading = true
|
||
listPlatformDeviceMapping(this.queryParams).then(res => {
|
||
this.list = res.rows; this.total = res.total; this.loading = false
|
||
// deviceName 由后端列表查询批量补全,前端无需再逐个 getDevice(N→0)
|
||
// 预加载网关设备的子映射(批量一次拉取,避免 N 次请求)
|
||
this.subMappingCache = {}
|
||
const gatewayIds = res.rows.filter(r => r.deviceType === 1).map(r => r.id).filter(Boolean)
|
||
if (gatewayIds.length) {
|
||
listSubMappingsBatch(gatewayIds).then(subRes => {
|
||
this.subMappingCache = subRes.data || {}
|
||
})
|
||
}
|
||
})
|
||
},
|
||
platformName(id) { const p = this.platformList.find(x => x.id === id); return p ? p.name : id },
|
||
deviceName(id) { return this.deviceMap[id] || id },
|
||
deviceOptionLabel(device) {
|
||
const typeText = device.deviceType === 2 || device.subDeviceCount > 0 ? '网关' : '直连'
|
||
const name = device.deviceName || device.serialNumber || device.deviceId
|
||
const sn = device.serialNumber ? `(${device.serialNumber})` : ''
|
||
return `${name}${sn} - ${typeText}`
|
||
},
|
||
subDeviceOptionLabel(device) {
|
||
const name = device.deviceName || device.serialNumber
|
||
const sn = device.serialNumber && device.deviceName ? `(${device.serialNumber})` : ''
|
||
return `${name}${sn}`
|
||
},
|
||
sensorLabel(v) {
|
||
const option = this.sensorTypeOptions.find(item => item.value === v)
|
||
return option ? option.label : v
|
||
},
|
||
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() {
|
||
this.form = {
|
||
enable: '1', deviceType: 0, pushInterfaceId: null, pushMode: 1, pushInterval: 5, reportIntervalSec: 300, deviceName: '',
|
||
platformId: null, platformUserAuthId: null, platformProjectId: null, platformProjectName: '',
|
||
deviceId: null, serialNumber: '', sensorType: '', remark: '', crossGatewayRefSerial: null
|
||
}
|
||
this.fieldMappingRows = []
|
||
this.subMappingRows = []
|
||
this.itemFieldMappingRows = []
|
||
this.referenceOptions = []
|
||
this.deviceOptions = []
|
||
this.subDeviceOptions = []
|
||
this.thingsModelOptions = []
|
||
this.interfaceOptions = []
|
||
this.projectOptions = []
|
||
this.initializedUserList = []
|
||
this.title = '新增设备绑定'; this.open = true
|
||
},
|
||
handleUpdate(row) {
|
||
this.form = { ...row, deviceName: row.deviceName || '', pushMode: row.pushMode || 1, pushInterval: row.pushInterval || 5, reportIntervalSec: 300, crossGatewayRefSerial: null }
|
||
this.fieldMappingRows = this.safeParseJson(row.fieldMapping, [])
|
||
this.subMappingRows = []
|
||
this.itemFieldMappingRows = []
|
||
this.referenceOptions = []
|
||
// 解析 extra_mapping 中的数据映射配置
|
||
if (row.extraMapping) {
|
||
try {
|
||
const extra = JSON.parse(row.extraMapping)
|
||
this.form.reportIntervalSec = extra.reportIntervalSec || 300
|
||
this.itemFieldMappingRows = extra.itemFieldMappings || []
|
||
if (extra.staticLevel && extra.staticLevel.referenceSerialNumber) {
|
||
this.form.crossGatewayRefSerial = extra.staticLevel.referenceSerialNumber
|
||
}
|
||
} catch (e) { /* ignore */ }
|
||
}
|
||
// 加载该平台的接口列表和用户列表
|
||
if (row.platformId) {
|
||
this.loadInterfaces(row.platformId)
|
||
this.loadInitializedUsers(row.platformId)
|
||
this.loadReferenceOptions(row.platformId, row.id)
|
||
}
|
||
// 加载用户的项目列表
|
||
if (row.platformUserAuthId) {
|
||
this.loadProjectsByUserAuth(row.platformUserAuthId)
|
||
}
|
||
// 加载子设备映射(网关设备)
|
||
if (row.deviceType === 1 && row.id) {
|
||
listSubMappings(row.id).then(res => {
|
||
this.subMappingRows = (res.data || []).map(s => {
|
||
const config = this.safeParseJson(s.onboardConfig, {})
|
||
return {
|
||
...s,
|
||
subDeviceName: s.subDeviceName || s.subSerialNumber,
|
||
sensorDepth: config.sensorDepth != null ? config.sensorDepth : null,
|
||
holeDepth: config.holeDepth != null ? config.holeDepth : null,
|
||
holeElevation: config.holeElevation != null ? config.holeElevation : null
|
||
}
|
||
})
|
||
})
|
||
}
|
||
this.loadDeviceContext(row.deviceId)
|
||
this.title = '编辑设备绑定'; this.open = true
|
||
},
|
||
handleDelete(row) {
|
||
this.$confirm('确认删除?').then(() => {
|
||
delPlatformDeviceMapping(row.id).then(() => { this.getList(); this.$modal.msgSuccess('删除成功') })
|
||
})
|
||
},
|
||
handleEnableChange(row) {
|
||
updatePlatformDeviceMapping({ id: row.id, enable: row.enable })
|
||
},
|
||
handleOnboard(row) {
|
||
this.$confirm(`确认对设备【${this.deviceName(row.deviceId)}】执行入网操作?`).then(() => {
|
||
onboardDevice(row.id).then(() => { this.$modal.msgSuccess('入网成功'); this.getList() })
|
||
})
|
||
},
|
||
onPlatformChange(platformId) {
|
||
this.interfaceOptions = []
|
||
this.form.pushInterfaceId = null
|
||
this.initializedUserList = []
|
||
this.form.platformUserAuthId = null
|
||
this.projectOptions = []
|
||
this.form.platformProjectId = null
|
||
this.form.platformProjectName = ''
|
||
this.referenceOptions = []
|
||
this.form.crossGatewayRefSerial = null
|
||
if (platformId) {
|
||
this.loadInterfaces(platformId)
|
||
this.loadInitializedUsers(platformId)
|
||
this.loadReferenceOptions(platformId, this.form.id)
|
||
}
|
||
},
|
||
loadInitializedUsers(platformId) {
|
||
if (!platformId) { this.initializedUserList = []; return }
|
||
getInitializedUsers(platformId).then(res => {
|
||
this.initializedUserList = res.data || []
|
||
}).catch(() => { this.initializedUserList = [] })
|
||
},
|
||
// 加载同平台其他静力水准网关已配置的基准点(跨网关基准点下拉框数据源)
|
||
loadReferenceOptions(platformId, excludeMappingId) {
|
||
if (!platformId) { this.referenceOptions = []; return }
|
||
listStaticLevelReferencePoints({ platformId, excludeMappingId: excludeMappingId || undefined }).then(res => {
|
||
this.referenceOptions = res.data || []
|
||
}).catch(() => { this.referenceOptions = [] })
|
||
},
|
||
onUserAuthChange(userAuthId) {
|
||
this.projectOptions = []
|
||
this.form.platformProjectId = null
|
||
this.form.platformProjectName = ''
|
||
if (userAuthId) {
|
||
this.loadProjectsByUserAuth(userAuthId)
|
||
}
|
||
},
|
||
loadProjectsByUserAuth(userAuthId) {
|
||
if (!userAuthId) { this.projectOptions = []; return }
|
||
getProjectsByUserAuthId(userAuthId).then(res => {
|
||
this.projectOptions = res.data || []
|
||
}).catch(() => { this.projectOptions = [] })
|
||
},
|
||
onProjectChange(projectId) {
|
||
if (projectId) {
|
||
const proj = this.projectOptions.find(p => String(p.id) === String(projectId))
|
||
this.form.platformProjectName = proj ? proj.name : ''
|
||
} else {
|
||
this.form.platformProjectName = ''
|
||
}
|
||
},
|
||
onDeviceTypeChange(val) {
|
||
if (val === 1 && this.subMappingRows.length === 0 && this.subDeviceOptions.length > 0) {
|
||
// 切换为网关设备时,自动用已加载的子设备列表预填
|
||
this.subMappingRows = this.subDeviceOptions.map(device => {
|
||
return {
|
||
subSerialNumber: device.serialNumber,
|
||
subDeviceName: device.deviceName || device.serialNumber,
|
||
deviceRole: 'monitor',
|
||
onboardConfig: JSON.stringify({ sensorId: device.serialNumber }),
|
||
platformDeviceId: '',
|
||
platformPointId: ''
|
||
}
|
||
})
|
||
}
|
||
},
|
||
loadInterfaces(platformId) {
|
||
listPlatformInterface({ platformId, pageNum: 1, pageSize: 100 }).then(res => {
|
||
this.interfaceOptions = res.rows || []
|
||
})
|
||
},
|
||
onPushInterfaceChange(interfaceId) {
|
||
// 根据接口的 isBatchPush 自动设置推送模式:0=实时推送(1),1=定时推送(2)
|
||
if (interfaceId) {
|
||
const iface = this.interfaceOptions.find(x => x.id === interfaceId)
|
||
if (iface) {
|
||
this.form.pushMode = iface.isBatchPush === 1 ? 2 : 1
|
||
}
|
||
} else {
|
||
this.form.pushMode = 1
|
||
}
|
||
// 从推送接口的 requestSchema 提取字段作为数据映射模板,切换接口时全量刷新
|
||
if (!interfaceId) return
|
||
const iface = this.interfaceOptions.find(x => x.id === interfaceId)
|
||
if (!iface || !iface.requestSchema) return
|
||
try {
|
||
const schema = JSON.parse(iface.requestSchema)
|
||
const oldFields = this.fieldMappingRows || []
|
||
const oldItemFields = this.itemFieldMappingRows || []
|
||
|
||
// 标量字段映射(body 层):所有设备类型都提取,同名平台字段保留已选标识
|
||
const scalarFields = schema.filter(f =>
|
||
f.type !== 'array' && (f.source === 'thingsModel' || f.source === 'identifier')
|
||
)
|
||
this.fieldMappingRows = scalarFields.map(s => ({
|
||
identifier: (oldFields.find(r => r.platformField === s.name) || {}).identifier || '',
|
||
platformField: s.name
|
||
}))
|
||
|
||
// 网关设备额外提取数组字段的 itemSchema
|
||
if (this.form.deviceType === 1) {
|
||
const arrayField = schema.find(f => f.type === 'array' && f.itemSchema && f.itemSchema.length > 0)
|
||
if (arrayField) {
|
||
const identifierFields = (arrayField.itemSchema || []).filter(s => s.source === 'thingsModel' || s.source === 'identifier')
|
||
this.itemFieldMappingRows = identifierFields.map(s => ({
|
||
platformField: s.name,
|
||
identifier: (oldItemFields.find(r => r.platformField === s.name) || {}).identifier || ''
|
||
}))
|
||
} else {
|
||
this.itemFieldMappingRows = []
|
||
}
|
||
}
|
||
} catch (e) { /* ignore */ }
|
||
},
|
||
onDeviceChange(deviceId) {
|
||
const d = this.deviceOptions.find(x => x.deviceId === deviceId)
|
||
if (d) {
|
||
this.form.serialNumber = d.serialNumber
|
||
this.form.deviceName = d.deviceName || d.serialNumber
|
||
this.loadSubDeviceOptions(d)
|
||
} else {
|
||
this.form.serialNumber = ''
|
||
this.form.deviceName = ''
|
||
this.subDeviceOptions = []
|
||
this.thingsModelOptions = []
|
||
}
|
||
this.loadThingsModel(deviceId)
|
||
},
|
||
onDeviceSelectVisible(visible) {
|
||
if (visible && this.deviceOptions.length === 0) {
|
||
this.searchDevice('')
|
||
}
|
||
},
|
||
searchDevice(query) {
|
||
const keyword = (query || '').trim()
|
||
this.deviceSearchLoading = true
|
||
const base = { pageNum: 1, pageSize: 50, entrance: 2, showChild: true }
|
||
const requests = []
|
||
if (!keyword) {
|
||
requests.push(listDeviceShort(base))
|
||
} else {
|
||
requests.push(listDeviceShort({ ...base, deviceName: keyword }))
|
||
requests.push(listDeviceShort({ ...base, serialNumber: keyword }))
|
||
}
|
||
Promise.all(requests.map(req => req.catch(() => ({ rows: [] })))).then(results => {
|
||
this.deviceOptions = this.mergeDeviceOptions(results.flatMap(res => res.rows || []))
|
||
}).finally(() => {
|
||
this.deviceSearchLoading = false
|
||
})
|
||
},
|
||
mergeDeviceOptions(rows) {
|
||
const map = new Map()
|
||
;(rows || []).forEach(item => {
|
||
if (!item || !item.deviceId || item.gwDevCode) return
|
||
if (!map.has(item.deviceId)) {
|
||
map.set(item.deviceId, item)
|
||
}
|
||
})
|
||
return Array.from(map.values())
|
||
},
|
||
loadDeviceContext(deviceId) {
|
||
if (!deviceId) return
|
||
getDevice(deviceId).then(res => {
|
||
const device = res.data || res
|
||
if (!device) return
|
||
this.deviceOptions = [{ deviceId: device.deviceId, deviceName: device.deviceName, serialNumber: device.serialNumber }]
|
||
this.form.deviceId = device.deviceId
|
||
this.form.serialNumber = device.serialNumber
|
||
this.form.deviceName = this.form.deviceName || device.deviceName || device.serialNumber
|
||
this.loadThingsModel(device.deviceId)
|
||
this.loadSubDeviceOptions(device)
|
||
}).catch(() => {
|
||
this.loadThingsModel(deviceId)
|
||
})
|
||
},
|
||
loadSubDeviceOptions(gateway) {
|
||
const gatewayDeviceId = gateway.deviceId
|
||
const gatewaySerialNumber = gateway.serialNumber
|
||
if (!gatewayDeviceId && !gatewaySerialNumber) {
|
||
this.subDeviceOptions = []
|
||
return
|
||
}
|
||
const promises = []
|
||
if (gatewayDeviceId) {
|
||
promises.push(listGateway({ gwDeviceId: gatewayDeviceId, pageNum: 1, pageSize: 500 }).then(res => res.rows || []).catch(() => []))
|
||
}
|
||
if (gatewaySerialNumber) {
|
||
promises.push(listDevice({ gwDevCode: gatewaySerialNumber, pageNum: 1, pageSize: 500 }).then(res => res.rows || []).catch(() => []))
|
||
}
|
||
Promise.all(promises).then(results => {
|
||
const optionMap = new Map()
|
||
results.forEach(rows => {
|
||
;(rows || []).forEach(item => {
|
||
const sn = (item.serialNumber || item.subDeviceNo || '').trim()
|
||
if (sn && !optionMap.has(sn)) {
|
||
optionMap.set(sn, { serialNumber: sn, deviceName: item.deviceName || item.subDeviceName || sn })
|
||
}
|
||
})
|
||
})
|
||
this.subDeviceOptions = Array.from(optionMap.values())
|
||
// 新增模式下,根据是否有子设备自动推断设备类型,减少人工选择出错
|
||
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 => {
|
||
return {
|
||
subSerialNumber: device.serialNumber,
|
||
subDeviceName: device.deviceName || device.serialNumber,
|
||
deviceRole: 'monitor',
|
||
onboardConfig: JSON.stringify({ sensorId: device.serialNumber }),
|
||
platformDeviceId: '',
|
||
platformPointId: ''
|
||
}
|
||
})
|
||
}
|
||
})
|
||
},
|
||
loadThingsModel(deviceId) {
|
||
if (!deviceId) return
|
||
listThingsModel({ deviceId, pageNum: 1, pageSize: 200 }).then(res => {
|
||
this.thingsModelOptions = res.rows || []
|
||
})
|
||
},
|
||
addFieldMapping() { this.fieldMappingRows.push({ identifier: '', platformField: '' }) },
|
||
addSubMapping() {
|
||
this.subMappingRows.push({ subSerialNumber: '', subDeviceName: '', deviceRole: 'monitor', onboardConfig: '', sensorDepth: null, holeDepth: null, holeElevation: null, platformDeviceId: '', platformPointId: '' })
|
||
},
|
||
onSubRoleChange(row) {
|
||
if (row.deviceRole === 'reference') {
|
||
this.subMappingRows.forEach(item => {
|
||
if (item !== row && item.deviceRole === 'reference') {
|
||
item.deviceRole = 'monitor'
|
||
}
|
||
})
|
||
}
|
||
this.refreshSubMappingOnboardConfigs()
|
||
if (this.form.sensorType === 'deep_inclino') this.refreshOnboardConfig()
|
||
},
|
||
onSubSerialChange(row) {
|
||
if (!row.subSerialNumber) return
|
||
const selected = this.subDeviceOptions.find(device => device.serialNumber === row.subSerialNumber)
|
||
if (selected) {
|
||
row.subDeviceName = selected.deviceName || selected.serialNumber
|
||
}
|
||
const config = this.safeParseJson(row.onboardConfig, {})
|
||
config.sensorId = config.sensorId || row.subSerialNumber
|
||
row.onboardConfig = JSON.stringify(config)
|
||
this.refreshSubMappingOnboardConfigs()
|
||
if (this.form.sensorType === 'deep_inclino') this.refreshOnboardConfig()
|
||
},
|
||
findReferenceRow(rows = this.subMappingRows) {
|
||
return (rows || []).find(r => r.deviceRole === 'reference' && r.subSerialNumber) || this.crossGatewayRefRow()
|
||
},
|
||
// 跨网关基准点(由下拉框选择,不在子表中)构造为虚拟基准点行,用于生成监测点入网参数
|
||
crossGatewayRefRow() {
|
||
if (this.form.sensorType !== 'static_level' || !this.form.crossGatewayRefSerial) return null
|
||
const option = this.referenceOptions.find(o => o.referenceSerialNumber === this.form.crossGatewayRefSerial)
|
||
const sensorId = (option && option.referenceSensorId) || this.form.crossGatewayRefSerial
|
||
return { subSerialNumber: this.form.crossGatewayRefSerial, onboardConfig: JSON.stringify({ sensorId }) }
|
||
},
|
||
onCrossGatewayRefChange() {
|
||
this.refreshSubMappingOnboardConfigs()
|
||
},
|
||
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 {
|
||
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)
|
||
})
|
||
},
|
||
refreshOnboardConfig() {
|
||
this.subMappingRows.forEach(row => {
|
||
const existing = this.safeParseJson(row.onboardConfig, {})
|
||
if (!existing.sensorId) existing.sensorId = row.subSerialNumber
|
||
if (this.form.sensorType === 'deep_inclino' && row.deviceRole === 'monitor') {
|
||
if (row.sensorDepth != null) existing.sensorDepth = row.sensorDepth
|
||
if (row.holeDepth != null) existing.holeDepth = row.holeDepth
|
||
if (row.holeElevation != null) existing.holeElevation = row.holeElevation
|
||
} else {
|
||
delete existing.sensorDepth
|
||
delete existing.holeDepth
|
||
delete existing.holeElevation
|
||
}
|
||
row.onboardConfig = JSON.stringify(existing)
|
||
})
|
||
},
|
||
syncHoleDepthToAllMonitors(value) {
|
||
this.subMappingRows.forEach(row => {
|
||
if (row.deviceRole === 'monitor') row.holeDepth = value
|
||
})
|
||
this.refreshOnboardConfig()
|
||
},
|
||
syncHoleElevationToAllMonitors(value) {
|
||
this.subMappingRows.forEach(row => {
|
||
if (row.deviceRole === 'monitor') row.holeElevation = value
|
||
})
|
||
this.refreshOnboardConfig()
|
||
},
|
||
normalizeSubMappingsForSubmit() {
|
||
const rows = (this.subMappingRows || [])
|
||
.filter(r => r.subSerialNumber)
|
||
.map(r => ({ ...r, deviceRole: r.deviceRole || 'monitor' }))
|
||
if (new Set(rows.map(r => r.subSerialNumber)).size !== rows.length) {
|
||
this.$modal.msgError('子设备编号不能重复')
|
||
return null
|
||
}
|
||
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') {
|
||
const hasCrossRef = !!this.form.crossGatewayRefSerial
|
||
if (hasCrossRef && references.length === 1) {
|
||
this.$modal.msgError('已选择跨网关基准点,子设备中不能再指定基准点')
|
||
return null
|
||
}
|
||
if ((references.length !== 1 && !hasCrossRef) || monitors.length === 0) {
|
||
this.$modal.msgError('静力水准需配置 1 个基准点(或选择跨网关基准点)和至少 1 个监测点')
|
||
return null
|
||
}
|
||
}
|
||
if (this.form.sensorType === 'deep_inclino') {
|
||
if (monitors.length === 0) {
|
||
this.$modal.msgError('深部位移至少配置 1 个监测点')
|
||
return null
|
||
}
|
||
const invalid = monitors.find(r => r.sensorDepth == null || r.holeDepth == null || r.holeElevation == null || r.sensorDepth > r.holeDepth)
|
||
if (invalid) {
|
||
this.$modal.msgError(`子设备 ${invalid.subSerialNumber} 的传感器深度、孔深、孔口高程必填,且传感器深度不能大于孔深`)
|
||
return null
|
||
}
|
||
}
|
||
const refRow = references[0] || this.crossGatewayRefRow()
|
||
return rows.map(row => {
|
||
const onboardConfig = this.safeParseJson(this.buildOnboardConfig(row, refRow), {})
|
||
if (this.form.sensorType === 'deep_inclino' && row.deviceRole === 'monitor') {
|
||
onboardConfig.sensorDepth = row.sensorDepth
|
||
onboardConfig.holeDepth = row.holeDepth
|
||
onboardConfig.holeElevation = row.holeElevation
|
||
}
|
||
return { ...row, onboardConfig: JSON.stringify(onboardConfig) }
|
||
})
|
||
},
|
||
safeParseJson(value, defaultValue) {
|
||
if (!value) return defaultValue
|
||
try { return JSON.parse(value) } catch (e) { return defaultValue }
|
||
},
|
||
cancel() { this.open = false },
|
||
submitForm() {
|
||
this.$refs.form.validate(valid => {
|
||
if (!valid) {
|
||
// 自动定位到第一个校验失败的字段
|
||
this.$nextTick(() => {
|
||
const firstError = this.$refs.form.$el.querySelector('.el-form-item.is-error')
|
||
if (firstError) {
|
||
firstError.scrollIntoView({ behavior: 'smooth', block: 'center' })
|
||
const input = firstError.querySelector('input, .el-select .el-input__inner, textarea')
|
||
if (input) input.focus()
|
||
}
|
||
})
|
||
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({
|
||
reportIntervalSec: this.form.reportIntervalSec || 300,
|
||
itemFieldMappings: this.itemFieldMappingRows.filter(im => im.identifier),
|
||
staticLevel: this.form.crossGatewayRefSerial
|
||
? { referenceSerialNumber: this.form.crossGatewayRefSerial }
|
||
: undefined
|
||
})
|
||
} else {
|
||
this.form.extraMapping = null
|
||
}
|
||
this.submitLoading = true
|
||
const api = this.form.id ? updatePlatformDeviceMapping : addPlatformDeviceMapping
|
||
api(this.form).then(res => {
|
||
const mappingId = this.form.id || (res.data && res.data.id) || res.data
|
||
// 网关设备:保存后同步子映射
|
||
if (this.form.deviceType === 1 && mappingId) {
|
||
const subData = normalizedSubRows
|
||
.map(r => ({
|
||
subSerialNumber: r.subSerialNumber,
|
||
subDeviceName: r.subDeviceName || r.subSerialNumber,
|
||
deviceRole: r.deviceRole || 'monitor',
|
||
onboardConfig: r.onboardConfig || null,
|
||
platformDeviceId: r.platformDeviceId || null,
|
||
platformPointId: r.platformPointId || null
|
||
}))
|
||
syncSubMappings(mappingId, subData).then(() => {
|
||
this.$modal.msgSuccess(this.form.id ? '修改成功' : '新增成功')
|
||
this.open = false; this.getList()
|
||
})
|
||
} else {
|
||
this.$modal.msgSuccess(this.form.id ? '修改成功' : '新增成功')
|
||
this.open = false; this.getList()
|
||
}
|
||
}).finally(() => { this.submitLoading = false })
|
||
})
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
.schema-tip {
|
||
font-size: 12px;
|
||
color: #909399;
|
||
line-height: 1.6;
|
||
}
|
||
.array-fields-section {
|
||
margin-bottom: 22px;
|
||
padding: 0 10px;
|
||
}
|
||
.array-fields-section .section-label {
|
||
font-size: 14px;
|
||
color: #606266;
|
||
font-weight: 600;
|
||
margin-bottom: 8px;
|
||
}
|
||
</style>
|