平台物模型管理完善

This commit is contained in:
ChenYi 2025-12-22 14:21:38 +08:00
parent 45240cac7e
commit ba5a5ee8f4
7 changed files with 525 additions and 133 deletions

View File

@ -3055,11 +3055,6 @@ export const DeviceThingModelPropertyUpdateInputSchema = {
description: '设备端物模型Id',
format: 'uuid'
},
filedType: {
type: 'string',
description: '物联网平台中对应产品物模型属性或者事件类型 JiShe.ServicePro.Core.DataDictionaryTypeConst',
nullable: true
},
ioTPlatformRawFieldName: {
minLength: 1,
type: 'string',
@ -5196,6 +5191,7 @@ export const IoTDBDynamicObjectPagedResultDtoSchema = {
} as const;
export const IoTPlatformProductInfoInputSchema = {
required: ['ioTPlatformType'],
type: 'object',
properties: {
ioTPlatformType: {
@ -5227,6 +5223,23 @@ export const IoTPlatformProductInfoOutputSchema = {
description: '平台产品信息'
} as const;
export const IoTPlatformProductPropertyInfoInputSchema = {
required: ['ioTPlatformProductId', 'ioTPlatformType'],
type: 'object',
properties: {
ioTPlatformType: {
'$ref': '#/components/schemas/IoTPlatformTypeEnum'
},
ioTPlatformProductId: {
minLength: 1,
type: 'string',
description: '物联网平台中对应的产品Id'
}
},
additionalProperties: false,
description: '产品属性信息输入'
} as const;
export const IoTPlatformThingModelCreateInputSchema = {
type: 'object',
properties: {
@ -5266,6 +5279,20 @@ export const IoTPlatformThingModelCreateInputSchema = {
isValueNeedConvert: {
type: 'boolean',
description: '是否需要值类型转换'
},
ioTPlatformRawFieldDataType: {
type: 'string',
description: '物联网平台中对应的产品物模型属性或者事件数据类型int等/>',
nullable: true
},
isSpecialIdentifier: {
type: 'boolean',
description: '是否是特殊物模型标识符'
},
ioTPlatformRawFieldExtension: {
type: 'string',
description: '物联网平台中对应产品物模型标识符扩展,用于扩展结构体类型',
nullable: true
}
},
additionalProperties: false
@ -5503,6 +5530,20 @@ export const IoTPlatformThingModelUpdateInputSchema = {
type: 'boolean',
description: '是否需要值类型转换'
},
ioTPlatformRawFieldDataType: {
type: 'string',
description: '物联网平台中对应的产品物模型属性或者事件数据类型int等/>',
nullable: true
},
isSpecialIdentifier: {
type: 'boolean',
description: '是否是特殊物模型标识符'
},
ioTPlatformRawFieldExtension: {
type: 'string',
description: '物联网平台中对应产品物模型标识符扩展,用于扩展结构体类型',
nullable: true
},
id: {
type: 'string',
format: 'uuid'

File diff suppressed because one or more lines are too long

View File

@ -1752,10 +1752,6 @@ export type DeviceThingModelPropertyUpdateInput = {
* Id
*/
deviceThingModelId: string;
/**
* JiShe.ServicePro.Core.DataDictionaryTypeConst
*/
filedType?: (string) | null;
/**
*
*/
@ -2586,7 +2582,7 @@ export type IoTDBDynamicObjectPagedResultDto = {
*
*/
export type IoTPlatformProductInfoInput = {
ioTPlatformType?: IoTPlatformTypeEnum;
ioTPlatformType: IoTPlatformTypeEnum;
};
/**
@ -2604,6 +2600,17 @@ export type IoTPlatformProductInfoOutput = {
productName?: (string) | null;
};
/**
*
*/
export type IoTPlatformProductPropertyInfoInput = {
ioTPlatformType: IoTPlatformTypeEnum;
/**
* Id
*/
ioTPlatformProductId: string;
};
export type IoTPlatformThingModelCreateInput = {
ioTPlatform?: IoTPlatformTypeEnum;
/**
@ -2634,6 +2641,18 @@ export type IoTPlatformThingModelCreateInput = {
*
*/
isValueNeedConvert?: boolean;
/**
* int等/>
*/
ioTPlatformRawFieldDataType?: (string) | null;
/**
*
*/
isSpecialIdentifier?: boolean;
/**
*
*/
ioTPlatformRawFieldExtension?: (string) | null;
};
/**
@ -2783,6 +2802,18 @@ export type IoTPlatformThingModelUpdateInput = {
*
*/
isValueNeedConvert?: boolean;
/**
* int等/>
*/
ioTPlatformRawFieldDataType?: (string) | null;
/**
*
*/
isSpecialIdentifier?: boolean;
/**
*
*/
ioTPlatformRawFieldExtension?: (string) | null;
id?: string;
};
@ -6238,6 +6269,16 @@ export type PostAggregationIoTplatformGetIoTplatformProductInfoAsyncResponse = (
export type PostAggregationIoTplatformGetIoTplatformProductInfoAsyncError = unknown;
export type PostAggregationIoTplatformGetIoTplatformProductPropertyInfoAsyncData = {
query?: {
input?: IoTPlatformProductPropertyInfoInput;
};
};
export type PostAggregationIoTplatformGetIoTplatformProductPropertyInfoAsyncResponse = (Array<SelectResult>);
export type PostAggregationIoTplatformGetIoTplatformProductPropertyInfoAsyncError = unknown;
export type PostIoTplatformThingModelInfoCreateAsyncData = {
query?: {
input?: IoTPlatformThingModelCreateInput;

View File

@ -9,6 +9,7 @@ import { Page, useVbenModal } from '@vben/common-ui';
import {
Button,
Input,
message as Message,
Modal,
Popover,
@ -30,6 +31,7 @@ import { Icon } from '#/components/icon';
import { Loading } from '#/components/Loading';
import { TableAction } from '#/components/table-action';
import { $t } from '#/locales';
import IoTPlatformThingModelDataSelect from '#/views/thingmodelinfo/IoTPlatformThingModelDataSelect.vue';
import {
addDeviceFormSchema,
@ -159,6 +161,72 @@ const cacheRefreshLoading = ref(false);
const pageLoading = ref(false);
const loadingTip = ref('缓存刷新中...');
const commandRow: Record<string, any> = ref({});
// -
interface CommandProperty {
id: string; //
propertyId: string; // ID
propertyName: string; //
platformFieldName: string; //
standardFieldName: string; //
value: string; //
}
const commandProperties = ref<CommandProperty[]>([
{
id: `prop-${Date.now()}`,
propertyId: '',
propertyName: '',
platformFieldName: '',
standardFieldName: '',
value: '',
},
]);
// -
const addCommandProperty = () => {
commandProperties.value.push({
id: `prop-${Date.now()}-${Math.random()}`,
propertyId: '',
propertyName: '',
platformFieldName: '',
standardFieldName: '',
value: '',
});
};
// -
const removeCommandProperty = (id: string) => {
const index = commandProperties.value.findIndex((p) => p.id === id);
if (index > -1) {
commandProperties.value.splice(index, 1);
}
//
if (commandProperties.value.length === 0) {
addCommandProperty();
}
};
//
const handlePropertyChange = (item: any, property: CommandProperty) => {
if (item) {
property.propertyId = item.id || item.value || '';
property.propertyName =
item.standardFieldDisplayName ||
item.ioTPlatformRawFieldName ||
item.label ||
'';
// 使使
property.platformFieldName = item.ioTPlatformRawFieldName || item.standardFieldName || '';
property.standardFieldName = item.standardFieldName || '';
} else {
property.propertyId = '';
property.propertyName = '';
property.platformFieldName = '';
property.standardFieldName = '';
}
};
const [UserModal, userModalApi] = useVbenModal({
draggable: true,
onConfirm: submit,
@ -173,8 +241,27 @@ const [CommandModal, commandModalApi] = useVbenModal({
onConfirm: submitCommand,
onBeforeClose: () => {
commandRow.value = {};
//
commandProperties.value = [
{
id: `prop-${Date.now()}`,
propertyId: '',
propertyName: '',
platformFieldName: '',
standardFieldName: '',
value: '',
},
];
return true;
},
onOpenChange: (isOpen: boolean) => {
if (isOpen && commandRow.value) {
// -
if (commandProperties.value.length === 0) {
addCommandProperty();
}
}
},
});
const [AddForm, addFormApi] = useVbenForm({
@ -454,24 +541,65 @@ const openAddModal = async () => {
const openCommandModal = (row: Record<string, any>) => {
commandRow.value = row;
//
commandProperties.value = [
{
id: `prop-${Date.now()}`,
propertyId: '',
propertyName: '',
platformFieldName: '',
standardFieldName: '',
value: '',
},
];
commandModalApi.open();
};
//
async function submitCommand() {
const { valid } = await commandFormApi.validate();
if (!valid) return;
// -
const invalidProperties = commandProperties.value.filter(
(p) => !p.propertyId || !p.value || p.value.trim() === '',
);
const formValues = await commandFormApi.getValues();
if (invalidProperties.length > 0) {
Message.error('请完整填写所有属性名称和指令值');
return;
}
// -
const validProperties = commandProperties.value.filter(
(p) => {
const key = p.platformFieldName || p.standardFieldName || p.propertyName;
return key && p.value && p.value.trim() !== '';
},
);
if (validProperties.length === 0) {
Message.error('请至少添加一个有效的属性-值对');
return;
}
try {
commandModalApi.setState({ loading: true, confirmLoading: true });
// -
const commandContent: Record<string, any> = {};
commandProperties.value.forEach((prop) => {
if (prop.value && prop.value.trim() !== '') {
// 使使使
const key = prop.platformFieldName || prop.standardFieldName || prop.propertyName;
if (key) {
commandContent[key] = prop.value;
}
}
});
//
const result = await postAggregationDeviceDeviceCommandForApiAsync({
body: {
Id: commandRow.value.id,
CommandContent: formValues.CommandContent,
id: commandRow.value.id,
commandContent: commandContent,
},
});
@ -479,6 +607,17 @@ async function submitCommand() {
Message.success('指令下发成功');
commandModalApi.close();
commandRow.value = {};
//
commandProperties.value = [
{
id: `prop-${Date.now()}`,
propertyId: '',
propertyName: '',
platformFieldName: '',
standardFieldName: '',
value: '',
},
];
} else {
Message.error('指令下发失败');
}
@ -852,8 +991,81 @@ const toolbarActions = computed(() => [
<UserModal :title="editRow.id ? $t('common.edit') : $t('common.add')" class="w-[800px]">
<component :is="editRow.id ? EditForm : AddForm" />
</UserModal>
<CommandModal :title="$t('abp.IoTDBBase.Command')" class="w-[600px]">
<CommandForm />
<CommandModal :title="$t('abp.IoTDBBase.Command')" class="w-[800px]">
<div class="command-form-container">
<div
v-for="(property, index) in commandProperties"
:key="property.id"
class="command-property-item"
style="margin-bottom: 16px; padding: 16px; border: 1px solid #d9d9d9; border-radius: 4px; background: #fafafa"
>
<div class="flex items-center justify-between mb-2">
<span class="text-sm font-medium" style="color: #666">
属性-值对 {{ index + 1 }}
</span>
<Button
v-if="commandProperties.length > 1"
type="text"
danger
size="small"
@click="removeCommandProperty(property.id)"
>
<template #icon>
<Icon icon="ant-design:delete-outlined" />
</template>
删除
</Button>
</div>
<div class="flex gap-4" style="align-items: flex-start">
<div style="flex: 1; min-width: 0; max-width: 50%">
<label class="block mb-2 text-sm" style="color: #333">
平台物模型属性 <span style="color: #ff4d4f">*</span>
</label>
<IoTPlatformThingModelDataSelect
:value="property.propertyId"
:ioTPlatform="
commandRow.ioTPlatform
? Number.parseInt(String(commandRow.ioTPlatform))
: null
"
:ioTPlatformProductId="commandRow.ioTPlatformProductId || null"
placeholder="请选择物模型属性"
style="width: 100%"
@update:value="
(val) => {
property.propertyId = val || '';
}
"
@item-change="(item) => handlePropertyChange(item, property)"
/>
</div>
<div style="flex: 1; min-width: 0">
<label class="block mb-2 text-sm" style="color: #333">
指令值 <span style="color: #ff4d4f">*</span>
</label>
<Input
v-model:value="property.value"
placeholder="请输入指令值"
style="width: 100%"
/>
</div>
</div>
<div v-if="property.propertyName" class="mt-2 text-xs" style="color: #999">
已选择属性: {{ property.propertyName }}
<span v-if="property.platformFieldName" style="margin-left: 8px; color: #666">
(平台字段: {{ property.platformFieldName }})
</span>
</div>
</div>
<div class="mt-4">
<Button type="dashed" block @click="addCommandProperty">
<template #icon>
<Icon icon="ant-design:plus-outlined" />
</template>
新增属性-值对
</Button>
</div>
</div>
</CommandModal>
<BatchAddModal title="批量添加设备" class="w-[800px]">
<BatchAddForm />

View File

@ -793,20 +793,7 @@ export const editDeviceFormSchemaEdit: any = computed(() => [
]);
export const commandFormSchema: any = computed(() => [
{
component: 'Textarea',
fieldName: 'CommandContent',
label: $t('abp.IoTDBBase.Command'),
componentProps: {
rows: 8,
placeholder: $t('common.pleaseInput') + $t('abp.IoTDBBase.Command'),
showCount: true,
maxLength: 1000,
},
rules: z.string().min(1, {
message: `${$t('common.pleaseInput')}${$t('abp.IoTDBBase.Command')}`,
}),
},
// 指令表单不再使用schema字段改为在模板中动态渲染
]);
export const batchAddDeviceFormSchema: any = computed(() => [

View File

@ -208,7 +208,8 @@ const [CopyModal, copyModalApi] = useVbenModal({
const [AddForm, addFormApi] = useVbenForm({
collapsed: false,
commonConfig: {
labelWidth: 110,
labelWidth: 150,
labelClass: 'whitespace-nowrap',
componentProps: {
class: 'w-4/5',
},
@ -223,7 +224,8 @@ const [AddForm, addFormApi] = useVbenForm({
const [EditForm, editFormApi] = useVbenForm({
collapsed: false,
commonConfig: {
labelWidth: 110,
labelWidth: 150,
labelClass: 'whitespace-nowrap',
componentProps: {
class: 'w-4/5',
},

View File

@ -217,57 +217,6 @@ export const addThingModelFormSchema = computed(() => [
},
},
},
{
component: 'Input',
fieldName: 'ioTPlatformRawFieldName',
label: $t('abp.thingModelInfos.IoTPlatformRawFieldName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder:
$t('common.pleaseInput') +
$t('abp.thingModelInfos.IoTPlatformRawFieldName'),
},
},
{
component: 'StandardThingModelCodeSelect',
fieldName: 'standardFieldDisplayName',
label: $t('abp.thingModelInfos.StandardFieldDisplayName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: (formValues: any) => ({
// 传入联动的类型编码(运行时具体值)
typeCode: formValues?.filedType ?? null,
onResolve: (item: any | null) => {
// 选择后自动回填:名称=displayText值类型=extendedAttribute(转大写)
formValues.standardFieldDisplayName = item?.displayText ?? '';
formValues.standardFieldName = item?.code ?? '';
formValues.standardFieldValueType = (item?.extendedAttribute ?? '')
.toString()
.toUpperCase();
},
placeholder:
$t('common.pleaseSelect') + $t('abp.thingModelInfos.StandardFieldName'),
}),
},
{
component: 'Input',
fieldName: 'standardFieldName',
label: $t('abp.thingModelInfos.StandardFieldName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder:
$t('common.pleaseInput') +
$t('abp.thingModelInfos.StandardFieldDisplayName'),
},
},
{
component: 'ApiSelect',
fieldName: 'standardFieldValueType',
@ -306,6 +255,57 @@ export const addThingModelFormSchema = computed(() => [
},
},
},
{
component: 'StandardThingModelCodeSelect',
fieldName: 'standardFieldDisplayName',
label: $t('abp.thingModelInfos.StandardFieldDisplayName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: (formValues: any) => ({
// 传入联动的类型编码(运行时具体值)
typeCode: formValues?.filedType ?? null,
onResolve: (item: any | null) => {
// 选择后自动回填:名称=displayText值类型=extendedAttribute(转大写)
formValues.standardFieldDisplayName = item?.displayText ?? '';
formValues.standardFieldName = item?.code ?? '';
formValues.standardFieldValueType = (item?.extendedAttribute ?? '')
.toString()
.toUpperCase();
},
placeholder:
$t('common.pleaseSelect') + $t('abp.thingModelInfos.StandardFieldName'),
}),
},
{
component: 'Input',
fieldName: 'standardFieldName',
label: $t('abp.thingModelInfos.StandardFieldName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder:
$t('common.pleaseInput') +
$t('abp.thingModelInfos.StandardFieldDisplayName'),
},
},
{
component: 'Input',
fieldName: 'ioTPlatformRawFieldName',
label: $t('abp.thingModelInfos.IoTPlatformRawFieldName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
placeholder:
$t('common.pleaseInput') +
$t('abp.thingModelInfos.IoTPlatformRawFieldName'),
},
},
{
component: 'Switch',
fieldName: 'isValueNeedConvert',
@ -315,6 +315,56 @@ export const addThingModelFormSchema = computed(() => [
style: { width: 'auto' },
},
},
{
component: 'ApiSelect',
fieldName: 'ioTPlatformRawFieldDataType',
label: '平台物模型值类型',
componentProps: {
api: getCommonGetSelectList,
params: {
query: {
typeName: 'StandardThingModelDataTypeEnum',
},
},
labelField: 'value',
valueField: 'secondValue',
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;
}
return [];
},
},
},
{
component: 'Switch',
fieldName: 'isSpecialIdentifier',
label: '是否特殊物模型标识符',
defaultValue: false,
componentProps: {
style: { width: 'auto' },
},
},
{
component: 'Textarea',
fieldName: 'ioTPlatformRawFieldExtension',
label: '平台物模型值类型扩展',
componentProps: {
rows: 4,
placeholder: '请输入平台原始字段扩展信息用于扩展结构体类型JSON格式',
},
},
]);
// 编辑物模型表单schema
@ -356,54 +406,6 @@ export const editThingModelFormSchema = computed(() => [
},
},
},
{
component: 'Input',
fieldName: 'ioTPlatformRawFieldName',
label: $t('abp.thingModelInfos.IoTPlatformRawFieldName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
// disabled: true, // 编辑时禁用
placeholder:
$t('common.pleaseInput') +
$t('abp.thingModelInfos.IoTPlatformRawFieldName'),
},
},
{
component: 'StandardThingModelCodeSelect',
fieldName: 'standardFieldDisplayName',
label: $t('abp.thingModelInfos.StandardFieldDisplayName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: (formValues: any) => ({
typeCode: formValues?.filedType ?? null,
disabled: true, // 编辑时禁用
onResolve: (item: any | null) => {
formValues.standardFieldDisplayName = item?.displayText ?? '';
formValues.standardFieldValueType = (item?.extendedAttribute ?? '')
.toString()
.toUpperCase();
},
placeholder:
$t('common.pleaseInput') +
$t('abp.thingModelInfos.StandardFieldDisplayName'),
}),
},
{
component: 'Input',
fieldName: 'standardFieldName',
label: $t('abp.thingModelInfos.StandardFieldName'),
rules: z.string().min(1, $t('common.required')),
componentProps: {
disabled: true, // 编辑时禁用
placeholder:
$t('common.pleaseSelect') + $t('abp.thingModelInfos.StandardFieldName'),
},
},
{
component: 'ApiSelect',
fieldName: 'standardFieldValueType',
@ -445,6 +447,54 @@ export const editThingModelFormSchema = computed(() => [
},
},
},
{
component: 'StandardThingModelCodeSelect',
fieldName: 'standardFieldDisplayName',
label: $t('abp.thingModelInfos.StandardFieldDisplayName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: (formValues: any) => ({
typeCode: formValues?.filedType ?? null,
disabled: true, // 编辑时禁用
onResolve: (item: any | null) => {
formValues.standardFieldDisplayName = item?.displayText ?? '';
formValues.standardFieldValueType = (item?.extendedAttribute ?? '')
.toString()
.toUpperCase();
},
placeholder:
$t('common.pleaseInput') +
$t('abp.thingModelInfos.StandardFieldDisplayName'),
}),
},
{
component: 'Input',
fieldName: 'standardFieldName',
label: $t('abp.thingModelInfos.StandardFieldName'),
rules: z.string().min(1, $t('common.required')),
componentProps: {
disabled: true, // 编辑时禁用
placeholder:
$t('common.pleaseSelect') + $t('abp.thingModelInfos.StandardFieldName'),
},
},
{
component: 'Input',
fieldName: 'ioTPlatformRawFieldName',
label: $t('abp.thingModelInfos.IoTPlatformRawFieldName'),
rules: z.preprocess(
(v) => (v == null ? '' : v),
z.string().min(1, $t('common.required')),
),
componentProps: {
// disabled: true, // 编辑时禁用
placeholder:
$t('common.pleaseInput') +
$t('abp.thingModelInfos.IoTPlatformRawFieldName'),
},
},
{
component: 'Switch',
fieldName: 'isValueNeedConvert',
@ -453,6 +503,55 @@ export const editThingModelFormSchema = computed(() => [
style: { width: 'auto' },
},
},
{
component: 'ApiSelect',
fieldName: 'ioTPlatformRawFieldDataType',
label: '平台物模型值类型',
componentProps: {
api: getCommonGetSelectList,
params: {
query: {
typeName: 'StandardThingModelDataTypeEnum',
},
},
labelField: 'value',
valueField: 'secondValue',
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;
}
return [];
},
},
},
{
component: 'Switch',
fieldName: 'isSpecialIdentifier',
label: '是否特殊物模型标识符',
componentProps: {
style: { width: 'auto' },
},
},
{
component: 'Textarea',
fieldName: 'ioTPlatformRawFieldExtension',
label: '平台物模型值类型扩展',
componentProps: {
rows: 4,
placeholder: '请输入平台原始字段扩展信息用于扩展结构体类型JSON格式',
},
},
]);
// 复制已有模型表单schema