Compare commits

...

5 Commits

Author SHA1 Message Date
ChenYi
cfbe8f4df0 根据物模型ID将解析脚本更新 2025-12-19 16:54:57 +08:00
ChenYi
9d521d74a5 函数构建成功 2025-12-19 15:50:20 +08:00
ChenYi
e365da3531 函数构建按钮 2025-12-19 15:45:19 +08:00
ChenYi
821abb0740 设备端物模型属性和指令界面修改 2025-12-19 14:57:43 +08:00
ChenYi
38427d204d 设备物模型属性和指令界面优化 2025-12-19 10:32:13 +08:00
8 changed files with 1663 additions and 215 deletions

View File

@ -1239,7 +1239,7 @@ export const CopyAnotherDeviceInputSchema = {
}
},
additionalProperties: false,
description: '复制已存在设备物模型信息'
description: '复制已存在设备物模型信息'
} as const;
export const CopyAnotherProductInputSchema = {
@ -1263,6 +1263,27 @@ export const CopyAnotherProductInputSchema = {
description: '复制已存在产品设备物模型信息'
} as const;
export const CopyIoTPlatformThingModelToDeviceInputSchema = {
type: 'object',
properties: {
ioTPlatform: {
'$ref': '#/components/schemas/IoTPlatformTypeEnum'
},
ioTPlatformProductId: {
type: 'string',
description: '物联网平台中对应的产品Id',
nullable: true
},
deviceThingModelId: {
type: 'string',
description: '当前设备端物模型数据Id',
format: 'uuid'
}
},
additionalProperties: false,
description: '通过平台端物模型快速创建设备端物模型信息'
} as const;
export const CopyStandardThingModelInputSchema = {
type: 'object',
properties: {
@ -2520,20 +2541,21 @@ export const DeviceThingModelCommandInfoUpdateInputSchema = {
} as const;
export const DeviceThingModelCreateInputSchema = {
required: ['deviceModelName', 'ioTPlatform', 'ioTPlatformProductId'],
type: 'object',
properties: {
deviceModelName: {
minLength: 1,
type: 'string',
description: '设备端物模型名称',
nullable: true
description: '设备端物模型名称'
},
ioTPlatform: {
'$ref': '#/components/schemas/IoTPlatformTypeEnum'
},
ioTPlatformProductId: {
minLength: 1,
type: 'string',
description: '物联网平台中对应的产品Id',
nullable: true
description: '物联网平台中对应的产品Id'
},
scriptName: {
type: 'string',
@ -2544,9 +2566,6 @@ export const DeviceThingModelCreateInputSchema = {
type: 'string',
description: '脚本函数体',
nullable: true
},
parsingSequence: {
'$ref': '#/components/schemas/ParsingSequenceTypeEnum'
}
},
additionalProperties: false
@ -2649,9 +2668,6 @@ export const DeviceThingModelManagementDtoSchema = {
description: '脚本函数体',
nullable: true
},
parsingSequence: {
'$ref': '#/components/schemas/ParsingSequenceTypeEnum'
},
functionAnalysisFlag: {
type: 'boolean',
description: '函数解析标记默认为false 不能解析'
@ -2749,7 +2765,7 @@ export const DeviceThingModelPageInputSchema = {
} as const;
export const DeviceThingModelPropertyCreateInputSchema = {
required: ['deviceThingModelId', 'ioTPlatformRawFieldDataType', 'ioTPlatformRawFieldName', 'standardFieldDisplayName', 'standardFieldName', 'standardFieldValueType'],
required: ['deviceThingModelId', 'ioTPlatformRawFieldDataType', 'ioTPlatformRawFieldName', 'nativeSkipNumber', 'nativeTakeNumber', 'parsingSequence', 'standardFieldDisplayName', 'standardFieldName', 'standardFieldValueType'],
type: 'object',
properties: {
deviceThingModelId: {
@ -2791,15 +2807,30 @@ export const DeviceThingModelPropertyCreateInputSchema = {
type: 'boolean',
description: '是否需要值类型转换'
},
skipNumber: {
nativeSkipNumber: {
type: 'integer',
description: '跳过数量',
description: '正序跳过数量',
format: 'int32'
},
takeNumber: {
nativeTakeNumber: {
type: 'integer',
description: '获取数量',
description: '正序获取数量',
format: 'int32'
},
parsingSequence: {
'$ref': '#/components/schemas/ParsingSequenceTypeEnum'
},
reversalSkipNumber: {
type: 'integer',
description: '反转跳过数量',
format: 'int32',
nullable: true
},
reversalTakeNumber: {
type: 'integer',
description: '反转获取数量',
format: 'int32',
nullable: true
}
},
additionalProperties: false,
@ -2893,6 +2924,11 @@ export const DeviceThingModelPropertyInfoDtoSchema = {
description: '物联网平台中对应的产品Id',
nullable: true
},
ioTPlatformThingModelDataId: {
type: 'string',
description: '平台端物模型数据Id',
format: 'uuid'
},
filedType: {
type: 'string',
description: '物联网平台中对应产品物模型属性或者事件类型 JiShe.ServicePro.Core.DataDictionaryTypeConst',
@ -2927,15 +2963,30 @@ export const DeviceThingModelPropertyInfoDtoSchema = {
type: 'boolean',
description: '是否需要值类型转换'
},
skipNumber: {
nativeSkipNumber: {
type: 'integer',
description: '跳过数量',
description: '正序跳过数量',
format: 'int32'
},
takeNumber: {
nativeTakeNumber: {
type: 'integer',
description: '获取数量',
description: '正序获取数量',
format: 'int32'
},
parsingSequence: {
'$ref': '#/components/schemas/ParsingSequenceTypeEnum'
},
reversalSkipNumber: {
type: 'integer',
description: '反转跳过数量',
format: 'int32',
nullable: true
},
reversalTakeNumber: {
type: 'integer',
description: '反转获取数量',
format: 'int32',
nullable: true
}
},
additionalProperties: false,
@ -3021,7 +3072,7 @@ export const DeviceThingModelPropertyPageInputSchema = {
} as const;
export const DeviceThingModelPropertyUpdateInputSchema = {
required: ['deviceThingModelId', 'id', 'ioTPlatformRawFieldDataType', 'ioTPlatformRawFieldName', 'standardFieldDisplayName', 'standardFieldName', 'standardFieldValueType'],
required: ['deviceThingModelId', 'id', 'ioTPlatformRawFieldDataType', 'ioTPlatformRawFieldName', 'nativeSkipNumber', 'nativeTakeNumber', 'parsingSequence', 'standardFieldDisplayName', 'standardFieldName', 'standardFieldValueType'],
type: 'object',
properties: {
deviceThingModelId: {
@ -3063,16 +3114,31 @@ export const DeviceThingModelPropertyUpdateInputSchema = {
type: 'boolean',
description: '是否需要值类型转换'
},
skipNumber: {
nativeSkipNumber: {
type: 'integer',
description: '跳过数量',
description: '正序跳过数量',
format: 'int32'
},
takeNumber: {
nativeTakeNumber: {
type: 'integer',
description: '获取数量',
description: '正序获取数量',
format: 'int32'
},
parsingSequence: {
'$ref': '#/components/schemas/ParsingSequenceTypeEnum'
},
reversalSkipNumber: {
type: 'integer',
description: '反转跳过数量',
format: 'int32',
nullable: true
},
reversalTakeNumber: {
type: 'integer',
description: '反转获取数量',
format: 'int32',
nullable: true
},
id: {
type: 'string',
format: 'uuid'
@ -3083,20 +3149,21 @@ export const DeviceThingModelPropertyUpdateInputSchema = {
} as const;
export const DeviceThingModelUpdateInputSchema = {
required: ['deviceModelName', 'ioTPlatform', 'ioTPlatformProductId'],
type: 'object',
properties: {
deviceModelName: {
minLength: 1,
type: 'string',
description: '设备端物模型名称',
nullable: true
description: '设备端物模型名称'
},
ioTPlatform: {
'$ref': '#/components/schemas/IoTPlatformTypeEnum'
},
ioTPlatformProductId: {
minLength: 1,
type: 'string',
description: '物联网平台中对应的产品Id',
nullable: true
description: '物联网平台中对应的产品Id'
},
scriptName: {
type: 'string',
@ -3108,9 +3175,6 @@ export const DeviceThingModelUpdateInputSchema = {
description: '脚本函数体',
nullable: true
},
parsingSequence: {
'$ref': '#/components/schemas/ParsingSequenceTypeEnum'
},
id: {
type: 'string',
format: 'uuid'
@ -8737,11 +8801,11 @@ export const ParameterApiDescriptionModelSchema = {
} as const;
export const ParsingSequenceTypeEnumSchema = {
enum: [1, 2],
enum: [1, 2, 3],
type: 'integer',
description: '解析顺序枚举',
format: 'int32',
'说明:': '正序=1,高低反转=2'
'说明:': '正序=1,高低反转=2,BCD码=3'
} as const;
export const PermissionOutputSchema = {

File diff suppressed because one or more lines are too long

View File

@ -233,7 +233,7 @@ export type ControllerInterfaceApiDescriptionModel = {
};
/**
*
*
*/
export type CopyAnotherDeviceInput = {
ioTPlatform?: IoTPlatformTypeEnum;
@ -262,6 +262,21 @@ export type CopyAnotherProductInput = {
sourceProductId?: (string) | null;
};
/**
*
*/
export type CopyIoTPlatformThingModelToDeviceInput = {
ioTPlatform?: IoTPlatformTypeEnum;
/**
* Id
*/
ioTPlatformProductId?: (string) | null;
/**
* Id
*/
deviceThingModelId?: string;
};
/**
*
*/
@ -1419,12 +1434,12 @@ export type DeviceThingModelCreateInput = {
/**
*
*/
deviceModelName?: (string) | null;
ioTPlatform?: IoTPlatformTypeEnum;
deviceModelName: string;
ioTPlatform: IoTPlatformTypeEnum;
/**
* Id
*/
ioTPlatformProductId?: (string) | null;
ioTPlatformProductId: string;
/**
*
*/
@ -1433,7 +1448,6 @@ export type DeviceThingModelCreateInput = {
*
*/
functionScript?: (string) | null;
parsingSequence?: ParsingSequenceTypeEnum;
};
/**
@ -1491,7 +1505,6 @@ export type DeviceThingModelManagementDto = {
*
*/
functionScript?: (string) | null;
parsingSequence?: ParsingSequenceTypeEnum;
/**
* false
*/
@ -1587,13 +1600,22 @@ export type DeviceThingModelPropertyCreateInput = {
*/
isValueNeedConvert?: boolean;
/**
*
*
*/
skipNumber?: number;
nativeSkipNumber: number;
/**
*
*
*/
takeNumber?: number;
nativeTakeNumber: number;
parsingSequence: ParsingSequenceTypeEnum;
/**
*
*/
reversalSkipNumber?: (number) | null;
/**
*
*/
reversalTakeNumber?: (number) | null;
};
/**
@ -1643,6 +1665,10 @@ export type DeviceThingModelPropertyInfoDto = {
* Id
*/
ioTPlatformProductId?: (string) | null;
/**
* Id
*/
ioTPlatformThingModelDataId?: string;
/**
* JiShe.ServicePro.Core.DataDictionaryTypeConst
*/
@ -1672,13 +1698,22 @@ export type DeviceThingModelPropertyInfoDto = {
*/
isValueNeedConvert?: boolean;
/**
*
*
*/
skipNumber?: number;
nativeSkipNumber?: number;
/**
*
*
*/
takeNumber?: number;
nativeTakeNumber?: number;
parsingSequence?: ParsingSequenceTypeEnum;
/**
*
*/
reversalSkipNumber?: (number) | null;
/**
*
*/
reversalTakeNumber?: (number) | null;
};
export type DeviceThingModelPropertyInfoDtoPagedResultDto = {
@ -1769,13 +1804,22 @@ export type DeviceThingModelPropertyUpdateInput = {
*/
isValueNeedConvert?: boolean;
/**
*
*
*/
skipNumber?: number;
nativeSkipNumber: number;
/**
*
*
*/
takeNumber?: number;
nativeTakeNumber: number;
parsingSequence: ParsingSequenceTypeEnum;
/**
*
*/
reversalSkipNumber?: (number) | null;
/**
*
*/
reversalTakeNumber?: (number) | null;
id: string;
};
@ -1783,12 +1827,12 @@ export type DeviceThingModelUpdateInput = {
/**
*
*/
deviceModelName?: (string) | null;
ioTPlatform?: IoTPlatformTypeEnum;
deviceModelName: string;
ioTPlatform: IoTPlatformTypeEnum;
/**
* Id
*/
ioTPlatformProductId?: (string) | null;
ioTPlatformProductId: string;
/**
*
*/
@ -1797,7 +1841,6 @@ export type DeviceThingModelUpdateInput = {
*
*/
functionScript?: (string) | null;
parsingSequence?: ParsingSequenceTypeEnum;
id?: string;
};
@ -4656,7 +4699,7 @@ export type ParameterApiDescriptionModel = {
/**
*
*/
export type ParsingSequenceTypeEnum = 1 | 2;
export type ParsingSequenceTypeEnum = 1 | 2 | 3;
export type PermissionOutput = {
grants?: Array<(string)> | null;
@ -5999,15 +6042,25 @@ export type PostDeviceThingModelManagementFindByPlatformProductIdAsyncResponse =
export type PostDeviceThingModelManagementFindByPlatformProductIdAsyncError = unknown;
export type PostDeviceThingModelManagementCopyAnotherThingModelAsyncData = {
export type PostDeviceThingModelManagementCopyAnotherDeviceThingModelAsyncData = {
query?: {
input?: CopyAnotherDeviceInput;
};
};
export type PostDeviceThingModelManagementCopyAnotherThingModelAsyncResponse = (boolean);
export type PostDeviceThingModelManagementCopyAnotherDeviceThingModelAsyncResponse = (boolean);
export type PostDeviceThingModelManagementCopyAnotherThingModelAsyncError = unknown;
export type PostDeviceThingModelManagementCopyAnotherDeviceThingModelAsyncError = unknown;
export type PostDeviceThingModelManagementCopyIoTplatformThingModelToDeviceAsyncData = {
query?: {
input?: CopyIoTPlatformThingModelToDeviceInput;
};
};
export type PostDeviceThingModelManagementCopyIoTplatformThingModelToDeviceAsyncResponse = (boolean);
export type PostDeviceThingModelManagementCopyIoTplatformThingModelToDeviceAsyncError = unknown;
export type PostDeviceThingModelManagementPageAsyncData = {
query?: {

View File

@ -171,11 +171,13 @@ const handleMenuClick = (e: any) => {
<Icon :icon="action.popConfirm.icon" />
</template>
<div
:class="
:class="[
action.disabled === true
? 'cursor-not-allowed text-gray-300'
: ''
"
: '',
action.class,
]"
:style="action.style"
>
<Icon v-if="action.icon" :icon="action.icon" />
<span class="ml-1">{{ action.text }}</span>
@ -184,11 +186,13 @@ const handleMenuClick = (e: any) => {
</template>
<template v-else>
<div
:class="
:class="[
action.disabled === true
? 'cursor-not-allowed text-gray-300'
: ''
"
: '',
action.class,
]"
:style="action.style"
>
<Icon v-if="action.icon" :icon="action.icon" />
{{ action.label }}

View File

@ -1,10 +1,21 @@
<script setup lang="ts">
import { ref } from 'vue';
import { nextTick, ref } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { useVbenModal, z } from '@vben/common-ui';
import { message as Message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { postDeviceThingModelManagementCommandPageAsync } from '#/api-client';
import {
postDeviceThingModelManagementCommandCreateAsync,
postDeviceThingModelManagementCommandDeleteAsync,
postDeviceThingModelManagementCommandFindByIdAsync,
postDeviceThingModelManagementCommandPageAsync,
postDeviceThingModelManagementCommandUpdateAsync,
postDeviceThingModelManagementPropertyPageAsync,
} from '#/api-client';
import { TableAction } from '#/components/table-action';
import { $t } from '#/locales';
defineOptions({
@ -14,6 +25,10 @@ defineOptions({
const deviceThingModelId = ref<string>('');
const deviceModelName = ref<string>('');
const editRow: Record<string, any> = ref({});
const propertyOptions = ref<Array<{ label: string; value: string }>>([]);
//
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: [],
@ -28,11 +43,33 @@ const [Grid, gridApi] = useVbenVxeGrid({
showOverflow: 'tooltip',
},
{
field: 'identifier',
title: '标识符',
minWidth: 140,
field: 'issueCommand',
title: '下发指令',
minWidth: 200,
showOverflow: 'tooltip',
},
{
field: 'propertyArray',
title: '属性标识符集合',
minWidth: 200,
showOverflow: 'tooltip',
formatter: ({ cellValue }: { cellValue: any }) => {
if (Array.isArray(cellValue)) {
return cellValue.join(', ');
}
if (typeof cellValue === 'string') {
try {
const parsed = JSON.parse(cellValue);
if (Array.isArray(parsed)) {
return parsed.join(', ');
}
} catch {
return cellValue;
}
}
return cellValue || '-';
},
},
{
field: 'action',
title: $t('common.action'),
@ -43,6 +80,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
],
height: 400,
pagerConfig: {},
toolbarConfig: {
custom: true,
},
proxyConfig: {
ajax: {
query: async ({ page }) => {
@ -63,15 +103,234 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
});
// /
const [CommandFormModal, commandFormModalApi] = useVbenModal({
draggable: true,
footer: true,
showCancelButton: true,
showConfirmButton: true,
onConfirm: submitCommand,
onBeforeClose: () => {
return true;
},
onOpenChange: async (isOpen: boolean) => {
if (isOpen) {
//
await loadPropertyOptions();
if (editRow.value.id) {
//
nextTick(async () => {
try {
const { data } = await postDeviceThingModelManagementCommandFindByIdAsync({
body: { id: editRow.value.id },
});
if (data) {
const values: any = { ...data };
// propertyArray
if (values.propertyArray) {
if (typeof values.propertyArray === 'string') {
try {
values.propertyArray = JSON.parse(values.propertyArray);
} catch {
values.propertyArray = [values.propertyArray];
}
}
if (!Array.isArray(values.propertyArray)) {
values.propertyArray = [];
}
} else {
values.propertyArray = [];
}
commandFormApi.setValues(values);
}
} catch (error) {
console.error('加载指令详情失败:', error);
}
});
} else {
//
commandFormApi.resetForm();
}
}
},
onCancel: () => {
commandFormModalApi.close();
},
});
//
async function loadPropertyOptions() {
if (!deviceThingModelId.value) {
return;
}
try {
const { data } = await postDeviceThingModelManagementPropertyPageAsync({
body: {
pageIndex: 1,
pageSize: 1000,
deviceThingModelId: deviceThingModelId.value,
isPage:false
},
});
const items = data?.items || [];
propertyOptions.value = items.map((item: any) => ({
label: `${item.standardFieldDisplayName || item.standardFieldName} (${item.standardFieldName})`,
value: item.standardFieldName || '',
}));
} catch (error) {
console.error('加载属性列表失败:', error);
}
}
//
const [CommandForm, commandFormApi] = useVbenForm({
collapsed: false,
commonConfig: {
labelWidth: 140,
componentProps: {
class: 'w-4/5',
},
},
layout: 'horizontal',
schema: [
{
component: 'Input',
fieldName: 'commandName',
label: '指令名称',
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
componentProps: {
placeholder: $t('common.pleaseInput') + '指令名称',
},
},
{
component: 'Textarea',
fieldName: 'issueCommand',
label: '下发指令',
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
componentProps: {
rows: 4,
placeholder: $t('common.pleaseInput') + '下发指令(完整的单个下发指令)',
},
},
{
component: 'Select',
fieldName: 'propertyArray',
label: '属性标识符集合',
rules: z
.preprocess((v) => (v == null ? [] : v), z.array(z.string()).min(1, '请至少选择一个属性标识符')),
// 使 options propertyOptions
componentProps: () => ({
mode: 'multiple',
placeholder: '请选择属性标识符',
options: propertyOptions.value,
showSearch: true,
filterOption: (input: string, option: any) => {
return String(option.label)
.toLowerCase()
.includes(input.toLowerCase());
},
}),
},
],
showCollapseButton: false,
showDefaultActions: false,
wrapperClass: 'grid-cols-1',
});
//
function openAddCommandModal() {
editRow.value = {};
commandFormModalApi.open();
}
//
function openEditCommandModal(record: any) {
editRow.value = record;
commandFormModalApi.open();
}
// /
async function submitCommand() {
const isEdit = !!editRow.value.id;
const { valid } = await commandFormApi.validate();
if (!valid) return;
const formValues = await commandFormApi.getValues();
const fetchParams: any = {
...formValues,
deviceThingModelId: deviceThingModelId.value,
// propertyArray
propertyArray: Array.isArray(formValues.propertyArray)
? formValues.propertyArray
: formValues.propertyArray
? [formValues.propertyArray]
: [],
...(isEdit && { id: editRow.value.id }),
};
try {
const api = isEdit
? postDeviceThingModelManagementCommandUpdateAsync
: postDeviceThingModelManagementCommandCreateAsync;
const resp = await api({ body: fetchParams });
if (resp.data) {
Message.success(
isEdit ? $t('common.editSuccess') : $t('common.addSuccess'),
);
commandFormModalApi.close();
editRow.value = {};
await nextTick();
if (gridApi && gridApi.reload) {
await gridApi.reload();
}
} else {
Message.error(
isEdit ? $t('common.editFail') : $t('common.addFail'),
);
}
} catch (error) {
console.error('提交指令失败:', error);
Message.error(
isEdit ? $t('common.editFail') : $t('common.addFail'),
);
}
}
//
async function onDeleteCommand(record: any) {
try {
const resp = await postDeviceThingModelManagementCommandDeleteAsync({
body: { id: record.id },
});
if (resp.data) {
Message.success($t('common.deleteSuccess'));
await nextTick();
if (gridApi && gridApi.reload) {
await gridApi.reload();
}
} else {
Message.error($t('common.deleteFail'));
}
} catch (error) {
console.error('删除指令失败:', error);
Message.error($t('common.deleteFail'));
}
}
const [Modal, modalApi] = useVbenModal({
onOpenChange(isOpen: boolean) {
if (isOpen) {
const data = modalApi.getData<Record<string, any>>();
deviceThingModelId.value = data?.deviceThingModelId || '';
deviceModelName.value = data?.deviceModelName || '';
if (gridApi && gridApi.reload) {
gridApi.reload();
}
// 使 nextTick Grid
nextTick(() => {
if (gridApi && gridApi.reload) {
gridApi.reload();
}
});
}
},
});
@ -80,9 +339,52 @@ const [Modal, modalApi] = useVbenModal({
<template>
<Modal :title="`指令管理 - ${deviceModelName || ''}`" class="w-[900px]">
<Grid>
<!-- 这里后续可以加 toolbar-actions新增指令编辑删除等 -->
<template #toolbar-actions>
<TableAction
:actions="[
{
label: $t('common.add'),
type: 'primary',
icon: 'ant-design:plus-outlined',
onClick: openAddCommandModal,
auth: ['AbpIdentity.Users.Create'],
},
]"
/>
</template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
size: 'small',
auth: ['AbpIdentity.Users.Update'],
onClick: openEditCommandModal.bind(null, row),
},
{
label: $t('common.delete'),
icon: 'ant-design:delete-outlined',
type: 'link',
size: 'small',
auth: ['AbpIdentity.Users.Delete'],
popConfirm: {
title: $t('common.askConfirmDelete'),
confirm: onDeleteCommand.bind(null, row),
},
},
]"
/>
</template>
</Grid>
<!-- 指令新增/编辑弹窗 -->
<CommandFormModal
:title="editRow.id ? $t('common.edit') : $t('common.add')"
class="w-[800px]"
>
<CommandForm />
</CommandFormModal>
</Modal>
</template>

View File

@ -1,10 +1,25 @@
<script setup lang="ts">
import { ref } from 'vue';
import { computed, nextTick, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { useVbenModal, z } from '@vben/common-ui';
import { message as Message } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import { postDeviceThingModelManagementPropertyPageAsync } from '#/api-client';
import {
getCommonGetSelectList,
postAggregationIoTplatformGetIoTplatformProductInfoAsync,
postDeviceThingModelManagementCopyAnotherDeviceThingModelAsync,
postDeviceThingModelManagementCopyIoTplatformThingModelToDeviceAsync,
postDeviceThingModelManagementPageAsync,
postDeviceThingModelManagementPropertyCreateAsync,
postDeviceThingModelManagementPropertyDeleteAsync,
postDeviceThingModelManagementPropertyFindByIdAsync,
postDeviceThingModelManagementPropertyPageAsync,
postDeviceThingModelManagementPropertyUpdateAsync,
} from '#/api-client';
import { TableAction } from '#/components/table-action';
import { $t } from '#/locales';
defineOptions({
@ -13,7 +28,12 @@ defineOptions({
const deviceThingModelId = ref<string>('');
const deviceModelName = ref<string>('');
const ioTPlatform = ref<number | string>('');
const ioTPlatformProductId = ref<string>('');
const editRow: Record<string, any> = ref({});
//
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: [],
@ -22,21 +42,83 @@ const [Grid, gridApi] = useVbenVxeGrid({
gridOptions: {
columns: [
{
field: 'propertyName',
title: '属性名称',
field: 'filedType',
title: '物模型类型',
minWidth: 160,
showOverflow: 'tooltip',
},
{
field: 'identifier',
title: '标识符',
field: 'standardFieldDisplayName',
title: '标准属性名称',
minWidth: 160,
showOverflow: 'tooltip',
},
{
field: 'standardFieldName',
title: '标准属性标识符',
minWidth: 160,
showOverflow: 'tooltip',
},
{
field: 'standardFieldValueType',
title: '标准属性值类型',
minWidth: 160,
showOverflow: 'tooltip',
},
{
field: 'ioTPlatformRawFieldName',
title: '平台属性标识符',
minWidth: 160,
showOverflow: 'tooltip',
},
{
field: 'ioTPlatformRawFieldDataType',
title: '平台属性值类型',
minWidth: 160,
showOverflow: 'tooltip',
},
{
field: 'isValueNeedConvert',
title: '值类型是否转换',
minWidth: 120,
showOverflow: 'tooltip',
formatter: ({ cellValue }: { cellValue: any }) => {
return cellValue ? '是' : '否';
},
},
{
field: 'parsingSequence',
title: '解析方式',
minWidth: 140,
showOverflow: 'tooltip',
formatter: ({ cellValue }: { cellValue: any }) => {
if (cellValue === 1) return '正序';
if (cellValue === 2) return '高低位反转';
return cellValue ?? '-';
},
},
{
field: 'nativeSkipNumber',
title: '正序跳过数量',
minWidth: 140,
showOverflow: 'tooltip',
},
{
field: 'dataType',
title: '数据类型',
minWidth: 120,
field: 'nativeTakeNumber',
title: '正序获取数量',
minWidth: 140,
showOverflow: 'tooltip',
},
{
field: 'reversalSkipNumber',
title: '反转跳过数量',
minWidth: 140,
showOverflow: 'tooltip',
},
{
field: 'reversalTakeNumber',
title: '反转获取数量',
minWidth: 140,
showOverflow: 'tooltip',
},
{
@ -49,19 +131,23 @@ const [Grid, gridApi] = useVbenVxeGrid({
],
height: 400,
pagerConfig: {},
toolbarConfig: {
custom: true,
},
proxyConfig: {
ajax: {
query: async ({ page }) => {
if (!deviceThingModelId.value) {
return { items: [], totalCount: 0 };
}
const { data } = await postDeviceThingModelManagementPropertyPageAsync({
body: {
pageIndex: page.currentPage,
pageSize: page.pageSize,
deviceThingModelId: deviceThingModelId.value,
},
});
const { data } =
await postDeviceThingModelManagementPropertyPageAsync({
body: {
pageIndex: page.currentPage,
pageSize: page.pageSize,
deviceThingModelId: deviceThingModelId.value,
},
});
return data || { items: [], totalCount: 0 };
},
},
@ -69,26 +155,691 @@ const [Grid, gridApi] = useVbenVxeGrid({
},
});
// /
const [PropertyFormModal, propertyFormModalApi] = useVbenModal({
draggable: true,
footer: true,
showCancelButton: true,
showConfirmButton: true,
onConfirm: submitProperty,
onBeforeClose: () => {
return true;
},
onOpenChange: (isOpen: boolean) => {
if (isOpen && editRow.value.id) {
//
nextTick(async () => {
try {
const { data } =
await postDeviceThingModelManagementPropertyFindByIdAsync({
body: { id: editRow.value.id },
});
if (data) {
const values: any = { ...data };
//
if (
values.parsingSequence !== undefined &&
values.parsingSequence !== null
) {
//
values.parsingSequence = String(values.parsingSequence);
}
propertyFormApi.setValues(values);
}
} catch (error) {
console.error('加载属性详情失败:', error);
}
});
} else if (isOpen) {
//
propertyFormApi.resetForm();
}
},
onCancel: () => {
propertyFormModalApi.close();
},
});
//
const [CopyPropertyModal, copyPropertyModalApi] = useVbenModal({
draggable: true,
footer: true,
showCancelButton: true,
showConfirmButton: true,
onConfirm: submitCopyProperty,
onBeforeClose: () => {
return true;
},
onCancel: () => {
copyPropertyModalApi.close();
},
});
//
const [CopyDeviceThingModelModal, copyDeviceThingModelModalApi] = useVbenModal({
draggable: true,
footer: true,
showCancelButton: true,
showConfirmButton: true,
onConfirm: submitCopyDeviceThingModel,
onBeforeClose: () => {
return true;
},
onCancel: () => {
copyDeviceThingModelModalApi.close();
},
});
// schema
const propertyFormSchema = computed(() => {
const isEdit = !!editRow.value.id;
return [
{
component: 'Input',
fieldName: 'standardFieldDisplayName',
label: '标准属性名称',
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder: `${$t('common.pleaseInput')}标准属性名称`,
disabled: isEdit, //
},
},
{
component: 'Input',
fieldName: 'standardFieldName',
label: '标准属性标识符',
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder: `${$t('common.pleaseInput')}标准属性标识符`,
disabled: isEdit, //
},
},
{
component: 'ApiSelect',
fieldName: 'standardFieldValueType',
label: '标准属性值类型',
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
api: getCommonGetSelectList,
params: {
query: {
typeName: 'StandardThingModelDataTypeEnum',
},
},
labelField: 'value',
valueField: 'secondValue',
optionsPropName: 'options',
immediate: true,
allowClear: true,
disabled: isEdit, //
placeholder: `${$t('common.pleaseSelect')}标准属性值类型`,
afterFetch: (res: any) => {
if (Array.isArray(res)) return res;
if (res && Array.isArray(res.items)) return res.items;
if (res && Array.isArray(res.data)) return res.data;
return [];
},
},
},
{
component: 'Input',
fieldName: 'ioTPlatformRawFieldName',
label: '平台属性标识符',
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder: `${$t('common.pleaseInput')}平台属性标识符`,
},
},
{
component: 'ApiSelect',
fieldName: 'ioTPlatformRawFieldDataType',
label: '平台属性值类型',
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
api: getCommonGetSelectList,
params: {
query: {
typeName: 'StandardThingModelDataTypeEnum',
},
},
labelField: 'value',
valueField: 'secondValue',
optionsPropName: 'options',
immediate: true,
allowClear: true,
placeholder: `${$t('common.pleaseSelect')}平台属性值类型`,
afterFetch: (res: any) => {
if (Array.isArray(res)) return res;
if (res && Array.isArray(res.items)) return res.items;
if (res && Array.isArray(res.data)) return res.data;
return [];
},
},
},
{
component: 'Switch',
fieldName: 'isValueNeedConvert',
label: '值类型是否转换',
componentProps: {
checkedChildren: '是',
unCheckedChildren: '否',
style: { width: 'auto' }, //
},
},
{
component: 'ApiSelect',
fieldName: 'parsingSequence',
label: '解析方式',
componentProps: {
api: getCommonGetSelectList,
params: {
query: {
typeName: 'ParsingSequenceTypeEnum',
},
},
labelField: 'value',
valueField: 'key',
optionsPropName: 'options',
immediate: true,
allowClear: true,
placeholder: `${$t('common.pleaseSelect')}解析方式`,
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;
}
// key
return items.map((item: any) => ({
...item,
key: String(item.key || item.value),
}));
},
},
},
{
component: 'InputNumber',
fieldName: 'nativeSkipNumber',
label: '正序跳过数量',
componentProps: {
min: 0,
placeholder: `${$t('common.pleaseInput')}正序跳过数量`,
},
},
{
component: 'InputNumber',
fieldName: 'nativeTakeNumber',
label: '正序获取数量',
componentProps: {
min: 0,
placeholder: `${$t('common.pleaseInput')}正序获取数量`,
},
},
{
component: 'InputNumber',
fieldName: 'reversalSkipNumber',
label: '反转跳过数量',
componentProps: {
min: 0,
placeholder: `${$t('common.pleaseInput')}反转跳过数量`,
},
},
{
component: 'InputNumber',
fieldName: 'reversalTakeNumber',
label: '反转获取数量',
componentProps: {
min: 0,
placeholder: `${$t('common.pleaseInput')}反转获取数量`,
},
},
];
});
//
const [PropertyForm, propertyFormApi] = useVbenForm({
collapsed: false,
commonConfig: {
labelWidth: 140,
componentProps: {
class: 'w-4/5',
},
},
layout: 'horizontal',
schema: propertyFormSchema.value,
showCollapseButton: false,
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
// editRow schema
watch(
() => editRow.value.id,
() => {
if (propertyFormApi && propertyFormApi.updateSchema) {
propertyFormApi.updateSchema(propertyFormSchema.value);
}
},
);
//
const [CopyPropertyForm, copyPropertyFormApi] = useVbenForm({
collapsed: false,
commonConfig: {
labelWidth: 140,
componentProps: {
class: 'w-4/5',
},
},
layout: 'horizontal',
schema: [
{
component: 'ApiSelect',
fieldName: 'ioTPlatform',
label: $t('abp.deviceInfos.ioTPlatform'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
api: getCommonGetSelectList,
params: {
query: {
typeName: 'IoTPlatformTypeEnum',
},
},
labelField: 'value',
valueField: 'key',
optionsPropName: 'options',
immediate: true,
allowClear: true,
placeholder:
$t('common.pleaseSelect') + $t('abp.deviceInfos.ioTPlatform'),
afterFetch: (res: any) => {
if (Array.isArray(res)) return res;
if (res && Array.isArray(res.items)) return res.items;
if (res && Array.isArray(res.data)) return res.data;
return [];
},
},
},
{
component: 'ApiSelect',
fieldName: 'ioTPlatformProductId',
label: $t('common.BelongingProductName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
dependencies: {
show(values: any) {
return !!values.ioTPlatform;
},
triggerFields: ['ioTPlatform'],
},
componentProps: (formValues: any) => {
const platform = formValues?.ioTPlatform;
return {
api: platform
? postAggregationIoTplatformGetIoTplatformProductInfoAsync
: null,
params: {
body: {
ioTPlatformType:
typeof platform === 'string'
? Number.parseInt(platform)
: platform,
},
},
labelField: 'productName',
valueField: 'ioTPlatformProductId',
optionsPropName: 'options',
immediate: false,
allowClear: true,
placeholder:
$t('common.pleaseSelect') + $t('common.BelongingProductName'),
afterFetch: (res: any) => {
if (Array.isArray(res)) return res;
if (res && Array.isArray(res.items)) return res.items;
if (res && Array.isArray(res.data)) return res.data;
if (res && res.data && Array.isArray(res.data.items)) {
return res.data.items;
}
return [];
},
};
},
},
],
showCollapseButton: false,
showDefaultActions: false,
wrapperClass: 'grid-cols-2',
});
//
const [CopyDeviceThingModelForm, copyDeviceThingModelFormApi] = useVbenForm({
collapsed: false,
commonConfig: {
labelWidth: 140,
componentProps: {
class: 'w-4/5',
},
},
layout: 'horizontal',
schema: [
{
component: 'ApiSelect',
fieldName: 'sourceDeviceThingModelId',
label: '选择设备端物模型',
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
api: postDeviceThingModelManagementPageAsync,
params: {
body: {
pageIndex: 1,
pageSize: 1000,
isPage: false, //
},
},
labelField: 'deviceModelName',
valueField: 'id',
optionsPropName: 'options',
immediate: true,
allowClear: true,
placeholder: '请选择要复制的设备端物模型',
afterFetch: (res: any) => {
if (Array.isArray(res)) return res;
if (res && Array.isArray(res.items)) return res.items;
if (res && Array.isArray(res.data)) return res.data;
if (res && res.data && Array.isArray(res.data.items)) {
return res.data.items;
}
return [];
},
},
},
],
showCollapseButton: false,
showDefaultActions: false,
wrapperClass: 'grid-cols-1',
});
//
function openAddPropertyModal() {
editRow.value = {};
propertyFormModalApi.open();
}
//
function openEditPropertyModal(record: any) {
editRow.value = record;
propertyFormModalApi.open();
}
//
function openCopyPropertyModal() {
copyPropertyFormApi.resetForm();
copyPropertyModalApi.open();
}
//
function openCopyDeviceThingModelModal() {
copyDeviceThingModelFormApi.resetForm();
copyDeviceThingModelModalApi.open();
}
// /
async function submitProperty() {
const isEdit = !!editRow.value.id;
const { valid } = await propertyFormApi.validate();
if (!valid) return;
const formValues = await propertyFormApi.getValues();
const fetchParams: any = {
...formValues,
deviceThingModelId: deviceThingModelId.value,
...(formValues.parsingSequence && {
//
parsingSequence: Number.parseInt(String(formValues.parsingSequence)),
}),
...(isEdit && { id: editRow.value.id }),
};
try {
const api = isEdit
? postDeviceThingModelManagementPropertyUpdateAsync
: postDeviceThingModelManagementPropertyCreateAsync;
const resp = await api({ body: fetchParams });
if (resp.data) {
Message.success(
isEdit ? $t('common.editSuccess') : $t('common.addSuccess'),
);
propertyFormModalApi.close();
editRow.value = {};
await nextTick();
if (gridApi && gridApi.reload) {
await gridApi.reload();
}
} else {
Message.error(isEdit ? $t('common.editFail') : $t('common.addFail'));
}
} catch (error) {
console.error('提交属性失败:', error);
Message.error(isEdit ? $t('common.editFail') : $t('common.addFail'));
}
}
//
async function submitCopyDeviceThingModel() {
const { valid } = await copyDeviceThingModelFormApi.validate();
if (!valid) return;
const formValues = await copyDeviceThingModelFormApi.getValues();
if (!formValues.sourceDeviceThingModelId) {
Message.warning('请选择要复制的设备端物模型');
return;
}
try {
const resp =
await postDeviceThingModelManagementCopyAnotherDeviceThingModelAsync({
body: {
ioTPlatform: Number.parseInt(String(ioTPlatform.value)) as 1 | 2,
deviceThingModelId: deviceThingModelId.value,
sourceProductId: formValues.sourceDeviceThingModelId,
},
});
if (resp.data) {
Message.success('快速复制设备端物模型属性成功');
copyDeviceThingModelModalApi.close();
await nextTick();
if (gridApi && gridApi.reload) {
await gridApi.reload();
}
} else {
Message.error('快速复制设备端物模型属性失败');
}
} catch (error) {
console.error('快速复制设备端物模型属性失败:', error);
Message.error('快速复制设备端物模型属性失败');
}
}
//
async function submitCopyProperty() {
const { valid } = await copyPropertyFormApi.validate();
if (!valid) return;
const formValues = await copyPropertyFormApi.getValues();
if (!formValues.ioTPlatform || !formValues.ioTPlatformProductId) {
Message.warning('请选择平台和产品');
return;
}
try {
const resp =
await postDeviceThingModelManagementCopyIoTplatformThingModelToDeviceAsync(
{
body: {
ioTPlatform: Number.parseInt(String(formValues.ioTPlatform)) as
| 1
| 2,
ioTPlatformProductId: String(formValues.ioTPlatformProductId),
deviceThingModelId: deviceThingModelId.value,
},
},
);
if (resp.data) {
Message.success('快速复制平台端物模型属性成功');
copyPropertyModalApi.close();
await nextTick();
if (gridApi && gridApi.reload) {
await gridApi.reload();
}
} else {
Message.error('快速复制平台端物模型属性失败');
}
} catch (error) {
console.error('快速复制平台端物模型属性失败:', error);
Message.error('快速复制平台端物模型属性失败');
}
}
//
async function onDeleteProperty(record: any) {
try {
const resp = await postDeviceThingModelManagementPropertyDeleteAsync({
body: { id: record.id },
});
if (resp.data) {
Message.success($t('common.deleteSuccess'));
await nextTick();
if (gridApi && gridApi.reload) {
await gridApi.reload();
}
} else {
Message.error($t('common.deleteFail'));
}
} catch (error) {
console.error('删除属性失败:', error);
Message.error($t('common.deleteFail'));
}
}
const [Modal, modalApi] = useVbenModal({
onOpenChange(isOpen: boolean) {
if (isOpen) {
const data = modalApi.getData<Record<string, any>>();
deviceThingModelId.value = data?.deviceThingModelId || '';
deviceModelName.value = data?.deviceModelName || '';
if (gridApi && gridApi.reload) {
gridApi.reload();
}
ioTPlatform.value = data?.ioTPlatform || '';
ioTPlatformProductId.value = data?.ioTPlatformProductId || '';
// 使 nextTick Grid
nextTick(() => {
if (gridApi && gridApi.reload) {
gridApi.reload();
}
});
}
},
});
</script>
<template>
<Modal :title="`属性管理 - ${deviceModelName || ''}`" class="w-[900px]">
<Modal :title="`属性管理 - ${deviceModelName || ''}`" class="w-[1200px]">
<Grid>
<!-- 这里后续可以加 toolbar-actions新增属性快速复制平台端物模型 -->
<template #toolbar-actions>
<TableAction :actions="[
{
label: $t('common.add'),
type: 'primary',
icon: 'ant-design:plus-outlined',
onClick: openAddPropertyModal,
auth: ['AbpIdentity.Users.Create'],
},
{
label: '快速复制平台端物模型',
type: 'default',
icon: 'ant-design:copy-outlined',
onClick: openCopyPropertyModal,
auth: ['AbpIdentity.Users.Create'],
},
{
label: '快速复制设备端物模型',
type: 'default',
icon: 'ant-design:copy-outlined',
onClick: openCopyDeviceThingModelModal,
auth: ['AbpIdentity.Users.Create'],
},
]" />
</template>
<template #action="{ row }">
<TableAction :actions="[
{
label: $t('common.edit'),
type: 'link',
size: 'small',
auth: ['AbpIdentity.Users.Update'],
onClick: openEditPropertyModal.bind(null, row),
},
{
label: $t('common.delete'),
icon: 'ant-design:delete-outlined',
type: 'link',
size: 'small',
auth: ['AbpIdentity.Users.Delete'],
popConfirm: {
title: $t('common.askConfirmDelete'),
confirm: onDeleteProperty.bind(null, row),
},
},
]" />
</template>
</Grid>
<!-- 属性新增/编辑弹窗 -->
<PropertyFormModal :title="editRow.id ? $t('common.edit') : $t('common.add')" class="w-[900px]">
<PropertyForm />
</PropertyFormModal>
<!-- 快速复制平台端物模型属性弹窗 -->
<CopyPropertyModal title="快速复制平台端物模型属性" class="w-[600px]">
<CopyPropertyForm />
</CopyPropertyModal>
<!-- 快速复制设备端物模型属性弹窗 -->
<CopyDeviceThingModelModal
title="快速复制设备端物模型属性"
class="w-[600px]"
>
<CopyDeviceThingModelForm />
</CopyDeviceThingModelModal>
</Modal>
</template>

View File

@ -5,21 +5,27 @@ import type { VxeGridProps } from '#/adapter/vxe-table';
import { h, nextTick, onMounted, ref } from 'vue';
import { useRoute } from 'vue-router';
import { Page, useVbenModal } from '@vben/common-ui';
import { Page, useVbenModal, z } from '@vben/common-ui';
import { message as Message, Tag } from 'ant-design-vue';
import { Button, message as Message, Tag } from 'ant-design-vue';
import { useVbenForm } from '#/adapter/form';
import { useVbenVxeGrid } from '#/adapter/vxe-table';
import {
postDeviceThingModelManagementBuildAnalysisScriptAsync,
postDeviceThingModelManagementBuildAnalysisScriptByIdAsync,
postDeviceThingModelManagementCreateAsync,
postDeviceThingModelManagementDeleteAsync,
postDeviceThingModelManagementMessageAnalysisTestAsync,
postDeviceThingModelManagementPageAsync,
postDeviceThingModelManagementUpdateAnalysisScriptByIdAsync,
postDeviceThingModelManagementUpdateAsync,
} from '#/api-client';
import { TableAction } from '#/components/table-action';
import { $t } from '#/locales';
import DeviceThingModelCommandModal from './DeviceThingModelCommandModal.vue';
import DeviceThingModelPropertyModal from './DeviceThingModelPropertyModal.vue';
import {
addDeviceThingModelFormSchema,
editDeviceThingModelFormSchema,
@ -27,9 +33,6 @@ import {
tableSchema,
} from './schema';
import DeviceThingModelPropertyModal from './DeviceThingModelPropertyModal.vue';
import DeviceThingModelCommandModal from './DeviceThingModelCommandModal.vue';
defineOptions({
name: 'DeviceThingModelManagement',
});
@ -53,25 +56,21 @@ const formOptions: VbenFormProps = {
submitOnChange: false,
handleValuesChange: async (values, changedFields) => {
//
if (changedFields.includes('ioTPlatform')) {
if (values.ioTPlatform) {
ioTPlatform.value = String(values.ioTPlatform);
if (gridApi?.formApi) {
await gridApi.formApi.setValues({
ioTPlatformProductId: undefined,
});
}
productId.value = '';
if (changedFields.includes('ioTPlatform') && values.ioTPlatform) {
ioTPlatform.value = String(values.ioTPlatform);
if (gridApi?.formApi) {
await gridApi.formApi.setValues({
ioTPlatformProductId: undefined,
});
}
productId.value = '';
}
// productId
if (changedFields.includes('ioTPlatformProductId')) {
if (values.ioTPlatformProductId) {
productId.value = String(values.ioTPlatformProductId);
} else {
productId.value = '';
}
productId.value = values.ioTPlatformProductId
? String(values.ioTPlatformProductId)
: '';
setTimeout(async () => {
if (gridApi && gridApi.reload) {
@ -119,9 +118,7 @@ const gridOptions: VxeGridProps<any> = {
//
const latestFormValues =
formValues ||
(gridApi && gridApi.formApi
? await gridApi.formApi.getValues()
: {});
(gridApi && gridApi.formApi ? await gridApi.formApi.getValues() : {});
// 使使
const currentPlatform =
@ -234,6 +231,80 @@ const [EditForm, editFormApi] = useVbenForm({
wrapperClass: 'grid-cols-2',
});
//
const [TestScriptModal, testScriptModalApi] = useVbenModal({
draggable: true,
footer: true,
showCancelButton: true,
showConfirmButton: true,
onConfirm: submitTestScript,
onBeforeClose: () => {
return true;
},
onCancel: () => {
testScriptModalApi.close();
},
});
//
const [TestScriptForm, testScriptFormApi] = useVbenForm({
collapsed: false,
commonConfig: {
labelWidth: 110,
componentProps: {
class: 'w-4/5',
},
},
layout: 'horizontal',
schema: [
{
component: 'Input',
fieldName: 'functionName',
label: '函数名称',
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder: `${$t('common.pleaseInput')}函数名称`,
},
},
{
component: 'Textarea',
fieldName: 'functionCode',
label: '函数代码',
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
rows: 8,
placeholder: `${$t('common.pleaseInput')}函数代码`,
},
},
{
component: 'Textarea',
fieldName: 'parameters',
label: '函数参数JSON格式',
componentProps: {
rows: 6,
placeholder:
'请输入函数参数JSON格式例如{"key1": "value1", "key2": "value2"}',
},
},
],
showCollapseButton: false,
showDefaultActions: false,
wrapperClass: 'grid-cols-1',
});
//
const testResult = ref<string>('');
//
const testDeviceThingModelId = ref<string>('');
const testDeviceModelName = ref<string>('');
// /
async function submit() {
const isEdit = !!editRow.value.id;
@ -267,15 +338,11 @@ async function submit() {
await gridApi.reload(latestValues);
}
} else {
Message.error(
isEdit ? $t('common.editFail') : $t('common.addFail'),
);
Message.error(isEdit ? $t('common.editFail') : $t('common.addFail'));
}
} catch (error) {
console.error('提交设备端物模型失败:', error);
Message.error(
isEdit ? $t('common.editFail') : $t('common.addFail'),
);
Message.error(isEdit ? $t('common.editFail') : $t('common.addFail'));
}
}
@ -290,6 +357,8 @@ async function openPropertyModal(record: any) {
propertyModalApi.setData({
deviceThingModelId: record.id,
deviceModelName: record.deviceModelName,
ioTPlatform: record.ioTPlatform,
ioTPlatformProductId: record.ioTPlatformProductId,
});
propertyModalApi.open();
}
@ -309,6 +378,163 @@ const openAddModal = async () => {
thingModelModalApi.open();
};
//
function openTestScriptModal(record?: any) {
// Id/
if (record && record.id) {
testDeviceThingModelId.value = record.id;
testDeviceModelName.value = record.deviceModelName || '';
} else {
testDeviceThingModelId.value = '';
testDeviceModelName.value = '';
}
testScriptFormApi.resetForm();
testResult.value = '';
testScriptModalApi.open();
}
//
async function buildAndOpenTestScript(record: any) {
if (!record.id) {
Message.warning('设备端物模型ID不存在');
return;
}
// true
if (record.functionAnalysisFlag === true) {
Message.warning('解析启用状态下不允许构建函数');
return;
}
try {
const resp =
await postDeviceThingModelManagementBuildAnalysisScriptByIdAsync({
body: { id: record.id },
});
console.log('函数构建接口返回:', resp.data);
//
openTestScriptModal(record);
//
await nextTick();
await testScriptFormApi.setValues({
functionName: resp.data?.functionName || '',
functionCode: resp.data?.functionCode || '',
parameters: '', //
});
Message.success('函数构建成功');
} catch (error) {
console.error('函数构建失败:', error);
Message.error('函数构建失败');
}
}
// /
async function enableAnalysisScript(record: any) {
if (!record.id) {
Message.warning('设备端物模型ID不存在');
return;
}
try {
const isEnabled = record.functionAnalysisFlag === true;
const resp =
await postDeviceThingModelManagementUpdateAnalysisScriptByIdAsync({
query: {
id: record.id,
},
});
if (resp.data) {
Message.success(isEnabled ? '禁用解析成功' : '启用解析成功');
//
if (gridApi && gridApi.reload) {
await gridApi.reload();
}
} else {
Message.error(isEnabled ? '禁用解析失败' : '启用解析失败');
}
} catch (error) {
console.error('解析启用失败:', error);
Message.error('解析状态更新失败');
}
}
//
async function submitTestScript() {
const { valid } = await testScriptFormApi.validate();
if (!valid) return;
const formValues = await testScriptFormApi.getValues();
// parameters JSON
let parameters: any = null;
if (formValues.parameters) {
try {
parameters = JSON.parse(formValues.parameters);
} catch {
Message.error('函数参数格式错误请输入有效的JSON格式');
return;
}
}
const fetchParams: any = {
functionName: formValues.functionName,
functionCode: formValues.functionCode,
...(parameters && { parameters }),
};
try {
const resp = await postDeviceThingModelManagementMessageAnalysisTestAsync({
body: fetchParams,
});
// JSON
testResult.value = JSON.stringify(resp.data || {}, null, 2);
Message.success('测试执行成功');
} catch (error) {
console.error('函数脚本测试失败:', error);
testResult.value = `测试失败: ${error instanceof Error ? error.message : String(error)}`;
Message.error('函数脚本测试失败');
}
}
//
async function updateFunctionScript() {
if (!testDeviceThingModelId.value) {
Message.warning('当前未关联具体设备端物模型,无法更新函数');
return;
}
const { valid } = await testScriptFormApi.validate();
if (!valid) return;
const formValues = await testScriptFormApi.getValues();
try {
const resp = await postDeviceThingModelManagementBuildAnalysisScriptAsync({
body: {
id: testDeviceThingModelId.value,
scriptName: formValues.functionName,
functionScript: formValues.functionCode,
},
});
if (resp.data) {
Message.success('更新函数成功');
} else {
Message.error('更新函数失败');
}
} catch (error) {
console.error('更新函数失败:', error);
Message.error('更新函数失败');
}
}
//
async function onDel(record: any) {
try {
@ -366,49 +592,49 @@ onMounted(async () => {
<Page auto-content-height>
<Grid>
<template #toolbar-actions>
<TableAction
:actions="[
{
label: $t('common.add'),
type: 'primary',
icon: 'ant-design:plus-outlined',
onClick: openAddModal.bind(null),
auth: ['AbpIdentity.Users.Create'],
},
]"
/>
<TableAction :actions="[
{
label: $t('common.add'),
type: 'primary',
icon: 'ant-design:plus-outlined',
onClick: openAddModal.bind(null),
auth: ['AbpIdentity.Users.Create'],
},
{
label: '函数脚本测试',
type: 'default',
icon: 'ant-design:code-outlined',
onClick: openTestScriptModal,
auth: ['AbpIdentity.Users.Create'],
},
]" />
</template>
<!-- 解析启用列 -->
<template #functionAnalysisFlag="{ row }">
<component
:is="
h(
Tag,
{ color: row.functionAnalysisFlag ? 'green' : 'red' },
() => (row.functionAnalysisFlag ? '是' : '否'),
)
"
/>
<component :is="h(Tag, { color: row.functionAnalysisFlag ? 'green' : 'red' }, () =>
row.functionAnalysisFlag ? '是' : '否',
)
" />
</template>
<template #action="{ row }">
<TableAction
:actions="[
{
label: $t('common.edit'),
type: 'link',
size: 'small',
auth: ['AbpIdentity.Users.Update'],
onClick: onEdit.bind(null, row),
},
{
label: '属性管理',
type: 'link',
size: 'small',
auth: ['AbpIdentity.Users.Update'],
onClick: openPropertyModal.bind(null, row),
},
<TableAction :actions="[
{
label: $t('common.edit'),
type: 'link',
size: 'small',
auth: ['AbpIdentity.Users.Update'],
onClick: onEdit.bind(null, row),
},
{
label: '属性管理',
type: 'link',
size: 'small',
auth: ['AbpIdentity.Users.Update'],
onClick: openPropertyModal.bind(null, row),
},
]" :drop-down-actions="[
{
label: '指令管理',
type: 'link',
@ -416,19 +642,44 @@ onMounted(async () => {
auth: ['AbpIdentity.Users.Update'],
onClick: openCommandModal.bind(null, row),
},
{
label: row.functionAnalysisFlag ? '禁用解析' : '启用解析',
type: 'input',
size: 'small',
auth: ['AbpIdentity.Users.Update'],
style: row.functionAnalysisFlag
? { color: '#ff4d4f' } //
: { color: '#52c41a' }, // 绿
popConfirm: {
title: row.functionAnalysisFlag
? '确定要禁用该设备端物模型的解析吗?'
: '确定要启用该设备端物模型的解析吗?',
confirm: enableAnalysisScript.bind(null, row),
},
},
{
label: '函数构建',
type: 'link',
size: 'small',
auth: ['AbpIdentity.Users.Update'],
disabled: row.functionAnalysisFlag === true,
popConfirm: {
title: '确定要为该设备端物模型执行函数构建吗?',
confirm: buildAndOpenTestScript.bind(null, row),
},
},
{
label: $t('common.delete'),
icon: 'ant-design:delete-outlined',
type: 'link',
size: 'small',
auth: ['AbpIdentity.Users.Delete'],
style: { color: '#ff4d4f' },
popConfirm: {
title: $t('common.askConfirmDelete'),
confirm: onDel.bind(null, row),
},
},
]"
/>
]" />
</template>
</Grid>
@ -438,13 +689,23 @@ onMounted(async () => {
<!-- 指令管理弹窗独立组件 -->
<CommandModal />
<ThingModelModal
:title="editRow.id ? $t('common.edit') : $t('common.add')"
class="w-[800px]"
>
<ThingModelModal :title="editRow.id ? $t('common.edit') : $t('common.add')" class="w-[800px]">
<component :is="editRow.id ? EditForm : AddForm" />
</ThingModelModal>
<!-- 函数脚本测试弹窗 -->
<TestScriptModal title="函数脚本测试" class="w-[900px]">
<div v-if="testDeviceModelName" class="mb-2 flex items-center justify-between text-base font-semibold">
<span>设备端物模型{{ testDeviceModelName }}</span>
<Button v-if="testDeviceThingModelId" type="primary" @click="updateFunctionScript">
更新函数
</Button>
</div>
<TestScriptForm />
<div v-if="testResult" class="mt-4">
<div class="mb-2 font-semibold">测试结果</div>
<pre class="max-h-96 overflow-auto rounded border bg-gray-50 p-4 text-sm">{{ testResult }}</pre>
</div>
</TestScriptModal>
</Page>
</template>

View File

@ -116,7 +116,7 @@ export const tableSchema = computed(() => [
minWidth: 120,
showOverflow: 'tooltip',
formatter: ({ cellValue }: { cellValue: any }) => {
const map: Record<string | number, string> = {
const map: Record<number | string, string> = {
1: 'CTWing',
2: 'OneNET',
};
@ -135,17 +135,6 @@ export const tableSchema = computed(() => [
minWidth: 160,
showOverflow: 'tooltip',
},
{
field: 'parsingSequence',
title: '解析顺序',
minWidth: 120,
showOverflow: 'tooltip',
formatter: ({ cellValue }: { cellValue: any }) => {
if (cellValue === 1) return '正序';
if (cellValue === 2) return '高低位反转';
return cellValue ?? '-';
},
},
{
field: 'functionAnalysisFlag',
title: '解析启用',
@ -163,7 +152,7 @@ export const tableSchema = computed(() => [
{
field: 'action',
title: $t('common.action'),
width: 200,
width: 160,
fixed: 'right',
slots: { default: 'action' },
},
@ -175,18 +164,22 @@ export const addDeviceThingModelFormSchema = computed(() => [
component: 'Input',
fieldName: 'deviceModelName',
label: '设备端物模型名称',
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder: $t('common.pleaseInput') + '设备端物模型名称',
placeholder: `${$t('common.pleaseInput')}设备端物模型名称`,
},
},
{
component: 'ApiSelect',
fieldName: 'ioTPlatform',
label: $t('abp.deviceInfos.ioTPlatform'),
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
api: getCommonGetSelectList,
params: {
@ -219,8 +212,10 @@ export const addDeviceThingModelFormSchema = computed(() => [
component: 'ApiSelect',
fieldName: 'ioTPlatformProductId',
label: $t('common.BelongingProductName'),
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: (formValues: any) => {
const platform = formValues?.ioTPlatform;
@ -265,10 +260,12 @@ export const addDeviceThingModelFormSchema = computed(() => [
component: 'Input',
fieldName: 'scriptName',
label: '脚本函数名称',
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder: $t('common.pleaseInput') + '脚本函数名称',
placeholder: `${$t('common.pleaseInput')}脚本函数名称`,
},
},
{
@ -288,18 +285,22 @@ export const editDeviceThingModelFormSchema = computed(() => [
component: 'Input',
fieldName: 'deviceModelName',
label: '设备端物模型名称',
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder: $t('common.pleaseInput') + '设备端物模型名称',
placeholder: `${$t('common.pleaseInput')}设备端物模型名称`,
},
},
{
component: 'ApiSelect',
fieldName: 'ioTPlatform',
label: $t('abp.deviceInfos.ioTPlatform'),
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
api: getCommonGetSelectList,
params: {
@ -333,8 +334,10 @@ export const editDeviceThingModelFormSchema = computed(() => [
component: 'ApiSelect',
fieldName: 'ioTPlatformProductId',
label: $t('common.BelongingProductName'),
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: (formValues: any) => {
const platform = formValues?.ioTPlatform;
@ -380,10 +383,12 @@ export const editDeviceThingModelFormSchema = computed(() => [
component: 'Input',
fieldName: 'scriptName',
label: '脚本函数名称',
rules: z
.preprocess((v) => (v == null ? '' : v), z.string().min(1, $t('common.required'))),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder: $t('common.pleaseInput') + '脚本函数名称',
placeholder: `${$t('common.pleaseInput')}脚本函数名称`,
},
},
{
@ -396,5 +401,3 @@ export const editDeviceThingModelFormSchema = computed(() => [
},
},
]);