iot产品管理问题
1.修复物模型列表无限加载的问题 2.修复物模型管理页面添加,TSL,编辑,删除,功能类型选项功能不用问题 3.修复TSL按钮物模型接口没有的问题 4.修复物模型新增编辑页面的属性不能正常编辑修改问题美化显示 iot设备管理问题 1.修复新增编辑页面缺少字段相关组件 2.修复设备详情中子页面不显示问题 3.修复设备详情子页面物模型数据页面不显示问题 4.修复模拟设备右侧不显示问题 右侧溢出,改为上下分栏 Signed-off-by: Administrator <425053404@qq.com>
This commit is contained in:
@@ -2,8 +2,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { ThingModelEvent } from '#/api/iot/thingmodel';
|
||||
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { isEmpty } from '@vben/utils';
|
||||
@@ -27,7 +25,7 @@ const thingModelEvent = useVModel(
|
||||
props,
|
||||
'modelValue',
|
||||
emits,
|
||||
) as Ref<ThingModelEvent>;
|
||||
) as Ref<any>;
|
||||
|
||||
// 默认选中,INFO 信息
|
||||
watch(
|
||||
@@ -43,9 +41,9 @@ watch(
|
||||
<Form.Item
|
||||
:rules="[{ required: true, message: '请选择事件类型', trigger: 'change' }]"
|
||||
label="事件类型"
|
||||
prop="event.type"
|
||||
name="event.type"
|
||||
>
|
||||
<Radio.Group v-model="thingModelEvent.type">
|
||||
<Radio.Group v-model:value="thingModelEvent.type">
|
||||
<Radio
|
||||
v-for="eventType in Object.values(IoTThingModelEventTypeEnum)"
|
||||
:key="eventType.value"
|
||||
@@ -57,7 +55,7 @@ watch(
|
||||
</Form.Item>
|
||||
<Form.Item label="输出参数">
|
||||
<ThingModelInputOutputParam
|
||||
v-model="thingModelEvent.outputData"
|
||||
v-model="thingModelEvent.outputParams"
|
||||
:direction="IoTThingModelParamDirectionEnum.OUTPUT"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -12,7 +12,7 @@ import { getDictOptions } from '@vben/hooks';
|
||||
import { $t } from '@vben/locales';
|
||||
import { cloneDeep } from '@vben/utils';
|
||||
|
||||
import { Button, Form, Input, message, Radio } from 'ant-design-vue';
|
||||
import { Form, Input, message, Modal, Radio } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
createThingModel,
|
||||
@@ -40,8 +40,8 @@ const dialogVisible = ref(false); // 弹窗的是否展示
|
||||
const dialogTitle = ref(''); // 弹窗的标题
|
||||
const formLoading = ref(false); // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
|
||||
const formType = ref(''); // 表单的类型:create - 新增;update - 修改
|
||||
const formData = ref<ThingModelData>({
|
||||
type: IoTThingModelTypeEnum.PROPERTY.toString(),
|
||||
const formData = ref<any>({
|
||||
type: IoTThingModelTypeEnum.PROPERTY,
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
property: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
@@ -49,8 +49,13 @@ const formData = ref<ThingModelData>({
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
},
|
||||
service: {},
|
||||
event: {},
|
||||
service: {
|
||||
inputParams: [],
|
||||
outputParams: [],
|
||||
},
|
||||
event: {
|
||||
outputParams: [],
|
||||
},
|
||||
});
|
||||
|
||||
const formRef = ref(); // 表单 Ref
|
||||
@@ -58,13 +63,19 @@ const formRef = ref(); // 表单 Ref
|
||||
/** 打开弹窗 */
|
||||
const open = async (type: string, id?: number) => {
|
||||
dialogVisible.value = true;
|
||||
dialogTitle.value = $t(`action.${type}`);
|
||||
// 设置标题:create -> 新增,update -> 编辑
|
||||
dialogTitle.value = type === 'create' ? $t('page.action.add') : $t('page.action.edit');
|
||||
formType.value = type;
|
||||
resetForm();
|
||||
if (id) {
|
||||
formLoading.value = true;
|
||||
try {
|
||||
formData.value = await getThingModel(id);
|
||||
const result = await getThingModel(id);
|
||||
// 转换类型为数字
|
||||
formData.value = {
|
||||
...result,
|
||||
type: Number(result.type),
|
||||
};
|
||||
// 情况一:属性初始化
|
||||
if (
|
||||
!formData.value.property ||
|
||||
@@ -77,20 +88,50 @@ const open = async (type: string, id?: number) => {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
};
|
||||
} else {
|
||||
// 确保 dataSpecs 和 dataSpecsList 存在
|
||||
if (!formData.value.property.dataSpecs) {
|
||||
formData.value.property.dataSpecs = {};
|
||||
}
|
||||
if (!formData.value.property.dataSpecsList) {
|
||||
formData.value.property.dataSpecsList = [];
|
||||
}
|
||||
// 如果 property.dataType 不存在,设置为默认值
|
||||
if (!formData.value.property.dataType) {
|
||||
formData.value.property.dataType = IoTDataSpecsDataTypeEnum.INT;
|
||||
}
|
||||
}
|
||||
// 情况二:服务初始化
|
||||
if (
|
||||
!formData.value.service ||
|
||||
Object.keys(formData.value.service).length === 0
|
||||
) {
|
||||
formData.value.service = {};
|
||||
formData.value.service = {
|
||||
inputParams: [],
|
||||
outputParams: [],
|
||||
};
|
||||
} else {
|
||||
// 确保参数数组存在
|
||||
if (!formData.value.service.inputParams) {
|
||||
formData.value.service.inputParams = [];
|
||||
}
|
||||
if (!formData.value.service.outputParams) {
|
||||
formData.value.service.outputParams = [];
|
||||
}
|
||||
}
|
||||
// 情况三:事件初始化
|
||||
if (
|
||||
!formData.value.event ||
|
||||
Object.keys(formData.value.event).length === 0
|
||||
) {
|
||||
formData.value.event = {};
|
||||
formData.value.event = {
|
||||
outputParams: [],
|
||||
};
|
||||
} else {
|
||||
// 确保参数数组存在
|
||||
if (!formData.value.event.outputParams) {
|
||||
formData.value.event.outputParams = [];
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
formLoading.value = false;
|
||||
@@ -137,6 +178,13 @@ function fillExtraAttributes(data: any) {
|
||||
data.dataType = data.service.dataType;
|
||||
data.service.identifier = data.identifier;
|
||||
data.service.name = data.name;
|
||||
// 保留输入输出参数,但如果为空数组则删除
|
||||
if (!data.service.inputParams || data.service.inputParams.length === 0) {
|
||||
delete data.service.inputParams;
|
||||
}
|
||||
if (!data.service.outputParams || data.service.outputParams.length === 0) {
|
||||
delete data.service.outputParams;
|
||||
}
|
||||
delete data.property;
|
||||
delete data.event;
|
||||
}
|
||||
@@ -146,6 +194,10 @@ function fillExtraAttributes(data: any) {
|
||||
data.dataType = data.event.dataType;
|
||||
data.event.identifier = data.identifier;
|
||||
data.event.name = data.name;
|
||||
// 保留输出参数,但如果为空数组则删除
|
||||
if (!data.event.outputParams || data.event.outputParams.length === 0) {
|
||||
delete data.event.outputParams;
|
||||
}
|
||||
delete data.property;
|
||||
delete data.service;
|
||||
}
|
||||
@@ -164,7 +216,7 @@ function removeDataSpecs(val: any) {
|
||||
/** 重置表单 */
|
||||
function resetForm() {
|
||||
formData.value = {
|
||||
type: IoTThingModelTypeEnum.PROPERTY.toString(),
|
||||
type: IoTThingModelTypeEnum.PROPERTY,
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
property: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
@@ -172,18 +224,27 @@ function resetForm() {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
},
|
||||
service: {},
|
||||
event: {},
|
||||
service: {
|
||||
inputParams: [],
|
||||
outputParams: [],
|
||||
},
|
||||
event: {
|
||||
outputParams: [],
|
||||
},
|
||||
};
|
||||
formRef.value?.resetFields();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal v-model="dialogVisible" :title="dialogTitle">
|
||||
<Modal
|
||||
v-model:open="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
:confirm-loading="formLoading"
|
||||
@ok="submitForm"
|
||||
>
|
||||
<Form
|
||||
ref="formRef"
|
||||
:loading="formLoading"
|
||||
:model="formData"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
@@ -191,12 +252,9 @@ function resetForm() {
|
||||
<Form.Item label="功能类型" name="type">
|
||||
<Radio.Group v-model:value="formData.type">
|
||||
<Radio.Button
|
||||
v-for="(dict, index) in getDictOptions(
|
||||
DICT_TYPE.IOT_THING_MODEL_TYPE,
|
||||
'number',
|
||||
)"
|
||||
:key="index"
|
||||
:value="dict.value"
|
||||
v-for="dict in getDictOptions(DICT_TYPE.IOT_THING_MODEL_TYPE)"
|
||||
:key="String(dict.value)"
|
||||
:value="Number(dict.value)"
|
||||
>
|
||||
{{ dict.label }}
|
||||
</Radio.Button>
|
||||
@@ -210,17 +268,17 @@ function resetForm() {
|
||||
</Form.Item>
|
||||
<!-- 属性配置 -->
|
||||
<ThingModelProperty
|
||||
v-if="formData.type === IoTThingModelTypeEnum.PROPERTY.toString()"
|
||||
v-if="formData.type === IoTThingModelTypeEnum.PROPERTY"
|
||||
v-model="formData.property"
|
||||
/>
|
||||
<!-- 服务配置 -->
|
||||
<ThingModelService
|
||||
v-if="formData.type === IoTThingModelTypeEnum.SERVICE.toString()"
|
||||
v-if="formData.type === IoTThingModelTypeEnum.SERVICE"
|
||||
v-model="formData.service"
|
||||
/>
|
||||
<!-- 事件配置 -->
|
||||
<ThingModelEvent
|
||||
v-if="formData.type === IoTThingModelTypeEnum.EVENT.toString()"
|
||||
v-if="formData.type === IoTThingModelTypeEnum.EVENT"
|
||||
v-model="formData.event"
|
||||
/>
|
||||
<Form.Item label="描述" name="desc">
|
||||
@@ -232,12 +290,5 @@ function resetForm() {
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
|
||||
<template #footer>
|
||||
<Button :disabled="formLoading" type="primary" @click="submitForm">
|
||||
确 定
|
||||
</Button>
|
||||
<Button @click="dialogVisible = false">取 消</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -29,6 +29,7 @@ const formData = ref<any>({
|
||||
dataSpecs: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
dataSpecsList: [],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -40,16 +41,22 @@ function openParamForm(val: any) {
|
||||
return;
|
||||
}
|
||||
// 编辑时回显数据
|
||||
const valData = val as any;
|
||||
formData.value = {
|
||||
identifier: val.identifier,
|
||||
name: val.name,
|
||||
description: val.description,
|
||||
identifier: valData?.identifier || '',
|
||||
name: valData?.name || '',
|
||||
description: valData?.description || '',
|
||||
property: {
|
||||
dataType: val.dataType,
|
||||
dataSpecs: val.dataSpecs,
|
||||
dataSpecsList: val.dataSpecsList,
|
||||
dataType: valData?.dataType || IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: valData?.dataSpecs ?? {},
|
||||
dataSpecsList: valData?.dataSpecsList ?? [],
|
||||
},
|
||||
};
|
||||
|
||||
// 确保 property.dataType 有值
|
||||
if (!formData.value.property.dataType) {
|
||||
formData.value.property.dataType = IoTDataSpecsDataTypeEnum.INT;
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除 param 项 */
|
||||
@@ -108,6 +115,7 @@ function resetForm() {
|
||||
dataSpecs: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
dataSpecsList: [],
|
||||
},
|
||||
};
|
||||
paramFormRef.value?.resetFields();
|
||||
@@ -122,35 +130,34 @@ function resetForm() {
|
||||
>
|
||||
<span>参数名称:{{ item.name }}</span>
|
||||
<div class="btn">
|
||||
<Button link type="primary" @click="openParamForm(item)"> 编辑 </Button>
|
||||
<Divider direction="vertical" />
|
||||
<Button link danger @click="deleteParamItem(index)"> 删除 </Button>
|
||||
<Button type="link" @click="openParamForm(item)">编辑</Button>
|
||||
<Divider type="vertical" />
|
||||
<Button type="link" danger @click="deleteParamItem(index)">删除</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button link type="primary" @click="openParamForm(null)"> +新增参数 </Button>
|
||||
<Button type="link" @click="openParamForm(null)">+新增参数</Button>
|
||||
|
||||
<!-- param 表单 -->
|
||||
<Modal v-model="dialogVisible" title="新增参数" append-to-body>
|
||||
<Modal
|
||||
v-model:open="dialogVisible"
|
||||
title="新增参数"
|
||||
:confirm-loading="formLoading"
|
||||
@ok="submitForm"
|
||||
>
|
||||
<Form
|
||||
ref="paramFormRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
label-width="100px"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<Form.Item label="参数名称" prop="name">
|
||||
<Input v-model="formData.name" placeholder="请输入功能名称" />
|
||||
<Form.Item label="参数名称" name="name">
|
||||
<Input v-model:value="formData.name" placeholder="请输入功能名称" />
|
||||
</Form.Item>
|
||||
<Form.Item label="标识符" prop="identifier">
|
||||
<Input v-model="formData.identifier" placeholder="请输入标识符" />
|
||||
<Form.Item label="标识符" name="identifier">
|
||||
<Input v-model:value="formData.identifier" placeholder="请输入标识符" />
|
||||
</Form.Item>
|
||||
<!-- 属性配置 -->
|
||||
<ThingModelProperty v-model="formData.property" is-params />
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button :disabled="formLoading" type="primary" @click="submitForm">
|
||||
确 定
|
||||
</Button>
|
||||
<Button @click="dialogVisible = false">取 消</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
@@ -11,7 +11,6 @@ import { isEmpty } from '@vben/utils';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Form, Input, Radio, Select } from 'ant-design-vue';
|
||||
|
||||
import { validateBoolName } from '#/api/iot/thingmodel';
|
||||
import {
|
||||
getDataTypeOptions,
|
||||
IoTDataSpecsDataTypeEnum,
|
||||
@@ -43,13 +42,8 @@ const getDataTypeOptions2 = computed(() => {
|
||||
if (!props.isStructDataSpecs) {
|
||||
return getDataTypeOptions();
|
||||
}
|
||||
const excludedTypes = new Set([
|
||||
IoTDataSpecsDataTypeEnum.ARRAY,
|
||||
IoTDataSpecsDataTypeEnum.STRUCT,
|
||||
]);
|
||||
return getDataTypeOptions().filter(
|
||||
(item: any) => !excludedTypes.has(item.value),
|
||||
);
|
||||
const excludedTypes = [IoTDataSpecsDataTypeEnum.STRUCT, IoTDataSpecsDataTypeEnum.ARRAY];
|
||||
return getDataTypeOptions().filter((item: any) => !excludedTypes.includes(item.value));
|
||||
}); // 获得数据类型列表
|
||||
|
||||
/** 属性值的数据类型切换时初始化相关数据 */
|
||||
@@ -58,11 +52,19 @@ function handleChange(dataType: any) {
|
||||
property.value.dataSpecsList = [];
|
||||
// 不是列表型数据才设置 dataSpecs.dataType
|
||||
![
|
||||
IoTDataSpecsDataTypeEnum.BOOL,
|
||||
IoTDataSpecsDataTypeEnum.ENUM,
|
||||
IoTDataSpecsDataTypeEnum.BOOL,
|
||||
IoTDataSpecsDataTypeEnum.STRUCT,
|
||||
].includes(dataType) && (property.value.dataSpecs.dataType = dataType);
|
||||
switch (dataType) {
|
||||
case IoTDataSpecsDataTypeEnum.ENUM: {
|
||||
property.value.dataSpecsList.push({
|
||||
dataType: IoTDataSpecsDataTypeEnum.ENUM,
|
||||
name: '', // 枚举项的名称
|
||||
value: undefined, // 枚举值
|
||||
});
|
||||
break;
|
||||
}
|
||||
case IoTDataSpecsDataTypeEnum.BOOL: {
|
||||
for (let i = 0; i < 2; i++) {
|
||||
property.value.dataSpecsList.push({
|
||||
@@ -73,15 +75,8 @@ function handleChange(dataType: any) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case IoTDataSpecsDataTypeEnum.ENUM: {
|
||||
property.value.dataSpecsList.push({
|
||||
dataType: IoTDataSpecsDataTypeEnum.ENUM,
|
||||
name: '', // 枚举项的名称
|
||||
value: undefined, // 枚举值
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
// useVModel 会自动同步数据到父组件,不需要手动 emit
|
||||
}
|
||||
|
||||
/** 默认选中读写 */
|
||||
@@ -91,9 +86,9 @@ watch(
|
||||
if (props.isStructDataSpecs || props.isParams) {
|
||||
return;
|
||||
}
|
||||
isEmpty(val) &&
|
||||
(property.value.accessMode =
|
||||
IoTThingModelAccessModeEnum.READ_WRITE.value);
|
||||
if (isEmpty(val)) {
|
||||
property.value.accessMode = IoTThingModelAccessModeEnum.READ_WRITE.value;
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
);
|
||||
@@ -101,12 +96,10 @@ watch(
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
:rules="[{ required: true, message: '请选择数据类型', trigger: 'change' }]"
|
||||
label="数据类型"
|
||||
prop="property.dataType"
|
||||
>
|
||||
<Select
|
||||
v-model="property.dataType"
|
||||
v-model:value="property.dataType"
|
||||
placeholder="请选择数据类型"
|
||||
@change="handleChange"
|
||||
>
|
||||
@@ -114,9 +107,10 @@ watch(
|
||||
<Select.Option
|
||||
v-for="option in getDataTypeOptions2"
|
||||
:key="option.value"
|
||||
:label="`${option.value}(${option.label})`"
|
||||
:value="option.value"
|
||||
/>
|
||||
>
|
||||
{{ `${option.value}(${option.label})` }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
<!-- 数值型配置 -->
|
||||
@@ -140,24 +134,17 @@ watch(
|
||||
v-if="property.dataType === IoTDataSpecsDataTypeEnum.BOOL"
|
||||
label="布尔值"
|
||||
>
|
||||
<template v-for="(item, index) in property.dataSpecsList" :key="item.value">
|
||||
<div class="w-1/1 mb-5px flex items-center justify-start">
|
||||
<template v-for="item in property.dataSpecsList" :key="item.value">
|
||||
<div class="flex items-center justify-start w-1/1 mb-5px">
|
||||
<span>{{ item.value }}</span>
|
||||
<span class="mx-2">-</span>
|
||||
<Form.Item
|
||||
:prop="`property.dataSpecsList[${index}].name`"
|
||||
:rules="[
|
||||
{ required: true, message: '枚举描述不能为空' },
|
||||
{ validator: validateBoolName, trigger: 'blur' },
|
||||
]"
|
||||
class="mb-0 flex-1"
|
||||
>
|
||||
<div class="flex-1">
|
||||
<Input
|
||||
v-model="item.name"
|
||||
v-model:value="item.name"
|
||||
:placeholder="`如:${item.value === 0 ? '关' : '开'}`"
|
||||
class="w-255px!"
|
||||
/>
|
||||
</Form.Item>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</Form.Item>
|
||||
@@ -165,21 +152,21 @@ watch(
|
||||
<Form.Item
|
||||
v-if="property.dataType === IoTDataSpecsDataTypeEnum.TEXT"
|
||||
label="数据长度"
|
||||
prop="property.dataSpecs.length"
|
||||
name="property.dataSpecs.length"
|
||||
>
|
||||
<Input
|
||||
v-model="property.dataSpecs.length"
|
||||
v-model:value="property.dataSpecs.length"
|
||||
class="w-255px!"
|
||||
placeholder="请输入文本字节长度"
|
||||
>
|
||||
<template #append>字节</template>
|
||||
<template #addonAfter>字节</template>
|
||||
</Input>
|
||||
</Form.Item>
|
||||
<!-- 时间型配置 -->
|
||||
<Form.Item
|
||||
v-if="property.dataType === IoTDataSpecsDataTypeEnum.DATE"
|
||||
label="时间格式"
|
||||
prop="date"
|
||||
name="date"
|
||||
>
|
||||
<Input
|
||||
class="w-255px!"
|
||||
@@ -200,13 +187,13 @@ watch(
|
||||
<Form.Item
|
||||
v-if="!isStructDataSpecs && !isParams"
|
||||
label="读写类型"
|
||||
prop="property.accessMode"
|
||||
name="property.accessMode"
|
||||
>
|
||||
<Radio.Group v-model="property.accessMode">
|
||||
<Radio.Group v-model:value="property.accessMode">
|
||||
<Radio
|
||||
v-for="accessMode in Object.values(IoTThingModelAccessModeEnum)"
|
||||
:key="accessMode.value"
|
||||
:label="accessMode.value"
|
||||
:value="accessMode.value"
|
||||
>
|
||||
{{ accessMode.label }}
|
||||
</Radio>
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { ThingModelService } from '#/api/iot/thingmodel';
|
||||
|
||||
import { watch } from 'vue';
|
||||
|
||||
import { isEmpty } from '@vben/utils';
|
||||
@@ -23,7 +21,7 @@ defineOptions({ name: 'ThingModelService' });
|
||||
|
||||
const props = defineProps<{ isStructDataSpecs?: boolean; modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const service = useVModel(props, 'modelValue', emits) as Ref<ThingModelService>;
|
||||
const service = useVModel(props, 'modelValue', emits) as Ref<any>;
|
||||
|
||||
/** 默认选中,ASYNC 异步 */
|
||||
watch(
|
||||
@@ -39,9 +37,9 @@ watch(
|
||||
<Form.Item
|
||||
:rules="[{ required: true, message: '请选择调用方式', trigger: 'change' }]"
|
||||
label="调用方式"
|
||||
prop="service.callType"
|
||||
name="service.callType"
|
||||
>
|
||||
<Radio.Group v-model="service.callType">
|
||||
<Radio.Group v-model:value="service.callType">
|
||||
<Radio
|
||||
v-for="callType in Object.values(IoTThingModelServiceCallTypeEnum)"
|
||||
:key="callType.value"
|
||||
@@ -53,13 +51,13 @@ watch(
|
||||
</Form.Item>
|
||||
<Form.Item label="输入参数">
|
||||
<ThingModelInputOutputParam
|
||||
v-model="service.inputData"
|
||||
v-model="service.inputParams"
|
||||
:direction="IoTThingModelParamDirectionEnum.INPUT"
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item label="输出参数">
|
||||
<ThingModelInputOutputParam
|
||||
v-model="service.outputData"
|
||||
v-model="service.outputParams"
|
||||
:direction="IoTThingModelParamDirectionEnum.OUTPUT"
|
||||
/>
|
||||
</Form.Item>
|
||||
|
||||
@@ -3,58 +3,115 @@ import type { Ref } from 'vue';
|
||||
|
||||
import type { IotProductApi } from '#/api/iot/product/product';
|
||||
|
||||
import { inject, onMounted, ref } from 'vue';
|
||||
import { computed, inject, ref, watch } from 'vue';
|
||||
|
||||
import { Modal, Radio } from 'ant-design-vue';
|
||||
import hljs from 'highlight.js'; // 导入代码高亮文件
|
||||
import json from 'highlight.js/lib/languages/json';
|
||||
import { Modal, Radio, Textarea } from 'ant-design-vue';
|
||||
|
||||
import { getThingModelListByProductId } from '#/api/iot/thingmodel';
|
||||
import { getThingModelTSL } from '#/api/iot/thingmodel';
|
||||
import { IOT_PROVIDE_KEY } from '#/views/iot/utils/constants';
|
||||
|
||||
import 'highlight.js/styles/github.css'; // 导入代码高亮样式
|
||||
|
||||
defineOptions({ name: 'ThingModelTSL' });
|
||||
|
||||
const dialogVisible = ref(false); // 弹窗的是否展示
|
||||
const dialogTitle = ref('物模型 TSL'); // 弹窗的标题
|
||||
const product = inject<Ref<IotProductApi.Product>>(IOT_PROVIDE_KEY.PRODUCT); // 注入产品信息
|
||||
const viewMode = ref('code'); // 查看模式:code-代码视图,editor-编辑器视图
|
||||
const viewMode = ref('view'); // 查看模式:view-代码视图,editor-编辑器视图
|
||||
|
||||
/** 打开弹窗 */
|
||||
function open() {
|
||||
const open = async () => {
|
||||
dialogVisible.value = true;
|
||||
}
|
||||
await getTsl();
|
||||
};
|
||||
defineExpose({ open });
|
||||
|
||||
/** 获取 TSL */
|
||||
const thingModelTSL = ref({});
|
||||
async function getTsl() {
|
||||
thingModelTSL.value = await getThingModelListByProductId(
|
||||
product?.value?.id || 0,
|
||||
);
|
||||
}
|
||||
const thingModelTSL = ref<any>({});
|
||||
const tslString = ref(''); // 用于编辑器的字符串格式
|
||||
|
||||
/** 初始化 */
|
||||
onMounted(async () => {
|
||||
// 注册代码高亮的各种语言
|
||||
hljs.registerLanguage('json', json);
|
||||
await getTsl();
|
||||
const getTsl = async () => {
|
||||
try {
|
||||
thingModelTSL.value = await getThingModelTSL(product?.value?.id || 0);
|
||||
// 将对象转换为格式化的 JSON 字符串
|
||||
tslString.value = JSON.stringify(thingModelTSL.value, null, 2);
|
||||
} catch (error) {
|
||||
console.error('获取 TSL 失败:', error);
|
||||
thingModelTSL.value = {};
|
||||
tslString.value = '{}';
|
||||
}
|
||||
};
|
||||
|
||||
/** 格式化的 TSL 用于只读展示 */
|
||||
const formattedTSL = computed(() => {
|
||||
try {
|
||||
if (typeof thingModelTSL.value === 'string') {
|
||||
return JSON.stringify(JSON.parse(thingModelTSL.value), null, 2);
|
||||
}
|
||||
return JSON.stringify(thingModelTSL.value, null, 2);
|
||||
} catch {
|
||||
return JSON.stringify(thingModelTSL.value, null, 2);
|
||||
}
|
||||
});
|
||||
|
||||
/** 监听编辑器内容变化,实时更新数据 */
|
||||
watch(tslString, (newValue) => {
|
||||
try {
|
||||
thingModelTSL.value = JSON.parse(newValue);
|
||||
} catch {
|
||||
// JSON 解析失败时保持原值
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Modal v-model="dialogVisible" :title="dialogTitle">
|
||||
<JsonEditor
|
||||
v-model="thingModelTSL"
|
||||
:mode="viewMode === 'editor' ? 'code' : 'view'"
|
||||
height="600px"
|
||||
/>
|
||||
<template #footer>
|
||||
<Radio.Group v-model="viewMode" size="small">
|
||||
<Radio.Button label="code">代码视图</Radio.Button>
|
||||
<Radio.Button label="editor">编辑器视图</Radio.Button>
|
||||
<Modal
|
||||
v-model:open="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
:footer="null"
|
||||
width="800px"
|
||||
>
|
||||
<div class="mb-4">
|
||||
<Radio.Group v-model:value="viewMode" size="small">
|
||||
<Radio.Button value="view">代码视图</Radio.Button>
|
||||
<Radio.Button value="editor">编辑器视图</Radio.Button>
|
||||
</Radio.Group>
|
||||
</template>
|
||||
</div>
|
||||
<!-- 代码视图 - 只读展示 -->
|
||||
<div v-if="viewMode === 'view'" class="json-viewer-container">
|
||||
<pre class="json-code"><code>{{ formattedTSL }}</code></pre>
|
||||
</div>
|
||||
<!-- 编辑器视图 - 可编辑 -->
|
||||
<Textarea
|
||||
v-else
|
||||
v-model:value="tslString"
|
||||
:rows="20"
|
||||
placeholder="请输入 JSON 格式的物模型 TSL"
|
||||
class="json-editor"
|
||||
/>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.json-viewer-container {
|
||||
background-color: #f5f5f5;
|
||||
border: 1px solid #d9d9d9;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.json-code {
|
||||
margin: 0;
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
color: #333;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.json-editor {
|
||||
font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', 'Consolas', monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
<script lang="ts" setup>
|
||||
import type { ThingModelData } from '#/api/iot/thingmodel';
|
||||
|
||||
import { computed } from 'vue';
|
||||
|
||||
import { Tooltip } from 'ant-design-vue';
|
||||
|
||||
import {
|
||||
getEventTypeLabel,
|
||||
getThingModelServiceCallTypeLabel,
|
||||
@@ -11,12 +15,32 @@ import {
|
||||
/** 数据定义展示组件 */
|
||||
defineOptions({ name: 'DataDefinition' });
|
||||
|
||||
defineProps<{ data: ThingModelData }>();
|
||||
const props = defineProps<{ data: ThingModelData }>();
|
||||
|
||||
// 格式化布尔值和枚举值列表为字符串
|
||||
const formattedDataSpecsList = computed(() => {
|
||||
if (!props.data.property?.dataSpecsList || props.data.property.dataSpecsList.length === 0) {
|
||||
return '';
|
||||
}
|
||||
return props.data.property.dataSpecsList
|
||||
.map(item => `${item.value}-${item.name}`)
|
||||
.join('、');
|
||||
});
|
||||
|
||||
// 显示的简短文本(第一个值)
|
||||
const shortText = computed(() => {
|
||||
if (!props.data.property?.dataSpecsList || props.data.property.dataSpecsList.length === 0) {
|
||||
return '-';
|
||||
}
|
||||
const first = props.data.property.dataSpecsList[0];
|
||||
const count = props.data.property.dataSpecsList.length;
|
||||
return count > 1 ? `${first.value}-${first.name} 等${count}项` : `${first.value}-${first.name}`;
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 属性 -->
|
||||
<template v-if="data.type === IoTThingModelTypeEnum.PROPERTY.toString()">
|
||||
<template v-if="Number(data.type) === IoTThingModelTypeEnum.PROPERTY">
|
||||
<!-- 非列表型:数值 -->
|
||||
<div
|
||||
v-if="
|
||||
@@ -28,12 +52,12 @@ defineProps<{ data: ThingModelData }>();
|
||||
"
|
||||
>
|
||||
取值范围:{{
|
||||
`${data.property?.dataSpecs.min}~${data.property?.dataSpecs.max}`
|
||||
`${data.property?.dataSpecs?.min}~${data.property?.dataSpecs?.max}`
|
||||
}}
|
||||
</div>
|
||||
<!-- 非列表型:文本 -->
|
||||
<div v-if="IoTDataSpecsDataTypeEnum.TEXT === data.property?.dataType">
|
||||
数据长度:{{ data.property?.dataSpecs.length }}
|
||||
数据长度:{{ data.property?.dataSpecs?.length }}
|
||||
</div>
|
||||
<!-- 列表型: 数组、结构、时间(特殊) -->
|
||||
<div
|
||||
@@ -55,28 +79,37 @@ defineProps<{ data: ThingModelData }>();
|
||||
)
|
||||
"
|
||||
>
|
||||
<div>
|
||||
{{
|
||||
IoTDataSpecsDataTypeEnum.BOOL === data.property?.dataType
|
||||
? '布尔值'
|
||||
: '枚举值'
|
||||
}}:
|
||||
</div>
|
||||
<div v-for="item in data.property?.dataSpecsList" :key="item.value">
|
||||
{{ `${item.name}-${item.value}` }}
|
||||
</div>
|
||||
<Tooltip :title="formattedDataSpecsList" placement="topLeft">
|
||||
<span class="data-specs-text">
|
||||
{{
|
||||
IoTDataSpecsDataTypeEnum.BOOL === data.property?.dataType
|
||||
? '布尔值'
|
||||
: '枚举值'
|
||||
}}:{{ shortText }}
|
||||
</span>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 服务 -->
|
||||
<div v-if="data.type === IoTThingModelTypeEnum.SERVICE.toString()">
|
||||
<div v-if="Number(data.type) === IoTThingModelTypeEnum.SERVICE">
|
||||
调用方式:{{
|
||||
getThingModelServiceCallTypeLabel(data.service?.callType as any)
|
||||
}}
|
||||
</div>
|
||||
<!-- 事件 -->
|
||||
<div v-if="data.type === IoTThingModelTypeEnum.EVENT.toString()">
|
||||
<div v-if="Number(data.type) === IoTThingModelTypeEnum.EVENT">
|
||||
事件类型:{{ getEventTypeLabel(data.event?.type as any) }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
<style lang="scss" scoped>
|
||||
.data-specs-text {
|
||||
cursor: help;
|
||||
border-bottom: 1px dashed #d9d9d9;
|
||||
|
||||
&:hover {
|
||||
border-bottom-color: #1890ff;
|
||||
color: #1890ff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -29,8 +29,8 @@ function handleChange(val: any) {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item label="元素类型" prop="property.dataSpecs.childDataType">
|
||||
<Radio.Group v-model="dataSpecs.childDataType" @change="handleChange">
|
||||
<Form.Item label="元素类型" name="property.dataSpecs.childDataType">
|
||||
<Radio.Group v-model:value="dataSpecs.childDataType" @change="handleChange">
|
||||
<template v-for="item in getDataTypeOptions()" :key="item.value">
|
||||
<Radio
|
||||
v-if="
|
||||
@@ -50,8 +50,8 @@ function handleChange(val: any) {
|
||||
</template>
|
||||
</Radio.Group>
|
||||
</Form.Item>
|
||||
<Form.Item label="元素个数" prop="property.dataSpecs.size">
|
||||
<Input v-model="dataSpecs.size" placeholder="请输入数组中的元素个数" />
|
||||
<Form.Item label="元素个数" name="property.dataSpecs.size">
|
||||
<Input v-model:value="dataSpecs.size" placeholder="请输入数组中的元素个数" />
|
||||
</Form.Item>
|
||||
<!-- Struct 型配置-->
|
||||
<ThingModelStructDataSpecs
|
||||
|
||||
@@ -2,31 +2,22 @@
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { DataSpecsEnumOrBoolData } from '#/api/iot/thingmodel';
|
||||
|
||||
import { isEmpty } from '@vben/utils';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
import { Button, Form, Input, message } from 'ant-design-vue';
|
||||
|
||||
import { IoTDataSpecsDataTypeEnum } from '#/views/iot/utils/constants';
|
||||
|
||||
/** 枚举型的 dataSpecs 配置组件 */
|
||||
defineOptions({ name: 'ThingModelEnumDataSpecs' });
|
||||
|
||||
const props = defineProps<{ modelValue: any }>();
|
||||
const emits = defineEmits(['update:modelValue']);
|
||||
const dataSpecsList = useVModel(props, 'modelValue', emits) as Ref<
|
||||
DataSpecsEnumOrBoolData[]
|
||||
>;
|
||||
const dataSpecsList = useVModel(props, 'modelValue', emits) as Ref<any[]>;
|
||||
|
||||
/** 添加枚举项 */
|
||||
function addEnum() {
|
||||
dataSpecsList.value.push({
|
||||
dataType: IoTDataSpecsDataTypeEnum.ENUM,
|
||||
name: '', // 枚举项的名称
|
||||
value: '', // 枚举值
|
||||
});
|
||||
} as any);
|
||||
}
|
||||
|
||||
/** 删除枚举项 */
|
||||
@@ -38,92 +29,10 @@ function deleteEnum(index: number) {
|
||||
dataSpecsList.value.splice(index, 1);
|
||||
}
|
||||
|
||||
/** 校验枚举值 */
|
||||
function validateEnumValue(_: any, value: any, callback: any) {
|
||||
if (isEmpty(value)) {
|
||||
callback(new Error('枚举值不能为空'));
|
||||
return;
|
||||
}
|
||||
if (Number.isNaN(Number(value))) {
|
||||
callback(new Error('枚举值必须是数字'));
|
||||
return;
|
||||
}
|
||||
// 检查枚举值是否重复
|
||||
const values = dataSpecsList.value.map((item) => item.value);
|
||||
if (values.filter((v) => v === value).length > 1) {
|
||||
callback(new Error('枚举值不能重复'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
/** 校验枚举描述 */
|
||||
function validateEnumName(_: any, value: string, callback: any) {
|
||||
if (isEmpty(value)) {
|
||||
callback(new Error('枚举描述不能为空'));
|
||||
return;
|
||||
}
|
||||
// 检查开头字符
|
||||
if (!/^[\u4E00-\u9FA5a-z0-9]/i.test(value)) {
|
||||
callback(new Error('枚举描述必须以中文、英文字母或数字开头'));
|
||||
return;
|
||||
}
|
||||
// 检查整体格式
|
||||
if (!/^[\u4E00-\u9FA5a-z0-9][\w\u4E00-\u9FA5-]*$/i.test(value)) {
|
||||
callback(new Error('枚举描述只能包含中文、英文字母、数字、下划线和短划线'));
|
||||
return;
|
||||
}
|
||||
// 检查长度(一个中文算一个字符)
|
||||
if (value.length > 20) {
|
||||
callback(new Error('枚举描述长度不能超过20个字符'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
/** 校验整个枚举列表 */
|
||||
function validateEnumList(_: any, __: any, callback: any) {
|
||||
if (isEmpty(dataSpecsList.value)) {
|
||||
callback(new Error('请至少添加一个枚举项'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否存在空值
|
||||
const hasEmptyValue = dataSpecsList.value.some(
|
||||
(item) => isEmpty(item.value) || isEmpty(item.name),
|
||||
);
|
||||
if (hasEmptyValue) {
|
||||
callback(new Error('存在未填写的枚举值或描述'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查枚举值是否都是数字
|
||||
const hasInvalidNumber = dataSpecsList.value.some((item) =>
|
||||
Number.isNaN(Number(item.value)),
|
||||
);
|
||||
if (hasInvalidNumber) {
|
||||
callback(new Error('存在非数字的枚举值'));
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否有重复的枚举值
|
||||
const values = dataSpecsList.value.map((item) => item.value);
|
||||
const uniqueValues = new Set(values);
|
||||
if (values.length !== uniqueValues.size) {
|
||||
callback(new Error('存在重复的枚举值'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Form.Item
|
||||
:rules="[
|
||||
{ required: true, validator: validateEnumList, trigger: 'change' },
|
||||
]"
|
||||
label="枚举项"
|
||||
>
|
||||
<Form.Item label="枚举项">
|
||||
<div class="flex flex-col">
|
||||
<div class="flex items-center">
|
||||
<span class="flex-1"> 参数值 </span>
|
||||
@@ -132,41 +41,27 @@ function validateEnumList(_: any, __: any, callback: any) {
|
||||
<div
|
||||
v-for="(item, index) in dataSpecsList"
|
||||
:key="index"
|
||||
class="mb-5px flex items-center justify-between"
|
||||
class="flex items-center justify-between mb-5px"
|
||||
>
|
||||
<Form.Item
|
||||
:prop="`property.dataSpecsList[${index}].value`"
|
||||
:rules="[
|
||||
{ required: true, message: '枚举值不能为空' },
|
||||
{ validator: validateEnumValue, trigger: 'blur' },
|
||||
]"
|
||||
class="mb-0 flex-1"
|
||||
>
|
||||
<Input v-model="item.value" placeholder="请输入枚举值,如'0'" />
|
||||
</Form.Item>
|
||||
<div class="flex-1">
|
||||
<Input v-model:value="item.value" placeholder="请输入枚举值,如'0'" />
|
||||
</div>
|
||||
<span class="mx-2">~</span>
|
||||
<Form.Item
|
||||
:prop="`property.dataSpecsList[${index}].name`"
|
||||
:rules="[
|
||||
{ required: true, message: '枚举描述不能为空' },
|
||||
{ validator: validateEnumName, trigger: 'blur' },
|
||||
]"
|
||||
class="mb-0 flex-1"
|
||||
>
|
||||
<Input v-model="item.name" placeholder="对该枚举项的描述" />
|
||||
</Form.Item>
|
||||
<Button class="ml-10px" link type="primary" @click="deleteEnum(index)">
|
||||
<div class="flex-1">
|
||||
<Input v-model:value="item.name" placeholder="对该枚举项的描述" />
|
||||
</div>
|
||||
<Button class="ml-10px" type="link" @click="deleteEnum(index)">
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
<Button link type="primary" @click="addEnum">+添加枚举项</Button>
|
||||
<Button type="link" @click="addEnum">+添加枚举项</Button>
|
||||
</div>
|
||||
</Form.Item>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-form-item) {
|
||||
.el-form-item {
|
||||
:deep(.ant-form-item) {
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,55 @@
|
||||
<!-- dataType:number 数组类型 -->
|
||||
<template>
|
||||
<Form.Item label="取值范围">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="flex-1">
|
||||
<Input v-model:value="dataSpecs.min" placeholder="请输入最小值" />
|
||||
</div>
|
||||
<span class="mx-2">~</span>
|
||||
<div class="flex-1">
|
||||
<Input v-model:value="dataSpecs.max" placeholder="请输入最大值" />
|
||||
</div>
|
||||
</div>
|
||||
</Form.Item>
|
||||
<Form.Item label="步长">
|
||||
<Input v-model:value="dataSpecs.step" placeholder="请输入步长" />
|
||||
</Form.Item>
|
||||
<Form.Item label="单位">
|
||||
<Select
|
||||
:model-value="
|
||||
dataSpecs.unit ? `${dataSpecs.unitName}-${dataSpecs.unit}` : ''
|
||||
"
|
||||
show-search
|
||||
placeholder="请选择单位"
|
||||
class="w-1/1"
|
||||
@change="unitChange"
|
||||
>
|
||||
<Select.Option
|
||||
v-for="(item, index) in getDictOptions(
|
||||
DICT_TYPE.IOT_THING_MODEL_UNIT,
|
||||
'string',
|
||||
)"
|
||||
:key="index"
|
||||
:value="`${item.label}-${item.value}`"
|
||||
>
|
||||
{{ `${item.label}-${item.value}` }}
|
||||
</Select.Option>
|
||||
</Select>
|
||||
</Form.Item>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Ref } from 'vue';
|
||||
|
||||
import type { DataSpecsNumberData } from '#/api/iot/thingmodel';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import { Form, Input, Select } from 'ant-design-vue';
|
||||
|
||||
import { DICT_TYPE } from '@vben/constants';
|
||||
import { getDictOptions } from '@vben/hooks';
|
||||
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
/** 数值型的 dataSpecs 配置组件 */
|
||||
defineOptions({ name: 'ThingModelNumberDataSpecs' });
|
||||
|
||||
@@ -21,132 +62,17 @@ const dataSpecs = useVModel(
|
||||
) as Ref<DataSpecsNumberData>;
|
||||
|
||||
/** 单位发生变化时触发 */
|
||||
const unitChange = (UnitSpecs: string) => {
|
||||
const [unitName, unit] = UnitSpecs.split('-');
|
||||
const unitChange = (UnitSpecs: any) => {
|
||||
if (!UnitSpecs) return;
|
||||
const [unitName, unit] = String(UnitSpecs).split('-');
|
||||
dataSpecs.value.unitName = unitName;
|
||||
dataSpecs.value.unit = unit;
|
||||
};
|
||||
|
||||
/** 校验最小值 */
|
||||
const validateMin = (_: any, __: any, callback: any) => {
|
||||
const min = Number(dataSpecs.value.min);
|
||||
const max = Number(dataSpecs.value.max);
|
||||
if (Number.isNaN(min)) {
|
||||
callback(new Error('请输入有效的数值'));
|
||||
return;
|
||||
}
|
||||
if (max !== undefined && !Number.isNaN(max) && min >= max) {
|
||||
callback(new Error('最小值必须小于最大值'));
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
};
|
||||
|
||||
/** 校验最大值 */
|
||||
const validateMax = (_: any, __: any, callback: any) => {
|
||||
const min = Number(dataSpecs.value.min);
|
||||
const max = Number(dataSpecs.value.max);
|
||||
if (Number.isNaN(max)) {
|
||||
callback(new Error('请输入有效的数值'));
|
||||
return;
|
||||
}
|
||||
if (min !== undefined && !Number.isNaN(min) && max <= min) {
|
||||
callback(new Error('最大值必须大于最小值'));
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
};
|
||||
|
||||
/** 校验步长 */
|
||||
const validateStep = (_: any, __: any, callback: any) => {
|
||||
const step = Number(dataSpecs.value.step);
|
||||
if (Number.isNaN(step)) {
|
||||
callback(new Error('请输入有效的数值'));
|
||||
return;
|
||||
}
|
||||
if (step <= 0) {
|
||||
callback(new Error('步长必须大于0'));
|
||||
return;
|
||||
}
|
||||
const min = Number(dataSpecs.value.min);
|
||||
const max = Number(dataSpecs.value.max);
|
||||
if (!Number.isNaN(min) && !Number.isNaN(max) && step > max - min) {
|
||||
callback(new Error('步长不能大于最大值和最小值的差值'));
|
||||
return;
|
||||
}
|
||||
|
||||
callback();
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-form-item label="取值范围">
|
||||
<div class="flex items-center justify-between">
|
||||
<el-form-item
|
||||
:rules="[
|
||||
{ required: true, message: '最小值不能为空' },
|
||||
{ validator: validateMin, trigger: 'blur' },
|
||||
]"
|
||||
class="mb-0"
|
||||
prop="property.dataSpecs.min"
|
||||
>
|
||||
<el-input v-model="dataSpecs.min" placeholder="请输入最小值" />
|
||||
</el-form-item>
|
||||
<span class="mx-2">~</span>
|
||||
<el-form-item
|
||||
:rules="[
|
||||
{ required: true, message: '最大值不能为空' },
|
||||
{ validator: validateMax, trigger: 'blur' },
|
||||
]"
|
||||
class="mb-0"
|
||||
prop="property.dataSpecs.max"
|
||||
>
|
||||
<el-input v-model="dataSpecs.max" placeholder="请输入最大值" />
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:rules="[
|
||||
{ required: true, message: '步长不能为空' },
|
||||
{ validator: validateStep, trigger: 'blur' },
|
||||
]"
|
||||
label="步长"
|
||||
prop="property.dataSpecs.step"
|
||||
>
|
||||
<el-input v-model="dataSpecs.step" placeholder="请输入步长" />
|
||||
</el-form-item>
|
||||
<el-form-item
|
||||
:rules="[{ required: true, message: '请选择单位' }]"
|
||||
label="单位"
|
||||
prop="property.dataSpecs.unit"
|
||||
>
|
||||
<el-select
|
||||
:model-value="
|
||||
dataSpecs.unit ? `${dataSpecs.unitName}-${dataSpecs.unit}` : ''
|
||||
"
|
||||
filterable
|
||||
placeholder="请选择单位"
|
||||
class="w-1/1"
|
||||
@change="unitChange"
|
||||
>
|
||||
<el-option
|
||||
v-for="(item, index) in getDictOptions(
|
||||
DICT_TYPE.IOT_THING_MODEL_UNIT,
|
||||
'string',
|
||||
)"
|
||||
:key="index"
|
||||
:label="`${item.label}-${item.value}`"
|
||||
:value="`${item.label}-${item.value}`"
|
||||
/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-form-item) {
|
||||
.el-form-item {
|
||||
:deep(.ant-form-item) {
|
||||
.ant-form-item {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,7 @@ const formData = ref<any>({
|
||||
dataSpecs: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
dataSpecsList: [],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -40,16 +41,22 @@ function openStructForm(val: any) {
|
||||
return;
|
||||
}
|
||||
// 编辑时回显数据
|
||||
const valData = val as any;
|
||||
formData.value = {
|
||||
identifier: val.identifier,
|
||||
name: val.name,
|
||||
description: val.description,
|
||||
identifier: valData?.identifier || '',
|
||||
name: valData?.name || '',
|
||||
description: valData?.description || '',
|
||||
property: {
|
||||
dataType: val.childDataType,
|
||||
dataSpecs: val.dataSpecs,
|
||||
dataSpecsList: val.dataSpecsList,
|
||||
dataType: valData?.childDataType || IoTDataSpecsDataTypeEnum.INT,
|
||||
dataSpecs: valData?.dataSpecs ?? {},
|
||||
dataSpecsList: valData?.dataSpecsList ?? [],
|
||||
},
|
||||
};
|
||||
|
||||
// 确保 property.dataType 有值
|
||||
if (!formData.value.property.dataType) {
|
||||
formData.value.property.dataType = IoTDataSpecsDataTypeEnum.INT;
|
||||
}
|
||||
}
|
||||
|
||||
/** 删除 struct 项 */
|
||||
@@ -102,19 +109,12 @@ function resetForm() {
|
||||
dataSpecs: {
|
||||
dataType: IoTDataSpecsDataTypeEnum.INT,
|
||||
},
|
||||
dataSpecsList: [],
|
||||
},
|
||||
};
|
||||
structFormRef.value?.resetFields();
|
||||
}
|
||||
|
||||
/** 校验 struct 不能为空 */
|
||||
function validateList(_: any, __: any, callback: any) {
|
||||
if (isEmpty(dataSpecsList.value)) {
|
||||
callback(new Error('struct 不能为空'));
|
||||
return;
|
||||
}
|
||||
callback();
|
||||
}
|
||||
|
||||
/** 组件初始化 */
|
||||
onMounted(async () => {
|
||||
@@ -126,51 +126,49 @@ onMounted(async () => {
|
||||
|
||||
<template>
|
||||
<!-- struct 数据展示 -->
|
||||
<Form.Item
|
||||
:rules="[{ required: true, validator: validateList, trigger: 'change' }]"
|
||||
label="JSON 对象"
|
||||
>
|
||||
<Form.Item label="属性对象">
|
||||
<div
|
||||
v-for="(item, index) in dataSpecsList"
|
||||
:key="index"
|
||||
class="px-10px mb-10px flex w-full justify-between bg-gray-100"
|
||||
>
|
||||
<span>参数名称:{{ item.name }}</span>
|
||||
<span>参数:{{ item.name }}</span>
|
||||
<div class="btn">
|
||||
<Button link type="primary" @click="openStructForm(item)">
|
||||
<Button type="link" @click="openStructForm(item)">
|
||||
编辑
|
||||
</Button>
|
||||
<Divider direction="vertical" />
|
||||
<Button link danger @click="deleteStructItem(index)"> 删除 </Button>
|
||||
<Divider type="vertical" />
|
||||
<Button type="link" danger @click="deleteStructItem(index)">
|
||||
删除
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
<Button link type="primary" @click="openStructForm(null)">
|
||||
<Button type="link" @click="openStructForm(null)">
|
||||
+新增参数
|
||||
</Button>
|
||||
</Form.Item>
|
||||
|
||||
<!-- struct 表单 -->
|
||||
<Modal v-model="dialogVisible" :title="dialogTitle" append-to-body>
|
||||
<Modal
|
||||
v-model:open="dialogVisible"
|
||||
:title="dialogTitle"
|
||||
:confirm-loading="formLoading"
|
||||
@ok="submitForm"
|
||||
>
|
||||
<Form
|
||||
ref="structFormRef"
|
||||
v-loading="formLoading"
|
||||
:model="formData"
|
||||
label-width="100px"
|
||||
:label-col="{ span: 6 }"
|
||||
:wrapper-col="{ span: 18 }"
|
||||
>
|
||||
<Form.Item label="参数名称" prop="name">
|
||||
<Input v-model="formData.name" placeholder="请输入功能名称" />
|
||||
<Form.Item label="参数名称" name="name">
|
||||
<Input v-model:value="formData.name" placeholder="请输入功能名称" />
|
||||
</Form.Item>
|
||||
<Form.Item label="标识符" prop="identifier">
|
||||
<Input v-model="formData.identifier" placeholder="请输入标识符" />
|
||||
<Form.Item label="标识符" name="identifier">
|
||||
<Input v-model:value="formData.identifier" placeholder="请输入标识符" />
|
||||
</Form.Item>
|
||||
<!-- 属性配置 -->
|
||||
<ThingModelProperty v-model="formData.property" is-struct-data-specs />
|
||||
</Form>
|
||||
<template #footer>
|
||||
<Button :disabled="formLoading" type="primary" @click="submitForm">
|
||||
确 定
|
||||
</Button>
|
||||
<Button @click="dialogVisible = false">取 消</Button>
|
||||
</template>
|
||||
</Modal>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user