docs(iot): 新增 codex 评审入口 + 登记异步竞态不修决策

- review-for-codex.md:本轮修复背景 + 决策 + 文件清单 + 给 codex
  的 5 项重点关注。与 bug.md(待修源头) / bug_ignore.md(不修登记)
  形成三文件分工,后续 codex / 二次评审不会丢上下文
- bug_ignore.md/I-1:登记 scene 表单 3 处异步竞态(property-selector
  / device-selector / device-control-config 的 watch 异步加载)不修决策。
  评估理由:触发窗口极窄、低频操作、~40 行 epoch 模板代码成本不匹配、
  vue3 源也未做。复评条件:API 层接入 AbortController 时顺手补、
  用户实际反馈、改后端 push 模式
pull/345/head
YunaiV 2026-05-21 21:26:36 +08:00
parent df8e23542b
commit 9d665bd6b9
25 changed files with 164 additions and 161 deletions

View File

@ -206,7 +206,7 @@ defineExpose({
</Select> </Select>
<Space> <Space>
<Button type="primary" @click="handleQuery"> <Button type="primary" @click="handleQuery">
<IconifyIcon icon="ep:search" class="mr-5px" /> 搜索 <IconifyIcon icon="ep:search" class="mr-[5px]" /> 搜索
</Button> </Button>
<Switch <Switch
v-model:checked="autoRefresh" v-model:checked="autoRefresh"

View File

@ -227,23 +227,25 @@ async function handlePropertyPost() {
async function handleEventPost(row: ThingModelApi.ThingModel) { async function handleEventPost(row: ThingModelApi.ThingModel) {
try { try {
const valueStr = formData.value[row.identifier!]; const valueStr = formData.value[row.identifier!];
let params: any = {}; let eventValue: any;
if (valueStr) { if (valueStr) {
try { try {
params = JSON.parse(valueStr); eventValue = JSON.parse(valueStr);
} catch { } catch {
message.error('事件参数格式错误请输入有效的JSON格式'); message.error('事件参数格式错误请输入有效的JSON格式');
return; return;
} }
} }
// IotDeviceEventPostReqDTO { identifier, value, time }
await sendDeviceMessage({ await sendDeviceMessage({
deviceId: props.device.id!, deviceId: props.device.id!,
method: IotDeviceMessageMethodEnum.EVENT_POST.method, method: IotDeviceMessageMethodEnum.EVENT_POST.method,
params: { params: {
identifier: row.identifier, identifier: row.identifier,
params, value: eventValue,
time: Date.now(),
}, },
}); });
@ -309,23 +311,24 @@ async function handlePropertySet() {
async function handleServiceInvoke(row: ThingModelApi.ThingModel) { async function handleServiceInvoke(row: ThingModelApi.ThingModel) {
try { try {
const valueStr = formData.value[row.identifier!]; const valueStr = formData.value[row.identifier!];
let params: any = {}; let inputParams: any = {};
if (valueStr) { if (valueStr) {
try { try {
params = JSON.parse(valueStr); inputParams = JSON.parse(valueStr);
} catch { } catch {
message.error('服务参数格式错误请输入有效的JSON格式'); message.error('服务参数格式错误请输入有效的JSON格式');
return; return;
} }
} }
// IotDeviceServiceInvokeReqDTO { identifier, inputParams }
await sendDeviceMessage({ await sendDeviceMessage({
deviceId: props.device.id!, deviceId: props.device.id!,
method: IotDeviceMessageMethodEnum.SERVICE_INVOKE.method, method: IotDeviceMessageMethodEnum.SERVICE_INVOKE.method,
params: { params: {
identifier: row.identifier, identifier: row.identifier,
params, inputParams,
}, },
}); });

View File

@ -286,7 +286,7 @@ onBeforeUnmount(() => {
<Switch <Switch
v-model:checked="autoRefresh" v-model:checked="autoRefresh"
checked-children="定时刷新" checked-children="定时刷新"
class="ml-20px" class="ml-[20px]"
un-checked-children="定时刷新" un-checked-children="定时刷新"
/> />
</div> </div>

View File

@ -212,7 +212,7 @@ onMounted(() => {
<Button <Button
v-if="hasAccessByCodes(['iot:device:update'])" v-if="hasAccessByCodes(['iot:device:update'])"
size="small" size="small"
class="!h-8 flex-1 rounded-md !border-[#1890ff] !text-[13px] !text-[#1890ff] transition-all duration-200 hover:!bg-[#1890ff] hover:!text-white" class="!h-8 min-w-0 flex-1 rounded-md !border-[#1890ff] !text-[13px] !text-[#1890ff] transition-all duration-200 hover:!bg-[#1890ff] hover:!text-white"
@click="emit('edit', item)" @click="emit('edit', item)"
> >
<IconifyIcon icon="lucide:edit" class="mr-1" /> <IconifyIcon icon="lucide:edit" class="mr-1" />
@ -221,7 +221,7 @@ onMounted(() => {
<Button <Button
v-if="hasAccessByCodes(['iot:device:query'])" v-if="hasAccessByCodes(['iot:device:query'])"
size="small" size="small"
class="!h-8 flex-1 rounded-md !border-[#52c41a] !text-[13px] !text-[#52c41a] transition-all duration-200 hover:!bg-[#52c41a] hover:!text-white" class="!h-8 min-w-0 flex-1 rounded-md !border-[#52c41a] !text-[13px] !text-[#52c41a] transition-all duration-200 hover:!bg-[#52c41a] hover:!text-white"
@click="emit('detail', item.id!)" @click="emit('detail', item.id!)"
> >
<IconifyIcon icon="lucide:eye" class="mr-1" /> <IconifyIcon icon="lucide:eye" class="mr-1" />
@ -230,7 +230,7 @@ onMounted(() => {
<Button <Button
v-if="hasAccessByCodes(['iot:device:message-query'])" v-if="hasAccessByCodes(['iot:device:message-query'])"
size="small" size="small"
class="!h-8 flex-1 rounded-md !border-[#fa8c16] !text-[13px] !text-[#fa8c16] transition-all duration-200 hover:!bg-[#fa8c16] hover:!text-white" class="!h-8 min-w-0 flex-1 rounded-md !border-[#fa8c16] !text-[13px] !text-[#fa8c16] transition-all duration-200 hover:!bg-[#fa8c16] hover:!text-white"
@click="emit('model', item.id!)" @click="emit('model', item.id!)"
> >
<IconifyIcon icon="lucide:database" class="mr-1" /> <IconifyIcon icon="lucide:database" class="mr-1" />

View File

@ -188,7 +188,7 @@ onMounted(() => {
<Button <Button
v-if="hasAccessByCodes(['iot:product:update'])" v-if="hasAccessByCodes(['iot:product:update'])"
size="small" size="small"
class="!h-8 flex-1 rounded-md !border-[#1890ff] !text-[13px] !text-[#1890ff] transition-all duration-200 hover:!bg-[#1890ff] hover:!text-white" class="!h-8 min-w-0 flex-1 rounded-md !border-[#1890ff] !text-[13px] !text-[#1890ff] transition-all duration-200 hover:!bg-[#1890ff] hover:!text-white"
@click="emit('edit', item)" @click="emit('edit', item)"
> >
<IconifyIcon icon="lucide:edit" class="mr-1" /> <IconifyIcon icon="lucide:edit" class="mr-1" />
@ -197,7 +197,7 @@ onMounted(() => {
<Button <Button
v-if="hasAccessByCodes(['iot:product:query'])" v-if="hasAccessByCodes(['iot:product:query'])"
size="small" size="small"
class="!h-8 flex-1 rounded-md !border-[#52c41a] !text-[13px] !text-[#52c41a] transition-all duration-200 hover:!bg-[#52c41a] hover:!text-white" class="!h-8 min-w-0 flex-1 rounded-md !border-[#52c41a] !text-[13px] !text-[#52c41a] transition-all duration-200 hover:!bg-[#52c41a] hover:!text-white"
@click="emit('detail', item.id)" @click="emit('detail', item.id)"
> >
<IconifyIcon icon="lucide:eye" class="mr-1" /> <IconifyIcon icon="lucide:eye" class="mr-1" />
@ -206,7 +206,7 @@ onMounted(() => {
<Button <Button
v-if="hasAccessByCodes(['iot:thing-model:query'])" v-if="hasAccessByCodes(['iot:thing-model:query'])"
size="small" size="small"
class="!h-8 flex-1 rounded-md !border-[#fa8c16] !text-[13px] !text-[#fa8c16] transition-all duration-200 hover:!bg-[#fa8c16] hover:!text-white" class="!h-8 min-w-0 flex-1 rounded-md !border-[#fa8c16] !text-[13px] !text-[#fa8c16] transition-all duration-200 hover:!bg-[#fa8c16] hover:!text-white"
@click="emit('thingModel', item.id)" @click="emit('thingModel', item.id)"
> >
<IconifyIcon icon="lucide:git-branch" class="mr-1" /> <IconifyIcon icon="lucide:git-branch" class="mr-1" />

View File

@ -165,7 +165,7 @@ async function loadThingModelProperties(productId: number) {
property.accessMode === IoTThingModelAccessModeEnum.WRITE_ONLY.value), property.accessMode === IoTThingModelAccessModeEnum.WRITE_ONLY.value),
); );
} catch (error) { } catch (error) {
console.error('加载物模型属性失败:', error); console.error('加载物模型属性失败 ', error);
thingModelProperties.value = []; thingModelProperties.value = [];
} finally { } finally {
loadingThingModel.value = false; loadingThingModel.value = false;
@ -193,7 +193,7 @@ async function loadServiceList(productId: number) {
serviceList.value = tslData.services; serviceList.value = tslData.services;
} catch (error) { } catch (error) {
console.error('加载服务列表失败:', error); console.error('加载服务列表失败 ', error);
serviceList.value = []; serviceList.value = [];
} finally { } finally {
loadingServices.value = false; loadingServices.value = false;

View File

@ -45,7 +45,7 @@ async function getDeviceList() {
const data = await getDeviceListByProductId(props.productId); const data = await getDeviceListByProductId(props.productId);
deviceList.value = [DEVICE_SELECTOR_OPTIONS.ALL_DEVICES, ...(data || [])]; deviceList.value = [DEVICE_SELECTOR_OPTIONS.ALL_DEVICES, ...(data || [])];
} catch (error) { } catch (error) {
console.error('获取设备列表失败:', error); console.error('获取设备列表失败 ', error);
deviceList.value = [DEVICE_SELECTOR_OPTIONS.ALL_DEVICES]; deviceList.value = [DEVICE_SELECTOR_OPTIONS.ALL_DEVICES];
} finally { } finally {
deviceLoading.value = false; deviceLoading.value = false;

View File

@ -117,7 +117,7 @@ async function loadThingModelTSL() {
const tsl = await getThingModelTSLByProductId(props.productId); const tsl = await getThingModelTSLByProductId(props.productId);
propertyList.value = parseThingModelData(tsl); propertyList.value = parseThingModelData(tsl);
} catch (error) { } catch (error) {
console.error('获取物模型 TSL 失败', error); console.error('获取物模型 TSL 失败 ', error);
propertyList.value = []; propertyList.value = [];
} finally { } finally {
loading.value = false; loading.value = false;

View File

@ -210,7 +210,7 @@ defineExpose({
</ElSelect> </ElSelect>
<div class="flex gap-1"> <div class="flex gap-1">
<ElButton type="primary" @click="handleQuery"> <ElButton type="primary" @click="handleQuery">
<IconifyIcon icon="ep:search" class="mr-5px" /> 搜索 <IconifyIcon icon="ep:search" class="mr-[5px]" /> 搜索
</ElButton> </ElButton>
<ElSwitch <ElSwitch
v-model="autoRefresh" v-model="autoRefresh"

View File

@ -77,9 +77,6 @@ function useFormSchema(): VbenFormSchema[] {
componentProps: { componentProps: {
placeholder: '请选择物模型属性', placeholder: '请选择物模型属性',
filterable: true, filterable: true,
filterMethod(input: string, option: any) {
return option.label.toLowerCase().includes(input.toLowerCase());
},
}, },
dependencies: { dependencies: {
triggerFields: [''], triggerFields: [''],

View File

@ -119,23 +119,25 @@ async function handlePropertyPost() {
async function handleEventPost(row: ThingModelApi.ThingModel) { async function handleEventPost(row: ThingModelApi.ThingModel) {
try { try {
const valueStr = formData.value[row.identifier!]; const valueStr = formData.value[row.identifier!];
let params: any = {}; let eventValue: any;
if (valueStr) { if (valueStr) {
try { try {
params = JSON.parse(valueStr); eventValue = JSON.parse(valueStr);
} catch { } catch {
ElMessage.error('事件参数格式错误请输入有效的JSON格式'); ElMessage.error('事件参数格式错误请输入有效的JSON格式');
return; return;
} }
} }
// IotDeviceEventPostReqDTO { identifier, value, time }
await sendDeviceMessage({ await sendDeviceMessage({
deviceId: props.device.id!, deviceId: props.device.id!,
method: IotDeviceMessageMethodEnum.EVENT_POST.method, method: IotDeviceMessageMethodEnum.EVENT_POST.method,
params: { params: {
identifier: row.identifier, identifier: row.identifier,
params, value: eventValue,
time: Date.now(),
}, },
}); });
@ -201,23 +203,24 @@ async function handlePropertySet() {
async function handleServiceInvoke(row: ThingModelApi.ThingModel) { async function handleServiceInvoke(row: ThingModelApi.ThingModel) {
try { try {
const valueStr = formData.value[row.identifier!]; const valueStr = formData.value[row.identifier!];
let params: any = {}; let inputParams: any = {};
if (valueStr) { if (valueStr) {
try { try {
params = JSON.parse(valueStr); inputParams = JSON.parse(valueStr);
} catch { } catch {
ElMessage.error('服务参数格式错误请输入有效的JSON格式'); ElMessage.error('服务参数格式错误请输入有效的JSON格式');
return; return;
} }
} }
// IotDeviceServiceInvokeReqDTO { identifier, inputParams }
await sendDeviceMessage({ await sendDeviceMessage({
deviceId: props.device.id!, deviceId: props.device.id!,
method: IotDeviceMessageMethodEnum.SERVICE_INVOKE.method, method: IotDeviceMessageMethodEnum.SERVICE_INVOKE.method,
params: { params: {
identifier: row.identifier, identifier: row.identifier,
params, inputParams,
}, },
}); });

View File

@ -287,7 +287,7 @@ onBeforeUnmount(() => {
<ElSwitch <ElSwitch
v-model="autoRefresh" v-model="autoRefresh"
active-text="定时刷新" active-text="定时刷新"
class="ml-20px" class="ml-[20px]"
inactive-text="定时刷新" inactive-text="定时刷新"
/> />
</div> </div>

View File

@ -208,7 +208,7 @@ onMounted(() => {
<ElButton <ElButton
v-if="hasAccessByCodes(['iot:device:update'])" v-if="hasAccessByCodes(['iot:device:update'])"
size="small" size="small"
class="!h-8 flex-1 rounded-md !border-[#1890ff] !text-[13px] !text-[#1890ff] transition-all duration-200 hover:!bg-[#1890ff] hover:!text-white" class="!h-8 min-w-0 flex-1 rounded-md !border-[#1890ff] !text-[13px] !text-[#1890ff] transition-all duration-200 hover:!bg-[#1890ff] hover:!text-white"
@click="emit('edit', item)" @click="emit('edit', item)"
> >
<IconifyIcon icon="lucide:edit" class="mr-1" /> <IconifyIcon icon="lucide:edit" class="mr-1" />
@ -217,7 +217,7 @@ onMounted(() => {
<ElButton <ElButton
v-if="hasAccessByCodes(['iot:device:query'])" v-if="hasAccessByCodes(['iot:device:query'])"
size="small" size="small"
class="!h-8 flex-1 rounded-md !border-[#52c41a] !text-[13px] !text-[#52c41a] transition-all duration-200 hover:!bg-[#52c41a] hover:!text-white" class="!h-8 min-w-0 flex-1 rounded-md !border-[#52c41a] !text-[13px] !text-[#52c41a] transition-all duration-200 hover:!bg-[#52c41a] hover:!text-white"
@click="emit('detail', item.id!)" @click="emit('detail', item.id!)"
> >
<IconifyIcon icon="lucide:eye" class="mr-1" /> <IconifyIcon icon="lucide:eye" class="mr-1" />
@ -226,7 +226,7 @@ onMounted(() => {
<ElButton <ElButton
v-if="hasAccessByCodes(['iot:device:message-query'])" v-if="hasAccessByCodes(['iot:device:message-query'])"
size="small" size="small"
class="!h-8 flex-1 rounded-md !border-[#fa8c16] !text-[13px] !text-[#fa8c16] transition-all duration-200 hover:!bg-[#fa8c16] hover:!text-white" class="!h-8 min-w-0 flex-1 rounded-md !border-[#fa8c16] !text-[13px] !text-[#fa8c16] transition-all duration-200 hover:!bg-[#fa8c16] hover:!text-white"
@click="emit('model', item.id!)" @click="emit('model', item.id!)"
> >
<IconifyIcon icon="lucide:database" class="mr-1" /> <IconifyIcon icon="lucide:database" class="mr-1" />

View File

@ -185,7 +185,7 @@ onMounted(() => {
<ElButton <ElButton
v-if="hasAccessByCodes(['iot:product:update'])" v-if="hasAccessByCodes(['iot:product:update'])"
size="small" size="small"
class="!h-8 flex-1 rounded-md !border-[#1890ff] !text-[13px] !text-[#1890ff] transition-all duration-200 hover:!bg-[#1890ff] hover:!text-white" class="!h-8 min-w-0 flex-1 rounded-md !border-[#1890ff] !text-[13px] !text-[#1890ff] transition-all duration-200 hover:!bg-[#1890ff] hover:!text-white"
@click="emit('edit', item)" @click="emit('edit', item)"
> >
<IconifyIcon icon="lucide:edit" class="mr-1" /> <IconifyIcon icon="lucide:edit" class="mr-1" />
@ -194,7 +194,7 @@ onMounted(() => {
<ElButton <ElButton
v-if="hasAccessByCodes(['iot:product:query'])" v-if="hasAccessByCodes(['iot:product:query'])"
size="small" size="small"
class="!h-8 flex-1 rounded-md !border-[#52c41a] !text-[13px] !text-[#52c41a] transition-all duration-200 hover:!bg-[#52c41a] hover:!text-white" class="!h-8 min-w-0 flex-1 rounded-md !border-[#52c41a] !text-[13px] !text-[#52c41a] transition-all duration-200 hover:!bg-[#52c41a] hover:!text-white"
@click="emit('detail', item.id)" @click="emit('detail', item.id)"
> >
<IconifyIcon icon="lucide:eye" class="mr-1" /> <IconifyIcon icon="lucide:eye" class="mr-1" />
@ -203,7 +203,7 @@ onMounted(() => {
<ElButton <ElButton
v-if="hasAccessByCodes(['iot:thing-model:query'])" v-if="hasAccessByCodes(['iot:thing-model:query'])"
size="small" size="small"
class="!h-8 flex-1 rounded-md !border-[#fa8c16] !text-[13px] !text-[#fa8c16] transition-all duration-200 hover:!bg-[#fa8c16] hover:!text-white" class="!h-8 min-w-0 flex-1 rounded-md !border-[#fa8c16] !text-[13px] !text-[#fa8c16] transition-all duration-200 hover:!bg-[#fa8c16] hover:!text-white"
@click="emit('thingModel', item.id)" @click="emit('thingModel', item.id)"
> >
<IconifyIcon icon="lucide:git-branch" class="mr-1" /> <IconifyIcon icon="lucide:git-branch" class="mr-1" />

View File

@ -156,7 +156,7 @@ function handleOperatorChange() {
</script> </script>
<template> <template>
<div class="gap-16px flex flex-col"> <div class="gap-[16px] flex flex-col">
<!-- 条件类型选择 --> <!-- 条件类型选择 -->
<ElRow :gutter="16"> <ElRow :gutter="16">
<ElCol :span="8"> <ElCol :span="8">
@ -213,7 +213,7 @@ function handleOperatorChange() {
v-if=" v-if="
condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_STATUS
" "
class="gap-16px flex flex-col" class="gap-[16px] flex flex-col"
> >
<!-- 状态和操作符选择 --> <!-- 状态和操作符选择 -->
<ElRow :gutter="16"> <ElRow :gutter="16">
@ -266,7 +266,7 @@ function handleOperatorChange() {
v-else-if=" v-else-if="
condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY condition.type === IotRuleSceneTriggerConditionTypeEnum.DEVICE_PROPERTY
" "
class="space-y-16px" class="space-y-[16px]"
> >
<!-- 属性配置 --> <!-- 属性配置 -->
<ElRow :gutter="16"> <ElRow :gutter="16">

View File

@ -178,7 +178,7 @@ async function loadThingModelProperties(productId: number) {
property.accessMode === IoTThingModelAccessModeEnum.WRITE_ONLY.value), property.accessMode === IoTThingModelAccessModeEnum.WRITE_ONLY.value),
); );
} catch (error) { } catch (error) {
console.error('加载物模型属性失败:', error); console.error('加载物模型属性失败 ', error);
thingModelProperties.value = []; thingModelProperties.value = [];
} finally { } finally {
loadingThingModel.value = false; loadingThingModel.value = false;
@ -206,7 +206,7 @@ async function loadServiceList(productId: number) {
serviceList.value = tslData.services; serviceList.value = tslData.services;
} catch (error) { } catch (error) {
console.error('加载服务列表失败:', error); console.error('加载服务列表失败 ', error);
serviceList.value = []; serviceList.value = [];
} finally { } finally {
loadingServices.value = false; loadingServices.value = false;
@ -339,7 +339,7 @@ watch(
</script> </script>
<template> <template>
<div class="gap-16px flex flex-col"> <div class="gap-[16px] flex flex-col">
<!-- 产品和设备选择 - 与触发器保持一致的分离式选择器 --> <!-- 产品和设备选择 - 与触发器保持一致的分离式选择器 -->
<ElRow :gutter="16"> <ElRow :gutter="16">
<ElCol :span="12"> <ElCol :span="12">
@ -362,7 +362,7 @@ watch(
</ElRow> </ElRow>
<!-- 服务选择 - 服务调用类型时显示 --> <!-- 服务选择 - 服务调用类型时显示 -->
<div v-if="action.productId && isServiceInvokeAction" class="space-y-16px"> <div v-if="action.productId && isServiceInvokeAction" class="space-y-[16px]">
<ElFormItem label="服务" required> <ElFormItem label="服务" required>
<ElSelect <ElSelect
v-model="action.identifier" v-model="action.identifier"
@ -393,7 +393,7 @@ watch(
</ElFormItem> </ElFormItem>
<!-- 服务参数配置 --> <!-- 服务参数配置 -->
<div v-if="action.identifier" class="space-y-16px"> <div v-if="action.identifier" class="space-y-[16px]">
<ElFormItem label="服务参数" required> <ElFormItem label="服务参数" required>
<JsonParamsInput <JsonParamsInput
v-model="paramsValue" v-model="paramsValue"
@ -406,7 +406,7 @@ watch(
</div> </div>
<!-- 控制参数配置 - 属性设置类型时显示 --> <!-- 控制参数配置 - 属性设置类型时显示 -->
<div v-if="action.productId && isPropertySetAction" class="space-y-16px"> <div v-if="action.productId && isPropertySetAction" class="space-y-[16px]">
<!-- 参数配置 --> <!-- 参数配置 -->
<ElFormItem label="参数" required> <ElFormItem label="参数" required>
<JsonParamsInput <JsonParamsInput

View File

@ -93,23 +93,23 @@ function removeConditionGroup() {
</script> </script>
<template> <template>
<div class="gap-16px flex flex-col"> <div class="gap-[16px] flex flex-col">
<!-- 主条件配置 - 默认直接展示 --> <!-- 主条件配置 - 默认直接展示 -->
<div class="space-y-16px"> <div class="space-y-[16px]">
<!-- 主条件配置 --> <!-- 主条件配置 -->
<div class="gap-16px flex flex-col"> <div class="gap-[16px] flex flex-col">
<!-- 主条件配置 --> <!-- 主条件配置 -->
<div class="space-y-16px"> <div class="space-y-[16px]">
<!-- 主条件头部 - 与附加条件组保持一致的绿色风格 --> <!-- 主条件头部 - 与附加条件组保持一致的绿色风格 -->
<div <div
class="p-16px rounded-8px flex items-center justify-between border border-green-200 bg-gradient-to-r from-green-50 to-emerald-50" class="p-[16px] rounded-[8px] flex items-center justify-between border border-green-200 bg-gradient-to-r from-green-50 to-emerald-50"
> >
<div class="gap-12px flex items-center"> <div class="gap-[12px] flex items-center">
<div <div
class="gap-8px text-16px font-600 flex items-center text-green-700" class="gap-[8px] text-[16px] font-600 flex items-center text-green-700"
> >
<div <div
class="w-24px h-24px text-12px flex items-center justify-center rounded-full bg-green-500 font-bold text-white" class="w-[24px] h-[24px] text-[12px] flex items-center justify-center rounded-full bg-green-500 font-bold text-white"
> >
</div> </div>
@ -131,19 +131,19 @@ function removeConditionGroup() {
</div> </div>
<!-- 条件组配置 --> <!-- 条件组配置 -->
<div class="space-y-16px"> <div class="space-y-[16px]">
<!-- 条件组配置 --> <!-- 条件组配置 -->
<div class="gap-16px flex flex-col"> <div class="gap-[16px] flex flex-col">
<!-- 条件组容器头部 --> <!-- 条件组容器头部 -->
<div <div
class="p-16px rounded-8px flex items-center justify-between border border-green-200 bg-gradient-to-r from-green-50 to-emerald-50" class="p-[16px] rounded-[8px] flex items-center justify-between border border-green-200 bg-gradient-to-r from-green-50 to-emerald-50"
> >
<div class="gap-12px flex items-center"> <div class="gap-[12px] flex items-center">
<div <div
class="gap-8px text-16px font-600 flex items-center text-green-700" class="gap-[8px] text-[16px] font-600 flex items-center text-green-700"
> >
<div <div
class="w-24px h-24px text-12px flex items-center justify-center rounded-full bg-green-500 font-bold text-white" class="w-[24px] h-[24px] text-[12px] flex items-center justify-center rounded-full bg-green-500 font-bold text-white"
> >
</div> </div>
@ -154,7 +154,7 @@ function removeConditionGroup() {
{{ trigger.conditionGroups?.length || 0 }} 个子条件组 {{ trigger.conditionGroups?.length || 0 }} 个子条件组
</ElTag> </ElTag>
</div> </div>
<div class="gap-8px flex items-center"> <div class="gap-[8px] flex items-center">
<ElButton <ElButton
type="primary" type="primary"
size="small" size="small"
@ -174,7 +174,7 @@ function removeConditionGroup() {
<!-- 子条件组列表 --> <!-- 子条件组列表 -->
<div <div
v-if="trigger.conditionGroups && trigger.conditionGroups.length > 0" v-if="trigger.conditionGroups && trigger.conditionGroups.length > 0"
class="space-y-16px" class="space-y-[16px]"
> >
<!-- 逻辑关系说明 --> <!-- 逻辑关系说明 -->
<div class="relative"> <div class="relative">
@ -185,17 +185,17 @@ function removeConditionGroup() {
> >
<!-- 子条件组容器 --> <!-- 子条件组容器 -->
<div <div
class="rounded-8px border-2 border-orange-200 bg-orange-50 shadow-sm transition-shadow hover:shadow-md" class="rounded-[8px] border-2 border-orange-200 bg-orange-50 shadow-sm transition-shadow hover:shadow-md"
> >
<div <div
class="p-16px rounded-t-6px flex items-center justify-between border-b border-orange-200 bg-gradient-to-r from-orange-50 to-yellow-50" class="p-[16px] rounded-t-6px flex items-center justify-between border-b border-orange-200 bg-gradient-to-r from-orange-50 to-yellow-50"
> >
<div class="gap-12px flex items-center"> <div class="gap-[12px] flex items-center">
<div <div
class="gap-8px text-16px font-600 flex items-center text-orange-700" class="gap-[8px] text-[16px] font-600 flex items-center text-orange-700"
> >
<div <div
class="w-24px h-24px text-12px flex items-center justify-center rounded-full bg-orange-500 font-bold text-white" class="w-[24px] h-[24px] text-[12px] flex items-center justify-center rounded-full bg-orange-500 font-bold text-white"
> >
{{ subGroupIndex + 1 }} {{ subGroupIndex + 1 }}
</div> </div>
@ -233,19 +233,19 @@ function removeConditionGroup() {
<!-- 子条件组间的'或'连接符 --> <!-- 子条件组间的'或'连接符 -->
<div <div
v-if="subGroupIndex < trigger.conditionGroups!.length - 1" v-if="subGroupIndex < trigger.conditionGroups!.length - 1"
class="py-12px flex items-center justify-center" class="py-[12px] flex items-center justify-center"
> >
<div class="gap-8px flex items-center"> <div class="gap-[8px] flex items-center">
<!-- 连接线 --> <!-- 连接线 -->
<div class="w-32px h-1px bg-orange-300"></div> <div class="w-[32px] h-[1px] bg-orange-300"></div>
<!-- 或标签 --> <!-- 或标签 -->
<div <div
class="px-16px py-6px rounded-full border-2 border-orange-300 bg-orange-100" class="px-[16px] py-[6px] rounded-full border-2 border-orange-300 bg-orange-100"
> >
<span class="text-14px font-600 text-orange-600"></span> <span class="text-[14px] font-600 text-orange-600"></span>
</div> </div>
<!-- 连接线 --> <!-- 连接线 -->
<div class="w-32px h-1px bg-orange-300"></div> <div class="w-[32px] h-[1px] bg-orange-300"></div>
</div> </div>
</div> </div>
</div> </div>
@ -255,13 +255,13 @@ function removeConditionGroup() {
<!-- 空状态 --> <!-- 空状态 -->
<div <div
v-else v-else
class="p-24px rounded-8px border-2 border-dashed border-orange-200 bg-orange-50 text-center" class="p-[24px] rounded-[8px] border-2 border-dashed border-orange-200 bg-orange-50 text-center"
> >
<div class="gap-12px flex flex-col items-center"> <div class="gap-[12px] flex flex-col items-center">
<IconifyIcon icon="lucide:plus" class="text-32px text-orange-400" /> <IconifyIcon icon="lucide:plus" class="text-[32px] text-orange-400" />
<div class="text-orange-600"> <div class="text-orange-600">
<p class="text-14px font-500 mb-4px">暂无子条件组</p> <p class="text-[14px] font-500 mb-[4px]">暂无子条件组</p>
<p class="text-12px">点击上方"添加子条件组"按钮开始配置</p> <p class="text-[12px]">点击上方"添加子条件组"按钮开始配置</p>
</div> </div>
</div> </div>
</div> </div>

View File

@ -110,7 +110,7 @@ function updateCondition(
> >
<!-- 条件配置 --> <!-- 条件配置 -->
<div <div
class="rounded-3px bg-fill-color-blank border border-border shadow-sm" class="rounded-[3px] bg-fill-color-blank border border-border shadow-sm"
> >
<div <div
class="rounded-t-1 bg-fill-color-blank flex items-center justify-between border-b border-border p-3" class="rounded-t-1 bg-fill-color-blank flex items-center justify-between border-b border-border p-3"

View File

@ -157,24 +157,24 @@ function onActionTypeChange(action: RuleSceneApi.Action, type: number) {
<template> <template>
<ElCard <ElCard
class="rounded-8px border border-[var(--el-border-color-light)]" class="rounded-[8px] border border-[var(--el-border-color-light)]"
shadow="never" shadow="never"
> >
<template #header> <template #header>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="gap-8px flex items-center"> <div class="gap-[8px] flex items-center">
<IconifyIcon <IconifyIcon
icon="ep:setting" icon="ep:setting"
class="text-18px text-[var(--el-color-primary)]" class="text-[18px] text-[var(--el-color-primary)]"
/> />
<span class="text-16px font-600 text-[var(--el-text-color-primary)]"> <span class="text-[16px] font-600 text-[var(--el-text-color-primary)]">
执行器配置 执行器配置
</span> </span>
<ElTag size="small" type="info"> <ElTag size="small" type="info">
{{ actions.length }} 个执行器 {{ actions.length }} 个执行器
</ElTag> </ElTag>
</div> </div>
<div class="gap-8px flex items-center"> <div class="gap-[8px] flex items-center">
<ElButton type="primary" size="small" @click="addAction"> <ElButton type="primary" size="small" @click="addAction">
<IconifyIcon icon="ep:plus" /> <IconifyIcon icon="ep:plus" />
添加执行器 添加执行器
@ -195,7 +195,7 @@ function onActionTypeChange(action: RuleSceneApi.Action, type: number) {
</div> </div>
<!-- 执行器列表 --> <!-- 执行器列表 -->
<div v-else class="space-y-24px"> <div v-else class="space-y-[24px]">
<div <div
v-for="(action, index) in actions" v-for="(action, index) in actions"
:key="`action-${index}`" :key="`action-${index}`"
@ -205,7 +205,7 @@ function onActionTypeChange(action: RuleSceneApi.Action, type: number) {
<div <div
class="flex items-center justify-between rounded-t-lg border-b border-blue-200 bg-gradient-to-r from-blue-50 to-sky-50 p-4" class="flex items-center justify-between rounded-t-lg border-b border-blue-200 bg-gradient-to-r from-blue-50 to-sky-50 p-4"
> >
<div class="gap-12px flex items-center"> <div class="gap-[12px] flex items-center">
<div <div
class="font-600 flex items-center gap-2 text-base text-blue-700" class="font-600 flex items-center gap-2 text-base text-blue-700"
> >
@ -224,7 +224,7 @@ function onActionTypeChange(action: RuleSceneApi.Action, type: number) {
{{ getActionTypeLabel(action.type as number) }} {{ getActionTypeLabel(action.type as number) }}
</ElTag> </ElTag>
</div> </div>
<div class="gap-8px flex items-center"> <div class="gap-[8px] flex items-center">
<ElButton <ElButton
v-if="actions.length > 1" v-if="actions.length > 1"
type="danger" type="danger"
@ -240,12 +240,12 @@ function onActionTypeChange(action: RuleSceneApi.Action, type: number) {
</div> </div>
<!-- 执行器内容区域 --> <!-- 执行器内容区域 -->
<div class="p-16px space-y-16px"> <div class="p-[16px] space-y-[16px]">
<!-- 执行类型选择 --> <!-- 执行类型选择 -->
<div class="w-full"> <div class="w-full">
<ElFormItem label="执行类型" required> <ElFormItem label="执行类型" required>
<ElSelect <ElSelect
v-model="action.type" :model-value="action.type"
@change="(value: any) => updateActionType(index, value)" @change="(value: any) => updateActionType(index, value)"
placeholder="请选择执行类型" placeholder="请选择执行类型"
class="w-full" class="w-full"
@ -279,7 +279,7 @@ function onActionTypeChange(action: RuleSceneApi.Action, type: number) {
<!-- 触发告警提示 - 触发告警时显示 --> <!-- 触发告警提示 - 触发告警时显示 -->
<div <div
v-if="action.type === IotRuleSceneActionTypeEnum.ALERT_TRIGGER" v-if="action.type === IotRuleSceneActionTypeEnum.ALERT_TRIGGER"
class="rounded-6px border border-[var(--el-border-color-light)] bg-[var(--el-fill-color-blank)] p-4" class="rounded-[6px] border border-[var(--el-border-color-light)] bg-[var(--el-fill-color-blank)] p-4"
> >
<div class="mb-2 flex items-center gap-2"> <div class="mb-2 flex items-center gap-2">
<IconifyIcon <IconifyIcon
@ -301,7 +301,7 @@ function onActionTypeChange(action: RuleSceneApi.Action, type: number) {
</div> </div>
<!-- 添加提示 --> <!-- 添加提示 -->
<div v-if="actions.length > 0" class="py-16px text-center"> <div v-if="actions.length > 0" class="py-[16px] text-center">
<ElButton type="primary" plain @click="addAction"> <ElButton type="primary" plain @click="addAction">
<IconifyIcon icon="ep:plus" /> <IconifyIcon icon="ep:plus" />
继续添加执行器 继续添加执行器

View File

@ -35,28 +35,28 @@ const formData = useVModel(props, 'modelValue', emit); // 表单数据
<template> <template>
<ElCard <ElCard
class="rounded-8px mb-10px border border-[var(--el-border-color-light)]" class="rounded-[8px] mb-[10px] border border-[var(--el-border-color-light)]"
shadow="never" shadow="never"
> >
<template #header> <template #header>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="gap-8px flex items-center"> <div class="gap-[8px] flex items-center">
<IconifyIcon <IconifyIcon
icon="ep:info-filled" icon="ep:info-filled"
class="text-18px text-[var(--el-color-primary)]" class="text-[18px] text-[var(--el-color-primary)]"
/> />
<span class="text-16px font-600 text-[var(--el-text-color-primary)]"> <span class="text-[16px] font-600 text-[var(--el-text-color-primary)]">
基础信息 基础信息
</span> </span>
</div> </div>
<div class="gap-8px flex items-center"> <div class="gap-[8px] flex items-center">
<DictTag :type="DICT_TYPE.COMMON_STATUS" :value="formData.status" /> <DictTag :type="DICT_TYPE.COMMON_STATUS" :value="formData.status" />
</div> </div>
</div> </div>
</template> </template>
<div class="p-0"> <div class="p-0">
<ElRow :gutter="24" class="mb-24px"> <ElRow :gutter="24" class="mb-[24px]">
<ElCol :span="12"> <ElCol :span="12">
<ElFormItem label="场景名称" prop="name" required> <ElFormItem label="场景名称" prop="name" required>
<ElInput <ElInput

View File

@ -135,17 +135,17 @@ onMounted(() => {
<template> <template>
<ElCard <ElCard
class="rounded-8px mb-10px border border-[var(--el-border-color-light)]" class="rounded-[8px] mb-[10px] border border-[var(--el-border-color-light)]"
shadow="never" shadow="never"
> >
<template #header> <template #header>
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="gap-8px flex items-center"> <div class="gap-[8px] flex items-center">
<IconifyIcon <IconifyIcon
icon="ep:lightning" icon="ep:lightning"
class="text-18px text-[var(--el-color-primary)]" class="text-[18px] text-[var(--el-color-primary)]"
/> />
<span class="text-16px font-600 text-[var(--el-text-color-primary)]"> <span class="text-[16px] font-600 text-[var(--el-text-color-primary)]">
触发器配置 触发器配置
</span> </span>
<ElTag size="small" type="info"> <ElTag size="small" type="info">
@ -159,24 +159,24 @@ onMounted(() => {
</div> </div>
</template> </template>
<div class="p-16px space-y-24px"> <div class="p-[16px] space-y-[24px]">
<!-- 触发器列表 --> <!-- 触发器列表 -->
<div v-if="triggers.length > 0" class="space-y-24px"> <div v-if="triggers.length > 0" class="space-y-[24px]">
<div <div
v-for="(triggerItem, index) in triggers" v-for="(triggerItem, index) in triggers"
:key="`trigger-${index}`" :key="`trigger-${index}`"
class="rounded-8px border-2 border-green-200 bg-green-50 shadow-sm transition-shadow hover:shadow-md" class="rounded-[8px] border-2 border-green-200 bg-green-50 shadow-sm transition-shadow hover:shadow-md"
> >
<!-- 触发器头部 - 绿色主题 --> <!-- 触发器头部 - 绿色主题 -->
<div <div
class="p-16px rounded-t-6px flex items-center justify-between border-b border-green-200 bg-gradient-to-r from-green-50 to-emerald-50" class="p-[16px] rounded-t-6px flex items-center justify-between border-b border-green-200 bg-gradient-to-r from-green-50 to-emerald-50"
> >
<div class="gap-12px flex items-center"> <div class="gap-[12px] flex items-center">
<div <div
class="gap-8px text-16px font-600 flex items-center text-green-700" class="gap-[8px] text-[16px] font-600 flex items-center text-green-700"
> >
<div <div
class="w-24px h-24px text-12px flex items-center justify-center rounded-full bg-green-500 font-bold text-white" class="w-[24px] h-[24px] text-[12px] flex items-center justify-center rounded-full bg-green-500 font-bold text-white"
> >
{{ index + 1 }} {{ index + 1 }}
</div> </div>
@ -190,7 +190,7 @@ onMounted(() => {
{{ getTriggerTypeLabel(triggerItem.type as number) }} {{ getTriggerTypeLabel(triggerItem.type as number) }}
</ElTag> </ElTag>
</div> </div>
<div class="gap-8px flex items-center"> <div class="gap-[8px] flex items-center">
<ElButton <ElButton
v-if="triggers.length > 1" v-if="triggers.length > 1"
type="danger" type="danger"
@ -206,7 +206,7 @@ onMounted(() => {
</div> </div>
<!-- 触发器内容区域 --> <!-- 触发器内容区域 -->
<div class="p-16px space-y-16px"> <div class="p-[16px] space-y-[16px]">
<!-- 设备触发配置 --> <!-- 设备触发配置 -->
<DeviceTriggerConfig <DeviceTriggerConfig
v-if="isDeviceTrigger(triggerItem.type as number)" v-if="isDeviceTrigger(triggerItem.type as number)"
@ -221,17 +221,17 @@ onMounted(() => {
<!-- 定时触发配置 --> <!-- 定时触发配置 -->
<div <div
v-else-if="triggerItem.type === IotRuleSceneTriggerTypeEnum.TIMER" v-else-if="triggerItem.type === IotRuleSceneTriggerTypeEnum.TIMER"
class="gap-16px flex flex-col" class="gap-[16px] flex flex-col"
> >
<div <div
class="gap-8px p-12px px-16px rounded-6px flex items-center border border-[var(--el-border-color-lighter)] bg-[var(--el-fill-color-light)]" class="gap-[8px] p-[12px] px-[16px] rounded-[6px] flex items-center border border-[var(--el-border-color-lighter)] bg-[var(--el-fill-color-light)]"
> >
<IconifyIcon <IconifyIcon
icon="lucide:timer" icon="lucide:timer"
class="text-18px text-[var(--el-color-danger)]" class="text-[18px] text-[var(--el-color-danger)]"
/> />
<span <span
class="text-14px font-500 text-[var(--el-text-color-primary)]" class="text-[14px] font-500 text-[var(--el-text-color-primary)]"
> >
定时触发配置 定时触发配置
</span> </span>
@ -239,7 +239,7 @@ onMounted(() => {
<!-- CRON 表达式配置 --> <!-- CRON 表达式配置 -->
<div <div
class="p-16px rounded-6px border border-[var(--el-border-color-lighter)] bg-[var(--el-fill-color-blank)]" class="p-[16px] rounded-[6px] border border-[var(--el-border-color-lighter)] bg-[var(--el-fill-color-blank)]"
> >
<ElFormItem label="CRON 表达式" required> <ElFormItem label="CRON 表达式" required>
<CronTab <CronTab
@ -264,12 +264,12 @@ onMounted(() => {
</div> </div>
<!-- 空状态 --> <!-- 空状态 -->
<div v-else class="py-40px text-center"> <div v-else class="py-[40px] text-center">
<ElEmpty description="暂无触发器"> <ElEmpty description="暂无触发器">
<template #description> <template #description>
<div class="space-y-8px"> <div class="space-y-[8px]">
<p class="text-[var(--el-text-color-secondary)]">暂无触发器配置</p> <p class="text-[var(--el-text-color-secondary)]">暂无触发器配置</p>
<p class="text-12px text-[var(--el-text-color-placeholder)]"> <p class="text-[12px] text-[var(--el-text-color-placeholder)]">
请使用上方的添加触发器按钮来设置触发规则 请使用上方的添加触发器按钮来设置触发规则
</p> </p>
</div> </div>

View File

@ -49,7 +49,7 @@ async function getDeviceList() {
const data = await getDeviceListByProductId(props.productId); const data = await getDeviceListByProductId(props.productId);
deviceList.value = data || []; deviceList.value = data || [];
} catch (error) { } catch (error) {
console.error('获取设备列表失败:', error); console.error('获取设备列表失败 ', error);
deviceList.value = []; deviceList.value = [];
} finally { } finally {
deviceList.value.unshift(DEVICE_SELECTOR_OPTIONS.ALL_DEVICES); deviceList.value.unshift(DEVICE_SELECTOR_OPTIONS.ALL_DEVICES);
@ -93,18 +93,18 @@ watch(
:label="device.deviceName" :label="device.deviceName"
:value="device.id" :value="device.id"
> >
<div class="py-4px flex w-full items-center justify-between"> <div class="py-[4px] flex w-full items-center justify-between">
<div class="flex-1"> <div class="flex-1">
<div <div
class="text-14px font-500 mb-2px text-[var(--el-text-color-primary)]" class="text-[14px] font-500 mb-[2px] text-[var(--el-text-color-primary)]"
> >
{{ device.deviceName }} {{ device.deviceName }}
</div> </div>
<div class="text-12px text-[var(--el-text-color-secondary)]"> <div class="text-[12px] text-[var(--el-text-color-secondary)]">
{{ device.deviceKey }} {{ device.deviceKey }}
</div> </div>
</div> </div>
<div class="gap-4px flex items-center" v-if="device.id > 0"> <div class="gap-[4px] flex items-center" v-if="device.id > 0">
<DictTag :type="DICT_TYPE.IOT_DEVICE_STATE" :value="device.state" /> <DictTag :type="DICT_TYPE.IOT_DEVICE_STATE" :value="device.state" />
</div> </div>
</div> </div>

View File

@ -250,18 +250,18 @@ watch(
:label="operator.label" :label="operator.label"
:value="operator.value" :value="operator.value"
> >
<div class="py-4px flex w-full items-center justify-between"> <div class="py-[4px] flex w-full items-center justify-between">
<div class="gap-8px flex items-center"> <div class="gap-[8px] flex items-center">
<div class="text-14px font-500 text-[var(--el-text-color-primary)]"> <div class="text-[14px] font-500 text-[var(--el-text-color-primary)]">
{{ operator.label }} {{ operator.label }}
</div> </div>
<div <div
class="text-12px px-6px py-2px rounded-4px bg-[var(--el-color-primary-light-9)] font-mono text-[var(--el-color-primary)]" class="text-[12px] px-[6px] py-[2px] rounded-[4px] bg-[var(--el-color-primary-light-9)] font-mono text-[var(--el-color-primary)]"
> >
{{ operator.symbol }} {{ operator.symbol }}
</div> </div>
</div> </div>
<div class="text-12px text-[var(--el-text-color-secondary)]"> <div class="text-[12px] text-[var(--el-text-color-secondary)]">
{{ operator.description }} {{ operator.description }}
</div> </div>
</div> </div>

View File

@ -69,14 +69,14 @@ onMounted(() => {
:label="product.name" :label="product.name"
:value="product.id" :value="product.id"
> >
<div class="py-4px flex w-full items-center justify-between"> <div class="py-[4px] flex w-full items-center justify-between">
<div class="flex-1"> <div class="flex-1">
<div <div
class="text-14px font-500 mb-2px text-[var(--el-text-color-primary)]" class="text-[14px] font-500 mb-[2px] text-[var(--el-text-color-primary)]"
> >
{{ product.name }} {{ product.name }}
</div> </div>
<div class="text-12px text-[var(--el-text-color-secondary)]"> <div class="text-[12px] text-[var(--el-text-color-secondary)]">
{{ product.productKey }} {{ product.productKey }}
</div> </div>
</div> </div>

View File

@ -144,11 +144,11 @@ async function fetchThingModelTSL() {
thingModelTSL.value = tslData; thingModelTSL.value = tslData;
parseThingModelData(); parseThingModelData();
} else { } else {
console.error('获取物模型TSL失败: 返回数据为空'); console.error('获取物模型 TSL 失败 返回数据为空');
propertyList.value = []; propertyList.value = [];
} }
} catch (error) { } catch (error) {
console.error('获取物模型TSL失败:', error); console.error('获取物模型 TSL 失败 ', error);
propertyList.value = []; propertyList.value = [];
} finally { } finally {
loading.value = false; loading.value = false;
@ -280,14 +280,14 @@ watch(
</script> </script>
<template> <template>
<div class="gap-8px flex items-center"> <div class="gap-[8px] flex items-center">
<ElSelect <ElSelect
v-model="localValue" v-model="localValue"
placeholder="请选择监控项" placeholder="请选择监控项"
filterable filterable
clearable clearable
@change="handleChange" @change="handleChange"
class="!w-150px" class="!w-[150px]"
:loading="loading" :loading="loading"
> >
<ElOptionGroup <ElOptionGroup
@ -301,11 +301,11 @@ watch(
:label="property.name" :label="property.name"
:value="property.identifier" :value="property.identifier"
> >
<div class="py-2px flex w-full items-center justify-between"> <div class="py-[2px] flex w-full items-center justify-between">
<span class="text-14px font-500 flex-1 truncate text-[var(--el-text-color-primary)]"> <span class="text-[14px] font-500 flex-1 truncate text-[var(--el-text-color-primary)]">
{{ property.name }} {{ property.name }}
</span> </span>
<ElTag size="small" class="ml-8px flex-shrink-0"> <ElTag size="small" class="ml-[8px] flex-shrink-0">
{{ property.identifier }} {{ property.identifier }}
</ElTag> </ElTag>
</div> </div>
@ -338,9 +338,9 @@ watch(
<template #default> <template #default>
<!-- 弹出层内容 --> <!-- 弹出层内容 -->
<div class="property-detail-content"> <div class="property-detail-content">
<div class="gap-8px mb-12px flex items-center"> <div class="gap-[8px] mb-[12px] flex items-center">
<IconifyIcon icon="ep:info-filled" class="text-16px text-[var(--el-color-info)]" /> <IconifyIcon icon="ep:info-filled" class="text-[16px] text-[var(--el-color-info)]" />
<span class="text-14px font-500 text-[var(--el-text-color-primary)]"> <span class="text-[14px] font-500 text-[var(--el-text-color-primary)]">
{{ selectedProperty.name }} {{ selectedProperty.name }}
</span> </span>
<ElTag size="small"> <ElTag size="small">
@ -348,42 +348,42 @@ watch(
</ElTag> </ElTag>
</div> </div>
<div class="space-y-8px ml-24px"> <div class="space-y-[8px] ml-[24px]">
<div class="gap-8px flex items-start"> <div class="gap-[8px] flex items-start">
<span class="text-12px min-w-60px flex-shrink-0 text-[var(--el-text-color-secondary)]"> <span class="text-[12px] min-w-[60px] flex-shrink-0 text-[var(--el-text-color-secondary)]">
标识符 标识符
</span> </span>
<span class="text-12px flex-1 text-[var(--el-text-color-primary)]"> <span class="text-[12px] flex-1 text-[var(--el-text-color-primary)]">
{{ selectedProperty.identifier }} {{ selectedProperty.identifier }}
</span> </span>
</div> </div>
<div <div
v-if="selectedProperty.description" v-if="selectedProperty.description"
class="gap-8px flex items-start" class="gap-[8px] flex items-start"
> >
<span class="text-12px min-w-60px flex-shrink-0 text-[var(--el-text-color-secondary)]"> <span class="text-[12px] min-w-[60px] flex-shrink-0 text-[var(--el-text-color-secondary)]">
描述 描述
</span> </span>
<span class="text-12px flex-1 text-[var(--el-text-color-primary)]"> <span class="text-[12px] flex-1 text-[var(--el-text-color-primary)]">
{{ selectedProperty.description }} {{ selectedProperty.description }}
</span> </span>
</div> </div>
<div v-if="selectedProperty.unit" class="gap-8px flex items-start"> <div v-if="selectedProperty.unit" class="gap-[8px] flex items-start">
<span class="text-12px min-w-60px flex-shrink-0 text-[var(--el-text-color-secondary)]"> <span class="text-[12px] min-w-[60px] flex-shrink-0 text-[var(--el-text-color-secondary)]">
单位 单位
</span> </span>
<span class="text-12px flex-1 text-[var(--el-text-color-primary)]"> <span class="text-[12px] flex-1 text-[var(--el-text-color-primary)]">
{{ selectedProperty.unit }} {{ selectedProperty.unit }}
</span> </span>
</div> </div>
<div v-if="selectedProperty.range" class="gap-8px flex items-start"> <div v-if="selectedProperty.range" class="gap-[8px] flex items-start">
<span class="text-12px min-w-60px flex-shrink-0 text-[var(--el-text-color-secondary)]"> <span class="text-[12px] min-w-[60px] flex-shrink-0 text-[var(--el-text-color-secondary)]">
取值范围 取值范围
</span> </span>
<span class="text-12px flex-1 text-[var(--el-text-color-primary)]"> <span class="text-[12px] flex-1 text-[var(--el-text-color-primary)]">
{{ selectedProperty.range }} {{ selectedProperty.range }}
</span> </span>
</div> </div>
@ -394,12 +394,12 @@ watch(
selectedProperty.type === IoTThingModelTypeEnum.PROPERTY && selectedProperty.type === IoTThingModelTypeEnum.PROPERTY &&
selectedProperty.accessMode selectedProperty.accessMode
" "
class="gap-8px flex items-start" class="gap-[8px] flex items-start"
> >
<span class="text-12px min-w-60px flex-shrink-0 text-[var(--el-text-color-secondary)]"> <span class="text-[12px] min-w-[60px] flex-shrink-0 text-[var(--el-text-color-secondary)]">
访问模式 访问模式
</span> </span>
<span class="text-12px flex-1 text-[var(--el-text-color-primary)]"> <span class="text-[12px] flex-1 text-[var(--el-text-color-primary)]">
{{ getAccessModeLabel(selectedProperty.accessMode) }} {{ getAccessModeLabel(selectedProperty.accessMode) }}
</span> </span>
</div> </div>
@ -409,12 +409,12 @@ watch(
selectedProperty.type === IoTThingModelTypeEnum.EVENT && selectedProperty.type === IoTThingModelTypeEnum.EVENT &&
selectedProperty.eventType selectedProperty.eventType
" "
class="gap-8px flex items-start" class="gap-[8px] flex items-start"
> >
<span class="text-12px min-w-60px flex-shrink-0 text-[var(--el-text-color-secondary)]"> <span class="text-[12px] min-w-[60px] flex-shrink-0 text-[var(--el-text-color-secondary)]">
事件类型 事件类型
</span> </span>
<span class="text-12px flex-1 text-[var(--el-text-color-primary)]"> <span class="text-[12px] flex-1 text-[var(--el-text-color-primary)]">
{{ getEventTypeLabel(selectedProperty.eventType) }} {{ getEventTypeLabel(selectedProperty.eventType) }}
</span> </span>
</div> </div>
@ -424,12 +424,12 @@ watch(
selectedProperty.type === IoTThingModelTypeEnum.SERVICE && selectedProperty.type === IoTThingModelTypeEnum.SERVICE &&
selectedProperty.callType selectedProperty.callType
" "
class="gap-8px flex items-start" class="gap-[8px] flex items-start"
> >
<span class="text-12px min-w-60px flex-shrink-0 text-[var(--el-text-color-secondary)]"> <span class="text-[12px] min-w-[60px] flex-shrink-0 text-[var(--el-text-color-secondary)]">
调用类型 调用类型
</span> </span>
<span class="text-12px flex-1 text-[var(--el-text-color-primary)]"> <span class="text-[12px] flex-1 text-[var(--el-text-color-primary)]">
{{ {{
getThingModelServiceCallTypeLabel(selectedProperty.callType) getThingModelServiceCallTypeLabel(selectedProperty.callType)
}} }}