modbus设置:操作便捷化

This commit is contained in:
zhugongping 2026-10-06 08:50:16 +08:00
parent 4cc487c00f
commit bfd7a74831
6 changed files with 586 additions and 112 deletions

View File

@ -426,11 +426,6 @@
"product.product-modbus.562372-44": "Add a single data register", "product.product-modbus.562372-44": "Add a single data register",
"product.product-modbus.562372-45": "Add multiple data registers", "product.product-modbus.562372-45": "Add multiple data registers",
"product.product-modbus.562372-46": "The default address cannot be empty", "product.product-modbus.562372-46": "The default address cannot be empty",
"product.product-modbus.562372-47": "Register address cannot be empty",
"product.product-modbus.562372-48": "Read only (0- No, 1- Yes) cannot be empty",
"product.product-modbus.562372-49": "Modbus data type cannot be empty",
"product.product-modbus.562372-50": "The number of reads cannot be empty",
"product.product-modbus.562372-51": "Register type 1-IO register 2-data register cannot be empty",
"product.product-modbus.562372-52": "Save successful", "product.product-modbus.562372-52": "Save successful",
"product.product-modbus.562372-53": "Modified successfully", "product.product-modbus.562372-53": "Modified successfully",
"product.product-modbus.562372-311": "Positional offset", "product.product-modbus.562372-311": "Positional offset",
@ -551,5 +546,21 @@
"product.product-things-model.142341-200": "Click symbols to build the formula", "product.product-things-model.142341-200": "Click symbols to build the formula",
"product.product-things-model.142341-201": "Reference product model identifiers", "product.product-things-model.142341-201": "Reference product model identifiers",
"product.product-things-model.142341-202": "No available identifiers", "product.product-things-model.142341-202": "No available identifiers",
"product.product-things-model.142341-203": "Add Calculated Model" "product.product-things-model.142341-203": "Add Calculated Model",
"product.product-modbus.562372-320": "Batch",
"product.product-modbus.562372-321": "Scope: all {n} rows on this page",
"product.product-modbus.562372-323": "{n} rows updated",
"product.product-modbus.562372-325": "skipped {m} rows (data type mismatch)",
"product.product-modbus.562372-326": "Batch delete",
"product.product-modbus.562372-327": "Select rows to delete first",
"product.product-modbus.562372-328": "The {n} selected rows ({m} already saved) will be removed locally and take effect after you click Save. Continue?",
"product.product-modbus.562372-329": "1 (16-bit)",
"product.product-modbus.562372-330": "2 (32-bit)",
"product.product-modbus.562372-331": "Click \"Edit registers\" to enter edit mode first",
"product.product-modbus.562372-333": "Scope: {n} selected rows",
"product.product-modbus.562372-334": "Advances by read count · Enter or blur to generate",
"product.product-modbus.562372-335": "Generate",
"product.product-modbus.562372-336": "Generated {n} addresses: {from} -> {to}",
"product.product-modbus.562372-337": "{m} duplicate slave + register address pairs on this page",
"product.product-modbus.562372-338": "Product is published; click \"Unpublish\" first to edit registers"
} }

View File

@ -426,11 +426,6 @@
"product.product-modbus.562372-44": "添加单个数据寄存器", "product.product-modbus.562372-44": "添加单个数据寄存器",
"product.product-modbus.562372-45": "添加多个数据寄存器", "product.product-modbus.562372-45": "添加多个数据寄存器",
"product.product-modbus.562372-46": "默认地址不能为空", "product.product-modbus.562372-46": "默认地址不能为空",
"product.product-modbus.562372-47": "寄存器地址不能为空",
"product.product-modbus.562372-48": "是否只读(0-否,1-是)不能为空",
"product.product-modbus.562372-49": "modbus数据类型不能为空",
"product.product-modbus.562372-50": "读取个数不能为空",
"product.product-modbus.562372-51": "寄存器类型 1-IO寄存器 2-数据寄存器不能为空",
"product.product-modbus.562372-52": "保存成功", "product.product-modbus.562372-52": "保存成功",
"product.product-modbus.562372-53": "修改成功", "product.product-modbus.562372-53": "修改成功",
"product.product-modbus.562372-311": "位偏移量", "product.product-modbus.562372-311": "位偏移量",
@ -551,5 +546,21 @@
"product.product-things-model.142341-200": "点击符号快速拼接", "product.product-things-model.142341-200": "点击符号快速拼接",
"product.product-things-model.142341-201": "引用产品物模型标识", "product.product-things-model.142341-201": "引用产品物模型标识",
"product.product-things-model.142341-202": "暂无可用标识", "product.product-things-model.142341-202": "暂无可用标识",
"product.product-things-model.142341-203": "新增计算模型" "product.product-things-model.142341-203": "新增计算模型",
"product.product-modbus.562372-320": "批量",
"product.product-modbus.562372-321": "作用:本页 {n} 行",
"product.product-modbus.562372-323": "已应用 {n} 行",
"product.product-modbus.562372-325": "跳过 {m} 行(数据类型不匹配)",
"product.product-modbus.562372-326": "批量删除",
"product.product-modbus.562372-327": "请先勾选要删除的行",
"product.product-modbus.562372-328": "将删除勾选的 {n} 行(含已保存 {m} 行),确认后需点击保存生效",
"product.product-modbus.562372-329": "1(16位)",
"product.product-modbus.562372-330": "2(32位)",
"product.product-modbus.562372-331": "请先点击「编辑寄存器」进入编辑状态",
"product.product-modbus.562372-333": "作用:已勾选 {n} 行",
"product.product-modbus.562372-334": "按读取个数递增 · 回车或失焦即生成",
"product.product-modbus.562372-335": "一键生成",
"product.product-modbus.562372-336": "已生成 {n} 行地址:{from} → {to}",
"product.product-modbus.562372-337": "本页有 {m} 处「从机地址 + 寄存器地址」重复,请检查",
"product.product-modbus.562372-338": "产品当前为已发布状态,请先点击「取消发布」后再编辑寄存器"
} }

View File

@ -0,0 +1,254 @@
<template>
<el-popover placement="bottom-start" width="240" trigger="click" :disabled="disabled" v-model="visible">
<span slot="reference" class="modbus-col-batch-header" :class="{ 'is-disabled': disabled }" @click.stop="handleTriggerClick">
<span class="modbus-col-batch-label">{{ label }}</span>
<span class="modbus-col-batch-badge" :class="{ 'is-magic': column === 'addressAuto' }" :title="column === 'addressAuto' ? $t('product.product-modbus.562372-335') : ''">
<i v-if="column === 'addressAuto'" class="el-icon-magic-stick"></i>
<template v-else>{{ $t('product.product-modbus.562372-320') }}</template>
</span>
</span>
<div class="modbus-col-batch-panel">
<div class="modbus-col-batch-scope">{{ scopeText }}</div>
<!-- 从机地址:数字回车 / 步进变化即生效 -->
<div v-if="column === 'slave'" class="modbus-col-batch-field">
<el-input-number v-model="slaveValue" size="mini" :min="1" :max="256" :label="$t('product.product-modbus.562372-29')" @change="handleInstantApply" @keyup.enter.native="handleNumberEnter"></el-input-number>
</div>
<!-- 是否只读(编码红线:0=只读、1=读写) -->
<div v-else-if="column === 'isReadonly'" class="modbus-col-batch-field">
<el-radio-group v-model="readonlyValue" @change="handleInstantApply">
<el-radio :label="0">{{ $t('product.product-modbus.562372-32') }}</el-radio>
<el-radio :label="1">{{ $t('product.product-modbus.562372-33') }}</el-radio>
</el-radio-group>
</div>
<!-- 位偏移量(IO 寄存器):数字回车 / 步进变化即生效 -->
<div v-else-if="column === 'bitOrder'" class="modbus-col-batch-field">
<el-input-number v-model="bitOrderValue" size="mini" :min="0" :max="15" :label="$t('product.product-modbus.562372-311')" @change="handleInstantApply" @keyup.enter.native="handleNumberEnter"></el-input-number>
</div>
<!-- 数据类型:选中即写入并联动读取个数,32 位类型在选项上标出 · 2 -->
<div v-else-if="column === 'dataType'" class="modbus-col-batch-field">
<el-select v-model="dataTypeValue" size="mini" :placeholder="$t('product.product-modbus.562372-42')" @change="handleInstantApply">
<el-option v-for="dict in dict.type.iot_modbus_data_type" :key="dict.value" :label="dictOptionLabel(dict)" :value="dict.value"></el-option>
</el-select>
</div>
<!-- 读取个数:选中即写入,数据类型不匹配的行跳过 -->
<div v-else-if="column === 'quantity'" class="modbus-col-batch-field">
<el-radio-group v-model="quantityValue" @change="handleInstantApply">
<el-radio :label="1">{{ $t('product.product-modbus.562372-329') }}</el-radio>
<el-radio :label="2">{{ $t('product.product-modbus.562372-330') }}</el-radio>
</el-radio-group>
<div class="modbus-col-batch-tip" v-if="quantityValue && quantitySkipCount > 0">
{{ $t('product.product-modbus.562372-325', { m: quantitySkipCount }) }}
</div>
</div>
<!-- 寄存器地址一键生成:填起始地址,回车(或改完失焦)即按各行读取个数依次生成 -->
<div v-else-if="column === 'addressAuto'" class="modbus-col-batch-field">
<el-input
v-model.number="startAddressValue"
size="mini"
:placeholder="$t('product.product-modbus.562372-30')"
@change="handleGenerateAddress"
@keyup.enter.native="handleGenerateAddress"
></el-input>
<div class="modbus-col-batch-tip is-hint">{{ $t('product.product-modbus.562372-334') }}</div>
</div>
</div>
</el-popover>
</template>
<script>
import { deriveQuantity } from '../modbus-derive';
/**
* 列头批量面板:一列一个入口,谁的值谁负责。
* 交互约定(2026-10-06 简化):
* - 整个列头即入口(列名 + 「批量」角标),点哪都能开;
* - **选择即生效**:下拉/单选选中即写入并收起;数字回车或步进变化即写入(无「应用」按钮);
* - 作用域由父组件通过 scopeRows + scopeMode 传入(有勾选行→勾选行,否则本页全部行)。
*/
export default {
name: 'modbus-column-batch',
dicts: ['iot_modbus_data_type'],
props: {
// 列头显示名(父组件传入 i18n 文案)
label: {
type: String,
default: '',
},
// 列标识:slave / isReadonly / bitOrder / dataType / quantity
column: {
type: String,
required: true,
},
// 仅编辑态可操作
disabled: {
type: Boolean,
default: false,
},
// 禁用时的提示文案(由父组件按真实原因给出:产品已发布 / 未进入编辑态)
disabledTip: {
type: String,
default: '',
},
// 作用行:勾选行优先,否则本页全部行(父组件算好传入)
scopeRows: {
type: Array,
default: () => [],
},
// 作用域来源:all=本页全部行,selected=勾选行
scopeMode: {
type: String,
default: 'all',
},
},
data() {
return {
visible: false,
// 本次打开面板是否已提交(防止 input-number 的 change 与 keyup.enter 双触发)
applied: false,
// 初值与"混合态"一致用 undefined(el-input-number 会把 null 解析成 0)
slaveValue: undefined,
readonlyValue: undefined,
bitOrderValue: undefined,
dataTypeValue: undefined,
quantityValue: undefined,
startAddressValue: undefined,
};
},
computed: {
scopeText() {
const key = this.scopeMode === 'selected' ? 'product.product-modbus.562372-333' : 'product.product-modbus.562372-321';
return this.$t(key, { n: this.scopeRows.length });
},
// 数据类型非空且派生读取个数与所选值不符的行数(这些行将跳过,不写出自相矛盾的行)
quantitySkipCount() {
if (this.column !== 'quantity' || !this.quantityValue) return 0;
return this.scopeRows.filter((row) => row.dataType && deriveQuantity(row.dataType) !== this.quantityValue).length;
},
// 一键生成需要明确的起始地址(0 是合法值,不能用真值判断)
canGenerate() {
const value = this.startAddressValue;
if (value === undefined || value === null || value === '') return false;
const num = Number(value);
return !isNaN(num) && num >= 0;
},
},
watch: {
// 打开时预填作用行的公共值:全部一致就显示该值,不一致则留空(提示值为混合态)
visible(val) {
if (val) this.syncFromScope();
},
},
methods: {
syncFromScope() {
this.applied = false;
const pick = (field) => {
const rows = this.scopeRows;
if (!rows.length) return undefined;
const first = rows[0][field];
return rows.every((row) => row[field] === first) ? first : undefined;
};
this.slaveValue = pick('slave');
this.readonlyValue = pick('isReadonly');
this.bitOrderValue = pick('bitOrder');
this.dataTypeValue = pick('dataType');
this.quantityValue = pick('quantity');
// 起始地址预填作用域首行地址(重排时通常保留起点)
this.startAddressValue = this.scopeRows.length ? this.scopeRows[0].address : undefined;
},
// 32 位类型在选项后标出占用的寄存器个数,选之前就知道会写 2
dictOptionLabel(dict) {
return deriveQuantity(dict.value) === 2 ? `${dict.label} · 2` : dict.label;
},
handleTriggerClick() {
if (this.disabled) {
this.$modal.msgWarning(this.disabledTip || this.$t('product.product-modbus.562372-331'));
}
},
// 数字输入框回车:失焦以触发 el-input-number 的提交(它自身不响应回车),值有变化才会 emit change
handleNumberEnter(event) {
if (event && event.target && event.target.blur) event.target.blur();
},
// 选择即生效:值变化即提交并收起面板
handleInstantApply(value) {
if (this.applied) return;
if (value === null || value === undefined || value === '') return;
this.applied = true;
this.$emit('apply', { column: this.column, value });
this.visible = false;
},
// 地址一键生成:起始地址回车、或改完失焦即生成(无需按钮;同一面板只提交一次)
// 注意这里用 el-input 而非 el-input-number:后者的 v-model 只在失焦/步进时提交,回车取到的会是旧值
handleGenerateAddress() {
if (this.applied || !this.canGenerate) return;
this.applied = true;
this.$emit('apply', { column: this.column, value: Number(this.startAddressValue) });
this.visible = false;
},
},
};
</script>
<style lang="scss">
.modbus-col-batch-header {
display: inline-flex;
align-items: center;
cursor: pointer;
.modbus-col-batch-label {
color: #606266;
}
.modbus-col-batch-badge {
margin-left: 4px;
font-size: 11px;
color: #c0c4cc;
line-height: 1;
&.is-magic {
font-size: 13px;
}
}
&:hover .modbus-col-batch-badge {
color: #409eff;
}
&.is-disabled {
cursor: not-allowed;
.modbus-col-batch-badge {
color: #e4e7ed;
}
}
}
.modbus-col-batch-panel {
.modbus-col-batch-scope {
font-size: 12px;
color: #909399;
line-height: 20px;
margin-bottom: 10px;
}
.modbus-col-batch-field {
margin-bottom: 4px;
}
.modbus-col-batch-tip {
font-size: 12px;
color: #e6a23c;
line-height: 18px;
margin-top: 6px;
&.is-hint {
color: #909399;
}
}
}
</style>

View File

@ -0,0 +1,44 @@
/**
* modbus 配置派生规则(唯一口径,页面与批量面板共用)
*
* 红线:`读取个数(quantity)` 是 `数据类型(dataType)` 的派生量,不是独立字段。
* 后端 ModbusProtocol#handleRead0304 完全按 quantity 分支解析:
* quantity==1 → 16 位(CalculateUtils.handleToUnSign16)
* quantity==2 → 32 位(CalculateUtils.parseValue)
* 其它值 → log.warn 直接丢弃
* 字典 iot_modbus_data_type(sql/yack-init.sql:2503-2511):
* bit=1 位;ushort/short=16 位 1 个寄存器;long/ulong/float 的 ABCD/CDAB 变体=32 位 2 个寄存器。
*/
/** 32 位数据类型集合(占 2 个寄存器) */
export const MODBUS_32BIT_TYPES = ['long-ABCD', 'long-CDAB', 'ulong-ABCD', 'ulong-CDAB', 'float-ABCD', 'float-CDAB'];
/** 16 位数据类型集合(占 1 个寄存器) */
export const MODBUS_16BIT_TYPES = ['ushort', 'short'];
/**
* 由数据类型推导读取个数
* @param {string} dataType 字典 iot_modbus_data_type 的值
* @returns {number|null} 1 | 2;dataType 为空或未知类型返回 null(不派生,避免把 data_type 为空的历史行改坏)
*/
export function deriveQuantity(dataType) {
if (!dataType) return null;
if (dataType === 'bit') return 1;
if (MODBUS_16BIT_TYPES.indexOf(dataType) !== -1) return 1;
if (MODBUS_32BIT_TYPES.indexOf(dataType) !== -1) return 2;
return null;
}
/**
* 单行占用的寄存器个数(一 key 生成地址序列时的步长)
* dataType 非空 → 以派生值为准(权威);dataType 为空的历史行 → 回退行内 quantity;再缺省 1
* @param {object} row modbus 配置行
* @returns {number} 1 | 2 | 行内 quantity
*/
export function resolveQuantity(row) {
if (!row) return 1;
const derived = deriveQuantity(row.dataType);
if (derived !== null) return derived;
const quantity = Number(row.quantity);
return quantity > 0 ? quantity : 1;
}

View File

@ -93,7 +93,8 @@
</div> </div>
</div> </div>
<el-table style="margin-bottom: 10px" v-loading="loadingIO" :data="configList" :key="configTableKey" data-key="id" ref="IOTable"> <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-24')" align="center" prop="sort"></el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-25')" align="center" prop="identifier"> <el-table-column :label="$t('product.product-modbus.562372-25')" align="center" prop="identifier">
<template slot-scope="scope"> <template slot-scope="scope">
@ -102,15 +103,13 @@
:disabled="!enableEditIO" :disabled="!enableEditIO"
v-model="scope.row.identifier" v-model="scope.row.identifier"
:placeholder="$t('product.product-modbus.562372-26')" :placeholder="$t('product.product-modbus.562372-26')"
:ref="`selectIo${scope.$index}`"
size="mini" size="mini"
@change="(e) => updateSelectThingsModel({ newVal: e, oldVal: $refs[`selectIo${scope.$index}`].value, justiceSelect: 'isSelectIo' })"
> >
<el-option key="0" label="" value="" disabled style="width: 300px"> <el-option key="0" label="" value="" disabled style="width: 300px">
<span style="float: left">{{ $t('product.product-modbus.562372-27') }}</span> <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> <span style="float: right; color: #8492a6; font-size: 13px">{{ $t('product.product-modbus.562372-28') }}</span>
</el-option> </el-option>
<el-option v-for="item in thingsModelList" :key="item.identifier" :label="`${item.modelName} (${item.identifier})`" :value="item.identifier" style="width: 300px" :disabled="!item.isSelectIo"> <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: left">{{ item.modelName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.identifier }}</span> <span style="float: right; color: #8492a6; font-size: 13px">{{ item.identifier }}</span>
</el-option> </el-option>
@ -118,6 +117,9 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-29')" align="center" prop="slave"> <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"> <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> <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> </template>
@ -128,19 +130,31 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-31')" align="center" prop="isReadonly"> <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"> <template slot-scope="scope">
<el-radio-group :disabled="!enableEditIO" size="mini" v-model="scope.row.isReadonly"> <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="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-button :label="1">{{ $t('product.product-modbus.562372-33') }}</el-radio-button>
</el-radio-group> </el-radio-group>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-311')" align="center" prop="bitOrder"> <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"> <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> <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> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-34')" align="center" class-name="small-padding fixed-width"> <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"> <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']"> <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') }} {{ $t('product.product-modbus.562372-35') }}
@ -180,14 +194,15 @@
<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']"> <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') }} {{ $t('product.product-modbus.562372-40') }}
</el-button> </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']"> <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') }} {{ $t('product.product-modbus.562372-41') }}
</el-button> </el-button>
</div> </div>
</div> </div>
</div> </div>
<el-table style="margin-bottom: 10px" v-loading="loadingData" :data="dataModbusList" :key="dataTableKey" ref="Dataable"> <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-24')" align="center" prop="sort"></el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-25')" align="center" prop="identifier" width="250px"> <el-table-column :label="$t('product.product-modbus.562372-25')" align="center" prop="identifier" width="250px">
<template slot-scope="scope"> <template slot-scope="scope">
@ -197,14 +212,12 @@
v-model="scope.row.identifier" v-model="scope.row.identifier"
:placeholder="$t('product.product-modbus.562372-26')" :placeholder="$t('product.product-modbus.562372-26')"
size="mini" size="mini"
:ref="`selectData${scope.$index}`"
@change="(e) => updateSelectThingsModel({ newVal: e, oldVal: $refs[`selectData${scope.$index}`].value, justiceSelect: 'isSelectData' })"
> >
<el-option key="0" label="" value="" disabled style="width: 300px"> <el-option key="0" label="" value="" disabled style="width: 300px">
<span style="float: left">{{ $t('product.product-modbus.562372-27') }}</span> <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> <span style="float: right; color: #8492a6; font-size: 13px">{{ $t('product.product-modbus.562372-28') }}</span>
</el-option> </el-option>
<el-option v-for="item in thingsModelList" :key="item.identifier" :label="`${item.modelName} (${item.identifier})`" :value="item.identifier" style="width: 300px" :disabled="!item.isSelectData"> <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: left">{{ item.modelName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.identifier }}</span> <span style="float: right; color: #8492a6; font-size: 13px">{{ item.identifier }}</span>
@ -212,26 +225,37 @@
</el-select> </el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-29')" align="center" prop="identifier" width="170px"> <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"> <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> <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> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-30')" align="center" prop="address" width="170px"> <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"> <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> <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> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-31')" align="center" prop="isReadonly" width="170px"> <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"> <template slot-scope="scope">
<el-radio-group :disabled="!enableEditData" size="mini" v-model="scope.row.isReadonly"> <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="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-button :label="1">{{ $t('product.product-modbus.562372-33') }}</el-radio-button>
</el-radio-group> </el-radio-group>
<!-- <el-tag v-show="!enableEditData">{{scope.row.isReadonly=='0'?'只读':'读写'}}</el-tag> -->
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-42')" align="center" prop="dataType" width="200px"> <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"> <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-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-option v-for="dict in dict.type.iot_modbus_data_type" :key="dict.value" :label="dict.label" :value="dict.value"></el-option>
@ -239,11 +263,20 @@
</template> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-43')" align="center" prop="quantity" width="170px"> <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"> <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> <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> </template>
</el-table-column> </el-table-column>
<el-table-column :label="$t('product.product-modbus.562372-34')" align="center" class-name="small-padding fixed-width"> <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"> <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']"> <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') }} {{ $t('product.product-modbus.562372-35') }}
@ -264,7 +297,7 @@
</el-row> </el-row>
<!-- 选择物模型 --> <!-- 选择物模型 -->
<things-list ref="thingsListRef" :productId="productId" @productEvent="getThingsData($event)" :justiceSelect="justiceSelect" /> <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" /> <import-batch ref="importBatchRef" :productId="productId" @data-imported="handleDataImport" :justiceSelect="justiceSelect" />
</div> </div>
@ -275,12 +308,14 @@ import { listConfig, addBatch } from '@/api/iot/modbusConfig';
import { getlListModbus } from '@/api/iot/model'; import { getlListModbus } from '@/api/iot/model';
import thingsList from './things-model-list'; import thingsList from './things-model-list';
import importBatch from './components/batchImportModbus'; import importBatch from './components/batchImportModbus';
import modbusColumnBatch from './components/modbusColumnBatch';
import { deriveQuantity, resolveQuantity } from './modbus-derive';
import Sortable from 'sortablejs'; import Sortable from 'sortablejs';
import { addOrUpdate, getByProductId } from '@/api/iot/params'; import { addOrUpdate, getByProductId } from '@/api/iot/params';
import { listProductJob } from '@/api/iot/modbusJob'; import { listProductJob } from '@/api/iot/modbusJob';
export default { export default {
name: 'product-modbus-copy', name: 'product-modbus',
dicts: ['iot_modbus_data_type', 'iot_yes_no', 'data_collect_type', 'device_status_deter', 'iot_modbus_poll_time'], dicts: ['iot_modbus_data_type', 'iot_yes_no', 'data_collect_type', 'device_status_deter', 'iot_modbus_poll_time'],
props: { props: {
product: { product: {
@ -291,16 +326,16 @@ export default {
components: { components: {
thingsList, thingsList,
importBatch, importBatch,
modbusColumnBatch,
}, },
watch: { watch: {
// 获取到父组件传递的productId后,刷新列表 // 获取到父组件传递的productId后,刷新列表
product: function (newVal, oldVal) { product: function (newVal, oldVal) {
this.productInfo = newVal; if (newVal && newVal.productId != 0) {
if (this.productInfo && this.productInfo.productId != 0) { this.thingsModelParams.productId = newVal.productId;
this.thingsModelParams.productId = this.productInfo.productId; this.queryParamsIO.productId = newVal.productId;
this.queryParamsIO.productId = this.productInfo.productId; this.queryParamsData.productId = newVal.productId;
this.queryParamsData.productId = this.productInfo.productId; this.productId = newVal.productId;
this.productId = this.productInfo.productId;
this.getIOList(); this.getIOList();
this.getDataList(); this.getDataList();
this.getThingsModelList(); this.getThingsModelList();
@ -311,25 +346,26 @@ export default {
enableEditIO: function (n, old) { enableEditIO: function (n, old) {
//设置行是否拖拽 //设置行是否拖拽
if (this.sortableIo) this.sortableIo.option('disabled', !n); if (this.sortableIo) this.sortableIo.option('disabled', !n);
//关闭编辑刷新页面 //关闭编辑刷新页面(保存与取消都会走到这里:重拉数据与物模型可选标记,并清空本次编辑的临时状态)
if (!n) { if (!n) {
this.getIOList(); this.getIOList();
this.getThingsModelList(); this.getThingsModelList();
this.ioSelection = [];
this.releasedIo = [];
this.delIoIds = [];
} }
//清空删除数组
this.delIoIds = [];
this.getThingsModelList();
}, },
enableEditData: function (n, old) { enableEditData: function (n, old) {
//设置行是否拖拽 //设置行是否拖拽
if (this.sortableData) this.sortableData.option('disabled', !n); if (this.sortableData) this.sortableData.option('disabled', !n);
//关闭编辑刷新页面 //关闭编辑刷新页面(保存与取消都会走到这里:重拉数据与物模型可选标记,并清空本次编辑的临时状态)
if (!n) { if (!n) {
this.getDataList(); this.getDataList();
this.getThingsModelList(); this.getThingsModelList();
this.dataSelection = [];
this.releasedData = [];
this.delDataIds = [];
} }
//清空删除数组
this.delDataIds = [];
}, },
}, },
data() { data() {
@ -337,14 +373,6 @@ export default {
// 遮罩层 // 遮罩层
loadingIO: true, loadingIO: true,
loadingData: true, loadingData: true,
// 选中数组
ids: [],
// 非单个禁用
single: true,
// 非多个禁用
multiple: true,
// 显示搜索条件
showSearch: true,
// 总条数 // 总条数
total: 0, total: 0,
// modbus配置表格数据 // modbus配置表格数据
@ -353,10 +381,6 @@ export default {
enableEditIO: false, enableEditIO: false,
//子设备配置是否可编辑 //子设备配置是否可编辑
enableSetSlave: false, enableSetSlave: false,
// 弹出层标题
title: '',
// 是否显示弹出层
open: false,
// 查询参数 // 查询参数
queryParamsIO: { queryParamsIO: {
pageNum: 1, pageNum: 1,
@ -392,10 +416,16 @@ export default {
// 数据寄存器遮罩层 // 数据寄存器遮罩层
dataLoading: true, dataLoading: true,
//IO表勾选行(列头批量作用域:有勾选=勾选行,否则本页全部行)
ioSelection: [],
//数据表勾选行
dataSelection: [],
//本次编辑中被删除行占用的物模型标识符(后端标记要保存后才变,期间放行其重新可选)
releasedIo: [],
releasedData: [],
//可选属性 //可选属性
thingsModelList: [], thingsModelList: [],
//物模型遮罩层
thingsLoading: true,
//thingsModel //thingsModel
thingsModelParams: { thingsModelParams: {
pageNum: 1, pageNum: 1,
@ -403,7 +433,6 @@ export default {
productId: 0, productId: 0,
isCalculated: 0, isCalculated: 0,
}, },
thingsTotal: 0,
// 表单参数 // 表单参数
form: { form: {
statusDeter: 1, statusDeter: 1,
@ -411,15 +440,9 @@ export default {
pollType: 0, pollType: 0,
deterTimer: '300', deterTimer: '300',
}, },
productInfo: {},
// 表单校验 // 表单校验
rules: { rules: {
slaveId: [{ required: true, message: this.$t('product.product-modbus.562372-46'), trigger: 'blur' }], slaveId: [{ required: true, message: this.$t('product.product-modbus.562372-46'), trigger: 'blur' }],
address: [{ required: true, message: this.$t('product.product-modbus.562372-47'), trigger: 'blur' }],
isReadonly: [{ required: true, message: this.$t('product.product-modbus.562372-48'), trigger: 'blur' }],
dataType: [{ required: true, message: this.$t('product.product-modbus.562372-49'), trigger: 'change' }],
quantity: [{ required: true, message: this.$t('product.product-modbus.562372-50'), trigger: 'blur' }],
type: [{ required: true, message: this.$t('product.product-modbus.562372-51'), trigger: 'change' }],
}, },
//IO行拖拽对象 //IO行拖拽对象
sortableIo: null, sortableIo: null,
@ -438,6 +461,35 @@ export default {
jobTotal: 0, 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() { mounted() {
this.rowDropIo(); this.rowDropIo();
this.rowDropData(); this.rowDropData();
@ -475,35 +527,143 @@ export default {
}, },
/** 查询产品物模型列表 */ /** 查询产品物模型列表 */
getThingsModelList() { getThingsModelList() {
this.thingsLoading = true;
getlListModbus(this.thingsModelParams).then((response) => { getlListModbus(this.thingsModelParams).then((response) => {
this.$refs.thingsListRef.modelList = response.rows; this.$refs.thingsListRef.modelList = response.rows;
this.thingsModelList = response.rows; this.thingsModelList = response.rows;
this.thingsTotal = response.total;
this.thingsLoading = false;
}); });
}, },
// 取消按钮 // IO表勾选(仅编辑态可勾选)
cancel() { ioSelectable() {
this.open = false; return this.enableEditIO;
this.reset();
}, },
// 表单重置 // 数据表勾选(仅编辑态可勾选)
reset() { dataSelectable() {
this.form = { return this.enableEditData;
statusDeter: 1, },
slaveId: null, handleIoSelectionChange(selection) {
// pollType: '0', this.ioSelection = selection;
deterTime: '300', },
}; handleDataSelectionChange(selection) {
this.resetForm('form'); this.dataSelection = selection;
},
// 按表格行序重排(传入的已是整个列表时直接返回,不产生新数组)
sortByTableOrder(rows, list) {
if (rows === list) return rows;
return rows.slice().sort((a, b) => list.indexOf(a) - list.indexOf(b));
}, },
// 多选框选中数据 /**
handleSelectionChange(selection) { * 寄存器地址一键生成:从起始地址开始,按各行占用的寄存器个数(读取个数)依次推进。
this.ids = selection.map((item) => item.id); * 步长口径 = modbus-derive#resolveQuantity:dataType 非空以派生值为准,dataType 为空的历史行回退行内 quantity,再缺省 1。
this.single = selection.length !== 1; * 仅数据寄存器使用(IO 寄存器同地址按 bitOrder 取位,地址不递增)。
this.multiple = !selection.length; */
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寄存器行操作 */ /** 新增IO寄存器行操作 */
handleAdd() { handleAdd() {
@ -591,6 +751,11 @@ export default {
const dataModbusList = []; const dataModbusList = [];
this.dataModbusList.forEach((dataModbus, index) => { this.dataModbusList.forEach((dataModbus, index) => {
if (dataModbus.identifier) { 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; dataModbus.sort = dataModbusList.length + 1;
dataModbusList.push(dataModbus); dataModbusList.push(dataModbus);
} }
@ -608,15 +773,13 @@ export default {
this.loadingData = false; this.loadingData = false;
}); });
}, },
/** 删除按钮操作 */ /** 删除按钮操作:本地删除 + 收集已保存行 id;释放的物模型占用由 isOptionDisabled 派生判定自动生效 */
handleDelete(row, index, list, justiceSelect) { handleDelete(row, index, list, justiceSelect) {
const item = list.splice(index, 1)[0]; list.splice(index, 1);
//如果是删除数据寄存器,并且有id就存id const delIds = justiceSelect === 'isSelectIo' ? this.delIoIds : this.delDataIds;
if (justiceSelect == 'isSelectData' && row.id) this.delDataIds.push(row.id); const released = justiceSelect === 'isSelectIo' ? this.releasedIo : this.releasedData;
//如果是删除io,并且有id就存id if (row.id) delIds.push(row.id);
if (justiceSelect == 'isSelectIo' && row.id) this.delIoIds.push(row.id); if (row.identifier) released.push(row.identifier);
//处理选项是否可选
this.updateSelectThingsModel({ justiceSelect, oldVal: item.identifier });
}, },
/*获取选择的物模型*/ /*获取选择的物模型*/
@ -635,7 +798,6 @@ export default {
type: this.justiceSelect == 'isSelectData' ? 2 : 1, type: this.justiceSelect == 'isSelectData' ? 2 : 1,
sort: list.length + 1, sort: list.length + 1,
}); });
this.updateSelectThingsModel({ justiceSelect: this.justiceSelect, newVal: id });
} }
}); });
}, },
@ -684,16 +846,6 @@ export default {
//将选择中那列查到新位置 //将选择中那列查到新位置
list.splice(newIndex, 0, curRow); list.splice(newIndex, 0, curRow);
}, },
/**
* 更新物模型是否可选
*/
updateSelectThingsModel({ oldVal, newVal, justiceSelect }) {
//直接改前后值
const oldIndex = oldVal ? this.thingsModelList.findIndex((thingsModel) => thingsModel.identifier == oldVal) : -1;
const newIndex = newVal ? this.thingsModelList.findIndex((thingsModel) => thingsModel.identifier == newVal) : -1;
if (oldIndex != -1) this.thingsModelList[oldIndex][justiceSelect] = true;
if (newIndex != -1) this.thingsModelList[newIndex][justiceSelect] = false;
},
/**批量导入 */ /**批量导入 */
batchImport(justiceSelect) { batchImport(justiceSelect) {
this.justiceSelect = justiceSelect; this.justiceSelect = justiceSelect;
@ -702,7 +854,7 @@ export default {
/**导出 */ /**导出 */
exportModbus(justiceSelect) { exportModbus(justiceSelect) {
const type = justiceSelect == 'isSelectData' ? 2 : 1; const type = justiceSelect == 'isSelectData' ? 2 : 1;
const name = justiceSelect == 'isSelectData' ? this.$t('product.product-modbus.562372-17') : this.$t('product.product-modbus.562372-38'); 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`); this.download('/modbus/config/exportModbus?type=' + type, {}, `${name}_${new Date().getTime()}.xlsx`);
}, },
/**获取modbus参数配置*/ /**获取modbus参数配置*/
@ -760,16 +912,13 @@ export default {
}, },
/** 查询轮训任务列列表 */ /** 查询轮训任务列列表 */
getTaskList(productId) { getTaskList(productId) {
this.loading = true;
const params = { const params = {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
productId: productId, productId: productId,
}; };
listProductJob(params).then((response) => { listProductJob(params).then((response) => {
this.jobList = response.rows;
this.jobTotal = response.total; this.jobTotal = response.total;
this.loading = false;
}); });
}, },
/**取消*/ /**取消*/

View File

@ -46,6 +46,11 @@ export default {
justiceSelect: { justiceSelect: {
type: String, type: String,
default: 'isSelectData' default: 'isSelectData'
},
// 父组件的「可否选中」派生判定(表内内存占用 + 刚删除释放 + 后端标记),与本页下拉同口径
judgeDisabled: {
type: Function,
default: null
} }
}, },
data() { data() {
@ -92,10 +97,7 @@ export default {
this.selectedList.forEach(selected => { this.selectedList.forEach(selected => {
const findIndex = this.modelList.findIndex(model => model.identifier == selected.identifier) const findIndex = this.modelList.findIndex(model => model.identifier == selected.identifier)
if (findIndex != -1) { if (findIndex != -1) {
const model = this.modelList[findIndex] this.$refs.thingsModelTable.toggleRowSelection(this.modelList[findIndex], true)
model.isSelectData = false
model.isSelectIo = false
this.$refs.thingsModelTable.toggleRowSelection(model, true)
} }
}) })
}) })
@ -132,8 +134,11 @@ export default {
closeDialog() { closeDialog() {
this.open = false; this.open = false;
}, },
//是否可选 //是否可选:优先走父组件派生判定(内存占用/刚删除释放/后端标记三合一),无判定时退回后端标记
selectable(item) { selectable(item) {
if (this.judgeDisabled) {
return !this.judgeDisabled(item, this.justiceSelect)
}
return item[this.justiceSelect] return item[this.justiceSelect]
} }
} }