fastbee/yack-web/src/views/iot/product/product-modbus.vue
2026-10-06 08:50:16 +08:00

976 lines
49 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="modbus-wrap">
<div style="margin-bottom: 0px">
<div class="card-title">
<div style="display: flex; justify-content: flex-start; margin-bottom: 30px">
<!-- <svg-icon icon-class="modbus" style="font-size: 20px;margin-top: 4px;margin-right: 10px;" /> -->
<div class="title" style="font-size: 22px">{{ $t('product.product-modbus.562372-0') }}</div>
<el-tooltip class="item" effect="dark" :content="$t('product.product-modbus.562372-1')" placement="top">
<i class="el-icon-question" style="margin-left: 10px; margin-top: 5px; font-size: large" />
</el-tooltip>
</div>
</div>
<div class="button-group" style="margin-top: -10px">
<el-button type="primary" plain icon="el-icon-edit" size="mini" @click="setSlave" v-if="!enableSetSlave && productInfo.status == 1">{{ $t('product.product-modbus.562372-2') }}</el-button>
<el-button type="primary" plain size="mini" @click="saveSlave" v-if="enableSetSlave" v-hasPermi="['modbus:config:edit']">{{ $t('product.product-modbus.562372-3') }}</el-button>
<el-button type="info" plain size="mini" @click="cancelSlave" v-if="enableSetSlave">{{ $t('product.product-modbus.562372-4') }}</el-button>
</div>
<el-form ref="form" :model="form" label-width="180px" :rules="rules" style="margin-left: -150px">
<el-form-item label="">
<el-tooltip placement="top">
<div slot="content" class="test_div">
{{ $t('product.product-modbus.562372-6') }}
<br />
{{ $t('product.product-modbus.562372-7') }}
<br />
{{ $t('product.product-modbus.562372-8') }}
</div>
<!-- elementui图标库:显示黑色的问号图标 -->
<i class="el-icon-question" style="color: dodgerblue" />
<span>{{ $t('product.product-modbus.562372-5') }}</span>
</el-tooltip>
<span style="margin-right: 10px">{{ $t('product.product-modbus.562372-5') }}</span>
<el-radio-group :disabled="!enableSetSlave" v-model="form.statusDeter">
<el-radio v-for="dict in dict.type.device_status_deter" :key="dict.value" :label="Number(dict.value)">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item v-if="form.statusDeter == '1'" label="">
<span>
<el-tooltip placement="top">
<div slot="content" class="test_div">
{{ $t('product.product-modbus.562372-10') }}
</div>
<!-- elementui图标库:显示黑色的问号图标 -->
<i class="el-icon-question" style="color: dodgerblue" />
</el-tooltip>
</span>
<span style="margin-right: 10px">{{ $t('product.product-modbus.562372-9') }}</span>
<el-select :disabled="!enableSetSlave" v-model="form.deterTimer" :placeholder="$t('product.product-modbus.562372-11')">
<el-option v-for="dict in dict.type.iot_modbus_poll_time" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
<span style="font-size: 14px; color: darkgrey">{{ $t('product.product-modbus.562372-12') }}</span>
</el-form-item>
<el-form-item label="">
<span style="margin-right: 10px">{{ $t('product.product-modbus.562372-13') }}</span>
<el-radio-group :disabled="!enableSetSlave" v-model="form.pollType" @change="changePollType">
<el-radio v-for="dict in dict.type.data_collect_type" :key="dict.value" :label="Number(dict.value)">{{ dict.label }}</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="" prop="slaveId">
<span style="margin-right: 10px">{{ $t('product.product-modbus.562372-14') }}</span>
<el-input :disabled="!enableSetSlave" v-model="form.slaveId" size="small" :label="$t('product.product-modbus.562372-15')" style="width: 230px" type="number"></el-input>
<span style="font-size: 14px; color: darkgrey">{{ $t('product.product-modbus.562372-16') }}</span>
</el-form-item>
</el-form>
<el-divider></el-divider>
</div>
<!-- IO寄存器 -->
<div class="head">
<div class="card-title">
<div style="display: flex; justify-content: flex-start; margin-bottom: 10px">
<!-- <svg-icon icon-class="modbus" style="font-size: 20px;margin-top: 4px;margin-right: 10px;" /> -->
<div class="title" style="font-size: 22px">{{ $t('product.product-modbus.562372-17') }}</div>
<el-tooltip class="item" effect="dark" :content="$t('product.product-modbus.562372-18')" placement="top">
<i class="el-icon-question" style="margin-left: 10px; margin-top: 5px; font-size: large" />
</el-tooltip>
</div>
</div>
<div class="button-group">
<el-button type="primary" plain icon="el-icon-edit" size="mini" @click="editIOModbus" v-if="!enableEditIO && productInfo.status == 1">{{ $t('product.product-modbus.562372-19') }}</el-button>
<el-button type="primary" plain size="mini" @click="submitFormIO" v-if="enableEditIO" v-hasPermi="['modbus:config:edit']">{{ $t('product.product-modbus.562372-22') }}</el-button>
<el-button type="info" plain size="mini" @click="handleCancelIO" v-if="enableEditIO">{{ $t('product.product-modbus.562372-23') }}</el-button>
<div class="right-btns">
<el-button type="primary" plain icon="el-icon-upload" v-if="productInfo.status == 1" size="mini" @click.stop="batchImport('isSelectIo')" v-hasPermi="['modbus:config:import']">
{{ $t('product.product-modbus.562372-20') }}
</el-button>
<el-button plain icon="el-icon-download" v-if="productInfo.status == 1" size="mini" @click.stop="exportModbus('isSelectIo')" v-hasPermi="['modbus:config:export']">
{{ $t('product.product-modbus.562372-21') }}
</el-button>
</div>
</div>
</div>
<el-table style="margin-bottom: 10px" v-loading="loadingIO" :data="configList" :key="configTableKey" data-key="id" ref="IOTable" @selection-change="handleIoSelectionChange">
<el-table-column type="selection" width="45" align="center" :selectable="ioSelectable" />
<el-table-column :label="$t('product.product-modbus.562372-24')" align="center" prop="sort"></el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-25')" align="center" prop="identifier">
<template slot-scope="scope">
<el-select
filterable
:disabled="!enableEditIO"
v-model="scope.row.identifier"
:placeholder="$t('product.product-modbus.562372-26')"
size="mini"
>
<el-option key="0" label="" value="" disabled style="width: 300px">
<span style="float: left">{{ $t('product.product-modbus.562372-27') }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ $t('product.product-modbus.562372-28') }}</span>
</el-option>
<el-option v-for="item in thingsModelList" :key="item.identifier" :label="`${item.modelName} (${item.identifier})`" :value="item.identifier" style="width: 300px" :disabled="isOptionDisabled(item, 'isSelectIo')">
<span style="float: left">{{ item.modelName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.identifier }}</span>
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-29')" align="center" prop="slave">
<template slot="header">
<modbus-column-batch :label="$t('product.product-modbus.562372-29')" column="slave" :disabled="!enableEditIO" :scope-rows="ioScopeRows" :scope-mode="ioScopeMode" :disabled-tip="editDisabledTip" @apply="(p) => onColumnApply('io', p)" />
</template>
<template slot-scope="scope">
<el-input-number :disabled="!enableEditIO" v-model="scope.row.slave" size="small" :min="1" :max="256" :label="$t('product.product-modbus.562372-29')"></el-input-number>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-30')" align="center" prop="address">
<template slot-scope="scope">
<el-input-number :disabled="!enableEditIO" v-model="scope.row.address" size="small" :min="0" :max="400000" :label="$t('product.product-modbus.562372-30')"></el-input-number>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-31')" align="center" prop="isReadonly">
<template slot="header">
<modbus-column-batch :label="$t('product.product-modbus.562372-31')" column="isReadonly" :disabled="!enableEditIO" :scope-rows="ioScopeRows" :scope-mode="ioScopeMode" :disabled-tip="editDisabledTip" @apply="(p) => onColumnApply('io', p)" />
</template>
<template slot-scope="scope">
<el-radio-group :disabled="!enableEditIO" size="mini" v-model="scope.row.isReadonly">
<el-radio-button :label="0">{{ $t('product.product-modbus.562372-32') }}</el-radio-button>
<el-radio-button :label="1">{{ $t('product.product-modbus.562372-33') }}</el-radio-button>
</el-radio-group>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-311')" align="center" prop="bitOrder">
<template slot="header">
<modbus-column-batch :label="$t('product.product-modbus.562372-311')" column="bitOrder" :disabled="!enableEditIO" :scope-rows="ioScopeRows" :scope-mode="ioScopeMode" :disabled-tip="editDisabledTip" @apply="(p) => onColumnApply('io', p)" />
</template>
<template slot-scope="scope">
<el-input-number :disabled="!enableEditIO" v-model="scope.row.bitOrder" size="small" :min="0" :max="15" :label="$t('product.product-modbus.562372-311')"></el-input-number>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-34')" align="center" class-name="small-padding fixed-width">
<template slot="header">
<span>{{ $t('product.product-modbus.562372-34') }}</span>
<el-button type="text" size="mini" icon="el-icon-delete" :disabled="!enableEditIO" @click="handleBatchDelete('io')" v-hasPermi="['iot:config:remove']">
{{ $t('product.product-modbus.562372-326') }}
</el-button>
</template>
<template slot-scope="scope">
<el-button v-if="enableEditIO" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row, scope.$index, configList, 'isSelectIo')" v-hasPermi="['iot:config:remove']">
{{ $t('product.product-modbus.562372-35') }}
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" :page.sync="queryParamsIO.pageNum" :limit.sync="queryParamsIO.pageSize" @pagination="getIOList" />
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-if="enableEditIO">{{ $t('product.product-modbus.562372-36') }}</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAddBatch('isSelectIo')" v-if="enableEditIO">{{ $t('product.product-modbus.562372-37') }}</el-button>
</el-col>
</el-row>
<!-- 数据寄存器 -->
<div class="head">
<el-divider></el-divider>
<div class="card-title">
<div style="display: flex; justify-content: flex-start; margin-bottom: 10px">
<!-- <svg-icon icon-class="modbus" style="font-size: 20px;margin-top: 4px;margin-right: 10px;" /> -->
<div class="title" style="font-size: 22px">{{ $t('product.product-modbus.562372-38') }}</div>
<el-tooltip class="item" effect="dark" :content="$t('product.product-modbus.562372-39')" placement="top">
<i class="el-icon-question" style="margin-left: 10px; margin-top: 5px; font-size: large" />
</el-tooltip>
</div>
</div>
<div class="button-group">
<el-button type="primary" plain icon="el-icon-edit" size="mini" @click="editDataModbus" v-if="!enableEditData && productInfo.status == 1">{{ $t('product.product-modbus.562372-19') }}</el-button>
<el-button type="primary" plain size="mini" @click="submitFormData" v-if="enableEditData" v-hasPermi="['modbus:config:edit']">{{ $t('product.product-modbus.562372-22') }}</el-button>
<el-button type="info" plain size="mini" @click="handleCancelData" v-if="enableEditData">{{ $t('product.product-modbus.562372-23') }}</el-button>
<div class="right-btns">
<el-button type="primary" plain icon="el-icon-upload" v-if="productInfo.status == 1" size="mini" @click.stop="batchImport('isSelectData')" v-hasPermi="['modbus:config:import']">
{{ $t('product.product-modbus.562372-40') }}
</el-button>
<el-button plain icon="el-icon-download" v-if="productInfo.status == 1" size="mini" @click.stop="exportModbus('isSelectData')" v-hasPermi="['modbus:config:export']">
{{ $t('product.product-modbus.562372-41') }}
</el-button>
</div>
</div>
</div>
<el-table style="margin-bottom: 10px" v-loading="loadingData" :data="dataModbusList" :key="dataTableKey" ref="Dataable" @selection-change="handleDataSelectionChange">
<el-table-column type="selection" width="45" align="center" :selectable="dataSelectable" />
<el-table-column :label="$t('product.product-modbus.562372-24')" align="center" prop="sort"></el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-25')" align="center" prop="identifier" width="250px">
<template slot-scope="scope">
<el-select
filterable
:disabled="!enableEditData"
v-model="scope.row.identifier"
:placeholder="$t('product.product-modbus.562372-26')"
size="mini"
>
<el-option key="0" label="" value="" disabled style="width: 300px">
<span style="float: left">{{ $t('product.product-modbus.562372-27') }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ $t('product.product-modbus.562372-28') }}</span>
</el-option>
<el-option v-for="item in thingsModelList" :key="item.identifier" :label="`${item.modelName} (${item.identifier})`" :value="item.identifier" style="width: 300px" :disabled="isOptionDisabled(item, 'isSelectData')">
<!-- 判断是否可选 -->
<span style="float: left">{{ item.modelName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.identifier }}</span>
</el-option>
</el-select>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-29')" align="center" prop="slave" width="170px">
<template slot="header">
<modbus-column-batch :label="$t('product.product-modbus.562372-29')" column="slave" :disabled="!enableEditData" :scope-rows="dataScopeRows" :scope-mode="dataScopeMode" :disabled-tip="editDisabledTip" @apply="(p) => onColumnApply('data', p)" />
</template>
<template slot-scope="scope">
<el-input-number :disabled="!enableEditData" v-model="scope.row.slave" size="small" :min="1" :max="256" :label="$t('product.product-modbus.562372-29')"></el-input-number>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-30')" align="center" prop="address" width="170px">
<template slot="header">
<modbus-column-batch :label="$t('product.product-modbus.562372-30')" column="addressAuto" :disabled="!enableEditData" :scope-rows="dataScopeRows" :scope-mode="dataScopeMode" :disabled-tip="editDisabledTip" @apply="(p) => onColumnApply('data', p)" />
</template>
<template slot-scope="scope">
<el-input-number :disabled="!enableEditData" v-model="scope.row.address" size="small" :min="0" :max="400000" :label="$t('product.product-modbus.562372-30')"></el-input-number>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-31')" align="center" prop="isReadonly" width="170px">
<template slot="header">
<modbus-column-batch :label="$t('product.product-modbus.562372-31')" column="isReadonly" :disabled="!enableEditData" :scope-rows="dataScopeRows" :scope-mode="dataScopeMode" :disabled-tip="editDisabledTip" @apply="(p) => onColumnApply('data', p)" />
</template>
<template slot-scope="scope">
<el-radio-group :disabled="!enableEditData" size="mini" v-model="scope.row.isReadonly">
<el-radio-button :label="0">{{ $t('product.product-modbus.562372-32') }}</el-radio-button>
<el-radio-button :label="1">{{ $t('product.product-modbus.562372-33') }}</el-radio-button>
</el-radio-group>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-42')" align="center" prop="dataType" width="200px">
<template slot="header">
<modbus-column-batch :label="$t('product.product-modbus.562372-42')" column="dataType" :disabled="!enableEditData" :scope-rows="dataScopeRows" :scope-mode="dataScopeMode" :disabled-tip="editDisabledTip" @apply="(p) => onColumnApply('data', p)" />
</template>
<template slot-scope="scope">
<el-select :disabled="!enableEditData" size="mini" v-model="scope.row.dataType" :placeholder="$t('product.product-modbus.562372-42')" style="display: inline-block; padding-right: 10px">
<el-option v-for="dict in dict.type.iot_modbus_data_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-43')" align="center" prop="quantity" width="170px">
<template slot="header">
<modbus-column-batch :label="$t('product.product-modbus.562372-43')" column="quantity" :disabled="!enableEditData" :scope-rows="dataScopeRows" :scope-mode="dataScopeMode" :disabled-tip="editDisabledTip" @apply="(p) => onColumnApply('data', p)" />
</template>
<template slot-scope="scope">
<el-input-number :disabled="!enableEditData" v-model="scope.row.quantity" size="small" :min="1" :max="256" :label="$t('product.product-modbus.562372-43')"></el-input-number>
</template>
</el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-34')" align="center" class-name="small-padding fixed-width">
<template slot="header">
<span>{{ $t('product.product-modbus.562372-34') }}</span>
<el-button type="text" size="mini" icon="el-icon-delete" :disabled="!enableEditData" @click="handleBatchDelete('data')" v-hasPermi="['iot:config:remove']">
{{ $t('product.product-modbus.562372-326') }}
</el-button>
</template>
<template slot-scope="scope">
<el-button v-if="enableEditData" size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row, scope.$index, dataModbusList, 'isSelectData')" v-hasPermi="['iot:config:remove']">
{{ $t('product.product-modbus.562372-35') }}
</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="dataTotal > 0" :total="dataTotal" :page.sync="queryParamsData.pageNum" :limit.sync="queryParamsData.pageSize" @pagination="getDataList" />
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAddData" v-if="enableEditData">{{ $t('product.product-modbus.562372-44') }}</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAddBatch('isSelectData')" v-if="enableEditData">{{ $t('product.product-modbus.562372-45') }}</el-button>
</el-col>
</el-row>
<!-- 选择物模型 -->
<things-list ref="thingsListRef" :productId="productId" @productEvent="getThingsData($event)" :justiceSelect="justiceSelect" :judge-disabled="isOptionDisabled" />
<!-- 批量导入 -->
<import-batch ref="importBatchRef" :productId="productId" @data-imported="handleDataImport" :justiceSelect="justiceSelect" />
</div>
</template>
<script>
import { listConfig, addBatch } from '@/api/iot/modbusConfig';
import { getlListModbus } from '@/api/iot/model';
import thingsList from './things-model-list';
import importBatch from './components/batchImportModbus';
import modbusColumnBatch from './components/modbusColumnBatch';
import { deriveQuantity, resolveQuantity } from './modbus-derive';
import Sortable from 'sortablejs';
import { addOrUpdate, getByProductId } from '@/api/iot/params';
import { listProductJob } from '@/api/iot/modbusJob';
export default {
name: 'product-modbus',
dicts: ['iot_modbus_data_type', 'iot_yes_no', 'data_collect_type', 'device_status_deter', 'iot_modbus_poll_time'],
props: {
product: {
type: Object,
default: null,
},
},
components: {
thingsList,
importBatch,
modbusColumnBatch,
},
watch: {
// 获取到父组件传递的productId后,刷新列表
product: function (newVal, oldVal) {
if (newVal && newVal.productId != 0) {
this.thingsModelParams.productId = newVal.productId;
this.queryParamsIO.productId = newVal.productId;
this.queryParamsData.productId = newVal.productId;
this.productId = newVal.productId;
this.getIOList();
this.getDataList();
this.getThingsModelList();
this.getParams();
this.sendPollType();
}
},
enableEditIO: function (n, old) {
//设置行是否拖拽
if (this.sortableIo) this.sortableIo.option('disabled', !n);
//关闭编辑刷新页面(保存与取消都会走到这里:重拉数据与物模型可选标记,并清空本次编辑的临时状态)
if (!n) {
this.getIOList();
this.getThingsModelList();
this.ioSelection = [];
this.releasedIo = [];
this.delIoIds = [];
}
},
enableEditData: function (n, old) {
//设置行是否拖拽
if (this.sortableData) this.sortableData.option('disabled', !n);
//关闭编辑刷新页面(保存与取消都会走到这里:重拉数据与物模型可选标记,并清空本次编辑的临时状态)
if (!n) {
this.getDataList();
this.getThingsModelList();
this.dataSelection = [];
this.releasedData = [];
this.delDataIds = [];
}
},
},
data() {
return {
// 遮罩层
loadingIO: true,
loadingData: true,
// 总条数
total: 0,
// modbus配置表格数据
configList: [],
//io表格是否可以编辑
enableEditIO: false,
//子设备配置是否可编辑
enableSetSlave: false,
// 查询参数
queryParamsIO: {
pageNum: 1,
pageSize: 10,
identifier: null,
address: null,
isReadonly: null,
dataType: null,
quantity: null,
type: 1,
productId: 0,
},
// 查询参数
queryParamsData: {
pageNum: 1,
pageSize: 10,
identifier: null,
address: null,
isReadonly: null,
dataType: null,
quantity: null,
type: 2,
productId: 0,
},
//产品id
productId: 0,
//数据寄存器
dataModbusList: [],
//io表格是否可以编辑
enableEditData: false,
//数据寄存器总条数
dataTotal: 0,
// 数据寄存器遮罩层
dataLoading: true,
//IO表勾选行(列头批量作用域:有勾选=勾选行,否则本页全部行)
ioSelection: [],
//数据表勾选行
dataSelection: [],
//本次编辑中被删除行占用的物模型标识符(后端标记要保存后才变,期间放行其重新可选)
releasedIo: [],
releasedData: [],
//可选属性
thingsModelList: [],
//thingsModel
thingsModelParams: {
pageNum: 1,
pageSize: 1000,
productId: 0,
isCalculated: 0,
},
// 表单参数
form: {
statusDeter: 1,
slaveId: null,
pollType: 0,
deterTimer: '300',
},
// 表单校验
rules: {
slaveId: [{ required: true, message: this.$t('product.product-modbus.562372-46'), trigger: 'blur' }],
},
//IO行拖拽对象
sortableIo: null,
//ioTable的key值,主要用于拖拽后重绘
configTableKey: 0,
//ioTable删除的id
delIoIds: [],
//数据寄存器行拖拽对象
sortableData: null,
//dataTable的key值,主要用于拖拽后重绘
dataTableKey: 1000,
//dataTable删除的id
delDataIds: [],
//判断多选物模型是io还是data,isSelectIo是判断是否可选io的,isSelectData是判断数据寄存器的,都是false不可选,true可选
justiceSelect: 'isSelectData',
jobTotal: 0,
};
},
computed: {
//直接读 prop,不再维护副本(副本只在 watch 触发时同步,一旦脱节:
//productInfo 停留为 {} → status==1 判定失败 → 「编辑寄存器」按钮不渲染,而批量入口仍可见,用户只会得到"请先点击编辑寄存器"的无效提示)
productInfo() {
return this.product || {};
},
//批量入口禁用原因(提示要指向用户当下真正该做的动作)
editDisabledTip() {
if (this.productInfo.status != 1) return this.$t('product.product-modbus.562372-338');
return this.$t('product.product-modbus.562372-331');
},
//列头批量作用域:有勾选行用勾选行,否则本页全部行(不再问第二遍)
//一律按表格行序返回:el-table 的 selection 是"勾选先后"顺序,地址生成必须按行序
ioScopeRows() {
const rows = this.ioSelection.length ? this.ioSelection : this.configList;
return this.sortByTableOrder(rows, this.configList);
},
dataScopeRows() {
const rows = this.dataSelection.length ? this.dataSelection : this.dataModbusList;
return this.sortByTableOrder(rows, this.dataModbusList);
},
//作用域来源(仅用于面板文案:作用:本页 N 行 / 已勾选 N 行)
ioScopeMode() {
return this.ioSelection.length ? 'selected' : 'all';
},
dataScopeMode() {
return this.dataSelection.length ? 'selected' : 'all';
},
},
mounted() {
this.rowDropIo();
this.rowDropData();
this.getTaskList(this.productInfo.productId);
this.sendPollType();
},
methods: {
/** 查询modbusIO寄存器列表 */
getIOList() {
this.loadingIO = true;
listConfig(this.queryParamsIO).then((response) => {
this.configList = response.rows;
this.total = response.total;
this.loadingIO = false;
});
},
//传值
sendPollType() {
this.$emit('sendPollType', this.form.pollType);
},
handleDataImport(data) {
this.configList = data;
},
/** 查询modbus数据寄存器列表 */
getDataList() {
this.loadingData = true;
listConfig(this.queryParamsData).then((response) => {
this.dataModbusList = response.rows;
this.dataTotal = response.total;
this.loadingData = false;
});
this.$nextTick(() => {
this.$refs.Dataable.bodyWrapper.scrollTop = 0;
});
},
/** 查询产品物模型列表 */
getThingsModelList() {
getlListModbus(this.thingsModelParams).then((response) => {
this.$refs.thingsListRef.modelList = response.rows;
this.thingsModelList = response.rows;
});
},
// IO表勾选(仅编辑态可勾选)
ioSelectable() {
return this.enableEditIO;
},
// 数据表勾选(仅编辑态可勾选)
dataSelectable() {
return this.enableEditData;
},
handleIoSelectionChange(selection) {
this.ioSelection = selection;
},
handleDataSelectionChange(selection) {
this.dataSelection = selection;
},
// 按表格行序重排(传入的已是整个列表时直接返回,不产生新数组)
sortByTableOrder(rows, list) {
if (rows === list) return rows;
return rows.slice().sort((a, b) => list.indexOf(a) - list.indexOf(b));
},
/**
* 寄存器地址一键生成:从起始地址开始,按各行占用的寄存器个数(读取个数)依次推进。
* 步长口径 = modbus-derive#resolveQuantity:dataType 非空以派生值为准,dataType 为空的历史行回退行内 quantity,再缺省 1。
* 仅数据寄存器使用(IO 寄存器同地址按 bitOrder 取位,地址不递增)。
*/
applyAddressAuto(table, rows, startAddress) {
if (!rows.length) return;
const list = table === 'io' ? this.configList : this.dataModbusList;
const from = Number(startAddress);
let cursor = from;
let last = from;
rows.forEach((row) => {
row.address = cursor;
last = cursor;
cursor += resolveQuantity(row);
});
this.$modal.msgSuccess(this.$t('product.product-modbus.562372-336', { n: rows.length, from, to: last }));
// 冲突检测:同从机地址 + 同寄存器地址会被 ModbusProtocol 分组解析互相覆盖,本页内先提示
const seen = Object.create(null);
let duplicate = 0;
list.forEach((row) => {
const key = `${row.slave}-${row.address}`;
if (seen[key]) duplicate++;
else seen[key] = true;
});
if (duplicate > 0) {
this.$modal.msgWarning(this.$t('product.product-modbus.562372-337', { m: duplicate }));
}
},
/**
* 物模型「可否选中」派生判定(全表统一口径,不再增量改写 thingsModelList):
* ① 本次编辑刚删除释放的标识符放行(后端标记要保存后才变)
* ② 当前表内存中已占用的标识符禁用(覆盖未保存的新增行)
* ③ 后端回传的 isSelectIo/isSelectData 兜底
*/
isOptionDisabled(item, justiceSelect) {
const released = justiceSelect === 'isSelectIo' ? this.releasedIo : this.releasedData;
if (released.indexOf(item.identifier) !== -1) return false;
const list = justiceSelect === 'isSelectIo' ? this.configList : this.dataModbusList;
if (list.some((row) => row.identifier && row.identifier === item.identifier)) return true;
return !item[justiceSelect];
},
/**
* 列头批量应用:一列一个入口,谁的值谁负责。
* - dataType:写入后联动 quantity = deriveQuantity(dataType)(字典每种类型位宽唯一)
* - quantity:仅允许 1/2;dataType 非空且派生值不符的行跳过(避免写出 ModbusProtocol 会丢弃的矛盾行)
* - addressAuto:一键生成地址序列(起始地址 + 按各行读取个数递增)
* - 其余列直接写
*/
onColumnApply(table, { column, value }) {
const rows = table === 'io' ? this.ioScopeRows : this.dataScopeRows;
if (column === 'addressAuto') {
this.applyAddressAuto(table, rows, value);
return;
}
let changed = 0;
let skipped = 0;
rows.forEach((row) => {
if (column === 'dataType') {
row.dataType = value;
const quantity = deriveQuantity(value);
if (quantity !== null) row.quantity = quantity;
changed++;
} else if (column === 'quantity') {
if (row.dataType && deriveQuantity(row.dataType) !== value) {
skipped++;
return;
}
row.quantity = value;
changed++;
} else {
row[column] = value;
changed++;
}
});
if (changed > 0) {
this.$modal.msgSuccess(this.$t('product.product-modbus.562372-323', { n: changed }));
}
if (skipped > 0) {
this.$modal.msgWarning(this.$t('product.product-modbus.562372-325', { m: skipped }));
}
},
/** 操作列头批量删除:必须先勾选行(删除高危,不做"本页全部"默认),本地删除、保存驱动提交 */
handleBatchDelete(table) {
const selection = table === 'io' ? this.ioSelection : this.dataSelection;
if (!selection.length) {
this.$modal.msgWarning(this.$t('product.product-modbus.562372-327'));
return;
}
const savedCount = selection.filter((row) => row.id).length;
this.$confirm(this.$t('product.product-modbus.562372-328', { n: selection.length, m: savedCount }), this.$t('product.product-modbus.562372-313'), {
confirmButtonText: this.$t('product.product-modbus.562372-314'),
cancelButtonText: this.$t('product.product-modbus.562372-315'),
type: 'warning',
})
.then(() => {
const list = table === 'io' ? this.configList : this.dataModbusList;
const delIds = table === 'io' ? this.delIoIds : this.delDataIds;
const released = table === 'io' ? this.releasedIo : this.releasedData;
selection.forEach((row) => {
const index = list.indexOf(row);
if (index !== -1) list.splice(index, 1);
if (row.id) delIds.push(row.id);
if (row.identifier) released.push(row.identifier);
});
if (table === 'io') this.ioSelection = [];
else this.dataSelection = [];
})
.catch(() => {});
},
/** 新增IO寄存器行操作 */
handleAdd() {
var d = {
identifier: '',
slave: 1,
address: 1,
isReadonly: 1,
type: 1,
quantity: 1,
sort: this.configList.length + 1,
};
this.configList.push(d);
setTimeout(() => {
this.$refs.IOTable.setCurrentRow(d);
}, 10);
},
/** 新增数据寄存器行操作 */
handleAddData() {
var d = {
identifier: '',
slave: 1,
address: 1,
isReadonly: 1,
dataType: 'ushort',
quantity: 1,
type: 2,
sort: this.dataModbusList.length + 1,
};
this.dataModbusList.push(d);
setTimeout(() => {
this.$refs.Dataable.setCurrentRow(d);
}, 10);
},
handleAddBatch(justiceSelect) {
this.justiceSelect = justiceSelect;
this.$refs.thingsListRef.open = true;
this.$refs.thingsListRef.selectedList = justiceSelect == 'isSelectData' ? this.dataModbusList : this.configList;
this.$refs.thingsListRef.getList();
},
/**点击编辑IO控制器*/
editIOModbus() {
this.enableEditIO = !this.enableEditIO;
},
/**点击取消IO寄存器编辑按钮*/
handleCancelIO() {
this.enableEditIO = !this.enableEditIO;
},
/**点击取消数据寄存器编辑按钮*/
handleCancelData() {
this.enableEditData = !this.enableEditData;
},
/**点击编辑数据寄存器*/
editDataModbus() {
this.enableEditData = !this.enableEditData;
},
/** IO寄存器提交按钮 */
submitFormIO() {
const configList = [];
this.configList.forEach((config, index) => {
if (config.identifier) {
config.sort = configList.length + 1;
configList.push(config);
}
});
this.loadingIO = true;
const data = { productId: this.productId, configList: configList, delIds: this.delIoIds };
addBatch(data)
.then((response) => {
this.$modal.msgSuccess('保存成功');
this.getIOList();
this.open = false;
this.loadingIO = false;
this.enableEditIO = false;
})
.catch((err) => {
this.loadingIO = false;
});
},
/** 数据寄存器提交按钮 */
submitFormData() {
const dataModbusList = [];
this.dataModbusList.forEach((dataModbus, index) => {
if (dataModbus.identifier) {
//读取个数是数据类型的派生量:提交前回算,但仅当 dataType 非空(历史行 data_type 可空,勿改坏)
if (dataModbus.dataType) {
const quantity = deriveQuantity(dataModbus.dataType);
if (quantity !== null) dataModbus.quantity = quantity;
}
dataModbus.sort = dataModbusList.length + 1;
dataModbusList.push(dataModbus);
}
});
this.loadingData = true;
const data = { productId: this.productId, configList: dataModbusList, delIds: this.delDataIds };
addBatch(data)
.then((response) => {
this.$modal.msgSuccess(this.$t('product.product-modbus.562372-52'));
this.open = false;
this.enableEditData = false;
this.loadingData = false;
})
.catch((err) => {
this.loadingData = false;
});
},
/** 删除按钮操作:本地删除 + 收集已保存行 id;释放的物模型占用由 isOptionDisabled 派生判定自动生效 */
handleDelete(row, index, list, justiceSelect) {
list.splice(index, 1);
const delIds = justiceSelect === 'isSelectIo' ? this.delIoIds : this.delDataIds;
const released = justiceSelect === 'isSelectIo' ? this.releasedIo : this.releasedData;
if (row.id) delIds.push(row.id);
if (row.identifier) released.push(row.identifier);
},
/*获取选择的物模型*/
getThingsData(ids) {
const list = this.justiceSelect == 'isSelectData' ? this.dataModbusList : this.configList;
ids.forEach((id, index) => {
const findIndex = list.findIndex((item) => item.identifier == id);
if (findIndex == -1) {
list.push({
identifier: id,
slave: 1,
address: 1,
isReadonly: 1,
dataType: 'ushort',
quantity: 1,
type: this.justiceSelect == 'isSelectData' ? 2 : 1,
sort: list.length + 1,
});
}
});
},
/**
* io拖拽行
*/
rowDropIo() {
const tbodyIo = this.$refs.IOTable.$el.children[2].children[0].children[1];
this.sortableIo = new Sortable(tbodyIo, {
disabled: true,
onEnd: ({ newIndex, oldIndex, to }) => {
this.dealDrop(this.configList, newIndex, oldIndex);
this.configTableKey++;
this.sortableIo.destroy();
this.$nextTick(() => {
this.rowDropIo();
this.sortableIo.option('disabled', false);
});
},
});
},
/**
* 数据寄存器拖拽
*/
rowDropData() {
const tbodyData = this.$refs.Dataable.$el.children[2].children[0].children[1];
this.sortableData = new Sortable(tbodyData, {
disabled: true,
onEnd: ({ newIndex, oldIndex, to }) => {
this.dealDrop(this.dataModbusList, newIndex, oldIndex);
this.dataTableKey++;
this.sortableData.destroy();
this.$nextTick(() => {
this.rowDropData();
this.sortableData.option('disabled', false);
});
},
});
},
//处理拖拽后的数据
dealDrop(list, newIndex, oldIndex) {
if (oldIndex == newIndex) return;
//找到选中那列,移除出数组
const curRow = list.splice(oldIndex, 1)[0];
//将选择中那列查到新位置
list.splice(newIndex, 0, curRow);
},
/**批量导入 */
batchImport(justiceSelect) {
this.justiceSelect = justiceSelect;
this.$refs.importBatchRef.upload.importDeviceDialog = true;
},
/**导出 */
exportModbus(justiceSelect) {
const type = justiceSelect == 'isSelectData' ? 2 : 1;
const name = justiceSelect == 'isSelectData' ? this.$t('product.product-modbus.562372-38') : this.$t('product.product-modbus.562372-17');
this.download('/modbus/config/exportModbus?type=' + type, {}, `${name}_${new Date().getTime()}.xlsx`);
},
/**获取modbus参数配置*/
getParams() {
const params = { productId: this.productId };
getByProductId(params).then((response) => {
if (response.data) {
this.form = response.data;
this.sendPollType();
}
});
},
/**编辑modbus配置参数*/
setSlave() {
this.enableSetSlave = !this.enableSetSlave;
},
/** 保存modbus配置参数 */
saveSlave() {
this.$refs['form'].validate((valid) => {
if (valid) {
this.enableSetSlave = !this.enableSetSlave;
this.form.productId = this.productId;
addOrUpdate(this.form).then((response) => {
this.$modal.msgSuccess(this.$t('product.product-modbus.562372-53'));
});
}
});
this.sendPollType();
},
//改变采集方式
changePollType() {
this.$nextTick(() => {
this.getTaskList(this.productInfo.productId);
});
setTimeout(() => {
if (this.form.pollType === 1) {
if (this.jobTotal > 0) {
this.$confirm(this.$t('product.product-modbus.562372-312'), this.$t('product.product-modbus.562372-313'), {
confirmButtonText: this.$t('product.product-modbus.562372-314'),
cancelButtonText: this.$t('product.product-modbus.562372-315'),
type: 'warning',
})
.then(() => {})
.catch(() => {
this.form.pollType = 0;
this.$message({
type: 'info',
message: this.$t('product.product-modbus.562372-316'),
});
});
}
}
}, 500);
},
/** 查询轮训任务列列表 */
getTaskList(productId) {
const params = {
pageNum: 1,
pageSize: 10,
productId: productId,
};
listProductJob(params).then((response) => {
this.jobTotal = response.total;
});
},
/**取消*/
cancelSlave() {
this.enableSetSlave = !this.enableSetSlave;
},
},
};
</script>
<style lang="scss">
.el-input-number.is-disabled .el-input__inner {
color: #111315;
}
.el-select .el-input.is-disabled .el-input__inner {
color: #0c0d0e;
}
.el-radio-button__orig-radio:disabled:checked + .el-radio-button__inner {
background-color: #409eff !important;
}
.head {
.head-title {
margin-bottom: 12px;
.title {
margin-top: 16px;
margin-bottom: 2px;
}
.tips {
color: grey;
line-height: 36px;
font-size: 14px;
}
}
.button-group {
margin-bottom: 15px;
display: flex;
.right-btns {
margin-left: auto;
}
}
}
.test_div {
width: 300px;
padding: 5px;
}
</style>