设备数据流转配置完成
This commit is contained in:
parent
cb3f0c1190
commit
6873c6aa97
@ -506,6 +506,32 @@ export const BatchCreateDeviceAggregationInputSchema = {
|
||||
description: '批量创建设备信息'
|
||||
} as const;
|
||||
|
||||
export const BatchSyncDeviceFromOneNETInputSchema = {
|
||||
required: ['ioTPlatform', 'ioTPlatformProductId'],
|
||||
type: 'object',
|
||||
properties: {
|
||||
ioTPlatform: {
|
||||
'$ref': '#/components/schemas/IoTPlatformTypeEnum'
|
||||
},
|
||||
ioTPlatformProductId: {
|
||||
minLength: 1,
|
||||
type: 'string',
|
||||
description: '物联网平台中对应的产品Id'
|
||||
},
|
||||
deviceAddresses: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
description: `要同步的设备地址列表(可选)。指定时只处理选中的设备,不按同步状态过滤;
|
||||
未指定则同步该产品下所有 IsPlatformPushSuccess == false 的设备。`,
|
||||
nullable: true
|
||||
}
|
||||
},
|
||||
additionalProperties: false,
|
||||
description: '从OneNET批量查询设备并同步本地'
|
||||
} as const;
|
||||
|
||||
export const BatchUpdateStatusInputSchema = {
|
||||
required: ['ids', 'upgradeStatus'],
|
||||
type: 'object',
|
||||
@ -1992,6 +2018,56 @@ export const DataEncryptionEnumSchema = {
|
||||
description: '数据加密方式'
|
||||
} as const;
|
||||
|
||||
export const DataPushDataTypeEnumSchema = {
|
||||
enum: ['Property', 'Event', 'Status'],
|
||||
type: 'string',
|
||||
description: '数据推送的数据类型枚举(区分推送规则作用于哪一类上报数据)'
|
||||
} as const;
|
||||
|
||||
export const DataPushRuleSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
ruleName: {
|
||||
type: 'string',
|
||||
description: '规则备注名,便于管理(可空)',
|
||||
nullable: true
|
||||
},
|
||||
enabled: {
|
||||
type: 'boolean',
|
||||
description: '是否启用该条规则'
|
||||
},
|
||||
dataType: {
|
||||
'$ref': '#/components/schemas/DataPushDataTypeEnum'
|
||||
},
|
||||
includeNames: {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'string'
|
||||
},
|
||||
description: `【冗余保留,暂未启用】更细粒度的白名单(属性→字段名;事件/状态→事件名)。
|
||||
当前推送粒度只到"属性/事件/状态"三类,整类整包推送;本字段留待后续细化,前端不填即可(留空 = 该数据类型整类整包推)。`,
|
||||
nullable: true
|
||||
},
|
||||
pushType: {
|
||||
'$ref': '#/components/schemas/DataPushTypeEnum'
|
||||
},
|
||||
pushServer: {
|
||||
type: 'string',
|
||||
description: '推送服务地址,例如 HTTP:http://192.168.1.1:8080,Redis:192.168.1.1:6379',
|
||||
nullable: true
|
||||
},
|
||||
pushInfo: {
|
||||
type: 'string',
|
||||
description: '推送信息:HTTP 为推送路径,Redis 为主题或 key',
|
||||
nullable: true
|
||||
}
|
||||
},
|
||||
additionalProperties: false,
|
||||
description: `数据推送规则:一条规则 = "数据类型 + 目的地"。
|
||||
粒度仅到属性/事件/状态三类,整类推或不推;同一数据类型的多个目的地用多条规则表达,天然支持分流。
|
||||
供产品实体、设备实体、DeviceCacheInfos、Application DTO 共用(以 JSON 序列化存储)。`
|
||||
} as const;
|
||||
|
||||
export const DataPushTypeEnumSchema = {
|
||||
enum: ['HttpPost', 'NormalReisSubscription', 'ReliableReisSubscription', 'RedisListPush'],
|
||||
type: 'string',
|
||||
@ -2209,6 +2285,35 @@ export const DeviceCommandForApiInputSchema = {
|
||||
description: '设备命令'
|
||||
} as const;
|
||||
|
||||
export const DeviceDataFlowConfigInputSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: '设备数据ID',
|
||||
format: 'uuid'
|
||||
},
|
||||
dataPushOverride: {
|
||||
type: 'boolean',
|
||||
description: '是否覆盖产品级推送配置:true 用本设备的推送配置,false 继承所属产品配置'
|
||||
},
|
||||
isNeedPushData: {
|
||||
type: 'boolean',
|
||||
description: '是否需要推送数据(设备级开关,仅 JiShe.ServicePro.DeviceManagement.DeviceInfos.Dto.DeviceDataFlowConfigInput.DataPushOverride 为 true 时生效)'
|
||||
},
|
||||
dataPushRules: {
|
||||
type: 'array',
|
||||
items: {
|
||||
'$ref': '#/components/schemas/DataPushRule'
|
||||
},
|
||||
description: '数据推送规则集合,一条规则 = "数据类型 + 白名单 + 目的地",仅 JiShe.ServicePro.DeviceManagement.DeviceInfos.Dto.DeviceDataFlowConfigInput.DataPushOverride 为 true 时生效',
|
||||
nullable: true
|
||||
}
|
||||
},
|
||||
additionalProperties: false,
|
||||
description: '设备数据流转配置输入参数(设备级覆盖产品配置)'
|
||||
} as const;
|
||||
|
||||
export const DeviceEncryptionMethodEnumSchema = {
|
||||
enum: ['Unspecified', 'MD5', 'SHA1', 'SHA256'],
|
||||
type: 'string',
|
||||
@ -2665,6 +2770,22 @@ export const DeviceManagementInfoDtoSchema = {
|
||||
type: 'string',
|
||||
description: '最后主站地址',
|
||||
nullable: true
|
||||
},
|
||||
dataPushOverride: {
|
||||
type: 'boolean',
|
||||
description: '是否覆盖产品级推送配置:true 用本设备的推送配置,false 继承所属产品配置'
|
||||
},
|
||||
isNeedPushData: {
|
||||
type: 'boolean',
|
||||
description: '是否需要推送数据(设备级开关,仅 JiShe.ServicePro.DeviceManagement.DeviceInfos.DeviceManagementInfoDto.DataPushOverride 为 true 时生效)'
|
||||
},
|
||||
dataPushRules: {
|
||||
type: 'array',
|
||||
items: {
|
||||
'$ref': '#/components/schemas/DataPushRule'
|
||||
},
|
||||
description: '数据推送规则集合,仅 JiShe.ServicePro.DeviceManagement.DeviceInfos.DeviceManagementInfoDto.DataPushOverride 为 true 时生效',
|
||||
nullable: true
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
@ -7764,19 +7885,14 @@ export const OneNETDataFlowConfigInputSchema = {
|
||||
},
|
||||
isNeedPushData: {
|
||||
type: 'boolean',
|
||||
description: '是否需要推送数据'
|
||||
description: '是否需要推送数据(产品级总开关)'
|
||||
},
|
||||
dataPushType: {
|
||||
'$ref': '#/components/schemas/DataPushTypeEnum'
|
||||
},
|
||||
dataPushServer: {
|
||||
type: 'string',
|
||||
description: '数据推送服务地址信息,例如HTTP地址:http://192.168.1.1:8080,Redis订阅地址:192.168.1.1:6379',
|
||||
nullable: true
|
||||
},
|
||||
dataPushInfo: {
|
||||
type: 'string',
|
||||
description: '数据推送信息,HTTP推送就是推送路径,Redis推送主题或者key',
|
||||
dataPushRules: {
|
||||
type: 'array',
|
||||
items: {
|
||||
'$ref': '#/components/schemas/DataPushRule'
|
||||
},
|
||||
description: '数据推送规则集合,一条规则 = "数据类型 + 白名单 + 目的地",区分属性/事件/状态并支持分流',
|
||||
nullable: true
|
||||
}
|
||||
},
|
||||
@ -7986,19 +8102,14 @@ export const OneNETProductInfoDtoSchema = {
|
||||
},
|
||||
isNeedPushData: {
|
||||
type: 'boolean',
|
||||
description: '是否需要推送数据'
|
||||
description: '是否需要推送数据(产品级总开关)'
|
||||
},
|
||||
dataPushType: {
|
||||
'$ref': '#/components/schemas/DataPushTypeEnum'
|
||||
},
|
||||
dataPushServer: {
|
||||
type: 'string',
|
||||
description: '数据推送服务地址信息,例如HTTP地址:http://192.168.1.1:8080,Redis订阅地址:192.168.1.1:6379',
|
||||
nullable: true
|
||||
},
|
||||
dataPushInfo: {
|
||||
type: 'string',
|
||||
description: '数据推送信息,HTTP推送就是推送路径,Redis推送主题或者key',
|
||||
dataPushRules: {
|
||||
type: 'array',
|
||||
items: {
|
||||
'$ref': '#/components/schemas/DataPushRule'
|
||||
},
|
||||
description: '数据推送规则集合,区分属性/事件/状态 + 白名单 + 目的地(与实体同为 List<DataPushRule>,Adapt 直接映射)',
|
||||
nullable: true
|
||||
}
|
||||
},
|
||||
@ -8464,6 +8575,11 @@ export const PageDeviceInputSchema = {
|
||||
},
|
||||
description: '设备地址列表(批量查询)',
|
||||
nullable: true
|
||||
},
|
||||
isPlatformPushSuccess: {
|
||||
type: 'boolean',
|
||||
description: '平台推送是否成功(null=不过滤,true=已同步,false=未同步)',
|
||||
nullable: true
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -195,19 +195,17 @@ export type BatchCreateDeviceAggregationInput = {
|
||||
};
|
||||
|
||||
/**
|
||||
* 从OneNET批量查询设备信息并同步更新本地
|
||||
* 从OneNET批量查询设备并同步本地
|
||||
*/
|
||||
export type BatchSyncDeviceFromOneNETInput = {
|
||||
/**
|
||||
* 物联网平台类型
|
||||
*/
|
||||
ioTPlatform: IoTPlatformTypeEnum;
|
||||
/**
|
||||
* 物联网平台中对应的产品Id
|
||||
*/
|
||||
ioTPlatformProductId: string;
|
||||
/**
|
||||
* 要同步的设备地址列表(可选)
|
||||
* 要同步的设备地址列表(可选)。指定时只处理选中的设备,不按同步状态过滤;
|
||||
* 未指定则同步该产品下所有 IsPlatformPushSuccess == false 的设备。
|
||||
*/
|
||||
deviceAddresses?: Array<(string)> | null;
|
||||
};
|
||||
@ -1128,6 +1126,42 @@ export type DataDictionaryDetailInfoDto = {
|
||||
*/
|
||||
export type DataEncryptionEnum = 'SM1' | 'SM2' | 'SM4' | 'DTLS' | 'Plaintext' | 'QuantumEncryption';
|
||||
|
||||
/**
|
||||
* 数据推送的数据类型枚举(区分推送规则作用于哪一类上报数据)
|
||||
*/
|
||||
export type DataPushDataTypeEnum = 'Property' | 'Event' | 'Status';
|
||||
|
||||
/**
|
||||
* 数据推送规则:一条规则 = "数据类型 + 目的地"。
|
||||
* 粒度仅到属性/事件/状态三类,整类推或不推;同一数据类型的多个目的地用多条规则表达,天然支持分流。
|
||||
* 供产品实体、设备实体、DeviceCacheInfos、Application DTO 共用(以 JSON 序列化存储)。
|
||||
*/
|
||||
export type DataPushRule = {
|
||||
/**
|
||||
* 规则备注名,便于管理(可空)
|
||||
*/
|
||||
ruleName?: (string) | null;
|
||||
/**
|
||||
* 是否启用该条规则
|
||||
*/
|
||||
enabled?: boolean;
|
||||
dataType?: DataPushDataTypeEnum;
|
||||
/**
|
||||
* 【冗余保留,暂未启用】更细粒度的白名单(属性→字段名;事件/状态→事件名)。
|
||||
* 当前推送粒度只到"属性/事件/状态"三类,整类整包推送;本字段留待后续细化,前端不填即可(留空 = 该数据类型整类整包推)。
|
||||
*/
|
||||
includeNames?: Array<(string)> | null;
|
||||
pushType?: DataPushTypeEnum;
|
||||
/**
|
||||
* 推送服务地址,例如 HTTP:http://192.168.1.1:8080,Redis:192.168.1.1:6379
|
||||
*/
|
||||
pushServer?: (string) | null;
|
||||
/**
|
||||
* 推送信息:HTTP 为推送路径,Redis 为主题或 key
|
||||
*/
|
||||
pushInfo?: (string) | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 数据推送类型枚举
|
||||
*/
|
||||
@ -1253,6 +1287,28 @@ export type DeviceCommandForApiInput = {
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* 设备数据流转配置输入参数(设备级覆盖产品配置)
|
||||
*/
|
||||
export type DeviceDataFlowConfigInput = {
|
||||
/**
|
||||
* 设备数据ID
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* 是否覆盖产品级推送配置:true 用本设备的推送配置,false 继承所属产品配置
|
||||
*/
|
||||
dataPushOverride?: boolean;
|
||||
/**
|
||||
* 是否需要推送数据(设备级开关,仅 JiShe.ServicePro.DeviceManagement.DeviceInfos.Dto.DeviceDataFlowConfigInput.DataPushOverride 为 true 时生效)
|
||||
*/
|
||||
isNeedPushData?: boolean;
|
||||
/**
|
||||
* 数据推送规则集合,一条规则 = "数据类型 + 白名单 + 目的地",仅 JiShe.ServicePro.DeviceManagement.DeviceInfos.Dto.DeviceDataFlowConfigInput.DataPushOverride 为 true 时生效
|
||||
*/
|
||||
dataPushRules?: Array<DataPushRule> | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 设备鉴权加密方式
|
||||
*/
|
||||
@ -1537,6 +1593,18 @@ export type DeviceManagementInfoDto = {
|
||||
* 最后主站地址
|
||||
*/
|
||||
finalMainStation?: (string) | null;
|
||||
/**
|
||||
* 是否覆盖产品级推送配置:true 用本设备的推送配置,false 继承所属产品配置
|
||||
*/
|
||||
dataPushOverride?: boolean;
|
||||
/**
|
||||
* 是否需要推送数据(设备级开关,仅 JiShe.ServicePro.DeviceManagement.DeviceInfos.DeviceManagementInfoDto.DataPushOverride 为 true 时生效)
|
||||
*/
|
||||
isNeedPushData?: boolean;
|
||||
/**
|
||||
* 数据推送规则集合,仅 JiShe.ServicePro.DeviceManagement.DeviceInfos.DeviceManagementInfoDto.DataPushOverride 为 true 时生效
|
||||
*/
|
||||
dataPushRules?: Array<DataPushRule> | null;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -4314,18 +4382,13 @@ export type OneNETDataFlowConfigInput = {
|
||||
*/
|
||||
id?: string;
|
||||
/**
|
||||
* 是否需要推送数据
|
||||
* 是否需要推送数据(产品级总开关)
|
||||
*/
|
||||
isNeedPushData?: boolean;
|
||||
dataPushType?: DataPushTypeEnum;
|
||||
/**
|
||||
* 数据推送服务地址信息,例如HTTP地址:http://192.168.1.1:8080,Redis订阅地址:192.168.1.1:6379
|
||||
* 数据推送规则集合,一条规则 = "数据类型 + 白名单 + 目的地",区分属性/事件/状态并支持分流
|
||||
*/
|
||||
dataPushServer?: (string) | null;
|
||||
/**
|
||||
* 数据推送信息,HTTP推送就是推送路径,Redis推送主题或者key
|
||||
*/
|
||||
dataPushInfo?: (string) | null;
|
||||
dataPushRules?: Array<DataPushRule> | null;
|
||||
};
|
||||
|
||||
export type OneNETProductInfoDto = {
|
||||
@ -4462,18 +4525,13 @@ export type OneNETProductInfoDto = {
|
||||
*/
|
||||
thingModelInfos?: (string) | null;
|
||||
/**
|
||||
* 是否需要推送数据
|
||||
* 是否需要推送数据(产品级总开关)
|
||||
*/
|
||||
isNeedPushData?: boolean;
|
||||
dataPushType?: DataPushTypeEnum;
|
||||
/**
|
||||
* 数据推送服务地址信息,例如HTTP地址:http://192.168.1.1:8080,Redis订阅地址:192.168.1.1:6379
|
||||
* 数据推送规则集合,区分属性/事件/状态 + 白名单 + 目的地(与实体同为 List<DataPushRule>,Adapt 直接映射)
|
||||
*/
|
||||
dataPushServer?: (string) | null;
|
||||
/**
|
||||
* 数据推送信息,HTTP推送就是推送路径,Redis推送主题或者key
|
||||
*/
|
||||
dataPushInfo?: (string) | null;
|
||||
dataPushRules?: Array<DataPushRule> | null;
|
||||
};
|
||||
|
||||
export type OneNETProductInfoDtoPagedResultDto = {
|
||||
@ -4737,7 +4795,7 @@ export type PageDeviceInput = {
|
||||
/**
|
||||
* 平台推送是否成功(null=不过滤,true=已同步,false=未同步)
|
||||
*/
|
||||
isPlatformPushSuccess?: boolean;
|
||||
isPlatformPushSuccess?: (boolean) | null;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -7238,13 +7296,15 @@ export type PostAggregationDeviceBatchCreateAsyncResponse = (boolean);
|
||||
|
||||
export type PostAggregationDeviceBatchCreateAsyncError = unknown;
|
||||
|
||||
export type PostAggregationDeviceBatchSyncFromOneNETAsyncData = {
|
||||
body?: BatchSyncDeviceFromOneNETInput;
|
||||
export type PostAggregationDeviceBatchSyncDeviceFromOneNetAsyncData = {
|
||||
query?: {
|
||||
input?: BatchSyncDeviceFromOneNETInput;
|
||||
};
|
||||
};
|
||||
|
||||
export type PostAggregationDeviceBatchSyncFromOneNETAsyncResponse = (boolean);
|
||||
export type PostAggregationDeviceBatchSyncDeviceFromOneNetAsyncResponse = (boolean);
|
||||
|
||||
export type PostAggregationDeviceBatchSyncFromOneNETAsyncError = unknown;
|
||||
export type PostAggregationDeviceBatchSyncDeviceFromOneNetAsyncError = unknown;
|
||||
|
||||
export type PostAggregationDeviceDeleteAsyncData = {
|
||||
query?: {
|
||||
@ -7480,6 +7540,16 @@ export type PostDeviceInfoPageResponse = (DeviceManagementInfoDtoPagedResultDto)
|
||||
|
||||
export type PostDeviceInfoPageError = unknown;
|
||||
|
||||
export type PostDeviceInfoDeviceDataFlowConfigData = {
|
||||
query?: {
|
||||
input?: DeviceDataFlowConfigInput;
|
||||
};
|
||||
};
|
||||
|
||||
export type PostDeviceInfoDeviceDataFlowConfigResponse = (DeviceManagementInfoDto);
|
||||
|
||||
export type PostDeviceInfoDeviceDataFlowConfigError = unknown;
|
||||
|
||||
export type PostDeviceInfoCacheDeviceDataToRedisResponse = (boolean);
|
||||
|
||||
export type PostDeviceInfoCacheDeviceDataToRedisError = unknown;
|
||||
|
||||
168
apps/web-antd/src/views/components/DataPushRuleEditor.vue
Normal file
168
apps/web-antd/src/views/components/DataPushRuleEditor.vue
Normal file
@ -0,0 +1,168 @@
|
||||
<script setup lang="ts">
|
||||
import type { DataPushRule } from '#/api-client';
|
||||
|
||||
import { ref, toRaw, watch } from 'vue';
|
||||
|
||||
import { Button, Input, Select, Switch } from 'ant-design-vue';
|
||||
|
||||
defineOptions({
|
||||
name: 'DataPushRuleEditor',
|
||||
});
|
||||
|
||||
const props = defineProps<{
|
||||
// 是否禁用编辑(如设备未开启"覆盖产品配置"时)
|
||||
disabled?: boolean;
|
||||
rules?: DataPushRule[] | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:rules', value: DataPushRule[]): void;
|
||||
}>();
|
||||
|
||||
// 数据类型 / 推送方式:与后端字符串枚举一一对应(生成的契约为字符串字面量)
|
||||
const dataTypeOptions = [
|
||||
{ label: '属性', value: 'Property' },
|
||||
{ label: '事件', value: 'Event' },
|
||||
{ label: '状态', value: 'Status' },
|
||||
];
|
||||
const pushTypeOptions = [
|
||||
{ label: 'HTTP POST', value: 'HttpPost' },
|
||||
{ label: 'Redis 普通订阅', value: 'NormalReisSubscription' },
|
||||
{ label: 'Redis 可靠订阅', value: 'ReliableReisSubscription' },
|
||||
{ label: 'Redis List', value: 'RedisListPush' },
|
||||
];
|
||||
|
||||
// 内部维护一份可编辑副本,变更后回抛给父级
|
||||
let seed = 0;
|
||||
// 记录最近一次抛出的数组引用:用于识别 v-model 回环,避免重建 list 导致输入框失焦
|
||||
let lastEmitted: DataPushRule[] | null = null;
|
||||
const list = ref<(DataPushRule & { _key: number })[]>([]);
|
||||
|
||||
watch(
|
||||
() => props.rules,
|
||||
(val) => {
|
||||
// 跳过自身 emit 造成的回环,仅在外部真正替换数据时重建。
|
||||
// 注意:父级用 ref 存储时会把数组包成 reactive 代理,故须用 toRaw 比较原始引用。
|
||||
if (toRaw(val) === lastEmitted) {
|
||||
return;
|
||||
}
|
||||
list.value = Array.isArray(val)
|
||||
? val.map((r) => ({ ...r, _key: seed++ }))
|
||||
: [];
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
|
||||
function emitChange() {
|
||||
// 抛出时剥离内部 _key,仅回传契约字段
|
||||
const out = list.value.map((r) => ({
|
||||
ruleName: r.ruleName,
|
||||
enabled: r.enabled,
|
||||
dataType: r.dataType,
|
||||
includeNames: r.includeNames ?? null,
|
||||
pushType: r.pushType,
|
||||
pushServer: r.pushServer,
|
||||
pushInfo: r.pushInfo,
|
||||
}));
|
||||
lastEmitted = out;
|
||||
emit('update:rules', out);
|
||||
}
|
||||
|
||||
function addRule() {
|
||||
list.value.push({
|
||||
_key: seed++,
|
||||
ruleName: '',
|
||||
enabled: true,
|
||||
dataType: 'Property',
|
||||
includeNames: null,
|
||||
pushType: 'HttpPost',
|
||||
pushServer: '',
|
||||
pushInfo: '',
|
||||
});
|
||||
emitChange();
|
||||
}
|
||||
|
||||
function removeRule(index: number) {
|
||||
list.value.splice(index, 1);
|
||||
emitChange();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<!-- 表头 -->
|
||||
<div class="mb-1 flex items-center gap-2 text-xs text-gray-500">
|
||||
<span class="w-[44px] flex-shrink-0">启用</span>
|
||||
<span class="w-[96px] flex-shrink-0">数据类型</span>
|
||||
<span class="w-[150px] flex-shrink-0">推送方式</span>
|
||||
<span class="flex-1">服务地址</span>
|
||||
<span class="flex-1">路径 / 主题 / KEY</span>
|
||||
<span class="w-[120px] flex-shrink-0">备注</span>
|
||||
<span class="w-[48px] flex-shrink-0">操作</span>
|
||||
</div>
|
||||
<!-- 规则行:用稳定 key 的 v-for,避免输入时被重建导致失焦 -->
|
||||
<div
|
||||
v-for="(item, index) in list"
|
||||
:key="item._key"
|
||||
class="mb-2 flex items-center gap-2"
|
||||
>
|
||||
<span class="w-[44px] flex-shrink-0">
|
||||
<Switch
|
||||
v-model:checked="item.enabled"
|
||||
size="small"
|
||||
:disabled="disabled"
|
||||
@change="emitChange"
|
||||
/>
|
||||
</span>
|
||||
<Select
|
||||
v-model:value="item.dataType"
|
||||
:options="dataTypeOptions"
|
||||
:disabled="disabled"
|
||||
class="w-[96px] flex-shrink-0"
|
||||
@change="emitChange"
|
||||
/>
|
||||
<Select
|
||||
v-model:value="item.pushType"
|
||||
:options="pushTypeOptions"
|
||||
:disabled="disabled"
|
||||
class="w-[150px] flex-shrink-0"
|
||||
@change="emitChange"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="item.pushServer"
|
||||
:disabled="disabled"
|
||||
class="flex-1"
|
||||
placeholder="HTTP:http://ip:port,Redis:ip:port"
|
||||
@change="emitChange"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="item.pushInfo"
|
||||
:disabled="disabled"
|
||||
class="flex-1"
|
||||
placeholder="HTTP 为路径,Redis 为主题或 key"
|
||||
@change="emitChange"
|
||||
/>
|
||||
<Input
|
||||
v-model:value="item.ruleName"
|
||||
:disabled="disabled"
|
||||
class="w-[120px] flex-shrink-0"
|
||||
placeholder="可空"
|
||||
@change="emitChange"
|
||||
/>
|
||||
<span class="w-[48px] flex-shrink-0">
|
||||
<Button
|
||||
type="link"
|
||||
danger
|
||||
size="small"
|
||||
:disabled="disabled"
|
||||
@click="removeRule(index)"
|
||||
>
|
||||
删除
|
||||
</Button>
|
||||
</span>
|
||||
</div>
|
||||
<Button type="dashed" block :disabled="disabled" @click="addRule">
|
||||
+ 添加推送规则
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { VbenFormProps } from '#/adapter/form';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { DataPushRule } from '#/api-client';
|
||||
|
||||
import type { Dayjs } from 'dayjs';
|
||||
|
||||
@ -18,6 +19,7 @@ import {
|
||||
Modal,
|
||||
Popover,
|
||||
Select,
|
||||
Switch,
|
||||
Table,
|
||||
Tag,
|
||||
} from 'ant-design-vue';
|
||||
@ -29,7 +31,7 @@ import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
getCommonGetSelectList,
|
||||
postAggregationDeviceBatchCreateAsync,
|
||||
postAggregationDeviceBatchSyncFromOneNETAsync,
|
||||
postAggregationDeviceBatchSyncDeviceFromOneNetAsync,
|
||||
postAggregationDeviceBindingDeviceThingModel,
|
||||
postAggregationDeviceCallDeviceServiceForApiAsync,
|
||||
postAggregationDeviceCreateAsync,
|
||||
@ -42,6 +44,7 @@ import {
|
||||
postAggregationDeviceRepushDeviceInfoToIoTplatform,
|
||||
postAggregationDeviceUpdateDeviceSecKeyForApiAsync,
|
||||
postDeviceInfoCacheDeviceDataToRedis,
|
||||
postDeviceInfoDeviceDataFlowConfig,
|
||||
postDeviceInfoPage,
|
||||
postIoTplatformThingModelInfoGetIoTplatformThingModelService,
|
||||
postIoTplatformThingModelInfoPageAsync,
|
||||
@ -50,6 +53,7 @@ import { Icon } from '#/components/icon';
|
||||
import { Loading } from '#/components/Loading';
|
||||
import { TableAction } from '#/components/table-action';
|
||||
import { $t } from '#/locales';
|
||||
import DataPushRuleEditor from '#/views/components/DataPushRuleEditor.vue';
|
||||
|
||||
import {
|
||||
addDeviceFormSchema,
|
||||
@ -2714,7 +2718,7 @@ async function handleBatchSync() {
|
||||
if (selectedAddresses.length > 0) {
|
||||
body.deviceAddresses = selectedAddresses;
|
||||
}
|
||||
const resp = await postAggregationDeviceBatchSyncFromOneNETAsync({ body });
|
||||
const resp = await postAggregationDeviceBatchSyncDeviceFromOneNetAsync({ body });
|
||||
if (resp.data) {
|
||||
// 后端已在后台逐个处理,接口不等待执行完毕,友好提示稍后刷新查看结果
|
||||
Message.success(
|
||||
@ -3047,6 +3051,82 @@ const toolbarActions = computed(() => [
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
// ============ 设备数据流转配置(设备级覆盖产品配置) ============
|
||||
const dataFlowRow = ref<Record<string, any>>({});
|
||||
const dataFlowOverride = ref(false);
|
||||
const dataFlowNeedPush = ref(false);
|
||||
const dataFlowRules = ref<DataPushRule[]>([]);
|
||||
|
||||
// 打开设备数据流转配置弹窗
|
||||
function openDataFlowModal(record: any) {
|
||||
if (!record.id) {
|
||||
Message.error('设备数据ID不存在,无法进行数据流转配置');
|
||||
return;
|
||||
}
|
||||
dataFlowRow.value = record;
|
||||
dataFlowOverride.value = record.dataPushOverride ?? false;
|
||||
dataFlowNeedPush.value = record.isNeedPushData ?? false;
|
||||
dataFlowRules.value = Array.isArray(record.dataPushRules)
|
||||
? record.dataPushRules
|
||||
: [];
|
||||
deviceDataFlowModalApi.open();
|
||||
}
|
||||
|
||||
// 提交设备数据流转配置
|
||||
async function submitDeviceDataFlowConfig() {
|
||||
if (!dataFlowRow.value.id) {
|
||||
Message.error('设备数据ID不存在,无法进行数据流转配置');
|
||||
return;
|
||||
}
|
||||
const override = dataFlowOverride.value;
|
||||
const needPush = dataFlowNeedPush.value;
|
||||
const rules = dataFlowRules.value ?? [];
|
||||
|
||||
// 仅在开启覆盖且需要推送时校验规则
|
||||
if (override && needPush) {
|
||||
if (rules.length === 0) {
|
||||
Message.error('请至少添加一条推送规则');
|
||||
return;
|
||||
}
|
||||
const invalid = rules.some(
|
||||
(r) => r.enabled && (!r.pushServer || !r.pushInfo),
|
||||
);
|
||||
if (invalid) {
|
||||
Message.error('启用的推送规则必须填写服务地址与推送路径/KEY');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await postDeviceInfoDeviceDataFlowConfig({
|
||||
body: {
|
||||
id: dataFlowRow.value.id,
|
||||
dataPushOverride: override,
|
||||
isNeedPushData: needPush,
|
||||
// 不覆盖时清空设备级规则,运行时将继承所属产品配置
|
||||
dataPushRules: override ? rules : [],
|
||||
},
|
||||
});
|
||||
if (resp.data) {
|
||||
Message.success('数据流转配置成功');
|
||||
deviceDataFlowModalApi.close();
|
||||
gridApi.reload();
|
||||
} else {
|
||||
Message.error('数据流转配置失败');
|
||||
}
|
||||
} catch {
|
||||
Message.error('数据流转配置失败');
|
||||
}
|
||||
}
|
||||
|
||||
const [DeviceDataFlowModal, deviceDataFlowModalApi] = useVbenModal({
|
||||
draggable: true,
|
||||
footer: true,
|
||||
showCancelButton: true,
|
||||
showConfirmButton: true,
|
||||
onConfirm: submitDeviceDataFlowConfig,
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -3163,6 +3243,10 @@ const toolbarActions = computed(() => [
|
||||
style="padding: 4px 8px; text-align: left">
|
||||
重推设备信息
|
||||
</Button>
|
||||
<Button type="text" size="small" @click="openDataFlowModal.bind(null, row)()"
|
||||
style="padding: 4px 8px; text-align: left">
|
||||
数据流转配置
|
||||
</Button>
|
||||
<Button type="text" size="small" @click="onDel.bind(null, row)()"
|
||||
style="padding: 4px 8px; color: #ff4d4f; text-align: left">
|
||||
{{ $t('common.delete') }}
|
||||
@ -3181,6 +3265,37 @@ const toolbarActions = computed(() => [
|
||||
<UserModal :title="editRow.id ? $t('common.edit') : $t('common.add')" class="w-[800px]">
|
||||
<component :is="editRow.id ? EditForm : AddForm" />
|
||||
</UserModal>
|
||||
<DeviceDataFlowModal
|
||||
:title="`数据流转配置 - ${dataFlowRow.deviceName || dataFlowRow.deviceAddress || ''}`"
|
||||
class="w-[900px]"
|
||||
>
|
||||
<div class="space-y-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-24 flex-shrink-0 text-sm text-gray-600">覆盖产品配置</span>
|
||||
<Switch
|
||||
v-model:checked="dataFlowOverride"
|
||||
checked-children="是"
|
||||
un-checked-children="否"
|
||||
/>
|
||||
<span class="text-xs text-gray-400">
|
||||
开启后使用本设备的推送配置,关闭则继承所属产品配置
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-24 flex-shrink-0 text-sm text-gray-600">是否推送数据</span>
|
||||
<Switch
|
||||
v-model:checked="dataFlowNeedPush"
|
||||
:disabled="!dataFlowOverride"
|
||||
checked-children="是"
|
||||
un-checked-children="否"
|
||||
/>
|
||||
</div>
|
||||
<DataPushRuleEditor
|
||||
v-model:rules="dataFlowRules"
|
||||
:disabled="!dataFlowOverride"
|
||||
/>
|
||||
</div>
|
||||
</DeviceDataFlowModal>
|
||||
<CommandModal
|
||||
:title="`设备操作 - ${commandRow.deviceName || commandRow.deviceAddress || ''}`"
|
||||
class="w-[1200px]"
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import type { VbenFormProps } from '#/adapter/form';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
import type { DataPushRule } from '#/api-client';
|
||||
|
||||
import { h, nextTick, ref } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
@ -12,7 +13,6 @@ import { message as Message, Tag } from 'ant-design-vue';
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
getCommonGetSelectList,
|
||||
postAggregationIoTplatformUpdateIoTplatformProductThingModelInfoAsync,
|
||||
postFilesDownload,
|
||||
postFilesUpload,
|
||||
@ -25,6 +25,7 @@ import {
|
||||
} from '#/api-client';
|
||||
import { TableAction } from '#/components/table-action';
|
||||
import { $t } from '#/locales';
|
||||
import DataPushRuleEditor from '#/views/components/DataPushRuleEditor.vue';
|
||||
|
||||
import {
|
||||
addProductFormSchema,
|
||||
@ -76,6 +77,8 @@ const gridOptions: VxeGridProps<any> = {
|
||||
const [Grid, gridApi] = useVbenVxeGrid({ formOptions, gridOptions });
|
||||
|
||||
const editRow: Record<string, any> = ref({});
|
||||
// 产品数据流转规则集合(多规则编辑器 v-model)
|
||||
const dataFlowRules = ref<DataPushRule[]>([]);
|
||||
// 声明文件变量,用于存储选择的文件
|
||||
let selectedFile: File | null = null;
|
||||
// 物模型更新 loading 状态,使用对象存储每行的 loading 状态
|
||||
@ -178,56 +181,6 @@ const [DataFlowForm, dataFlowFormApi] = useVbenForm({
|
||||
class: 'ml-6',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'ApiSelect',
|
||||
fieldName: 'dataPushType',
|
||||
label: '数据推送类型',
|
||||
componentProps: {
|
||||
api: getCommonGetSelectList,
|
||||
params: {
|
||||
query: {
|
||||
typeName: 'DataPushTypeEnum',
|
||||
},
|
||||
},
|
||||
labelField: 'value',
|
||||
valueField: 'key',
|
||||
optionsPropName: 'options',
|
||||
immediate: true,
|
||||
allowClear: true,
|
||||
placeholder: '请选择推送类型',
|
||||
afterFetch: (res: any) => {
|
||||
let items = [];
|
||||
if (Array.isArray(res)) {
|
||||
items = res;
|
||||
} else if (res && Array.isArray(res.items)) {
|
||||
items = res.items;
|
||||
} else if (res && Array.isArray(res.data)) {
|
||||
items = res.data;
|
||||
}
|
||||
return items.map((item: any) => ({
|
||||
...item,
|
||||
key: String(item.key || item.value),
|
||||
}));
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'dataPushServer',
|
||||
label: '推送服务器地址',
|
||||
componentProps: {
|
||||
placeholder:
|
||||
'HTTP:如 http://192.168.1.1:8080,Redis:如 192.168.1.1:6379',
|
||||
},
|
||||
},
|
||||
{
|
||||
component: 'Input',
|
||||
fieldName: 'dataPushInfo',
|
||||
label: '推送路径、KEY',
|
||||
componentProps: {
|
||||
placeholder: 'HTTP 为推送路径,Redis 为主题或 key',
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
@ -411,13 +364,10 @@ async function onDataFlowConfig(record: any) {
|
||||
editRow.value = record;
|
||||
dataFlowFormApi.setValues({
|
||||
isNeedPushData: record.isNeedPushData ?? false,
|
||||
dataPushType:
|
||||
record.dataPushType !== undefined && record.dataPushType !== null
|
||||
? String(record.dataPushType)
|
||||
: undefined,
|
||||
dataPushServer: record.dataPushServer,
|
||||
dataPushInfo: record.dataPushInfo,
|
||||
});
|
||||
dataFlowRules.value = Array.isArray(record.dataPushRules)
|
||||
? record.dataPushRules
|
||||
: [];
|
||||
dataFlowModalApi.open();
|
||||
}
|
||||
|
||||
@ -428,23 +378,19 @@ async function submitDataFlowConfig() {
|
||||
}
|
||||
|
||||
const values = await dataFlowFormApi.getValues();
|
||||
const rules = dataFlowRules.value ?? [];
|
||||
|
||||
// 当需要推送数据时,校验其他字段必填
|
||||
// 开启推送时校验:至少一条规则,且启用的规则必须填服务地址与推送信息
|
||||
if (values.isNeedPushData) {
|
||||
if (
|
||||
values.dataPushType === undefined ||
|
||||
values.dataPushType === null ||
|
||||
values.dataPushType === ''
|
||||
) {
|
||||
Message.error('请选择数据推送类型');
|
||||
if (rules.length === 0) {
|
||||
Message.error('请至少添加一条推送规则');
|
||||
return;
|
||||
}
|
||||
if (!values.dataPushServer) {
|
||||
Message.error('请输入推送服务器地址');
|
||||
return;
|
||||
}
|
||||
if (!values.dataPushInfo) {
|
||||
Message.error('请输入推送路径或 KEY');
|
||||
const invalid = rules.some(
|
||||
(r) => r.enabled && (!r.pushServer || !r.pushInfo),
|
||||
);
|
||||
if (invalid) {
|
||||
Message.error('启用的推送规则必须填写服务地址与推送路径/KEY');
|
||||
return;
|
||||
}
|
||||
}
|
||||
@ -454,12 +400,7 @@ async function submitDataFlowConfig() {
|
||||
body: {
|
||||
id: editRow.value.id,
|
||||
isNeedPushData: values.isNeedPushData ?? false,
|
||||
dataPushType:
|
||||
values.dataPushType !== undefined && values.dataPushType !== null
|
||||
? Number(values.dataPushType)
|
||||
: undefined,
|
||||
dataPushServer: values.dataPushServer,
|
||||
dataPushInfo: values.dataPushInfo,
|
||||
dataPushRules: rules,
|
||||
},
|
||||
});
|
||||
if (resp.data) {
|
||||
@ -655,8 +596,9 @@ async function onThingModelUpdate(record: any) {
|
||||
<component :is="editRow.id ? EditForm : AddForm" />
|
||||
</UserModal>
|
||||
|
||||
<DataFlowModal title="数据流转配置" class="w-[800px]">
|
||||
<DataFlowModal title="数据流转配置" class="w-[900px]">
|
||||
<DataFlowForm />
|
||||
<DataPushRuleEditor v-model:rules="dataFlowRules" />
|
||||
</DataFlowModal>
|
||||
</Page>
|
||||
</template>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user