20260309:设备批量新增
This commit is contained in:
parent
2cfc9c35ea
commit
9536ae19b1
@ -21,6 +21,7 @@ import com.fastbee.iot.model.DeviceImportVO;
|
||||
import com.fastbee.iot.model.DeviceRelateUserInput;
|
||||
import com.fastbee.iot.model.SerialNumberVO;
|
||||
import com.fastbee.iot.model.dto.ThingsModelDTO;
|
||||
import com.fastbee.iot.model.gateWay.SubDeviceCreateByProductVO;
|
||||
import com.fastbee.iot.service.IDeviceService;
|
||||
import com.github.pagehelper.PageInfo;
|
||||
import io.swagger.annotations.Api;
|
||||
@ -493,4 +494,15 @@ public class DeviceController extends BaseController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据子产品批量新增子设备
|
||||
*/
|
||||
@PreAuthorize("@ss.hasPermi('iot:device:add')")
|
||||
@Log(title = "批量新增子设备", businessType = BusinessType.INSERT)
|
||||
@PostMapping("/addBatchByProduct")
|
||||
@ApiOperation("根据子产品批量新增子设备")
|
||||
public AjaxResult addBatchByProduct(@RequestBody SubDeviceCreateByProductVO vo) {
|
||||
int count = deviceService.addSubDevicesByProducts(vo);
|
||||
return AjaxResult.success("成功新增 " + count + " 个子设备");
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,6 +16,7 @@ import java.io.UnsupportedEncodingException;
|
||||
import java.math.BigDecimal;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Objects;
|
||||
|
||||
import static com.fastbee.common.exception.ServiceExceptionUtil.exception;
|
||||
|
||||
@ -43,7 +44,7 @@ public class BaiduILocationService implements ILocationService {
|
||||
|
||||
log.info("结构化地址:{}", address);
|
||||
String baseUrl = FastBeeConstant.MAP.BAIDU_GEOCODING;
|
||||
address = StringUtils.isEmpty(address) ? "湖南省长沙市岳麓区" : address;
|
||||
address = StringUtils.isEmpty(address) || Objects.equals("内网IP", address) ? "湖南省长沙市岳麓区" : address;
|
||||
String encodedAddress;
|
||||
try {
|
||||
encodedAddress = URLEncoder.encode(address, StandardCharsets.UTF_8.toString());
|
||||
|
||||
@ -0,0 +1,36 @@
|
||||
package com.fastbee.iot.model.gateWay;
|
||||
|
||||
import io.swagger.annotations.ApiModelProperty;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 根据子产品批量新增子设备VO
|
||||
*/
|
||||
@Data
|
||||
public class SubDeviceCreateByProductVO {
|
||||
@ApiModelProperty("网关设备id")
|
||||
private Long gwDeviceId;
|
||||
|
||||
@ApiModelProperty("子产品设备创建项")
|
||||
private List<SubProductDeviceItem> items;
|
||||
|
||||
@Data
|
||||
public static class SubProductDeviceItem {
|
||||
@ApiModelProperty("子产品id")
|
||||
private Long subProductId;
|
||||
|
||||
@ApiModelProperty("子设备详情列表")
|
||||
private List<DeviceDetail> devices;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class DeviceDetail {
|
||||
@ApiModelProperty("自定义设备名称")
|
||||
private String deviceName;
|
||||
|
||||
@ApiModelProperty("自定义从机地址")
|
||||
private Integer slaveId;
|
||||
}
|
||||
}
|
||||
@ -51,4 +51,10 @@ public class SubDeviceListVO {
|
||||
private Date createTime;
|
||||
|
||||
private Long subProductId;
|
||||
|
||||
@ApiModelProperty("子产品名称")
|
||||
private String subProductName;
|
||||
|
||||
@ApiModelProperty("在线状态")
|
||||
private Integer status;
|
||||
}
|
||||
|
||||
@ -10,6 +10,7 @@ import com.fastbee.iot.model.*;
|
||||
import com.fastbee.iot.model.ThingsModels.ThingsModelShadow;
|
||||
import com.fastbee.common.core.thingsModel.ThingsModelValuesInput;
|
||||
import com.fastbee.iot.model.dto.ThingsModelDTO;
|
||||
import com.fastbee.iot.model.gateWay.SubDeviceCreateByProductVO;
|
||||
import org.quartz.SchedulerException;
|
||||
|
||||
import java.util.List;
|
||||
@ -146,6 +147,14 @@ public interface IDeviceService {
|
||||
*/
|
||||
public Device insertDevice(Device device);
|
||||
|
||||
/**
|
||||
* 根据子产品批量新增子设备并绑定到网关
|
||||
*
|
||||
* @param vo 包含网关设备id和子产品设备创建项列表
|
||||
* @return 新增的子设备数量
|
||||
*/
|
||||
public int addSubDevicesByProducts(SubDeviceCreateByProductVO vo);
|
||||
|
||||
/**
|
||||
* 设备关联用户
|
||||
*
|
||||
|
||||
@ -43,6 +43,7 @@ import com.fastbee.iot.model.ThingsModels.ThingsModelValueItem;
|
||||
import com.fastbee.iot.model.ThingsModels.ValueItem;
|
||||
import com.fastbee.iot.model.dto.ThingsModelDTO;
|
||||
import com.fastbee.iot.model.gateWay.ProductSubGatewayVO;
|
||||
import com.fastbee.iot.model.gateWay.SubDeviceCreateByProductVO;
|
||||
import com.fastbee.iot.model.gateWay.SubDeviceListVO;
|
||||
import com.fastbee.iot.service.*;
|
||||
import com.fastbee.iot.tdengine.service.ILogService;
|
||||
@ -1189,6 +1190,109 @@ public class DeviceServiceImpl implements IDeviceService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据子产品批量新增子设备并绑定到网关(支持自定义每个设备的名称和从机地址)
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public int addSubDevicesByProducts(SubDeviceCreateByProductVO vo) {
|
||||
Long gwDeviceId = vo.getGwDeviceId();
|
||||
List<SubDeviceCreateByProductVO.SubProductDeviceItem> items = vo.getItems();
|
||||
if (items == null || items.isEmpty()) {
|
||||
throw new ServiceException("子产品创建项不能为空");
|
||||
}
|
||||
Device gwDevice = deviceMapper.selectDeviceByDeviceId(gwDeviceId);
|
||||
if (gwDevice == null) {
|
||||
throw new ServiceException("网关设备不存在");
|
||||
}
|
||||
Product product = productService.selectProductByProductId(gwDevice.getProductId());
|
||||
if (product == null) {
|
||||
throw new ServiceException("网关产品不存在");
|
||||
}
|
||||
List<Long> subProductIdList = items.stream()
|
||||
.map(SubDeviceCreateByProductVO.SubProductDeviceItem::getSubProductId)
|
||||
.collect(Collectors.toList());
|
||||
Map<Long, List<SubDeviceCreateByProductVO.DeviceDetail>> deviceDetailMap = items.stream()
|
||||
.collect(Collectors.toMap(
|
||||
SubDeviceCreateByProductVO.SubProductDeviceItem::getSubProductId,
|
||||
i -> i.getDevices() != null ? i.getDevices() : Collections.emptyList()));
|
||||
ProductSubGateway query = new ProductSubGateway();
|
||||
query.setGwProductId(product.getProductId());
|
||||
List<ProductSubGatewayVO> allSubProducts = productSubGatewayMapper.selectListVO(query);
|
||||
List<ProductSubGatewayVO> selectedSubProducts = allSubProducts.stream()
|
||||
.filter(p -> subProductIdList.contains(p.getSubProductId()))
|
||||
.sorted(Comparator.comparing(ProductSubGatewayVO::getSlaveId))
|
||||
.collect(Collectors.toList());
|
||||
if (CollectionUtils.isEmpty(selectedSubProducts)) {
|
||||
throw new ServiceException("未找到匹配的子产品");
|
||||
}
|
||||
List<Long> productIds = selectedSubProducts.stream()
|
||||
.map(ProductSubGatewayVO::getSubProductId).collect(Collectors.toList());
|
||||
LambdaQueryWrapper<ProductModbusJob> jobQuery = new LambdaQueryWrapper<>();
|
||||
jobQuery.in(ProductModbusJob::getProductId, productIds);
|
||||
List<ProductModbusJob> productModbusJobList = productModbusJobMapper.selectList(jobQuery);
|
||||
Map<Long, List<ProductModbusJob>> productModbusJobMap = productModbusJobList.stream()
|
||||
.collect(Collectors.groupingBy(ProductModbusJob::getProductId));
|
||||
int totalCount = 0;
|
||||
for (ProductSubGatewayVO subProduct : selectedSubProducts) {
|
||||
List<SubDeviceCreateByProductVO.DeviceDetail> details =
|
||||
deviceDetailMap.getOrDefault(subProduct.getSubProductId(), Collections.emptyList());
|
||||
if (details.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
for (int i = 0; i < details.size(); i++) {
|
||||
SubDeviceCreateByProductVO.DeviceDetail detail = details.get(i);
|
||||
String deviceName = StringUtils.isNotEmpty(detail.getDeviceName())
|
||||
? detail.getDeviceName()
|
||||
: subProduct.getSubProductName() + "_" + subProduct.getSlaveId() + "_" + (i + 1);
|
||||
Integer slaveId = detail.getSlaveId() != null
|
||||
? detail.getSlaveId()
|
||||
: subProduct.getSlaveId();
|
||||
Device addDevice = new Device();
|
||||
addDevice.setDeviceName(deviceName);
|
||||
addDevice.setProductId(subProduct.getSubProductId());
|
||||
addDevice.setProductName(subProduct.getSubProductName());
|
||||
addDevice.setSerialNumber(this.generationDeviceNum(1));
|
||||
addDevice.setTenantId(gwDevice.getTenantId());
|
||||
addDevice.setTenantName(gwDevice.getTenantName());
|
||||
addDevice.setCreateBy(SecurityUtils.getUsername());
|
||||
addDevice.setFirmwareVersion(new BigDecimal(1));
|
||||
addDevice.setRssi(0);
|
||||
addDevice.setIsShadow(0);
|
||||
addDevice.setLocationWay(subProduct.getSubLocationWay());
|
||||
addDevice.setImgUrl(product.getImgUrl());
|
||||
addDevice.setCreateTime(new Date());
|
||||
deviceMapper.insertDevice(addDevice);
|
||||
SubGateway subGateway = new SubGateway();
|
||||
subGateway.setGwDeviceId(gwDeviceId);
|
||||
subGateway.setSubDeviceId(addDevice.getDeviceId());
|
||||
subGateway.setSlaveId(slaveId);
|
||||
subGateway.setCreateBy(SecurityUtils.getUsername());
|
||||
subGateway.setCreateTime(new Date());
|
||||
subGatewayMapper.insertGateway(subGateway);
|
||||
List<ProductModbusJob> modbusJobs = productModbusJobMap.get(subProduct.getSubProductId());
|
||||
if (!CollectionUtils.isEmpty(modbusJobs)) {
|
||||
for (ProductModbusJob pmj : modbusJobs) {
|
||||
ModbusJob modbusJob = new ModbusJob();
|
||||
modbusJob.setJobName(pmj.getJobName());
|
||||
modbusJob.setSubDeviceId(addDevice.getDeviceId());
|
||||
modbusJob.setSubSerialNumber(addDevice.getSerialNumber());
|
||||
modbusJob.setCommand(pmj.getCommand());
|
||||
modbusJob.setStatus(pmj.getStatus());
|
||||
modbusJob.setRemark(pmj.getRemark());
|
||||
modbusJobService.insertModbusJob(modbusJob);
|
||||
if (FastBeeConstant.PROTOCOL.ModbusTcp.equals(product.getProtocolCode())) {
|
||||
redisCache.getCacheModbusTcpId(gwDevice.getSerialNumber());
|
||||
}
|
||||
}
|
||||
}
|
||||
itslValueCache.addCacheDeviceStatus(addDevice.getProductId(), addDevice.getSerialNumber());
|
||||
totalCount++;
|
||||
}
|
||||
}
|
||||
return totalCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 终端-设备关联用户
|
||||
*
|
||||
|
||||
@ -30,6 +30,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="subDeviceNo" column="serial_number"/>
|
||||
<result property="createTime" column="create_time" />
|
||||
<result property="subProductId" column="sub_product_id"/>
|
||||
<result property="subProductName" column="sub_product_name"/>
|
||||
<result property="status" column="status"/>
|
||||
<result property="id" column="id" />
|
||||
</resultMap>
|
||||
|
||||
@ -47,8 +49,12 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</sql>
|
||||
|
||||
<select id="selectGatewayList" parameterType="com.fastbee.iot.domain.SubGateway" resultMap="GatewaySubDeviceListResult">
|
||||
select ga.id, ga.gw_device_id, ga.sub_device_id, ga.slave_id, ga.create_time ,d.device_name, d.serial_number, d.product_id sub_product_id
|
||||
from iot_sub_gateway ga inner join iot_device d on ga.sub_device_id = d.device_id
|
||||
select ga.id, ga.gw_device_id, ga.sub_device_id, ga.slave_id, ga.create_time,
|
||||
d.device_name, d.serial_number, d.product_id sub_product_id, d.status,
|
||||
p.product_name as sub_product_name
|
||||
from iot_sub_gateway ga
|
||||
inner join iot_device d on ga.sub_device_id = d.device_id
|
||||
left join iot_product p on d.product_id = p.product_id
|
||||
<where>
|
||||
<if test="gwDeviceId != null and gwDeviceId != 0">
|
||||
gw_device_id = #{gwDeviceId}
|
||||
@ -57,6 +63,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
sub_device_id = #{subDeviceId}
|
||||
</if>
|
||||
</where>
|
||||
order by p.product_name, ga.slave_id
|
||||
</select>
|
||||
|
||||
<select id="selectGatewayById" parameterType="Long" resultMap="GatewayResult">
|
||||
|
||||
@ -70,6 +70,15 @@ export function editGatewayBatch(data) {
|
||||
});
|
||||
}
|
||||
|
||||
// 根据子产品批量新增子设备
|
||||
export function createSubDeviceByProducts(data) {
|
||||
return request({
|
||||
url: '/iot/device/addBatchByProduct',
|
||||
method: 'post',
|
||||
data: data,
|
||||
});
|
||||
}
|
||||
|
||||
///产品///
|
||||
// 查询网关与子产品关联列表
|
||||
export function gatewayProductList(query) {
|
||||
|
||||
@ -571,12 +571,30 @@
|
||||
"device.running-status.866086-29": "The number of elements does not match, the number of array elements is",
|
||||
"device.running-status.866086-30": "Number, separated by English commas.",
|
||||
"device.running-status.866086-31": "Equipment upgrade",
|
||||
"device.sub.083943-0": "Add sub devices",
|
||||
"device.sub.binding": "Binding sub devices",
|
||||
"device.sub.createByProduct": "Add Sub Devices",
|
||||
"device.sub-device-create.title": "Add Sub Devices by Products",
|
||||
"device.sub-device-create.productListTitle": "Sub Product List",
|
||||
"device.sub-device-create.noSubProducts": "No sub products found. Please configure sub products in product management first.",
|
||||
"device.sub-device-create.enabled": "Enabled",
|
||||
"device.sub-device-create.deviceConfig": "Device Config",
|
||||
"device.sub-device-create.deviceCount": "Device Count",
|
||||
"device.sub-device-create.deviceListTitle": "Device List",
|
||||
"device.sub-device-create.summary": "Selected {0} sub products, will create {1} sub devices",
|
||||
"device.sub-device-create.devicePrefix": "Sub Device",
|
||||
"device.sub-device-create.deviceNameLabel": "Device Name",
|
||||
"device.sub-device-create.deviceNamePlaceholder": "Please enter device name",
|
||||
"device.sub-device-create.slaveIdLabel": "Slave Address",
|
||||
"device.sub-device-create.slaveIdPlaceholder": "Please enter slave address",
|
||||
"device.sub-device-create.selectTip": "Please enable at least one sub product and set device count",
|
||||
"device.sub-device-create.success": "Sub devices added successfully",
|
||||
"device.sub.083943-1": "Remove sub devices",
|
||||
"device.sub.083943-2": "Set sub device address",
|
||||
"device.sub.083943-3": "Sub device address",
|
||||
"device.sub.083943-4": "Are you sure to delete the data item with device number {0]?",
|
||||
"device.sub.083943-5": "Save successful",
|
||||
"device.sub.subProductName": "Sub Product",
|
||||
"device.sub.onlineStatus": "Online Status",
|
||||
"device.sub-device-list.323213-0": "Select device",
|
||||
"device.sub-device-list.323213-1": "Please enter the sub device number",
|
||||
"device.sub-device-list.323213-2": "name",
|
||||
|
||||
@ -571,12 +571,30 @@
|
||||
"device.running-status.866086-29": "元素个数不匹配,数组元素个数为",
|
||||
"device.running-status.866086-30": "个,以英文逗号分隔。",
|
||||
"device.running-status.866086-31": "设备升级",
|
||||
"device.sub.083943-0": "添加子设备",
|
||||
"device.sub.binding": "绑定子设备",
|
||||
"device.sub.createByProduct": "新增子设备",
|
||||
"device.sub-device-create.title": "根据子产品新增子设备",
|
||||
"device.sub-device-create.productListTitle": "子产品列表",
|
||||
"device.sub-device-create.noSubProducts": "当前网关产品下暂无子产品,请先在产品管理中配置子产品",
|
||||
"device.sub-device-create.enabled": "已启用",
|
||||
"device.sub-device-create.deviceConfig": "子设备配置",
|
||||
"device.sub-device-create.deviceCount": "子设备数量",
|
||||
"device.sub-device-create.deviceListTitle": "子设备列表",
|
||||
"device.sub-device-create.summary": "已选择 {0} 个子产品,将新增 {1} 个子设备",
|
||||
"device.sub-device-create.devicePrefix": "子设备",
|
||||
"device.sub-device-create.deviceNameLabel": "子设备名称",
|
||||
"device.sub-device-create.deviceNamePlaceholder": "请输入子设备名称",
|
||||
"device.sub-device-create.slaveIdLabel": "从机地址",
|
||||
"device.sub-device-create.slaveIdPlaceholder": "请输入从机地址",
|
||||
"device.sub-device-create.selectTip": "请至少启用一个子产品并设置设备数量",
|
||||
"device.sub-device-create.success": "新增子设备成功",
|
||||
"device.sub.083943-1": "移除子设备",
|
||||
"device.sub.083943-2": "设置子设备地址",
|
||||
"device.sub.083943-3": "子设备地址",
|
||||
"device.sub.083943-4": "是否确认删除设备编号为{0]的数据项?",
|
||||
"device.sub.083943-5": "保存成功",
|
||||
"device.sub.subProductName": "所属子产品",
|
||||
"device.sub.onlineStatus": "在线状态",
|
||||
"device.sub-device-list.323213-0": "选择设备",
|
||||
"device.sub-device-list.323213-1": "请输入子设备编号",
|
||||
"device.sub-device-list.323213-2": "名称",
|
||||
|
||||
@ -2,7 +2,10 @@
|
||||
<div class="device-sub-container">
|
||||
<el-form :model="queryParams" ref="queryForm" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item>
|
||||
<el-button plain type="primary" icon="el-icon-plus" size="mini" @click="handleAdd">{{ $t('device.sub.083943-0') }}</el-button>
|
||||
<el-button plain type="primary" icon="el-icon-plus" size="mini" @click="handleAdd">{{ $t('device.sub.binding') }}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button plain type="success" icon="el-icon-plus" size="mini" @click="handleCreateByProduct">{{ $t('device.sub.createByProduct') }}</el-button>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button plain type="danger" icon="el-icon-delete" size="mini" @click="handleDelete" :disabled="this.ids.length == 0">{{ $t('device.sub.083943-1') }}</el-button>
|
||||
@ -22,8 +25,13 @@
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
|
||||
<el-table v-loading="loading" :data="deviceList" @selection-change="handleSelectionChange" :border="false">
|
||||
<el-table v-loading="loading" :data="deviceList" @selection-change="handleSelectionChange" :border="true" :span-method="mergeProductRows">
|
||||
<el-table-column type="selection" width="55" align="center" />
|
||||
<el-table-column :label="$t('device.sub.subProductName')" align="center" prop="subProductName" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-tag size="small" effect="plain">{{ scope.row.subProductName || '-' }}</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('device.device-edit.148398-1')" align="center" prop="subDeviceName" />
|
||||
<el-table-column :label="$t('device.device-edit.148398-7')" align="center" prop="subDeviceNo" />
|
||||
<el-table-column :label="$t('device.sub.083943-3')" align="center" prop="slaveId" width="180px">
|
||||
@ -31,22 +39,31 @@
|
||||
<el-input style="width: 120px; text-align: center" :disabled="!isSet" v-model="scope.row.slaveId" size="small" :placeholder="$t('device.sub.083943-2')"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<!-- <el-table-column label="在线状态" align="center" prop="status">-->
|
||||
<!-- <template slot-scope="scope">-->
|
||||
<!-- <dict-tag :options="dict.type.iot_device_status" :value="scope.row.status" />-->
|
||||
<!-- </template>-->
|
||||
<!-- </el-table-column>-->
|
||||
<el-table-column :label="$t('device.sub.onlineStatus')" align="center" prop="status">
|
||||
<template slot-scope="scope">
|
||||
<dict-tag :options="dict.type.iot_device_status" :value="scope.row.status" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('creatTime')" align="center" prop="createTime" width="180">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime, '{y}-{m}-{d}') }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('opation')" align="center" class-name="small-padding fixed-width" width="150">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-view" @click="goDeviceDetail(scope.row.subDeviceId)">{{ $t('detail') }}</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)">{{ $t('device.sub.083943-1') }}</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" />
|
||||
|
||||
<!-- 子设备列表 -->
|
||||
<subDeviceList ref="subDeviceList" :gateway="gateway" @addSuccess="addSuccess"></subDeviceList>
|
||||
|
||||
<!-- 根据子产品新增子设备 -->
|
||||
<subDeviceCreate ref="subDeviceCreate" :gateway="gateway" @addSuccess="addSuccess"></subDeviceCreate>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@ -54,6 +71,7 @@
|
||||
import { listGateway, delGateway, editGatewayBatch } from '@/api/iot/gateway';
|
||||
|
||||
import subDeviceList from './sub-device-list';
|
||||
import subDeviceCreate from './sub-device-create';
|
||||
|
||||
export default {
|
||||
name: 'device-sub',
|
||||
@ -65,6 +83,7 @@ export default {
|
||||
},
|
||||
components: {
|
||||
subDeviceList,
|
||||
subDeviceCreate,
|
||||
},
|
||||
dicts: ['iot_device_status'],
|
||||
data() {
|
||||
@ -98,6 +117,8 @@ export default {
|
||||
gateway: {},
|
||||
//是否可设置子设备地址
|
||||
isSet: false,
|
||||
// 子产品行合并信息
|
||||
productSpans: [],
|
||||
};
|
||||
},
|
||||
|
||||
@ -117,13 +138,39 @@ export default {
|
||||
methods: {
|
||||
/** 查询子设备列表 */
|
||||
getList() {
|
||||
// this.loading = true;
|
||||
listGateway(this.queryParams).then((response) => {
|
||||
this.deviceList = response.rows;
|
||||
this.total = response.total;
|
||||
this.loading = false;
|
||||
this.computeProductSpans();
|
||||
});
|
||||
},
|
||||
computeProductSpans() {
|
||||
const spans = [];
|
||||
const list = this.deviceList || [];
|
||||
let i = 0;
|
||||
while (i < list.length) {
|
||||
const productId = list[i].subProductId;
|
||||
let count = 1;
|
||||
while (i + count < list.length && list[i + count].subProductId === productId) {
|
||||
count++;
|
||||
}
|
||||
for (let j = 0; j < count; j++) {
|
||||
spans.push(j === 0 ? count : 0);
|
||||
}
|
||||
i += count;
|
||||
}
|
||||
this.productSpans = spans;
|
||||
},
|
||||
mergeProductRows({ row, column, rowIndex, columnIndex }) {
|
||||
if (columnIndex === 1) {
|
||||
const span = this.productSpans[rowIndex];
|
||||
if (span > 0) {
|
||||
return { rowspan: span, colspan: 1 };
|
||||
}
|
||||
return { rowspan: 0, colspan: 0 };
|
||||
}
|
||||
},
|
||||
// 取消按钮
|
||||
cancel() {
|
||||
this.open = false;
|
||||
@ -162,6 +209,14 @@ export default {
|
||||
this.$refs.subDeviceList.openDeviceList = true;
|
||||
this.$refs.subDeviceList.getList();
|
||||
},
|
||||
/** 根据子产品新增子设备 */
|
||||
handleCreateByProduct() {
|
||||
if (!this.device || !this.device.productId) {
|
||||
this.$modal.msgWarning(this.$t('device.device-edit.148398-72'));
|
||||
return;
|
||||
}
|
||||
this.$refs.subDeviceCreate.open(this.device.productId);
|
||||
},
|
||||
/** 删除按钮操作 */
|
||||
handleDelete(row) {
|
||||
const deviceIds = row.deviceId || this.ids;
|
||||
@ -195,6 +250,15 @@ export default {
|
||||
addSuccess() {
|
||||
this.getList();
|
||||
},
|
||||
/** 跳转子设备详情页 */
|
||||
goDeviceDetail(deviceId) {
|
||||
if (!deviceId) return;
|
||||
const route = this.$router.resolve({
|
||||
path: '/iot/device-edit',
|
||||
query: { deviceId: deviceId, isSubDev: 0, pageNum: 1, activeName: 'basic' },
|
||||
});
|
||||
window.open(route.href, '_blank');
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
355
yack-web/src/views/iot/device/sub-device-create.vue
Normal file
355
yack-web/src/views/iot/device/sub-device-create.vue
Normal file
@ -0,0 +1,355 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
:title="$t('device.sub-device-create.title')"
|
||||
:visible.sync="visible"
|
||||
width="720px"
|
||||
append-to-body
|
||||
:close-on-click-modal="false"
|
||||
@close="handleClose"
|
||||
>
|
||||
<div v-loading="loading" class="sub-device-create">
|
||||
<div class="section-title">
|
||||
<i class="el-icon-s-grid" style="margin-right: 4px"></i>
|
||||
{{ $t('device.sub-device-create.productListTitle') }}
|
||||
</div>
|
||||
|
||||
<div v-if="subProductList.length === 0 && !loading" class="empty-tip">
|
||||
{{ $t('device.sub-device-create.noSubProducts') }}
|
||||
</div>
|
||||
|
||||
<el-collapse v-model="activeNames">
|
||||
<el-collapse-item
|
||||
v-for="(item, index) in subProductList"
|
||||
:key="item.subProductId"
|
||||
:name="index"
|
||||
>
|
||||
<template slot="title">
|
||||
<div class="product-header">
|
||||
<i class="el-icon-cpu" style="color: #409eff; margin-right: 6px"></i>
|
||||
<span class="product-name">{{ item.subProductName }}</span>
|
||||
<el-tag v-if="item.enabled" size="mini" type="success" effect="plain" style="margin-left: 8px">
|
||||
{{ $t('device.sub-device-create.enabled') }}
|
||||
</el-tag>
|
||||
<el-tag v-if="item.enabled" size="mini" effect="plain" style="margin-left: 4px">
|
||||
x{{ item.devices.length }}
|
||||
</el-tag>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="product-body">
|
||||
<el-row :gutter="24" class="config-row">
|
||||
<el-col :span="10">
|
||||
<div class="config-item">
|
||||
<span class="config-label">{{ $t('device.sub-device-create.deviceConfig') }}</span>
|
||||
<el-switch v-model="item.enabled" active-color="#409eff" @change="onEnableChange(index, $event)" />
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="14">
|
||||
<div class="config-item">
|
||||
<span class="config-label">{{ $t('device.sub-device-create.deviceCount') }}</span>
|
||||
<el-input-number
|
||||
v-model="item.count"
|
||||
:min="1"
|
||||
:max="10"
|
||||
:disabled="!item.enabled"
|
||||
size="small"
|
||||
@change="onCountChange(index)"
|
||||
@input.native="onCountInput(index, $event)"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
|
||||
<div v-if="item.enabled && item.devices.length > 0" class="device-list-section">
|
||||
<div class="device-list-title">
|
||||
<i class="el-icon-menu" style="margin-right: 4px; color: #409eff"></i>
|
||||
{{ $t('device.sub-device-create.deviceListTitle') }}
|
||||
</div>
|
||||
<el-collapse v-model="item.expandedDevices" class="device-collapse">
|
||||
<el-collapse-item
|
||||
v-for="(dev, dIdx) in item.devices"
|
||||
:key="dIdx"
|
||||
:name="dIdx"
|
||||
>
|
||||
<template slot="title">
|
||||
<div class="device-header">
|
||||
<i class="el-icon-monitor" style="color: #909399; margin-right: 6px"></i>
|
||||
<span>{{ dev.deviceName || ($t('device.sub-device-create.devicePrefix') + (dIdx + 1)) }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<div class="device-form">
|
||||
<el-row :gutter="16">
|
||||
<el-col :span="14">
|
||||
<div class="device-form-item">
|
||||
<span class="device-form-label">{{ $t('device.sub-device-create.deviceNameLabel') }}</span>
|
||||
<el-input
|
||||
v-model="dev.deviceName"
|
||||
size="small"
|
||||
:placeholder="$t('device.sub-device-create.deviceNamePlaceholder')"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
<el-col :span="10">
|
||||
<div class="device-form-item">
|
||||
<span class="device-form-label">{{ $t('device.sub-device-create.slaveIdLabel') }}</span>
|
||||
<el-input-number
|
||||
v-model="dev.slaveId"
|
||||
:min="1"
|
||||
:max="255"
|
||||
size="small"
|
||||
:placeholder="$t('device.sub-device-create.slaveIdPlaceholder')"
|
||||
controls-position="right"
|
||||
/>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
</div>
|
||||
</div>
|
||||
</el-collapse-item>
|
||||
</el-collapse>
|
||||
|
||||
<div v-if="totalDeviceCount > 0" class="summary-bar">
|
||||
<i class="el-icon-info" style="color: #409eff; margin-right: 4px"></i>
|
||||
{{ $t('device.sub-device-create.summary', [enabledProductCount, totalDeviceCount]) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" :loading="submitting" :disabled="totalDeviceCount === 0" @click="handleConfirm">
|
||||
{{ $t('confirm') }}
|
||||
</el-button>
|
||||
<el-button @click="handleClose">{{ $t('cancel') }}</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { gatewayProductList, createSubDeviceByProducts } from '@/api/iot/gateway';
|
||||
|
||||
export default {
|
||||
name: 'sub-device-create',
|
||||
props: {
|
||||
gateway: {
|
||||
type: Object,
|
||||
default: null,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
visible: false,
|
||||
loading: false,
|
||||
submitting: false,
|
||||
subProductList: [],
|
||||
activeNames: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
enabledProducts() {
|
||||
return this.subProductList.filter((p) => p.enabled);
|
||||
},
|
||||
enabledProductCount() {
|
||||
return this.enabledProducts.length;
|
||||
},
|
||||
totalDeviceCount() {
|
||||
return this.enabledProducts.reduce((sum, p) => sum + (p.devices ? p.devices.length : 0), 0);
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
open(productId) {
|
||||
this.visible = true;
|
||||
this.subProductList = [];
|
||||
this.activeNames = [];
|
||||
this.getSubProducts(productId);
|
||||
},
|
||||
getSubProducts(productId) {
|
||||
this.loading = true;
|
||||
gatewayProductList({ gwProductId: productId, pageNum: 1, pageSize: 100 })
|
||||
.then((response) => {
|
||||
const rows = response.rows || [];
|
||||
this.subProductList = rows.map((item) => ({
|
||||
...item,
|
||||
enabled: false,
|
||||
count: 1,
|
||||
devices: [],
|
||||
expandedDevices: [],
|
||||
}));
|
||||
this.activeNames = rows.map((_, idx) => idx);
|
||||
})
|
||||
.finally(() => {
|
||||
this.loading = false;
|
||||
});
|
||||
},
|
||||
onEnableChange(index, val) {
|
||||
const item = this.subProductList[index];
|
||||
if (val) {
|
||||
this.rebuildDevices(index);
|
||||
} else {
|
||||
this.$set(item, 'devices', []);
|
||||
this.$set(item, 'expandedDevices', []);
|
||||
}
|
||||
},
|
||||
onCountChange(index) {
|
||||
this.rebuildDevices(index);
|
||||
},
|
||||
onCountInput(index, e) {
|
||||
this.$nextTick(() => {
|
||||
this.rebuildDevices(index);
|
||||
});
|
||||
},
|
||||
rebuildDevices(index) {
|
||||
const item = this.subProductList[index];
|
||||
if (!item.enabled) return;
|
||||
const count = Math.max(1, Math.min(10, item.count || 1));
|
||||
const baseSlaveId = item.slaveId || 1;
|
||||
const oldDevices = item.devices || [];
|
||||
const devices = [];
|
||||
for (let i = 0; i < count; i++) {
|
||||
if (i < oldDevices.length) {
|
||||
devices.push(oldDevices[i]);
|
||||
} else {
|
||||
devices.push({
|
||||
deviceName: item.subProductName + '_' + (i + 1),
|
||||
slaveId: baseSlaveId + i,
|
||||
});
|
||||
}
|
||||
}
|
||||
this.$set(item, 'devices', devices);
|
||||
this.$set(item, 'expandedDevices', devices.map((_, i) => i));
|
||||
},
|
||||
handleConfirm() {
|
||||
if (this.totalDeviceCount === 0) {
|
||||
this.$modal.msgWarning(this.$t('device.sub-device-create.selectTip'));
|
||||
return;
|
||||
}
|
||||
const items = this.enabledProducts.map((p) => ({
|
||||
subProductId: p.subProductId,
|
||||
devices: (p.devices || []).map((d) => ({
|
||||
deviceName: d.deviceName,
|
||||
slaveId: d.slaveId,
|
||||
})),
|
||||
}));
|
||||
this.submitting = true;
|
||||
createSubDeviceByProducts({
|
||||
gwDeviceId: this.gateway.gwDeviceId,
|
||||
items: items,
|
||||
})
|
||||
.then((response) => {
|
||||
this.$modal.msgSuccess(response.msg || this.$t('device.sub-device-create.success'));
|
||||
this.visible = false;
|
||||
this.$emit('addSuccess');
|
||||
})
|
||||
.finally(() => {
|
||||
this.submitting = false;
|
||||
});
|
||||
},
|
||||
handleClose() {
|
||||
this.visible = false;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.sub-device-create {
|
||||
max-height: 520px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.section-title {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
color: #303133;
|
||||
margin-bottom: 12px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.empty-tip {
|
||||
text-align: center;
|
||||
color: #909399;
|
||||
padding: 40px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
.product-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.product-name {
|
||||
color: #303133;
|
||||
}
|
||||
.product-body {
|
||||
padding: 8px 0 4px;
|
||||
}
|
||||
.config-row {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.config-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
.config-label {
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.device-list-section {
|
||||
margin-top: 8px;
|
||||
}
|
||||
.device-list-title {
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
margin-bottom: 6px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
.device-collapse {
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.device-collapse >>> .el-collapse-item__header {
|
||||
padding-left: 14px;
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
font-size: 13px;
|
||||
background: #fafafa;
|
||||
}
|
||||
.device-collapse >>> .el-collapse-item__wrap {
|
||||
border-bottom: 1px solid #f2f6fc;
|
||||
}
|
||||
.device-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 13px;
|
||||
color: #606266;
|
||||
}
|
||||
.device-form {
|
||||
padding: 12px 14px 8px;
|
||||
}
|
||||
.device-form-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.device-form-label {
|
||||
font-size: 12px;
|
||||
color: #909399;
|
||||
white-space: nowrap;
|
||||
min-width: 56px;
|
||||
}
|
||||
.summary-bar {
|
||||
margin-top: 16px;
|
||||
padding: 10px 14px;
|
||||
background: #ecf5ff;
|
||||
border-radius: 4px;
|
||||
font-size: 13px;
|
||||
color: #409eff;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
</style>
|
||||
Loading…
Reference in New Issue
Block a user