fix: 修复 iot 模块 ele 端迁移阻塞的类型 / 字段问题(P0)

- ele property-selector / device-control-config,改用 ThingModelApi 命名空间引用
- ele api/iot/thingmodel,补 ThingModelTSL 类型,收敛 getThingModelTSLByProductId 返回值
- ele api/iot/rule/scene,SceneRule 补 lastTriggeredTime 字段
- ele views/iot/rule/scene/data.ts,useGridColumns 对齐 antd 的 5 列结构
- ele views/iot/home/modules/message-trend-card,ElSelect 改用 ElOption 子节点
- ele / antd api/iot/rule/scene,Action.params 类型 Record<string, any> 改为 string

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
pull/345/head
YunaiV 2026-05-21 10:20:30 +08:00
parent f02a5975b8
commit 0d175cbe9c
7 changed files with 68 additions and 61 deletions

View File

@ -46,7 +46,7 @@ export namespace RuleSceneApi {
identifier?: string; identifier?: string;
value?: any; value?: any;
alertConfigId?: number; alertConfigId?: number;
params?: Record<string, any>; params?: string;
} }
} }

View File

@ -11,6 +11,7 @@ export namespace RuleSceneApi {
status?: number; status?: number;
triggers?: Trigger[]; triggers?: Trigger[];
actions?: Action[]; actions?: Action[];
lastTriggeredTime?: Date;
createTime?: Date; createTime?: Date;
} }
@ -46,7 +47,7 @@ export namespace RuleSceneApi {
identifier?: string; identifier?: string;
value?: any; value?: any;
alertConfigId?: number; alertConfigId?: number;
params?: Record<string, any>; params?: string;
} }
} }

View File

@ -68,6 +68,15 @@ export namespace ThingModelApi {
dataSpecsList?: any[]; dataSpecsList?: any[];
} }
/** IoT 物模型 TSL 响应 */
export interface ThingModelTSL {
productId?: number;
productKey?: string;
properties?: Property[];
events?: Event[];
services?: Service[];
}
/** IoT 数据定义(数值型) */ /** IoT 数据定义(数值型) */
export interface DataSpecsNumberData { export interface DataSpecsNumberData {
min?: number | string; min?: number | string;
@ -236,7 +245,10 @@ export function deleteThingModel(id: number) {
/** 获取物模型 TSL */ /** 获取物模型 TSL */
export function getThingModelTSLByProductId(productId: number) { export function getThingModelTSLByProductId(productId: number) {
return requestClient.get<any>('/iot/thing-model/get-tsl', { return requestClient.get<ThingModelApi.ThingModelTSL>(
params: { productId }, '/iot/thing-model/get-tsl',
}); {
params: { productId },
},
);
} }

View File

@ -10,7 +10,7 @@ import { getDictOptions } from '@vben/hooks';
import { EchartsUI, useEcharts } from '@vben/plugins/echarts'; import { EchartsUI, useEcharts } from '@vben/plugins/echarts';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import { ElCard, ElEmpty, ElSelect } from 'element-plus'; import { ElCard, ElEmpty, ElOption, ElSelect } from 'element-plus';
import { getDeviceMessageSummaryByDate } from '#/api/iot/statistics'; import { getDeviceMessageSummaryByDate } from '#/api/iot/statistics';
import ShortcutDateRangePicker from '#/components/shortcut-date-range-picker/shortcut-date-range-picker.vue'; import ShortcutDateRangePicker from '#/components/shortcut-date-range-picker/shortcut-date-range-picker.vue';
@ -145,11 +145,17 @@ onMounted(() => {
<span class="text-sm text-gray-500">时间间隔</span> <span class="text-sm text-gray-500">时间间隔</span>
<ElSelect <ElSelect
v-model="queryParams.interval" v-model="queryParams.interval"
:options="intervalOptions"
placeholder="间隔类型" placeholder="间隔类型"
:style="{ width: '80px' }" :style="{ width: '80px' }"
@change="handleIntervalChange" @change="handleIntervalChange"
/> >
<ElOption
v-for="opt in intervalOptions"
:key="opt.value"
:label="opt.label"
:value="opt.value"
/>
</ElSelect>
</div> </div>
</div> </div>
</div> </div>

View File

@ -89,49 +89,45 @@ export function useGridFormSchema(): VbenFormSchema[] {
export function useGridColumns(): VxeTableGridOptions['columns'] { export function useGridColumns(): VxeTableGridOptions['columns'] {
return [ return [
{ type: 'checkbox', width: 40 }, { type: 'checkbox', width: 40 },
{
field: 'id',
title: '规则编号',
minWidth: 80,
},
{ {
field: 'name', field: 'name',
title: '规则名称', title: '规则名称',
minWidth: 150,
},
{
field: 'description',
title: '规则描述',
minWidth: 200, minWidth: 200,
align: 'left',
showOverflow: false,
slots: { default: 'name' },
}, },
{ {
field: 'status', field: 'triggers',
title: '规则状态', title: '触发条件',
minWidth: 100, minWidth: 280,
cellRender: { align: 'left',
name: 'CellDict', showOverflow: false,
props: { type: DICT_TYPE.COMMON_STATUS }, slots: { default: 'triggers' },
},
}, },
{ {
field: 'actionCount', field: 'actions',
title: '执行动作数', title: '执行动作',
minWidth: 100, minWidth: 250,
align: 'left',
showOverflow: false,
slots: { default: 'actionsCol' },
}, },
{ {
field: 'executeCount', field: 'lastTriggeredTime',
title: '执行次数', title: '最近触发',
minWidth: 100, width: 180,
slots: { default: 'lastTriggeredTime' },
}, },
{ {
field: 'createTime', field: 'createTime',
title: '创建时间', title: '创建时间',
minWidth: 180, width: 180,
formatter: 'formatDateTime', formatter: 'formatDateTime',
}, },
{ {
title: '操作', title: '操作',
width: 240, width: 210,
fixed: 'right', fixed: 'right',
slots: { default: 'actions' }, slots: { default: 'actions' },
}, },

View File

@ -1,10 +1,7 @@
<!-- 设备控制配置组件 --> <!-- 设备控制配置组件 -->
<script setup lang="ts"> <script setup lang="ts">
import type { Action } from '#/api/iot/rule/scene'; import type { Action } from '#/api/iot/rule/scene';
import type { import type { ThingModelApi } from '#/api/iot/thingmodel';
ThingModelProperty,
ThingModelService,
} from '#/api/iot/thingmodel';
import { computed, onMounted, ref, watch } from 'vue'; import { computed, onMounted, ref, watch } from 'vue';
@ -44,10 +41,10 @@ const emit = defineEmits<{
const action = useVModel(props, 'modelValue', emit); const action = useVModel(props, 'modelValue', emit);
const thingModelProperties = ref<ThingModelProperty[]>([]); // const thingModelProperties = ref<ThingModelApi.Property[]>([]); //
const loadingThingModel = ref(false); // const loadingThingModel = ref(false); //
const selectedService = ref<null | ThingModelService>(null); // const selectedService = ref<null | ThingModelApi.Service>(null); //
const serviceList = ref<ThingModelService[]>([]); // const serviceList = ref<ThingModelApi.Service[]>([]); //
const loadingServices = ref(false); // const loadingServices = ref(false); //
// //
@ -178,7 +175,7 @@ async function loadThingModelProperties(productId: number) {
// accessMode 'w' // accessMode 'w'
thingModelProperties.value = tslData.properties.filter( thingModelProperties.value = tslData.properties.filter(
(property: ThingModelProperty) => (property: ThingModelApi.Property) =>
property.accessMode && property.accessMode &&
(property.accessMode === IoTThingModelAccessModeEnum.READ_WRITE.value || (property.accessMode === IoTThingModelAccessModeEnum.READ_WRITE.value ||
property.accessMode === IoTThingModelAccessModeEnum.WRITE_ONLY.value), property.accessMode === IoTThingModelAccessModeEnum.WRITE_ONLY.value),

View File

@ -1,11 +1,6 @@
<!-- 属性选择器组件 --> <!-- 属性选择器组件 -->
<script setup lang="ts"> <script setup lang="ts">
import type { import type { ThingModelApi } from '#/api/iot/thingmodel';
ThingModelApi,
ThingModelEvent,
ThingModelProperty,
ThingModelService,
} from '#/api/iot/thingmodel';
import { computed, ref, watch } from 'vue'; import { computed, ref, watch } from 'vue';
@ -61,18 +56,18 @@ interface PropertySelectorItem {
range?: string; range?: string;
eventType?: string; eventType?: string;
callType?: string; callType?: string;
inputParams?: ThingModelParam[]; inputParams?: ThingModelApi.Param[];
outputParams?: ThingModelParam[]; outputParams?: ThingModelApi.Param[];
property?: ThingModelProperty; property?: ThingModelApi.Property;
event?: ThingModelEvent; event?: ThingModelApi.Event;
service?: ThingModelService; service?: ThingModelApi.Service;
} }
const localValue = useVModel(props, 'modelValue', emit); const localValue = useVModel(props, 'modelValue', emit);
const loading = ref(false); // const loading = ref(false); //
const propertyList = ref<ThingModelApi.Property[]>([]); // const propertyList = ref<PropertySelectorItem[]>([]); //
const thingModelTSL = ref<null | ThingModelApi.ThingModel>(null); // TSL const thingModelTSL = ref<null | ThingModelApi.ThingModelTSL>(null); // TSL
// //
const propertyGroups = computed(() => { const propertyGroups = computed(() => {
@ -169,10 +164,10 @@ function parseThingModelData() {
if (tsl.properties && Array.isArray(tsl.properties)) { if (tsl.properties && Array.isArray(tsl.properties)) {
tsl.properties.forEach((prop) => { tsl.properties.forEach((prop) => {
properties.push({ properties.push({
identifier: prop.identifier, identifier: prop.identifier!,
name: prop.name, name: prop.name!,
description: prop.description, description: prop.description,
dataType: prop.dataType, dataType: prop.dataType!,
type: IoTThingModelTypeEnum.PROPERTY, type: IoTThingModelTypeEnum.PROPERTY,
accessMode: prop.accessMode, accessMode: prop.accessMode,
required: prop.required, required: prop.required,
@ -187,8 +182,8 @@ function parseThingModelData() {
if (tsl.events && Array.isArray(tsl.events)) { if (tsl.events && Array.isArray(tsl.events)) {
tsl.events.forEach((event) => { tsl.events.forEach((event) => {
properties.push({ properties.push({
identifier: event.identifier, identifier: event.identifier!,
name: event.name, name: event.name!,
description: event.description, description: event.description,
dataType: 'struct', dataType: 'struct',
type: IoTThingModelTypeEnum.EVENT, type: IoTThingModelTypeEnum.EVENT,
@ -204,8 +199,8 @@ function parseThingModelData() {
if (tsl.services && Array.isArray(tsl.services)) { if (tsl.services && Array.isArray(tsl.services)) {
tsl.services.forEach((service) => { tsl.services.forEach((service) => {
properties.push({ properties.push({
identifier: service.identifier, identifier: service.identifier!,
name: service.name, name: service.name!,
description: service.description, description: service.description,
dataType: 'struct', dataType: 'struct',
type: IoTThingModelTypeEnum.SERVICE, type: IoTThingModelTypeEnum.SERVICE,