Compare commits
2 Commits
337697d4a7
...
471bd7d8f6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
471bd7d8f6 | ||
|
|
ec4420084f |
@ -12471,6 +12471,30 @@ export const UpdatePermissionsDtoSchema = {
|
||||
additionalProperties: false
|
||||
} as const;
|
||||
|
||||
export const UpdatePlatformThingModelCommandInputSchema = {
|
||||
required: ['id', 'issueCommand', 'operateType'],
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: '操作指令记录Id',
|
||||
format: 'uuid'
|
||||
},
|
||||
operateType: {
|
||||
type: 'integer',
|
||||
description: '待下发的操作指令类型,例如阀控操作时,是拉闸还是合闸,根据StandardFieldName进行分组',
|
||||
format: 'int32'
|
||||
},
|
||||
issueCommand: {
|
||||
minLength: 1,
|
||||
type: 'string',
|
||||
description: '完整透明转发指令。取值须为 2 位控制命令类型 N1(1A=拉闸,1B=合闸允许)或完整 645 报文(68 开头、16 结尾)'
|
||||
}
|
||||
},
|
||||
additionalProperties: false,
|
||||
description: '更新平台端物模型操作指令'
|
||||
} as const;
|
||||
|
||||
export const UpdateRoleInputSchema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
|
||||
File diff suppressed because one or more lines are too long
@ -6993,6 +6993,24 @@ export type UpdatePermissionsDto = {
|
||||
permissions?: Array<UpdatePermissionDto> | null;
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新平台端物模型操作指令
|
||||
*/
|
||||
export type UpdatePlatformThingModelCommandInput = {
|
||||
/**
|
||||
* 操作指令记录Id
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* 待下发的操作指令类型,例如阀控操作时,是拉闸还是合闸,根据StandardFieldName进行分组
|
||||
*/
|
||||
operateType: number;
|
||||
/**
|
||||
* 完整透明转发指令。取值须为 2 位控制命令类型 N1(1A=拉闸,1B=合闸允许)或完整 645 报文(68 开头、16 结尾)
|
||||
*/
|
||||
issueCommand: string;
|
||||
};
|
||||
|
||||
export type UpdateRoleInput = {
|
||||
roleId?: string;
|
||||
roleInfo?: IdentityRoleUpdateDto;
|
||||
@ -8352,6 +8370,42 @@ export type PostIoTplatformThingModelInfoCreateIoTplatformThingModelCommandRespo
|
||||
|
||||
export type PostIoTplatformThingModelInfoCreateIoTplatformThingModelCommandError = unknown;
|
||||
|
||||
export type PostIoTplatformThingModelInfoGetIoTplatformThingModelCommandListData = {
|
||||
query?: {
|
||||
/**
|
||||
* 平台端物模型数据Id
|
||||
*/
|
||||
input?: IdInput;
|
||||
};
|
||||
};
|
||||
|
||||
export type PostIoTplatformThingModelInfoGetIoTplatformThingModelCommandListResponse = (Array<IoTPlatformThingModelCommandDto>);
|
||||
|
||||
export type PostIoTplatformThingModelInfoGetIoTplatformThingModelCommandListError = unknown;
|
||||
|
||||
export type PostIoTplatformThingModelInfoUpdateIoTplatformThingModelCommandData = {
|
||||
query?: {
|
||||
input?: UpdatePlatformThingModelCommandInput;
|
||||
};
|
||||
};
|
||||
|
||||
export type PostIoTplatformThingModelInfoUpdateIoTplatformThingModelCommandResponse = (IoTPlatformThingModelCommandDto);
|
||||
|
||||
export type PostIoTplatformThingModelInfoUpdateIoTplatformThingModelCommandError = unknown;
|
||||
|
||||
export type PostIoTplatformThingModelInfoDeleteIoTplatformThingModelCommandData = {
|
||||
query?: {
|
||||
/**
|
||||
* 操作指令记录Id
|
||||
*/
|
||||
input?: IdInput;
|
||||
};
|
||||
};
|
||||
|
||||
export type PostIoTplatformThingModelInfoDeleteIoTplatformThingModelCommandResponse = (boolean);
|
||||
|
||||
export type PostIoTplatformThingModelInfoDeleteIoTplatformThingModelCommandError = unknown;
|
||||
|
||||
export type PostIoTplatformThingModelInfoExportPlatformThingModelData = {
|
||||
query?: {
|
||||
/**
|
||||
@ -8886,6 +8940,19 @@ export type PostPermissionsUpdateResponse = (unknown | void);
|
||||
|
||||
export type PostPermissionsUpdateError = (RemoteServiceErrorResponse);
|
||||
|
||||
export type GetPulsarDiagnosticsPartitionedStatsData = {
|
||||
query?: {
|
||||
/**
|
||||
* 主题短名(默认主数据主题 onenet.received.datachange.event,走配置默认租户/命名空间);也可传 persistent:// 全名
|
||||
*/
|
||||
topic?: string;
|
||||
};
|
||||
};
|
||||
|
||||
export type GetPulsarDiagnosticsPartitionedStatsResponse = (unknown);
|
||||
|
||||
export type GetPulsarDiagnosticsPartitionedStatsError = unknown;
|
||||
|
||||
export type PostRolesAllResponse = (IdentityRoleDtoListResultDto);
|
||||
|
||||
export type PostRolesAllError = (RemoteServiceErrorResponse);
|
||||
|
||||
@ -347,6 +347,7 @@
|
||||
"IssuePayload": "IssuePayload",
|
||||
"ResponseRawMessage": "ResponseRawMessage",
|
||||
"ResponsePayload": "ResponsePayload",
|
||||
"TelemetrySourceName": "TelemetrySourceName"
|
||||
"TelemetrySourceName": "TelemetrySourceName",
|
||||
"TelemetryType": "TelemetryType"
|
||||
}
|
||||
}
|
||||
|
||||
@ -342,6 +342,7 @@
|
||||
"IssuePayload": "下发消息体",
|
||||
"ResponseRawMessage": "下发结果原始内容",
|
||||
"ResponsePayload": "下发结果",
|
||||
"TelemetrySourceName": "遥测指令来源"
|
||||
"TelemetrySourceName": "遥测指令来源",
|
||||
"TelemetryType": "指令类型"
|
||||
}
|
||||
}
|
||||
|
||||
@ -939,6 +939,12 @@ const serviceCallOptions = ref<
|
||||
>([]);
|
||||
const selectedServiceIndex = ref<number | undefined>(undefined);
|
||||
const serviceCallFormValues = ref<Record<string, string>>({});
|
||||
/**
|
||||
* 拉合闸类型(OperateType)。
|
||||
* 它不是物模型 InputData 的字段——平台端(OneNET)物模型里并没有这一项,
|
||||
* 选项来自服务列表返回的 secondValue 枚举,提交时单独并入 serviceParams。
|
||||
*/
|
||||
const breakerOperateType = ref<string | undefined>(undefined);
|
||||
|
||||
/** 服务调用接口返回,展示在弹窗内;不自动关闭弹窗 */
|
||||
const serviceCallResultText = ref('');
|
||||
@ -1040,7 +1046,11 @@ function isOperateBreakerServiceItem(item: {
|
||||
return key === OPERATE_BREAKER_SERVICE_KEY;
|
||||
}
|
||||
|
||||
/** 拉合闸类型参数:与物模型 InputData 标识符 / 展示名对应 */
|
||||
/**
|
||||
* 拉合闸类型字段。
|
||||
* 正常情况下平台端物模型的 InputData 里不该有它(它是后台内部的操作意图,OneNET 侧没有对应字段),
|
||||
* 这里用于把历史数据中误配的同名项从参数区剔除,避免和独立的拉合闸类型下拉重复采集。
|
||||
*/
|
||||
function isBreakerOperateTypeField(f: ServiceParamField): boolean {
|
||||
const n = f.name.trim().toLowerCase();
|
||||
if (f.label.includes('拉合闸类型') || f.label.includes('操作类型')) {
|
||||
@ -1194,9 +1204,14 @@ const serviceParamFields = computed(() => {
|
||||
if (!currentServiceItem.value) {
|
||||
return [];
|
||||
}
|
||||
return normalizeServiceThirdValue(
|
||||
const fields = normalizeServiceThirdValue(
|
||||
getServiceParamSource(currentServiceItem.value),
|
||||
);
|
||||
// 拉合闸类型由参数区外的独立下拉采集,历史数据若把它误配进 InputData,这里剔除以免重复
|
||||
if (isOperateBreakerServiceItem(currentServiceItem.value)) {
|
||||
return fields.filter((f) => !isBreakerOperateTypeField(f));
|
||||
}
|
||||
return fields;
|
||||
});
|
||||
|
||||
watch(
|
||||
@ -1222,6 +1237,19 @@ const breakerOperateTypeOptions = computed(() =>
|
||||
extractBreakerOperateTypeOptionsFromSecondValue(currentServiceItem.value),
|
||||
);
|
||||
|
||||
/** 拉合闸服务须先选拉闸/合闸:后端据此选取平台端配置的下发报文 */
|
||||
const needBreakerOperateType = computed(() =>
|
||||
isOperateBreakerServiceItem(currentServiceItem.value),
|
||||
);
|
||||
|
||||
/** 拉合闸报文(OperateCommand)为选填的手工覆盖入口,留空则用平台端按类型配置的默认报文 */
|
||||
function isBreakerIssueCommandField(f: ServiceParamField): boolean {
|
||||
if (!needBreakerOperateType.value) {
|
||||
return false;
|
||||
}
|
||||
return f.name.trim().toLowerCase() === 'operatecommand';
|
||||
}
|
||||
|
||||
function shouldUseValveCommandTypeSelectForField(f: ServiceParamField): boolean {
|
||||
if (!isValveControlServiceItem(currentServiceItem.value)) {
|
||||
return false;
|
||||
@ -1229,14 +1257,6 @@ function shouldUseValveCommandTypeSelectForField(f: ServiceParamField): boolean
|
||||
return isValveCommandTypeField(f);
|
||||
}
|
||||
|
||||
function shouldUseBreakerOperateTypeSelectForField(
|
||||
f: ServiceParamField,
|
||||
): boolean {
|
||||
if (!isOperateBreakerServiceItem(currentServiceItem.value)) {
|
||||
return false;
|
||||
}
|
||||
return isBreakerOperateTypeField(f);
|
||||
}
|
||||
|
||||
function parseJsonMaybe(raw: unknown): unknown {
|
||||
if (typeof raw !== 'string') {
|
||||
@ -1354,6 +1374,8 @@ watch(
|
||||
() => currentServiceItem.value,
|
||||
async (item) => {
|
||||
valveCommandTypeOptions.value = [];
|
||||
// 拉合闸是危险操作,切换服务后必须重新选择,不做默认值
|
||||
breakerOperateType.value = undefined;
|
||||
if (!item) {
|
||||
return;
|
||||
}
|
||||
@ -1410,6 +1432,7 @@ const [ServiceCallModal, serviceCallModalApi] = useVbenModal({
|
||||
serviceCallFormValues.value = {};
|
||||
valveCommandTypeOptions.value = [];
|
||||
valveCommandTypeLoading.value = false;
|
||||
breakerOperateType.value = undefined;
|
||||
clearServiceCallResult();
|
||||
return true;
|
||||
},
|
||||
@ -1433,7 +1456,6 @@ const serviceCallModalState = serviceCallModalApi.useStore();
|
||||
function coerceServiceCallParamValue(
|
||||
fieldName: string,
|
||||
raw: string,
|
||||
ctx: { isOperateBreakerService: boolean },
|
||||
): { ok: true; value: unknown } | { ok: false; message: string } {
|
||||
const key = fieldName.trim().toLowerCase();
|
||||
if (key === 'quantity') {
|
||||
@ -1443,17 +1465,6 @@ function coerceServiceCallParamValue(
|
||||
}
|
||||
return { ok: true, value: n };
|
||||
}
|
||||
// 拉合闸服务:操作类型(OperateType)须为数值,不能传字符串
|
||||
if (ctx.isOperateBreakerService && key === 'operatetype') {
|
||||
const n = Number(raw);
|
||||
if (Number.isNaN(n)) {
|
||||
return {
|
||||
ok: false,
|
||||
message: '操作类型(OperateType)需为有效数字',
|
||||
};
|
||||
}
|
||||
return { ok: true, value: n };
|
||||
}
|
||||
return { ok: true, value: raw };
|
||||
}
|
||||
|
||||
@ -1477,13 +1488,10 @@ async function submitDeviceServiceCall() {
|
||||
return;
|
||||
}
|
||||
const serviceParams: Record<string, unknown> = {};
|
||||
const coerceCtx = {
|
||||
isOperateBreakerService: isOperateBreakerServiceItem(item),
|
||||
};
|
||||
for (const f of serviceParamFields.value) {
|
||||
const v = (serviceCallFormValues.value[f.name] ?? '').trim();
|
||||
if (v !== '') {
|
||||
const coerced = coerceServiceCallParamValue(f.name, v, coerceCtx);
|
||||
const coerced = coerceServiceCallParamValue(f.name, v);
|
||||
if (!coerced.ok) {
|
||||
Message.warning(coerced.message);
|
||||
return;
|
||||
@ -1491,6 +1499,21 @@ async function submitDeviceServiceCall() {
|
||||
serviceParams[f.name] = coerced.value;
|
||||
}
|
||||
}
|
||||
|
||||
// 拉合闸类型不在物模型 InputData 里,这里单独并入;后端据此选取已配置的下发报文,必填
|
||||
if (needBreakerOperateType.value) {
|
||||
const rawOperateType = (breakerOperateType.value ?? '').trim();
|
||||
if (!rawOperateType) {
|
||||
Message.warning('请选择拉合闸类型');
|
||||
return;
|
||||
}
|
||||
const operateTypeValue = Number(rawOperateType);
|
||||
if (Number.isNaN(operateTypeValue)) {
|
||||
Message.warning('拉合闸类型取值异常,请重新选择');
|
||||
return;
|
||||
}
|
||||
serviceParams.OperateType = operateTypeValue;
|
||||
}
|
||||
try {
|
||||
serviceCallModalApi.setState({ loading: true, confirmLoading: true });
|
||||
const { data } = await postAggregationDeviceCallDeviceServiceForApiAsync({
|
||||
@ -3988,6 +4011,30 @@ const [DeviceDataFlowModal, deviceDataFlowModalApi] = useVbenModal({
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<template v-if="needBreakerOperateType">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-14 flex-shrink-0 text-right text-sm text-gray-600">
|
||||
操作
|
||||
</span>
|
||||
<Select
|
||||
v-model:value="breakerOperateType"
|
||||
:options="breakerOperateTypeOptions"
|
||||
allow-clear
|
||||
class="min-w-0 flex-1"
|
||||
placeholder="请选择拉闸 / 合闸"
|
||||
size="small"
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
v-if="breakerOperateTypeOptions.length === 0"
|
||||
class="pl-16 text-xs text-red-500"
|
||||
>
|
||||
未获取到拉合闸类型选项,请检查该产品的物模型服务配置
|
||||
</div>
|
||||
<div v-else class="pl-16 text-xs text-gray-400">
|
||||
下发报文由平台物模型中该类型配置的默认指令决定
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="serviceParamFields.length > 0">
|
||||
<div class="text-sm font-medium text-gray-700">参数</div>
|
||||
<div
|
||||
@ -4002,8 +4049,17 @@ const [DeviceDataFlowModal, deviceDataFlowModalApi] = useVbenModal({
|
||||
class="w-[5.5rem] flex-shrink-0 truncate text-right text-sm text-gray-600"
|
||||
:title="f.label"
|
||||
>{{ f.label }}</span>
|
||||
<Input
|
||||
v-if="isBreakerIssueCommandField(f)"
|
||||
v-model:value="serviceCallFormValues[f.name]"
|
||||
allow-clear
|
||||
class="min-w-0 flex-1"
|
||||
size="small"
|
||||
placeholder="选填,留空用已配置指令"
|
||||
title="仅在需要临时覆盖已配置指令时填写:2 位控制命令类型 N1(1A=拉闸、1B=合闸允许),或完整 645 报文(68 开头、16 结尾)"
|
||||
/>
|
||||
<DatePicker
|
||||
v-if="shouldUseStartTimePickerForField(f)"
|
||||
v-else-if="shouldUseStartTimePickerForField(f)"
|
||||
show-time
|
||||
format="YYYY-MM-DD HH:mm:ss"
|
||||
class="min-w-0 flex-1"
|
||||
@ -4013,15 +4069,6 @@ const [DeviceDataFlowModal, deviceDataFlowModalApi] = useVbenModal({
|
||||
:value="parseServiceCallStartTimeValue(serviceCallFormValues[f.name])"
|
||||
@update:value="onServiceCallStartTimeChange(f.name, $event)"
|
||||
/>
|
||||
<Select
|
||||
v-else-if="shouldUseBreakerOperateTypeSelectForField(f)"
|
||||
v-model:value="serviceCallFormValues[f.name]"
|
||||
:options="breakerOperateTypeOptions"
|
||||
allow-clear
|
||||
class="min-w-0 flex-1"
|
||||
size="small"
|
||||
placeholder="请选择拉合闸类型"
|
||||
/>
|
||||
<Select
|
||||
v-else-if="shouldUseValveCommandTypeSelectForField(f)"
|
||||
v-model:value="serviceCallFormValues[f.name]"
|
||||
|
||||
@ -77,8 +77,8 @@ export const tableSchema: any = computed((): VxeGridProps['columns'] => [
|
||||
minWidth: '120',
|
||||
},
|
||||
{
|
||||
field: 'messageType',
|
||||
title: $t('abp.CTWingLog.MessageType'),
|
||||
field: 'telemetryTypeName',
|
||||
title: $t('abp.telemetryLog.TelemetryType'),
|
||||
minWidth: '120',
|
||||
},
|
||||
{
|
||||
|
||||
@ -2,28 +2,38 @@
|
||||
import type { VbenFormProps } from '#/adapter/form';
|
||||
import type { VxeGridProps } from '#/adapter/vxe-table';
|
||||
|
||||
import { h, nextTick, onMounted, ref, watch } from 'vue';
|
||||
import { computed, h, nextTick, onMounted, ref, watch } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import { Page, useVbenModal } from '@vben/common-ui';
|
||||
|
||||
import { Button, Input, Select, message as Message, Tag } from 'ant-design-vue';
|
||||
import {
|
||||
Button,
|
||||
Input,
|
||||
message as Message,
|
||||
Popconfirm,
|
||||
Select,
|
||||
Tag,
|
||||
} from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from '#/adapter/form';
|
||||
import { useVbenVxeGrid } from '#/adapter/vxe-table';
|
||||
import {
|
||||
getCommonGetSelectList,
|
||||
postAggregationIoTplatformUpdateIoTplatformProductThingModelInfoAsync,
|
||||
postIoTplatformThingModelInfoCreateIoTplatformThingModelCommand,
|
||||
postIoTplatformThingModelInfoCopyAnotherThingModelAsync,
|
||||
postIoTplatformThingModelInfoCopyStandardThingModel,
|
||||
postIoTplatformThingModelInfoCreateAsync,
|
||||
postIoTplatformThingModelInfoCreateIoTplatformThingModelCommand,
|
||||
postIoTplatformThingModelInfoDeleteAsync,
|
||||
postIoTplatformThingModelInfoDeleteIoTplatformThingModelCommand,
|
||||
postIoTplatformThingModelInfoExportPlatformThingModel,
|
||||
postIoTplatformThingModelInfoGetIoTplatformThingModelCommandList,
|
||||
postIoTplatformThingModelInfoGetIoTplatformThingModelService,
|
||||
postIoTplatformThingModelInfoImportPlatformThingModel,
|
||||
postIoTplatformThingModelInfoPageAsync,
|
||||
postIoTplatformThingModelInfoUpdateAsync,
|
||||
postIoTplatformThingModelInfoUpdateIoTplatformThingModelCommand,
|
||||
} from '#/api-client';
|
||||
import { TableAction } from '#/components/table-action';
|
||||
import { $t } from '#/locales';
|
||||
@ -379,6 +389,13 @@ type OperateServiceItem = {
|
||||
thirdValue?: unknown;
|
||||
};
|
||||
|
||||
type PlatformThingModelCommand = {
|
||||
id?: null | string;
|
||||
issueCommand?: null | string;
|
||||
operateType?: null | number;
|
||||
standardFieldName?: null | string;
|
||||
};
|
||||
|
||||
const commandEditRow = ref<Record<string, any>>({});
|
||||
const operateServiceLoading = ref(false);
|
||||
const createCommandLoading = ref(false);
|
||||
@ -386,6 +403,36 @@ const operateServiceOptions = ref<OperateServiceItem[]>([]);
|
||||
const operateTypeOptions = ref<Array<{ label: string; value: number }>>([]);
|
||||
const selectedOperateType = ref<number>();
|
||||
const operateIssueCommandText = ref('');
|
||||
/** 该物模型下已配置的指令,弹窗打开及每次增删改后刷新 */
|
||||
const commandList = ref<PlatformThingModelCommand[]>([]);
|
||||
const commandListLoading = ref(false);
|
||||
/** 正在编辑的指令 Id;为空表示当前是新增模式 */
|
||||
const editingCommandId = ref<string | undefined>(undefined);
|
||||
|
||||
/** 指令内容格式说明,与后端 Dlt645IssueCommandFormat 的判定规则保持一致 */
|
||||
const ISSUE_COMMAND_FORMAT_HINT =
|
||||
'填 2 位控制命令类型 N1:1A=拉闸、1B=合闸允许(另有 2A/2B 报警与解除、3A/3B 保电与解除),下发时按设备地址自动组装 645 帧,控制码 C 恒为 1CH 无需填写;或填完整 645 报文(68 开头、16 结尾)原样下发。';
|
||||
|
||||
function describeOperateType(operateType?: null | number): string {
|
||||
if (operateType == null) {
|
||||
return '-';
|
||||
}
|
||||
const matched = operateTypeOptions.value.find((o) => o.value === operateType);
|
||||
return matched?.label ?? String(operateType);
|
||||
}
|
||||
|
||||
/** 已配置过的操作类型不可再新增,只能编辑(后端同样会查重) */
|
||||
const selectableOperateTypeOptions = computed(() => {
|
||||
const usedTypes = new Set(
|
||||
commandList.value
|
||||
.filter((c) => String(c.id ?? '') !== String(editingCommandId.value ?? ''))
|
||||
.map((c) => c.operateType),
|
||||
);
|
||||
return operateTypeOptions.value.map((opt) => ({
|
||||
...opt,
|
||||
disabled: usedTypes.has(opt.value),
|
||||
}));
|
||||
});
|
||||
|
||||
function normalizeOperateTypeOptions(
|
||||
raw: unknown,
|
||||
@ -452,7 +499,7 @@ const [OperateCommandModal, operateCommandModalApi] = useVbenModal({
|
||||
footer: true,
|
||||
showCancelButton: true,
|
||||
showConfirmButton: true,
|
||||
confirmText: '添加',
|
||||
confirmText: '保存',
|
||||
onConfirm: submitOperateCommand,
|
||||
onBeforeClose: () => {
|
||||
commandEditRow.value = {};
|
||||
@ -461,6 +508,9 @@ const [OperateCommandModal, operateCommandModalApi] = useVbenModal({
|
||||
selectedOperateType.value = undefined;
|
||||
operateIssueCommandText.value = '';
|
||||
createCommandLoading.value = false;
|
||||
commandList.value = [];
|
||||
commandListLoading.value = false;
|
||||
editingCommandId.value = undefined;
|
||||
return true;
|
||||
},
|
||||
onOpenChange: async (isOpen: boolean) => {
|
||||
@ -468,6 +518,7 @@ const [OperateCommandModal, operateCommandModalApi] = useVbenModal({
|
||||
return;
|
||||
}
|
||||
await fetchOperateServiceOptions();
|
||||
await fetchCommandList();
|
||||
},
|
||||
});
|
||||
|
||||
@ -956,10 +1007,8 @@ async function fetchOperateServiceOptions() {
|
||||
operateTypeOptions.value = normalizeOperateTypeOptions(
|
||||
firstService?.secondValue,
|
||||
);
|
||||
selectedOperateType.value =
|
||||
operateTypeOptions.value.length > 0
|
||||
? operateTypeOptions.value[0]?.value
|
||||
: undefined;
|
||||
// 拉闸/合闸配错代价高,不做默认选中,强制显式选择
|
||||
selectedOperateType.value = undefined;
|
||||
if (operateServiceOptions.value.length === 0) {
|
||||
Message.warning('当前属性标识符暂无可操作指令');
|
||||
}
|
||||
@ -988,6 +1037,72 @@ function openOperateCommandModal(record: Record<string, any>) {
|
||||
operateCommandModalApi.open();
|
||||
}
|
||||
|
||||
/** 拉取当前物模型下已配置的指令 */
|
||||
async function fetchCommandList() {
|
||||
if (!commandEditRow.value?.id) {
|
||||
commandList.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
commandListLoading.value = true;
|
||||
try {
|
||||
const { data } =
|
||||
await postIoTplatformThingModelInfoGetIoTplatformThingModelCommandList({
|
||||
body: { id: String(commandEditRow.value.id) },
|
||||
});
|
||||
commandList.value = Array.isArray(data)
|
||||
? (data as PlatformThingModelCommand[])
|
||||
: [];
|
||||
} catch (error) {
|
||||
console.error('获取操作指令列表失败:', error);
|
||||
Message.error('获取操作指令列表失败');
|
||||
commandList.value = [];
|
||||
} finally {
|
||||
commandListLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 进入编辑模式:把选中行回填到下方表单 */
|
||||
function startEditCommand(record: PlatformThingModelCommand) {
|
||||
editingCommandId.value = String(record.id ?? '');
|
||||
selectedOperateType.value = record.operateType ?? undefined;
|
||||
operateIssueCommandText.value = String(record.issueCommand ?? '');
|
||||
}
|
||||
|
||||
/** 退出编辑模式,回到新增状态 */
|
||||
function resetCommandForm() {
|
||||
editingCommandId.value = undefined;
|
||||
selectedOperateType.value = undefined;
|
||||
operateIssueCommandText.value = '';
|
||||
}
|
||||
|
||||
async function deleteOperateCommand(record: PlatformThingModelCommand) {
|
||||
if (!record?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const { data } =
|
||||
await postIoTplatformThingModelInfoDeleteIoTplatformThingModelCommand({
|
||||
body: { id: String(record.id) },
|
||||
});
|
||||
if (data) {
|
||||
Message.success('操作指令删除成功');
|
||||
// 删掉的正是编辑中的那条时,表单要退回新增状态,避免保存到已不存在的记录
|
||||
if (String(record.id) === String(editingCommandId.value ?? '')) {
|
||||
resetCommandForm();
|
||||
}
|
||||
await fetchCommandList();
|
||||
await gridApi.reload();
|
||||
} else {
|
||||
Message.error('操作指令删除失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('操作指令删除失败:', error);
|
||||
Message.error(resolveBackendMessage(error, '操作指令删除失败'));
|
||||
}
|
||||
}
|
||||
|
||||
async function submitOperateCommand() {
|
||||
if (!commandEditRow.value?.id) {
|
||||
return;
|
||||
@ -997,16 +1112,24 @@ async function submitOperateCommand() {
|
||||
return;
|
||||
}
|
||||
|
||||
let issueCommand = operateIssueCommandText.value.trim();
|
||||
const issueCommand = operateIssueCommandText.value.trim();
|
||||
if (!issueCommand) {
|
||||
Message.warning('请填写指令内容');
|
||||
return;
|
||||
}
|
||||
|
||||
const isEditing = !!editingCommandId.value;
|
||||
createCommandLoading.value = true;
|
||||
try {
|
||||
const resp =
|
||||
await postIoTplatformThingModelInfoCreateIoTplatformThingModelCommand({
|
||||
const resp = isEditing
|
||||
? await postIoTplatformThingModelInfoUpdateIoTplatformThingModelCommand({
|
||||
body: {
|
||||
id: String(editingCommandId.value),
|
||||
operateType: selectedOperateType.value,
|
||||
issueCommand,
|
||||
},
|
||||
})
|
||||
: await postIoTplatformThingModelInfoCreateIoTplatformThingModelCommand({
|
||||
body: {
|
||||
thingModelDataId: String(commandEditRow.value.id),
|
||||
operateType: selectedOperateType.value,
|
||||
@ -1014,20 +1137,39 @@ async function submitOperateCommand() {
|
||||
},
|
||||
});
|
||||
if (resp.data) {
|
||||
Message.success('默认操作指令添加成功');
|
||||
operateCommandModalApi.close();
|
||||
Message.success(isEditing ? '操作指令修改成功' : '操作指令添加成功');
|
||||
// 弹窗不关闭:同一服务通常要连着配拉闸和合闸两条
|
||||
resetCommandForm();
|
||||
await fetchCommandList();
|
||||
await gridApi.reload();
|
||||
} else {
|
||||
Message.error('默认操作指令添加失败');
|
||||
Message.error(isEditing ? '操作指令修改失败' : '操作指令添加失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('默认操作指令添加失败:', error);
|
||||
Message.error('默认操作指令添加失败');
|
||||
console.error('操作指令保存失败:', error);
|
||||
// 后端会校验指令格式与重复配置,错误信息比前端兜底文案更具体,优先展示
|
||||
Message.error(
|
||||
resolveBackendMessage(
|
||||
error,
|
||||
isEditing ? '操作指令修改失败' : '操作指令添加失败',
|
||||
),
|
||||
);
|
||||
} finally {
|
||||
createCommandLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/** 取后端返回的业务错误信息(如指令格式不合法、指令已存在),取不到再用兜底文案 */
|
||||
function resolveBackendMessage(error: any, fallback: string): string {
|
||||
const backendMessage =
|
||||
error?.response?.data?.error?.message ??
|
||||
error?.data?.error?.message ??
|
||||
error?.error?.message;
|
||||
return typeof backendMessage === 'string' && backendMessage
|
||||
? backendMessage
|
||||
: fallback;
|
||||
}
|
||||
|
||||
// 模型刷新
|
||||
async function onThingModelRefresh() {
|
||||
const formValues = gridApi?.formApi ? await gridApi.formApi.getValues() : {};
|
||||
@ -1463,19 +1605,88 @@ async function onDel(record: any) {
|
||||
<CopyStandardForm />
|
||||
</CopyStandardModal>
|
||||
|
||||
<OperateCommandModal title="默认操作指令添加" class="w-[640px]">
|
||||
<OperateCommandModal title="默认操作指令配置" class="w-[720px]">
|
||||
<div v-if="operateServiceLoading" class="py-8 text-center text-gray-500">
|
||||
正在加载可操作指令列表...
|
||||
</div>
|
||||
<div v-else class="space-y-3">
|
||||
<div class="text-xs text-gray-500">
|
||||
属性标识符:{{ commandEditRow.ioTPlatformRawFieldName || '-' }}
|
||||
标准标识符:{{ commandEditRow.standardFieldName || '-' }}
|
||||
<span class="ml-3">
|
||||
平台标识符:{{ commandEditRow.ioTPlatformRawFieldName || '-' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
<div class="text-sm font-medium text-gray-700">已配置指令</div>
|
||||
<div
|
||||
v-if="commandListLoading"
|
||||
class="py-3 text-center text-xs text-gray-400"
|
||||
>
|
||||
加载中...
|
||||
</div>
|
||||
<div
|
||||
v-else-if="commandList.length === 0"
|
||||
class="rounded border border-dashed px-3 py-3 text-xs text-gray-400"
|
||||
>
|
||||
尚未配置任何操作指令。拉闸、合闸需各配一条,缺失的那一类调用时会被拒绝下发。
|
||||
</div>
|
||||
<div v-else class="divide-y rounded border">
|
||||
<div
|
||||
v-for="cmd in commandList"
|
||||
:key="String(cmd.id)"
|
||||
class="flex items-center gap-2 px-3 py-2"
|
||||
:class="
|
||||
String(cmd.id) === String(editingCommandId ?? '')
|
||||
? 'bg-blue-50'
|
||||
: ''
|
||||
"
|
||||
>
|
||||
<Tag class="flex-shrink-0">
|
||||
{{ describeOperateType(cmd.operateType) }}
|
||||
</Tag>
|
||||
<span
|
||||
class="min-w-0 flex-1 truncate font-mono text-xs text-gray-700"
|
||||
:title="String(cmd.issueCommand ?? '')"
|
||||
>
|
||||
{{ cmd.issueCommand || '-' }}
|
||||
</span>
|
||||
<Button type="link" size="small" @click="startEditCommand(cmd)">
|
||||
{{ $t('common.edit') }}
|
||||
</Button>
|
||||
<Popconfirm
|
||||
:title="`确认删除${describeOperateType(cmd.operateType)}指令?`"
|
||||
@confirm="deleteOperateCommand(cmd)"
|
||||
>
|
||||
<Button type="link" size="small" danger>
|
||||
{{ $t('common.delete') }}
|
||||
</Button>
|
||||
</Popconfirm>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2 rounded border bg-gray-50 p-3">
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-sm font-medium text-gray-700">
|
||||
{{ editingCommandId ? '编辑指令' : '新增指令' }}
|
||||
</span>
|
||||
<Button
|
||||
v-if="editingCommandId"
|
||||
type="link"
|
||||
size="small"
|
||||
@click="resetCommandForm"
|
||||
>
|
||||
取消编辑
|
||||
</Button>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="w-24 flex-shrink-0 text-right text-sm text-gray-600">操作类型</span>
|
||||
<span
|
||||
class="w-24 flex-shrink-0 text-right text-sm text-gray-600"
|
||||
>操作类型</span>
|
||||
<Select
|
||||
v-model:value="selectedOperateType"
|
||||
:options="operateTypeOptions"
|
||||
:options="selectableOperateTypeOptions"
|
||||
allow-clear
|
||||
class="min-w-0 flex-1"
|
||||
placeholder="请选择操作类型"
|
||||
@ -1483,26 +1694,28 @@ async function onDel(record: any) {
|
||||
/>
|
||||
</div>
|
||||
<div class="space-y-1">
|
||||
<div class="text-sm font-medium text-gray-700">指令内容</div>
|
||||
<div class="text-sm text-gray-600">指令内容</div>
|
||||
<Input.TextArea
|
||||
v-model:value="operateIssueCommandText"
|
||||
:rows="4"
|
||||
placeholder="请输入默认操作指令内容"
|
||||
:rows="3"
|
||||
placeholder="如 1A(拉闸)/ 1B(合闸允许),或 68…16 完整 645 报文"
|
||||
/>
|
||||
<div class="text-xs text-gray-400">
|
||||
{{ ISSUE_COMMAND_FORMAT_HINT }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<template #footer>
|
||||
<div class="flex w-full items-center justify-end gap-2">
|
||||
<Button @click="operateCommandModalApi.close()">
|
||||
{{ $t('common.cancel') }}
|
||||
</Button>
|
||||
<Button @click="operateCommandModalApi.close()"> 关闭 </Button>
|
||||
<Button
|
||||
type="primary"
|
||||
:loading="createCommandLoading"
|
||||
:disabled="operateServiceLoading"
|
||||
@click="submitOperateCommand"
|
||||
>
|
||||
添加
|
||||
{{ editingCommandId ? '保存修改' : '添加' }}
|
||||
</Button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user