Compare commits

..

2 Commits

Author SHA1 Message Date
ChenYi
471bd7d8f6 完善遥测指令类型 2026-07-29 10:39:26 +08:00
ChenYi
ec4420084f 调用OneNET平台设备服务完善 2026-07-28 17:22:23 +08:00
8 changed files with 481 additions and 86 deletions

View File

@ -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 位控制命令类型 N11A=拉闸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

View File

@ -6993,6 +6993,24 @@ export type UpdatePermissionsDto = {
permissions?: Array<UpdatePermissionDto> | null;
};
/**
*
*/
export type UpdatePlatformThingModelCommandInput = {
/**
* Id
*/
id: string;
/**
* StandardFieldName进行分组
*/
operateType: number;
/**
* 2 N11A=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);

View File

@ -347,6 +347,7 @@
"IssuePayload": "IssuePayload",
"ResponseRawMessage": "ResponseRawMessage",
"ResponsePayload": "ResponsePayload",
"TelemetrySourceName": "TelemetrySourceName"
"TelemetrySourceName": "TelemetrySourceName",
"TelemetryType": "TelemetryType"
}
}

View File

@ -342,6 +342,7 @@
"IssuePayload": "下发消息体",
"ResponseRawMessage": "下发结果原始内容",
"ResponsePayload": "下发结果",
"TelemetrySourceName": "遥测指令来源"
"TelemetrySourceName": "遥测指令来源",
"TelemetryType": "指令类型"
}
}

View File

@ -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 位控制命令类型 N11A=拉闸、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]"

View File

@ -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',
},
{

View File

@ -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 位控制命令类型 N11A=拉闸、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>