20260326:数据推送
This commit is contained in:
parent
b7e0984654
commit
c9620a344c
@ -8,9 +8,9 @@ package com.fastbee.common.enums;
|
||||
*/
|
||||
public enum DataLevelEnum {
|
||||
|
||||
GREEN(1, "✅ 正常", "#009900"),
|
||||
YELLOW(2, "⚠️ 告警", "#FF9900"),
|
||||
RED(3, "🔴 警报", "#FF0000");
|
||||
GREEN(1, "正常", "#009900"),
|
||||
YELLOW(2, "告警", "#FF9900"),
|
||||
RED(3, "警报", "#FF0000");
|
||||
|
||||
private final int code;
|
||||
private final String label;
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package com.fastbee.iot.data.push;
|
||||
|
||||
import com.alibaba.fastjson2.JSON;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.fastbee.common.constant.FastBeeConstant;
|
||||
import com.fastbee.common.core.notify.NotifySendResponse;
|
||||
import com.fastbee.common.core.notify.config.WeChatConfigParams;
|
||||
import com.fastbee.common.core.notify.msg.WechatMsgParams;
|
||||
import com.fastbee.common.core.redis.RedisCache;
|
||||
import com.fastbee.common.core.redis.RedisKeyBuilder;
|
||||
import com.fastbee.common.enums.DataLevelEnum;
|
||||
@ -11,15 +13,17 @@ import com.fastbee.common.utils.StringUtils;
|
||||
import com.fastbee.common.utils.wechat.WechatUtils;
|
||||
import com.fastbee.common.wechat.WeChatAppResult;
|
||||
import com.fastbee.iot.domain.PushModelItem;
|
||||
import com.fastbee.iot.domain.SocialUser;
|
||||
import com.fastbee.iot.domain.ThingsModelPushConfig;
|
||||
import com.fastbee.iot.model.ThingsModels.ValueItem;
|
||||
import com.fastbee.iot.service.IThingsModelPushConfigService;
|
||||
import com.fastbee.notify.domain.NotifyChannel;
|
||||
import com.fastbee.iot.service.ISocialUserService;
|
||||
import com.fastbee.notify.core.wechat.vo.TemplateDataVo;
|
||||
import com.fastbee.notify.core.wechat.vo.WeChatPublicAccountPushVO;
|
||||
import com.fastbee.notify.domain.NotifyChannel;
|
||||
import com.fastbee.notify.domain.NotifyTemplate;
|
||||
import com.fastbee.notify.service.INotifyChannelService;
|
||||
import com.fastbee.iot.service.ISocialUserService;
|
||||
import com.fastbee.iot.domain.SocialUser;
|
||||
import com.fastbee.notify.service.INotifyTemplateService;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.springframework.http.HttpEntity;
|
||||
@ -28,28 +32,31 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.util.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 物模型数据推送核心处理器
|
||||
* 读取 Redis 中设备最新上报值,按配置的阈值评估数据等级,
|
||||
* 同等级数据合并为一条微信服务号模板消息推送。
|
||||
*
|
||||
* @author fastbee
|
||||
* <p>
|
||||
* 复用"运维管理 - 通知模板"中已配置的微信公众号模板(notify_template),
|
||||
* 从中读取 templateId、content(变量)、redirectUrl 等信息。
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ThingsModelPushHandler {
|
||||
|
||||
private static final RestTemplate REST_TEMPLATE = new RestTemplate();
|
||||
private static final Pattern VAR_PATTERN = Pattern.compile("\\{\\{([^}]+)}}");
|
||||
|
||||
@Resource
|
||||
private IThingsModelPushConfigService pushConfigService;
|
||||
@Resource
|
||||
private INotifyTemplateService notifyTemplateService;
|
||||
@Resource
|
||||
private INotifyChannelService notifyChannelService;
|
||||
@Resource
|
||||
private ISocialUserService socialUserService;
|
||||
@ -57,20 +64,35 @@ public class ThingsModelPushHandler {
|
||||
private RedisCache redisCache;
|
||||
|
||||
/**
|
||||
* 按推送配置ID执行推送(读取该配置涉及设备的最新 Redis 值)
|
||||
*
|
||||
* @param config 推送配置(含 items、userIds)
|
||||
* 按推送配置执行推送
|
||||
*/
|
||||
public void doPush(ThingsModelPushConfig config) {
|
||||
if (CollectionUtils.isEmpty(config.getItems())) {
|
||||
log.warn("[Push] configId={} 没有配置物模型项,跳过", config.getId());
|
||||
return;
|
||||
}
|
||||
if (config.getDeviceId() == null || config.getDeviceId() <= 0
|
||||
|| StringUtils.isEmpty(config.getSerialNumber())) {
|
||||
log.warn("[Push] configId={} 未指定具体设备,跳过", config.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取通知渠道(公众号)
|
||||
NotifyChannel channel = notifyChannelService.selectNotifyChannelById(config.getChannelId());
|
||||
// ---------- 从通知模板获取微信配置 ----------
|
||||
NotifyTemplate notifyTemplate = notifyTemplateService.selectNotifyTemplateById(config.getTemplateId());
|
||||
if (notifyTemplate == null) {
|
||||
log.warn("[Push] configId={} 通知模板不存在 templateId={}", config.getId(), config.getTemplateId());
|
||||
return;
|
||||
}
|
||||
WechatMsgParams msgParams = JSONObject.parseObject(notifyTemplate.getMsgParams(), WechatMsgParams.class);
|
||||
if (msgParams == null || StringUtils.isEmpty(msgParams.getTemplateId())) {
|
||||
log.warn("[Push] configId={} 通知模板 msgParams 不完整", config.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// ---------- 从通知渠道获取 appId/appSecret ----------
|
||||
NotifyChannel channel = notifyChannelService.selectNotifyChannelById(notifyTemplate.getChannelId());
|
||||
if (channel == null) {
|
||||
log.warn("[Push] configId={} 渠道不存在 channelId={}", config.getId(), config.getChannelId());
|
||||
log.warn("[Push] configId={} 渠道不存在 channelId={}", config.getId(), notifyTemplate.getChannelId());
|
||||
return;
|
||||
}
|
||||
WeChatConfigParams wxConfig = JSONObject.parseObject(channel.getConfigContent(), WeChatConfigParams.class);
|
||||
@ -79,7 +101,7 @@ public class ThingsModelPushHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取 access_token
|
||||
// ---------- 获取 access_token ----------
|
||||
WeChatAppResult tokenResult = WechatUtils.getAccessToken(wxConfig.getAppId(), wxConfig.getAppSecret());
|
||||
if (tokenResult == null || StringUtils.isEmpty(tokenResult.getAccessToken())) {
|
||||
log.error("[Push] configId={} 获取微信 access_token 失败", config.getId());
|
||||
@ -87,14 +109,7 @@ public class ThingsModelPushHandler {
|
||||
}
|
||||
String pushUrl = FastBeeConstant.URL.WX_PUBLIC_ACCOUNT_TEMPLATE_SEND_URL_PREFIX + tokenResult.getAccessToken();
|
||||
|
||||
// 确定要查询的设备编号列表
|
||||
List<String> serialNumbers = resolveSerialNumbers(config);
|
||||
if (CollectionUtils.isEmpty(serialNumbers)) {
|
||||
log.warn("[Push] configId={} 没有找到对应设备,跳过", config.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取接收用户 openId 列表
|
||||
// ---------- 获取接收用户 openId ----------
|
||||
List<Long> userIds = config.getUserIds();
|
||||
if (CollectionUtils.isEmpty(userIds)) {
|
||||
log.warn("[Push] configId={} 没有配置接收用户,跳过", config.getId());
|
||||
@ -103,28 +118,31 @@ public class ThingsModelPushHandler {
|
||||
List<SocialUser> socialUsers = socialUserService.listWechatPublicAccountOpenId(
|
||||
userIds.stream().map(Object::toString).collect(Collectors.toList()));
|
||||
Map<Long, String> openIdMap = socialUsers.stream()
|
||||
.filter(u -> StringUtils.isNotEmpty(u.getOpenId()))
|
||||
.collect(Collectors.toMap(SocialUser::getSysUserId, SocialUser::getOpenId, (a, b) -> b));
|
||||
|
||||
// 对每台设备推送
|
||||
for (String serialNumber : serialNumbers) {
|
||||
pushForDevice(config, serialNumber, pushUrl, openIdMap);
|
||||
if (openIdMap.isEmpty()) {
|
||||
log.warn("[Push] configId={} 所有用户均未绑定微信公众号,跳过", config.getId());
|
||||
return;
|
||||
}
|
||||
|
||||
// ---------- 执行推送 ----------
|
||||
pushForDevice(config, msgParams, pushUrl, openIdMap);
|
||||
|
||||
// 更新最近推送时间
|
||||
pushConfigService.updateLastPushTime(config.getId());
|
||||
}
|
||||
|
||||
/**
|
||||
* 对单台设备执行推送
|
||||
* 对单台设备按等级分组推送
|
||||
*/
|
||||
private void pushForDevice(ThingsModelPushConfig config, String serialNumber,
|
||||
private void pushForDevice(ThingsModelPushConfig config, WechatMsgParams msgParams,
|
||||
String pushUrl, Map<Long, String> openIdMap) {
|
||||
String serialNumber = config.getSerialNumber().toUpperCase();
|
||||
String redisKey = RedisKeyBuilder.buildTSLVCacheKey(config.getProductId(), serialNumber);
|
||||
|
||||
// 按等级分组收集显示文本
|
||||
Map<DataLevelEnum, List<String>> levelTextMap = new LinkedHashMap<>();
|
||||
for (DataLevelEnum level : DataLevelEnum.values()) {
|
||||
levelTextMap.put(level, new ArrayList<>());
|
||||
for (DataLevelEnum lvl : DataLevelEnum.values()) {
|
||||
levelTextMap.put(lvl, new ArrayList<>());
|
||||
}
|
||||
|
||||
for (PushModelItem item : config.getItems()) {
|
||||
@ -134,8 +152,7 @@ public class ThingsModelPushHandler {
|
||||
}
|
||||
String value;
|
||||
try {
|
||||
ValueItem vi = JSON.parseObject(cacheValue, ValueItem.class);
|
||||
value = vi.getValue();
|
||||
value = JSON.parseObject(cacheValue, ValueItem.class).getValue();
|
||||
} catch (Exception e) {
|
||||
value = cacheValue;
|
||||
}
|
||||
@ -143,57 +160,69 @@ public class ThingsModelPushHandler {
|
||||
value, item.getGreenMin(), item.getGreenMax(),
|
||||
item.getYellowMin(), item.getYellowMax());
|
||||
|
||||
String displayName = StringUtils.isNotEmpty(item.getModelName()) ? item.getModelName() : item.getIdentifier();
|
||||
String text = displayName + "=" + value + (StringUtils.isNotEmpty(item.getUnit()) ? item.getUnit() : "");
|
||||
String displayName = StringUtils.isNotEmpty(item.getModelName())
|
||||
? item.getModelName() : item.getIdentifier();
|
||||
String text = displayName + "=" + value
|
||||
+ (StringUtils.isNotEmpty(item.getUnit()) ? item.getUnit() : "");
|
||||
levelTextMap.get(level).add(text);
|
||||
}
|
||||
|
||||
// 对每个非空等级发一条消息
|
||||
for (Map.Entry<DataLevelEnum, List<String>> entry : levelTextMap.entrySet()) {
|
||||
if (CollectionUtils.isEmpty(entry.getValue())) {
|
||||
continue;
|
||||
}
|
||||
DataLevelEnum level = entry.getKey();
|
||||
String dataText = String.join(";", entry.getValue());
|
||||
sendTemplateMsg(config, serialNumber, level, dataText, pushUrl, openIdMap);
|
||||
sendTemplateMsg(config, msgParams, serialNumber, level, dataText, pushUrl, openIdMap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送单条微信模板消息(带颜色标记)
|
||||
* 发送单条微信模板消息。
|
||||
* 从通知模板的 content 字段提取 {{变量.DATA}} 变量名顺序,
|
||||
* 按顺序映射:第1个→设备名称,第2个→数据等级,第3个→监测数据。
|
||||
*/
|
||||
private void sendTemplateMsg(ThingsModelPushConfig config, String serialNumber,
|
||||
DataLevelEnum level, String dataText,
|
||||
private void sendTemplateMsg(ThingsModelPushConfig config, WechatMsgParams msgParams,
|
||||
String serialNumber, DataLevelEnum level, String dataText,
|
||||
String pushUrl, Map<Long, String> openIdMap) {
|
||||
// 微信模板格式(需在公众号后台与此对应):
|
||||
// {{first.DATA}}
|
||||
// 设备编号:{{keyword1.DATA}}
|
||||
// 数据等级:{{keyword2.DATA}}
|
||||
// 监测数据:{{keyword3.DATA}}
|
||||
// {{remark.DATA}}
|
||||
Map<String, Object> data = new LinkedHashMap<>();
|
||||
data.put("first", new TemplateDataVo("设备数据监测通知", "#333333"));
|
||||
data.put("keyword1", new TemplateDataVo(serialNumber, "#333333"));
|
||||
data.put("keyword2", new TemplateDataVo(level.getLabel(), level.getColor()));
|
||||
data.put("keyword3", new TemplateDataVo(dataText, level.getColor()));
|
||||
data.put("remark", new TemplateDataVo("请及时关注设备状态", "#999999"));
|
||||
List<String> varNames = extractVarNames(msgParams.getContent());
|
||||
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||
String deviceDisplay = StringUtils.isNotEmpty(config.getDeviceName())
|
||||
? config.getDeviceName() : serialNumber;
|
||||
for (int i = 0; i < varNames.size(); i++) {
|
||||
String varName = varNames.get(i);
|
||||
if (i == 0) {
|
||||
map.put(varName, deviceDisplay);
|
||||
} else if (i == 1) {
|
||||
map.put(varName, level.getLabel());
|
||||
} else if (i == 2) {
|
||||
map.put(varName, dataText);
|
||||
} else {
|
||||
map.put(varName, "");
|
||||
}
|
||||
}
|
||||
|
||||
// 与 WeChatPushServiceImpl 保持一致的 map 迭代方式
|
||||
Map<String, Object> sendMap = new LinkedHashMap<>();
|
||||
for (Map.Entry<String, String> m : map.entrySet()) {
|
||||
sendMap.put(m.getKey(), new TemplateDataVo(m.getValue(), level.getColor()));
|
||||
}
|
||||
|
||||
for (Map.Entry<Long, String> userEntry : openIdMap.entrySet()) {
|
||||
String openId = userEntry.getValue();
|
||||
if (StringUtils.isEmpty(openId)) {
|
||||
continue;
|
||||
}
|
||||
WeChatPublicAccountPushVO vo = new WeChatPublicAccountPushVO();
|
||||
vo.setTouser(openId);
|
||||
vo.setTemplateId(config.getWxTemplateId());
|
||||
vo.setData(data);
|
||||
if (StringUtils.isNotEmpty(config.getRedirectUrl())) {
|
||||
vo.setUrl(config.getRedirectUrl());
|
||||
vo.setTemplateId(msgParams.getTemplateId());
|
||||
vo.setData(sendMap);
|
||||
if (StringUtils.isNotEmpty(msgParams.getRedirectUrl())) {
|
||||
vo.setUrl(msgParams.getRedirectUrl());
|
||||
}
|
||||
try {
|
||||
NotifySendResponse resp = doPost(JSON.toJSONString(vo), pushUrl);
|
||||
log.info("[Push] configId={} sn={} userId={} level={} status={}",
|
||||
config.getId(), serialNumber, userEntry.getKey(), level.getLabel(), resp.getStatus());
|
||||
config.getId(), serialNumber, userEntry.getKey(),
|
||||
level.getLabel(), resp.getStatus());
|
||||
} catch (Exception e) {
|
||||
log.error("[Push] configId={} sn={} userId={} 推送异常: {}",
|
||||
config.getId(), serialNumber, userEntry.getKey(), e.getMessage());
|
||||
@ -201,13 +230,25 @@ public class ThingsModelPushHandler {
|
||||
}
|
||||
}
|
||||
|
||||
private List<String> extractVarNames(String content) {
|
||||
List<String> names = new ArrayList<>();
|
||||
if (StringUtils.isEmpty(content)) {
|
||||
return names;
|
||||
}
|
||||
Matcher matcher = VAR_PATTERN.matcher(content);
|
||||
while (matcher.find()) {
|
||||
names.add(matcher.group(1).replace(".DATA", ""));
|
||||
}
|
||||
return names;
|
||||
}
|
||||
|
||||
private NotifySendResponse doPost(String json, String url) {
|
||||
NotifySendResponse resp = new NotifySendResponse();
|
||||
HttpHeaders headers = new HttpHeaders();
|
||||
headers.setContentType(MediaType.parseMediaType("application/json; charset=UTF-8"));
|
||||
headers.add("Accept", MediaType.APPLICATION_JSON.toString());
|
||||
ResponseEntity<String> result = REST_TEMPLATE.postForEntity(url,
|
||||
new HttpEntity<>(json, headers), String.class);
|
||||
ResponseEntity<String> result = REST_TEMPLATE.postForEntity(
|
||||
url, new HttpEntity<>(json, headers), String.class);
|
||||
String body = result.getBody();
|
||||
resp.setResultContent(body);
|
||||
try {
|
||||
@ -217,28 +258,4 @@ public class ThingsModelPushHandler {
|
||||
}
|
||||
return resp;
|
||||
}
|
||||
|
||||
/**
|
||||
* 确定推送涉及的设备编号列表
|
||||
* device_id=0 → 产品级,需查产品下所有设备
|
||||
* device_id>0 → 指定设备
|
||||
*/
|
||||
private List<String> resolveSerialNumbers(ThingsModelPushConfig config) {
|
||||
if (config.getDeviceId() != null && config.getDeviceId() > 0) {
|
||||
if (StringUtils.isNotEmpty(config.getSerialNumber())) {
|
||||
return Collections.singletonList(config.getSerialNumber().toUpperCase());
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
// 产品级:从 Redis 中找该产品所有已有 TSLV 缓存的设备(通过前缀扫描)
|
||||
String pattern = FastBeeConstant.REDIS.DEVICE_PRE_KEY + config.getProductId() + "_*";
|
||||
Collection<String> keys = redisCache.keys(pattern);
|
||||
if (keys == null || keys.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
String prefix = FastBeeConstant.REDIS.DEVICE_PRE_KEY + config.getProductId() + "_";
|
||||
return keys.stream()
|
||||
.map(k -> k.substring(prefix.length()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,26 +31,26 @@ public class ThingsModelPushConfig extends BaseEntity {
|
||||
@ApiModelProperty("产品ID")
|
||||
private Long productId;
|
||||
|
||||
@ApiModelProperty("产品名称(非持久化字段)")
|
||||
@ApiModelProperty("产品名称(非持久化,JOIN查询)")
|
||||
private String productName;
|
||||
|
||||
@ApiModelProperty("设备ID,0=产品级")
|
||||
@ApiModelProperty("设备ID(必须指定具体设备)")
|
||||
private Long deviceId;
|
||||
|
||||
@ApiModelProperty("设备名称(非持久化,JOIN查询)")
|
||||
private String deviceName;
|
||||
|
||||
@ApiModelProperty("设备类型:1=直连 2=网关(非持久化,JOIN查询)")
|
||||
private Integer deviceType;
|
||||
|
||||
@ApiModelProperty("设备编号")
|
||||
private String serialNumber;
|
||||
|
||||
@ApiModelProperty("通知渠道ID")
|
||||
private Long channelId;
|
||||
@ApiModelProperty("通知模板ID(关联 notify_template)")
|
||||
private Long templateId;
|
||||
|
||||
@ApiModelProperty("通知渠道名称(非持久化字段)")
|
||||
private String channelName;
|
||||
|
||||
@ApiModelProperty("微信公众号模板ID")
|
||||
private String wxTemplateId;
|
||||
|
||||
@ApiModelProperty("消息点击跳转URL")
|
||||
private String redirectUrl;
|
||||
@ApiModelProperty("通知模板名称(非持久化,JOIN查询)")
|
||||
private String templateName;
|
||||
|
||||
@ApiModelProperty("推送间隔(分钟),0=仅手动推送")
|
||||
private Integer pushInterval;
|
||||
|
||||
@ -9,11 +9,11 @@
|
||||
<result property="productId" column="product_id"/>
|
||||
<result property="productName" column="product_name"/>
|
||||
<result property="deviceId" column="device_id"/>
|
||||
<result property="deviceName" column="device_name"/>
|
||||
<result property="deviceType" column="device_type"/>
|
||||
<result property="serialNumber" column="serial_number"/>
|
||||
<result property="channelId" column="channel_id"/>
|
||||
<result property="channelName" column="channel_name"/>
|
||||
<result property="wxTemplateId" column="wx_template_id"/>
|
||||
<result property="redirectUrl" column="redirect_url"/>
|
||||
<result property="templateId" column="template_id"/>
|
||||
<result property="templateName" column="template_name"/>
|
||||
<result property="pushInterval" column="push_interval"/>
|
||||
<result property="lastPushTime" column="last_push_time"/>
|
||||
<result property="status" column="status"/>
|
||||
@ -52,36 +52,38 @@
|
||||
|
||||
<sql id="baseColumns">
|
||||
c.id, c.config_name, c.product_id, c.device_id, c.serial_number,
|
||||
c.channel_id, c.wx_template_id, c.redirect_url,
|
||||
c.push_interval, c.last_push_time, c.status,
|
||||
c.template_id, c.push_interval, c.last_push_time, c.status,
|
||||
c.tenant_id, c.tenant_name,
|
||||
c.create_by, c.create_time, c.update_by, c.update_time,
|
||||
p.product_name, nc.name AS channel_name
|
||||
p.product_name, p.device_type, d.device_name, nt.name AS template_name
|
||||
</sql>
|
||||
|
||||
<sql id="baseJoins">
|
||||
FROM iot_things_model_push_config c
|
||||
LEFT JOIN iot_device d ON c.device_id = d.device_id
|
||||
LEFT JOIN iot_product p ON c.product_id = p.product_id
|
||||
LEFT JOIN notify_template nt ON c.template_id = nt.id
|
||||
</sql>
|
||||
|
||||
<select id="selectById" resultMap="ConfigResult">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM iot_things_model_push_config c
|
||||
LEFT JOIN iot_product p ON c.product_id = p.product_id
|
||||
LEFT JOIN notify_channel nc ON c.channel_id = nc.id
|
||||
<include refid="baseJoins"/>
|
||||
WHERE c.id = #{id} AND c.del_flag = '0'
|
||||
</select>
|
||||
|
||||
<select id="selectList" resultMap="ConfigResult">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM iot_things_model_push_config c
|
||||
LEFT JOIN iot_product p ON c.product_id = p.product_id
|
||||
LEFT JOIN notify_channel nc ON c.channel_id = nc.id
|
||||
<include refid="baseJoins"/>
|
||||
WHERE c.del_flag = '0'
|
||||
<if test="configName != null and configName != ''">
|
||||
AND c.config_name LIKE CONCAT('%', #{configName}, '%')
|
||||
</if>
|
||||
<if test="productId != null">
|
||||
AND c.product_id = #{productId}
|
||||
</if>
|
||||
<if test="deviceId != null">
|
||||
AND c.device_id = #{deviceId}
|
||||
</if>
|
||||
<if test="serialNumber != null and serialNumber != ''">
|
||||
AND c.serial_number LIKE CONCAT('%', #{serialNumber}, '%')
|
||||
</if>
|
||||
<if test="status != null">
|
||||
AND c.status = #{status}
|
||||
</if>
|
||||
@ -93,30 +95,25 @@
|
||||
|
||||
<select id="selectAllEnabled" resultMap="ConfigResult">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM iot_things_model_push_config c
|
||||
LEFT JOIN iot_product p ON c.product_id = p.product_id
|
||||
LEFT JOIN notify_channel nc ON c.channel_id = nc.id
|
||||
<include refid="baseJoins"/>
|
||||
WHERE c.del_flag = '0' AND c.status = 1 AND c.push_interval > 0
|
||||
</select>
|
||||
|
||||
<select id="selectEnabledByProductAndDevice" resultMap="ConfigResult">
|
||||
SELECT <include refid="baseColumns"/>
|
||||
FROM iot_things_model_push_config c
|
||||
LEFT JOIN iot_product p ON c.product_id = p.product_id
|
||||
LEFT JOIN notify_channel nc ON c.channel_id = nc.id
|
||||
<include refid="baseJoins"/>
|
||||
WHERE c.del_flag = '0' AND c.status = 1
|
||||
AND c.product_id = #{productId}
|
||||
AND (c.device_id = 0 OR c.device_id = #{deviceId})
|
||||
AND c.device_id = #{deviceId}
|
||||
</select>
|
||||
|
||||
<insert id="insert" useGeneratedKeys="true" keyProperty="id">
|
||||
INSERT INTO iot_things_model_push_config
|
||||
(config_name, product_id, device_id, serial_number, channel_id,
|
||||
wx_template_id, redirect_url, push_interval, status,
|
||||
(config_name, product_id, device_id, serial_number, template_id,
|
||||
push_interval, status,
|
||||
tenant_id, tenant_name, create_by, create_time, del_flag)
|
||||
VALUES
|
||||
(#{configName}, #{productId}, #{deviceId}, #{serialNumber}, #{channelId},
|
||||
#{wxTemplateId}, #{redirectUrl}, #{pushInterval}, #{status},
|
||||
(#{configName}, #{productId}, #{deviceId}, #{serialNumber}, #{templateId},
|
||||
#{pushInterval}, #{status},
|
||||
#{tenantId}, #{tenantName}, #{createBy}, #{createTime}, '0')
|
||||
</insert>
|
||||
|
||||
@ -127,9 +124,7 @@
|
||||
<if test="productId != null">product_id = #{productId},</if>
|
||||
<if test="deviceId != null">device_id = #{deviceId},</if>
|
||||
<if test="serialNumber != null">serial_number = #{serialNumber},</if>
|
||||
<if test="channelId != null">channel_id = #{channelId},</if>
|
||||
<if test="wxTemplateId != null">wx_template_id = #{wxTemplateId},</if>
|
||||
<if test="redirectUrl != null">redirect_url = #{redirectUrl},</if>
|
||||
<if test="templateId != null">template_id = #{templateId},</if>
|
||||
<if test="pushInterval != null">push_interval = #{pushInterval},</if>
|
||||
<if test="status != null">status = #{status},</if>
|
||||
update_by = #{updateBy},
|
||||
|
||||
@ -7,11 +7,9 @@ CREATE TABLE IF NOT EXISTS `iot_things_model_push_config` (
|
||||
`id` BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
`config_name` VARCHAR(128) NOT NULL COMMENT '配置名称',
|
||||
`product_id` BIGINT NOT NULL COMMENT '产品ID',
|
||||
`device_id` BIGINT DEFAULT 0 COMMENT '设备ID,0=产品级(产品下所有设备)',
|
||||
`serial_number` VARCHAR(64) DEFAULT NULL COMMENT '设备编号(device_id>0时有效)',
|
||||
`channel_id` BIGINT NOT NULL COMMENT '通知渠道ID (notify_channel.id,类型为 public_account)',
|
||||
`wx_template_id` VARCHAR(128) NOT NULL COMMENT '微信公众号模板ID(在微信公众平台配置)',
|
||||
`redirect_url` VARCHAR(512) DEFAULT NULL COMMENT '消息点击跳转URL',
|
||||
`device_id` BIGINT DEFAULT NULL COMMENT '设备ID(必须指定具体设备)',
|
||||
`serial_number` VARCHAR(64) DEFAULT NULL COMMENT '设备编号',
|
||||
`template_id` BIGINT NOT NULL COMMENT '通知模板ID(关联 notify_template.id)',
|
||||
`push_interval` INT DEFAULT 60 COMMENT '推送间隔(分钟),0=仅手动推送',
|
||||
`last_push_time` DATETIME DEFAULT NULL COMMENT '最近一次推送时间',
|
||||
`status` TINYINT DEFAULT 1 COMMENT '状态:0=停用 1=启用',
|
||||
@ -23,8 +21,8 @@ CREATE TABLE IF NOT EXISTS `iot_things_model_push_config` (
|
||||
`update_time` DATETIME DEFAULT NULL COMMENT '更新时间',
|
||||
`del_flag` CHAR(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
|
||||
PRIMARY KEY (`id`),
|
||||
KEY `idx_product_id` (`product_id`),
|
||||
KEY `idx_device_id` (`device_id`)
|
||||
KEY `idx_device_id` (`device_id`),
|
||||
KEY `idx_template_id` (`template_id`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='物模型数据推送配置';
|
||||
|
||||
|
||||
@ -59,11 +57,9 @@ CREATE TABLE IF NOT EXISTS `iot_push_user` (
|
||||
-- sys_menu 菜单权限(挂在"规则引擎"父菜单 parent_id=3051 下)
|
||||
-- ============================================================
|
||||
|
||||
-- 主菜单:数据推送配置
|
||||
INSERT INTO `sys_menu` (menu_id, menu_name, parent_id, order_num, `path`, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES (3430, '数据推送配置', 3051, 9, 'pushConfig', 'iot/push/index', NULL, 1, 0, 'C', '0', '0', 'iot:push:list', 'notification', 'admin', now(), 'admin', now(), '微信服务号物模型数据推送');
|
||||
|
||||
-- 按钮权限
|
||||
INSERT INTO `sys_menu` (menu_id, menu_name, parent_id, order_num, `path`, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES (3431, '推送配置查询', 3430, 1, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:push:query', '#', 'admin', now(), 'admin', now(), '');
|
||||
|
||||
@ -78,3 +74,17 @@ VALUES (3434, '推送配置删除', 3430, 4, '#', '', NULL, 1, 0, 'F', '0', '0',
|
||||
|
||||
INSERT INTO `sys_menu` (menu_id, menu_name, parent_id, order_num, `path`, component, query, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, update_by, update_time, remark)
|
||||
VALUES (3435, '手动立即推送', 3430, 5, '#', '', NULL, 1, 0, 'F', '0', '0', 'iot:push:push', '#', 'admin', now(), 'admin', now(), '');
|
||||
|
||||
-- ============================================================
|
||||
-- 升级脚本:如已执行过旧版本建表,执行以下语句迁移字段
|
||||
-- ============================================================
|
||||
ALTER TABLE `iot_things_model_push_config`
|
||||
ADD COLUMN IF NOT EXISTS `template_id` BIGINT DEFAULT NULL COMMENT '通知模板ID(关联 notify_template.id)' AFTER `serial_number`,
|
||||
ADD INDEX IF NOT EXISTS `idx_template_id` (`template_id`);
|
||||
|
||||
-- 迁移完成后可删除旧字段(按需执行):
|
||||
-- ALTER TABLE `iot_things_model_push_config`
|
||||
-- DROP COLUMN IF EXISTS `channel_id`,
|
||||
-- DROP COLUMN IF EXISTS `wx_template_id`,
|
||||
-- DROP COLUMN IF EXISTS `wx_msg_content`,
|
||||
-- DROP COLUMN IF EXISTS `redirect_url`;
|
||||
|
||||
@ -2,14 +2,15 @@
|
||||
<div class="iot-push-config">
|
||||
<!-- 搜索栏 -->
|
||||
<el-card v-show="showSearch" style="margin-bottom: 10px">
|
||||
<el-form @submit.native.prevent :model="queryParams" ref="queryForm" :inline="true" size="mini" label-width="80px" style="margin-bottom: -18px">
|
||||
<el-form @submit.native.prevent :model="queryParams" ref="queryForm" :inline="true"
|
||||
size="mini" label-width="80px" style="margin-bottom: -18px">
|
||||
<el-form-item label="配置名称" prop="configName">
|
||||
<el-input v-model="queryParams.configName" placeholder="请输入配置名称" clearable @keyup.enter.native="handleQuery" />
|
||||
<el-input v-model="queryParams.configName" placeholder="请输入配置名称"
|
||||
clearable @keyup.enter.native="handleQuery" />
|
||||
</el-form-item>
|
||||
<el-form-item label="产品" prop="productId">
|
||||
<el-select v-model="queryParams.productId" placeholder="请选择产品" clearable filterable style="width:160px">
|
||||
<el-option v-for="p in productOptions" :key="p.productId" :label="p.productName" :value="p.productId" />
|
||||
</el-select>
|
||||
<el-form-item label="设备编号" prop="serialNumber">
|
||||
<el-input v-model="queryParams.serialNumber" placeholder="请输入设备编号"
|
||||
clearable @keyup.enter.native="handleQuery" style="width:160px" />
|
||||
</el-form-item>
|
||||
<el-form-item label="状态" prop="status">
|
||||
<el-select v-model="queryParams.status" placeholder="请选择状态" clearable style="width:100px">
|
||||
@ -27,15 +28,21 @@
|
||||
<el-card>
|
||||
<el-row :gutter="10" style="margin-bottom: 15px">
|
||||
<el-col :span="1.5">
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd" v-hasPermi="['iot:push:add']">新增</el-button>
|
||||
<el-button type="primary" plain icon="el-icon-plus" size="mini"
|
||||
@click="handleAdd" v-hasPermi="['iot:push:add']">新增</el-button>
|
||||
</el-col>
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="configList" border>
|
||||
<el-table-column label="配置名称" align="center" prop="configName" min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="产品" align="center" prop="productName" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="通知渠道" align="center" prop="channelName" min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="配置名称" align="center" prop="configName"
|
||||
min-width="140" show-overflow-tooltip />
|
||||
<el-table-column label="设备名称" align="center" prop="deviceName"
|
||||
min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="设备编号" align="center" prop="serialNumber"
|
||||
min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="通知模板" align="center" prop="templateName"
|
||||
min-width="120" show-overflow-tooltip />
|
||||
<el-table-column label="推送间隔" align="center" width="110">
|
||||
<template slot-scope="scope">
|
||||
<span v-if="scope.row.pushInterval > 0">每 {{ scope.row.pushInterval }} 分钟</span>
|
||||
@ -50,12 +57,14 @@
|
||||
<el-table-column label="状态" align="center" width="80">
|
||||
<template slot-scope="scope">
|
||||
<el-switch v-model="scope.row.status" :active-value="1" :inactive-value="0"
|
||||
@change="handleStatusChange(scope.row)" v-hasPermi="['iot:push:edit']" />
|
||||
@change="handleStatusChange(scope.row)"
|
||||
v-hasPermi="['iot:push:edit']" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" align="center" width="200" fixed="right">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" v-hasPermi="['iot:push:edit']">编辑</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-edit"
|
||||
@click="handleUpdate(scope.row)" v-hasPermi="['iot:push:edit']">编辑</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-video-play" style="color:#67C23A"
|
||||
@click="handleManualPush(scope.row)" v-hasPermi="['iot:push:push']">立即推送</el-button>
|
||||
<el-button size="mini" type="text" icon="el-icon-delete" style="color:#F56C6C"
|
||||
@ -64,13 +73,16 @@
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNum" :limit.sync="queryParams.pageSize" @pagination="getList" />
|
||||
<pagination v-show="total > 0" :total="total"
|
||||
:page.sync="queryParams.pageNum"
|
||||
:limit.sync="queryParams.pageSize"
|
||||
@pagination="getList" />
|
||||
</el-card>
|
||||
|
||||
<!-- 新增/编辑对话框 -->
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogOpen" width="900px" append-to-body
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogOpen" width="960px" append-to-body
|
||||
:close-on-click-modal="false" :close-on-press-escape="false">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="100px">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="110px">
|
||||
<el-row :gutter="20">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="配置名称" prop="configName">
|
||||
@ -78,34 +90,37 @@
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="产品" prop="productId">
|
||||
<el-select v-model="form.productId" placeholder="请选择产品" filterable style="width:100%"
|
||||
@change="onProductChange">
|
||||
<el-option v-for="p in productOptions" :key="p.productId" :label="p.productName" :value="p.productId" />
|
||||
<el-form-item label="选择设备" prop="deviceId">
|
||||
<el-select v-model="form.deviceId" placeholder="输入设备名称或编号搜索"
|
||||
filterable remote :remote-method="remoteSearchDevice"
|
||||
:loading="deviceSearchLoading" style="width:100%"
|
||||
@change="onDeviceChange">
|
||||
<el-option v-for="d in deviceOptions" :key="d.deviceId"
|
||||
:label="deviceLabel(d)" :value="d.deviceId">
|
||||
<span>{{ d.deviceName }}</span>
|
||||
<span style="color:#909399;font-size:12px;margin-left:6px">{{ d.serialNumber }}</span>
|
||||
<el-tag size="mini" :type="d.deviceType === 2 ? 'warning' : 'success'"
|
||||
style="margin-left:6px;zoom:0.85">
|
||||
{{ d.deviceType === 2 ? '网关' : '直连' }}
|
||||
</el-tag>
|
||||
</el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="通知渠道" prop="channelId">
|
||||
<el-select v-model="form.channelId" placeholder="请选择公众号渠道" filterable style="width:100%">
|
||||
<el-option v-for="c in channelOptions" :key="c.id" :label="c.name" :value="c.id" />
|
||||
<el-form-item label="通知模板" prop="templateId">
|
||||
<el-select v-model="form.templateId" placeholder="请选择通知模板(微信公众号类型)"
|
||||
filterable style="width:100%">
|
||||
<el-option v-for="t in templateOptions" :key="t.id"
|
||||
:label="t.name" :value="t.id" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="微信模板ID" prop="wxTemplateId">
|
||||
<el-input v-model="form.wxTemplateId" placeholder="在微信公众号后台获取模板ID" />
|
||||
<div class="form-tip">请在「运维管理 - 通知模板」中配置微信公众号模板</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="推送间隔(分)" prop="pushInterval">
|
||||
<el-input-number v-model="form.pushInterval" :min="0" :max="1440" style="width:100%"
|
||||
placeholder="0=仅手动推送" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="跳转URL">
|
||||
<el-input v-model="form.redirectUrl" placeholder="消息点击跳转链接(选填)" />
|
||||
<el-input-number v-model="form.pushInterval" :min="0" :max="1440"
|
||||
style="width:100%" placeholder="0=仅手动推送" />
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="24">
|
||||
@ -120,18 +135,21 @@
|
||||
|
||||
<!-- 物模型选择与阈值配置 -->
|
||||
<el-divider content-position="left">监测物模型及数据等级阈值</el-divider>
|
||||
<div style="margin-bottom:10px;color:#606266;font-size:12px">
|
||||
绿色(✅ 正常):值在 [绿下限, 绿上限] 范围内;
|
||||
黄色(⚠️ 告警):值在 [黄下限, 黄上限] 范围内;
|
||||
红色(🔴 警报):其余情况。留空表示不限。
|
||||
<div class="form-tip" style="margin-bottom:8px">
|
||||
绿色(正常):值在 [绿下限, 绿上限] 范围内;
|
||||
黄色(告警):值在 [黄下限, 黄上限] 范围内;
|
||||
红色(警报):其余情况。留空表示不限。
|
||||
</div>
|
||||
<el-table :data="form.items" size="mini" border>
|
||||
<el-table-column label="物模型" min-width="160">
|
||||
<el-table-column label="物模型属性" min-width="170">
|
||||
<template slot-scope="scope">
|
||||
<el-select v-model="scope.row.identifier" placeholder="选择物模型属性" filterable
|
||||
size="mini" @change="(v) => onModelItemChange(v, scope.row)">
|
||||
<el-select v-model="scope.row.identifier" placeholder="选择属性"
|
||||
filterable size="mini"
|
||||
:disabled="!form.productId"
|
||||
@change="(v) => onModelItemChange(v, scope.row)">
|
||||
<el-option v-for="m in thingsModelOptions" :key="m.identifier"
|
||||
:label="m.modelName + ' (' + m.identifier + ')'" :value="m.identifier" />
|
||||
:label="m.modelName + ' (' + m.identifier + ')'"
|
||||
:value="m.identifier" />
|
||||
</el-select>
|
||||
</template>
|
||||
</el-table-column>
|
||||
@ -160,19 +178,22 @@
|
||||
<el-input v-model="scope.row.yellowMax" size="mini" placeholder="不限" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="70" align="center">
|
||||
<el-table-column label="操作" width="60" align="center">
|
||||
<template slot-scope="scope">
|
||||
<el-button type="text" icon="el-icon-delete" style="color:#F56C6C"
|
||||
size="mini" @click="removeModelItem(scope.$index)" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-button size="mini" icon="el-icon-plus" style="margin-top:8px" @click="addModelItem">添加物模型</el-button>
|
||||
<el-button size="mini" icon="el-icon-plus" style="margin-top:8px"
|
||||
:disabled="!form.productId"
|
||||
@click="addModelItem">添加物模型</el-button>
|
||||
<span v-if="!form.productId" class="form-tip" style="margin-left:8px">请先选择设备</span>
|
||||
|
||||
<!-- 接收用户 -->
|
||||
<el-divider content-position="left">接收用户(需已绑定微信公众号)</el-divider>
|
||||
<el-select v-model="form.userIds" multiple filterable placeholder="请选择接收用户"
|
||||
style="width:100%" size="small">
|
||||
<el-select v-model="form.userIds" multiple filterable
|
||||
placeholder="请选择接收用户" style="width:100%" size="small">
|
||||
<el-option v-for="u in userOptions" :key="u.userId" :value="u.userId"
|
||||
:label="u.nickName + ' (' + u.userName + ')'" />
|
||||
</el-select>
|
||||
@ -191,19 +212,18 @@ import {
|
||||
listPushConfig, getPushConfig, addPushConfig, updatePushConfig,
|
||||
changePushConfigStatus, delPushConfig, manualPush
|
||||
} from '@/api/iot/pushConfig';
|
||||
import { listProduct } from '@/api/iot/product';
|
||||
import { listDevice } from '@/api/iot/device';
|
||||
import { listModel } from '@/api/iot/model';
|
||||
import { listChannel } from '@/api/notify/channel';
|
||||
import { listTemplate } from '@/api/notify/template';
|
||||
import { listUser } from '@/api/system/user';
|
||||
|
||||
const emptyForm = () => ({
|
||||
id: null,
|
||||
configName: '',
|
||||
productId: null,
|
||||
deviceId: 0,
|
||||
channelId: null,
|
||||
wxTemplateId: '',
|
||||
redirectUrl: '',
|
||||
deviceId: null,
|
||||
serialNumber: '',
|
||||
templateId: null,
|
||||
pushInterval: 60,
|
||||
status: 1,
|
||||
items: [],
|
||||
@ -218,28 +238,27 @@ export default {
|
||||
showSearch: true,
|
||||
total: 0,
|
||||
configList: [],
|
||||
queryParams: { pageNum: 1, pageSize: 10, configName: '', productId: null, status: null },
|
||||
queryParams: { pageNum: 1, pageSize: 10, configName: '', serialNumber: '', status: null },
|
||||
dialogOpen: false,
|
||||
dialogTitle: '',
|
||||
submitLoading: false,
|
||||
form: emptyForm(),
|
||||
rules: {
|
||||
configName: [{ required: true, message: '请输入配置名称', trigger: 'blur' }],
|
||||
productId: [{ required: true, message: '请选择产品', trigger: 'change' }],
|
||||
channelId: [{ required: true, message: '请选择通知渠道', trigger: 'change' }],
|
||||
wxTemplateId:[{ required: true, message: '请输入微信模板ID', trigger: 'blur' }],
|
||||
deviceId: [{ required: true, message: '请选择设备', trigger: 'change' }],
|
||||
templateId: [{ required: true, message: '请选择通知模板', trigger: 'change' }],
|
||||
pushInterval: [{ required: true, message: '请填写推送间隔', trigger: 'blur' }]
|
||||
},
|
||||
productOptions: [],
|
||||
channelOptions: [],
|
||||
deviceOptions: [],
|
||||
deviceSearchLoading: false,
|
||||
templateOptions: [],
|
||||
thingsModelOptions: [],
|
||||
userOptions: []
|
||||
};
|
||||
},
|
||||
created() {
|
||||
this.getList();
|
||||
this.loadProductOptions();
|
||||
this.loadChannelOptions();
|
||||
this.loadTemplateOptions();
|
||||
this.loadUserOptions();
|
||||
},
|
||||
methods: {
|
||||
@ -253,21 +272,54 @@ export default {
|
||||
handleQuery() { this.queryParams.pageNum = 1; this.getList(); },
|
||||
resetQuery() {
|
||||
this.resetForm('queryForm');
|
||||
this.queryParams = { pageNum: 1, pageSize: 10, configName: '', productId: null, status: null };
|
||||
this.queryParams = { pageNum: 1, pageSize: 10, configName: '', serialNumber: '', status: null };
|
||||
this.getList();
|
||||
},
|
||||
loadProductOptions() {
|
||||
listProduct({ pageNum: 1, pageSize: 999 }).then(res => {
|
||||
this.productOptions = res.rows || [];
|
||||
|
||||
remoteSearchDevice(keyword) {
|
||||
if (!keyword) return;
|
||||
this.deviceSearchLoading = true;
|
||||
const baseQuery = { pageNum: 1, pageSize: 30 };
|
||||
// 关键字同时匹配 deviceName 和 serialNumber
|
||||
const byName = listDevice({ ...baseQuery, deviceType: null, deviceName: keyword });
|
||||
const bySn = listDevice({ ...baseQuery, deviceType: null, serialNumber: keyword });
|
||||
Promise.all([byName, bySn]).then(([r1, r2]) => {
|
||||
const rows1 = r1.rows || [];
|
||||
const rows2 = r2.rows || [];
|
||||
// 合并去重(以 deviceId 为 key),只保留直连(1)和网关(2)设备
|
||||
const map = new Map();
|
||||
[...rows1, ...rows2].forEach(d => {
|
||||
if ((d.deviceType === 1 || d.deviceType === 2) && !map.has(d.deviceId)) {
|
||||
map.set(d.deviceId, d);
|
||||
}
|
||||
});
|
||||
this.deviceOptions = Array.from(map.values());
|
||||
}).finally(() => { this.deviceSearchLoading = false; });
|
||||
},
|
||||
|
||||
deviceLabel(d) {
|
||||
const typeStr = d.deviceType === 2 ? '[网关]' : '[直连]';
|
||||
return typeStr + ' ' + d.deviceName + ' (' + d.serialNumber + ')';
|
||||
},
|
||||
|
||||
onDeviceChange(deviceId) {
|
||||
const device = this.deviceOptions.find(d => d.deviceId === deviceId);
|
||||
if (!device) return;
|
||||
this.form.productId = device.productId;
|
||||
this.form.serialNumber = device.serialNumber;
|
||||
this.form.items = [];
|
||||
this.thingsModelOptions = [];
|
||||
listModel({ productId: device.productId, type: 1, pageNum: 1, pageSize: 500 }).then(res => {
|
||||
this.thingsModelOptions = res.rows || [];
|
||||
});
|
||||
},
|
||||
loadChannelOptions() {
|
||||
// 只加载微信公众号渠道
|
||||
listChannel({ channelType: 'wechat', provider: 'public_account', pageNum: 1, pageSize: 100 }).then(res => {
|
||||
this.channelOptions = res.rows || [];
|
||||
|
||||
loadTemplateOptions() {
|
||||
listTemplate({ provider: 'public_account', pageNum: 1, pageSize: 200 }).then(res => {
|
||||
this.templateOptions = res.rows || [];
|
||||
}).catch(() => {
|
||||
listChannel({ pageNum: 1, pageSize: 100 }).then(res => {
|
||||
this.channelOptions = (res.rows || []).filter(c => c.provider === 'public_account');
|
||||
listTemplate({ pageNum: 1, pageSize: 200 }).then(res => {
|
||||
this.templateOptions = (res.rows || []).filter(t => t.provider === 'public_account');
|
||||
});
|
||||
});
|
||||
},
|
||||
@ -276,14 +328,7 @@ export default {
|
||||
this.userOptions = res.rows || [];
|
||||
});
|
||||
},
|
||||
onProductChange(productId) {
|
||||
this.thingsModelOptions = [];
|
||||
this.form.items = [];
|
||||
if (!productId) return;
|
||||
listModel({ productId, type: 1, pageNum: 1, pageSize: 500 }).then(res => {
|
||||
this.thingsModelOptions = res.rows || [];
|
||||
});
|
||||
},
|
||||
|
||||
onModelItemChange(identifier, row) {
|
||||
const model = this.thingsModelOptions.find(m => m.identifier === identifier);
|
||||
if (model) {
|
||||
@ -304,8 +349,10 @@ export default {
|
||||
removeModelItem(index) {
|
||||
this.form.items.splice(index, 1);
|
||||
},
|
||||
|
||||
handleAdd() {
|
||||
this.form = emptyForm();
|
||||
this.deviceOptions = [];
|
||||
this.thingsModelOptions = [];
|
||||
this.dialogTitle = '新增推送配置';
|
||||
this.dialogOpen = true;
|
||||
@ -319,6 +366,14 @@ export default {
|
||||
userIds: data.userIds || [],
|
||||
items: data.items || []
|
||||
};
|
||||
if (data.deviceId) {
|
||||
this.deviceOptions = [{
|
||||
deviceId: data.deviceId,
|
||||
deviceName: data.deviceName || data.serialNumber,
|
||||
serialNumber: data.serialNumber || '',
|
||||
deviceType: data.deviceType || 1
|
||||
}];
|
||||
}
|
||||
if (data.productId) {
|
||||
listModel({ productId: data.productId, type: 1, pageNum: 1, pageSize: 500 }).then(r => {
|
||||
this.thingsModelOptions = r.rows || [];
|
||||
@ -328,6 +383,7 @@ export default {
|
||||
this.dialogOpen = true;
|
||||
});
|
||||
},
|
||||
|
||||
handleStatusChange(row) {
|
||||
const text = row.status === 1 ? '启用' : '停用';
|
||||
this.$confirm(`确认${text}【${row.configName}】?`, '提示', { type: 'warning' }).then(() => {
|
||||
@ -352,6 +408,7 @@ export default {
|
||||
});
|
||||
});
|
||||
},
|
||||
|
||||
submitForm() {
|
||||
this.$refs.form.validate(valid => {
|
||||
if (!valid) return;
|
||||
@ -359,8 +416,7 @@ export default {
|
||||
this.$message.warning('请至少添加一个监测物模型');
|
||||
return;
|
||||
}
|
||||
const hasEmptyIdentifier = this.form.items.some(i => !i.identifier);
|
||||
if (hasEmptyIdentifier) {
|
||||
if (this.form.items.some(i => !i.identifier)) {
|
||||
this.$message.warning('存在未选择物模型的行,请完善');
|
||||
return;
|
||||
}
|
||||
@ -381,4 +437,9 @@ export default {
|
||||
.iot-push-config .el-divider {
|
||||
margin: 15px 0 10px;
|
||||
}
|
||||
.form-tip {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
</style>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user