Browse Source

!754 IoT: 场景联动
Merge pull request !754 from puhui999/feature/iot

芋道源码 1 year ago
parent
commit
966f1b8b7e

+ 14 - 10
src/api/iot/rule/databridge/index.ts

@@ -80,17 +80,21 @@ export interface RedisStreamMQConfig extends Config {
 }
 
 /** 数据桥梁类型 */
-// TODO @puhui999:枚举用 number 可以么?
 export const IoTDataBridgeConfigType = {
-  HTTP: '1',
-  TCP: '2',
-  WEBSOCKET: '3',
-  MQTT: '10',
-  DATABASE: '20',
-  REDIS_STREAM: '21',
-  ROCKETMQ: '30',
-  RABBITMQ: '31',
-  KAFKA: '32'
+  HTTP: 1,
+  TCP: 2,
+  WEBSOCKET: 3,
+  MQTT: 10,
+  DATABASE: 20,
+  REDIS_STREAM: 21,
+  ROCKETMQ: 30,
+  RABBITMQ: 31,
+  KAFKA: 32
+} as const
+
+export const IotDataBridgeDirectionEnum = {
+  INPUT: 1, // 输入
+  OUTPUT: 2 // 输出
 } as const
 
 // 数据桥梁 API

+ 3 - 13
src/api/iot/rule/scene/index.ts

@@ -1,15 +1,5 @@
 import request from '@/config/axios'
-import { IotRuleSceneTriggerConfig } from '@/api/iot/rule/scene/scene.types'
-
-// IoT 场景联动 VO
-export interface RuleSceneVO {
-  id?: number // 场景编号
-  name: string // 场景名称
-  description?: string // 场景描述
-  status: number // 场景状态
-  triggers: IotRuleSceneTriggerConfig[] // 触发器数组
-  actions?: any[] // 执行器数组
-}
+import { IotRuleScene } from './scene.types'
 
 // IoT 场景联动 API
 export const RuleSceneApi = {
@@ -24,12 +14,12 @@ export const RuleSceneApi = {
   },
 
   // 新增场景联动
-  createRuleScene: async (data: RuleSceneVO) => {
+  createRuleScene: async (data: IotRuleScene) => {
     return await request.post({ url: `/iot/rule-scene/create`, data })
   },
 
   // 修改场景联动
-  updateRuleScene: async (data: RuleSceneVO) => {
+  updateRuleScene: async (data: IotRuleScene) => {
     return await request.put({ url: `/iot/rule-scene/update`, data })
   },
 

+ 107 - 198
src/api/iot/rule/scene/scene.types.ts

@@ -1,222 +1,131 @@
 /**
- * 场景规则触发器配置
+ * IoT 场景联动接口定义
  */
-export interface IotRuleSceneTriggerConfig {
-  /**
-   * 触发类型
-   * - 1: 设备触发
-   * - 2: 定时触发
-   */
-  type: number
-  /** 产品标识 */
-  productKey: string
-  /** 设备名称数组 */
-  deviceNames: string[]
-  /** 触发条件数组。条件之间是"或"的关系 */
-  conditions: IotRuleSceneTriggerCondition[]
-  /** CRON 表达式。当 type = 2 时必填 */
-  cronExpression?: string
-}
 
-/**
- * 触发条件
- */
-export interface IotRuleSceneTriggerCondition {
-  /**
-   * 消息类型
-   * - property: 属性上报
-   * - event: 事件上报
-   */
-  type: string
-  /** 消息标识符 */
-  identifier?: string
-  /** 参数数组。参数之间是"或"的关系 */
-  parameters: IotRuleSceneTriggerConditionParameter[]
-}
+// 枚举定义
+const IotRuleSceneTriggerTypeEnum = {
+  DEVICE: 1, // 设备触发
+  TIMER: 2 // 定时触发
+} as const
 
-/**
- * 触发条件参数
- */
-export interface IotRuleSceneTriggerConditionParameter {
-  /** 标识符(属性、事件、服务) */
-  identifier: string
-  /**
-   * 操作符
-   */
-  operator: string
-  /**
-   * 比较值
-   * 如果有多个值,则使用 "," 分隔,类似 "1,2,3"
-   */
-  value: string
-}
+const IotRuleSceneActionTypeEnum = {
+  DEVICE_CONTROL: 1, // 设备执行
+  ALERT: 2, // 告警执行
+  DATA_BRIDGE: 3 // 桥接执行
+} as const
 
-/**
- * 执行器配置
- */
-export interface IotRuleSceneActionConfig {
-  /**
-   * 执行类型
-   * - 1: 设备控制
-   * - 2: 数据桥接
-   */
-  type: number
-  /** 设备控制配置。当 type = 1 时必填 */
-  deviceControl?: IotRuleSceneActionDeviceControl
-  /** 数据桥接编号。当 type = 2 时必填 */
-  dataBridgeId?: number
-}
+const IotDeviceMessageTypeEnum = {
+  PROPERTY: 'property', // 属性
+  SERVICE: 'service', // 服务
+  EVENT: 'event' // 事件
+} as const
 
-/**
- * 执行设备控制
- */
-export interface IotRuleSceneActionDeviceControl {
-  /** 产品标识 */
-  productKey: string
-  /** 设备名称数组 */
-  deviceNames: string[]
-  /**
-   * 消息类型
-   * - property: 属性
-   * - service: 服务
-   */
-  type: string
-  /**
-   * 消息标识符
-   * - property_set: 属性设置
-   * - service_invoke: 服务调用
-   */
-  identifier: string
-  /** 具体数据 */
-  data: Record<string, any>
-}
+const IotDeviceMessageIdentifierEnum = {
+  PROPERTY_SET: 'set', // 属性设置
+  SERVICE_INVOKE: '${identifier}' // 服务调用
+} as const
 
-/**
- * 场景规则创建/更新请求
- */
-export interface IotRuleSceneSaveReqVO {
-  /** 场景规则编号 */
-  id?: number
-  /** 场景规则名称 */
-  name: string
-  /** 场景规则状态(0=禁用 1=启用) */
-  status: number
-  /** 触发器配置 */
-  triggerConfig: IotRuleSceneTriggerConfig
-  /** 执行动作配置数组 */
-  actionConfigs: IotRuleSceneActionConfig[]
-  /** 备注 */
-  remark?: string
+const IotRuleSceneTriggerConditionParameterOperatorEnum = {
+  EQUALS: { name: '等于', value: '=' }, // 等于
+  NOT_EQUALS: { name: '不等于', value: '!=' }, // 不等于
+  GREATER_THAN: { name: '大于', value: '>' }, // 大于
+  GREATER_THAN_OR_EQUALS: { name: '大于等于', value: '>=' }, // 大于等于
+  LESS_THAN: { name: '小于', value: '<' }, // 小于
+  LESS_THAN_OR_EQUALS: { name: '小于等于', value: '<=' }, // 小于等于
+  IN: { name: '在...之中', value: 'in' }, // 在...之中
+  NOT_IN: { name: '不在...之中', value: 'not in' }, // 不在...之中
+  BETWEEN: { name: '在...之间', value: 'between' }, // 在...之间
+  NOT_BETWEEN: { name: '不在...之间', value: 'not between' }, // 不在...之间
+  LIKE: { name: '字符串匹配', value: 'like' }, // 字符串匹配
+  NOT_NULL: { name: '非空', value: 'not null' } // 非空
+} as const
+
+const IotAlertConfigReceiveTypeEnum = {
+  SMS: 1, // 短信
+  MAIL: 2, // 邮箱
+  NOTIFY: 3 // 通知
+} as const
+
+// 基础接口
+interface TenantBaseDO {
+  createTime?: Date // 创建时间
+  updateTime?: Date // 更新时间
+  creator?: string // 创建者
+  updater?: string // 更新者
+  deleted?: boolean // 是否删除
+  tenantId?: number // 租户编号
 }
 
-/**
- * 场景规则响应
- */
-export interface IotRuleSceneRespVO {
-  /** 场景规则编号 */
-  id: number
-  /** 场景规则名称 */
-  name: string
-  /** 场景规则状态(0=禁用 1=启用) */
-  status: number
-  /** 触发器配置 */
-  triggerConfig: IotRuleSceneTriggerConfig
-  /** 执行动作配置数组 */
-  actionConfigs: IotRuleSceneActionConfig[]
-  /** 备注 */
-  remark?: string
-  /** 创建时间 */
-  createTime: Date
+// 触发条件参数
+interface TriggerConditionParameter {
+  identifier: string // 标识符(属性、事件、服务)
+  operator: string // 操作符
+  value: string // 比较值
 }
 
-/**
- * 场景规则分页项
- */
-export interface IotRuleScenePageItemRespVO extends IotRuleSceneRespVO {
-  /** 触发次数 */
-  triggerCount: number
-  /** 最后触发时间 */
-  lastTriggerTime?: Date
+// 触发条件
+interface TriggerCondition {
+  type: string // 消息类型
+  identifier: string // 消息标识符
+  parameters: TriggerConditionParameter[] // 参数数组
 }
 
-/**
- * 场景规则分页请求
- */
-export interface IotRuleScenePageReqVO {
-  /** 场景规则名称 */
-  name?: string
-  /** 场景规则状态(0=禁用 1=启用) */
-  status?: number
-  /** 创建时间 */
-  createTime?: [Date, Date]
-  /** 页码 */
-  pageNo?: number
-  /** 每页条数 */
-  pageSize?: number
+// 触发器配置
+interface TriggerConfig {
+  type: number // 触发类型
+  productKey: string // 产品标识
+  deviceNames: string[] // 设备名称数组
+  conditions?: TriggerCondition[] // 触发条件数组
+  cronExpression?: string // CRON 表达式
 }
 
-/**
- * 场景规则类型枚举
- */
-export enum IotRuleSceneTriggerTypeEnum {
-  /** 设备触发 */
-  DEVICE = 1,
-  /** 定时触发 */
-  TIMER = 2
+// 执行设备控制
+interface ActionDeviceControl {
+  productKey: string // 产品标识
+  deviceNames: string[] // 设备名称数组
+  type: string // 消息类型
+  identifier: string // 消息标识符
+  data: Record<string, any> // 具体数据
 }
 
-/**
- * 场景规则动作类型枚举
- */
-export enum IotRuleSceneActionTypeEnum {
-  /** 设备控制 */
-  DEVICE_CONTROL = 1,
-  /** 数据桥接 */
-  DATA_BRIDGE = 2
+// 告警执行配置
+interface ActionAlert {
+  receiveType: number // 接收方式
+  phoneNumbers?: string[] // 手机号列表
+  emails?: string[] // 邮箱列表
+  content: string // 通知内容
 }
 
-/**
- * 设备消息类型枚举
- */
-export enum IotDeviceMessageTypeEnum {
-  /** 属性 */
-  PROPERTY = 'property',
-  /** 事件 */
-  EVENT = 'event',
-  /** 服务 */
-  SERVICE = 'service'
+// 执行器配置
+interface ActionConfig {
+  type: number // 执行类型
+  deviceControl?: ActionDeviceControl // 设备控制
+  alert?: ActionAlert // 告警执行
+  dataBridgeId?: number // 数据桥接编号
 }
 
-/**
- * 设备消息标识符枚举
- */
-export enum IotDeviceMessageIdentifierEnum {
-  /** 属性上报 */
-  PROPERTY_REPORT = 'property_report',
-  /** 属性设置 */
-  PROPERTY_SET = 'property_set',
-  /** 事件上报 */
-  EVENT_REPORT = 'event_report',
-  /** 服务调用 */
-  SERVICE_INVOKE = 'service_invoke'
+// 主接口
+interface IotRuleScene extends TenantBaseDO {
+  id: number // 场景编号
+  name: string // 场景名称
+  description: string // 场景描述
+  status: number // 场景状态
+  triggers: TriggerConfig[] // 触发器数组
+  actions: ActionConfig[] // 执行器数组
 }
 
-/**
- * 触发条件参数操作符枚举
- */
-export enum IotRuleSceneTriggerConditionParameterOperatorEnum {
-  /** 等于 */
-  EQ = 'eq',
-  /** 大于 */
-  GT = 'gt',
-  /** 大于等于 */
-  GTE = 'gte',
-  /** 小于 */
-  LT = 'lt',
-  /** 小于等于 */
-  LTE = 'lte',
-  /** 范围 */
-  BETWEEN = 'between',
-  /** 在列表中 */
-  IN = 'in'
+export {
+  IotRuleScene,
+  TriggerConfig,
+  TriggerCondition,
+  TriggerConditionParameter,
+  ActionConfig,
+  ActionDeviceControl,
+  ActionAlert,
+  IotRuleSceneTriggerTypeEnum,
+  IotRuleSceneActionTypeEnum,
+  IotDeviceMessageTypeEnum,
+  IotDeviceMessageIdentifierEnum,
+  IotRuleSceneTriggerConditionParameterOperatorEnum,
+  IotAlertConfigReceiveTypeEnum
 }

+ 1 - 1
src/api/iot/thingmodel/index.ts

@@ -61,7 +61,7 @@ export const ThingModelApi = {
   // 获得产品物模型 TSL
   getThingModelTSLByProductId: async (productId: number) => {
     return await request.get({
-      url: `/iot/thing-model/tsl-by-product-id?productId=${productId}`
+      url: `/iot/thing-model/get-tsl?productId=${productId}`
     })
   },
 

+ 0 - 1
src/views/iot/device/device/components/IoTDeviceTableSelect.vue → src/views/iot/device/device/components/DeviceTableSelect.vue

@@ -1,4 +1,3 @@
-<!-- TODO @puhui999:IoT 前缀去掉哈,文件名 -->
 <!-- IoT 设备选择,使用弹窗展示 -->
 <template>
   <Dialog :title="dialogTitle" v-model="dialogVisible" :appendToBody="true" width="60%">

+ 0 - 1
src/views/iot/product/product/components/IoTProductTableSelect.vue → src/views/iot/product/product/components/ProductTableSelect.vue

@@ -1,4 +1,3 @@
-<!-- TODO @puhui999:IoT 前缀去掉哈,文件名 -->
 <!-- IoT 产品选择,使用弹窗展示 -->
 <template>
   <Dialog :title="dialogTitle" v-model="dialogVisible" :appendToBody="true" width="60%">

+ 17 - 12
src/views/iot/rule/databridge/IoTDataBridgeForm.vue

@@ -46,7 +46,7 @@
         v-if="showConfig(IoTDataBridgeConfigType.RABBITMQ)"
         v-model="formData.config"
       />
-      <RedisStreamMQConfigForm
+      <RedisStreamConfigForm
         v-if="showConfig(IoTDataBridgeConfigType.REDIS_STREAM)"
         v-model="formData.config"
       />
@@ -73,13 +73,19 @@
 </template>
 <script lang="ts" setup>
 import { DICT_TYPE, getDictObj, getIntDictOptions } from '@/utils/dict'
-import { DataBridgeApi, DataBridgeVO, IoTDataBridgeConfigType } from '@/api/iot/rule/databridge'
+import { CommonStatusEnum } from '@/utils/constants'
+import {
+  DataBridgeApi,
+  DataBridgeVO,
+  IoTDataBridgeConfigType,
+  IotDataBridgeDirectionEnum
+} from '@/api/iot/rule/databridge'
 import {
   HttpConfigForm,
   KafkaMQConfigForm,
   MqttConfigForm,
   RabbitMQConfigForm,
-  RedisStreamMQConfigForm,
+  RedisStreamConfigForm,
   RocketMQConfigForm
 } from './config'
 
@@ -94,9 +100,9 @@ const dialogTitle = ref('') // 弹窗的标题
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 const formType = ref('') // 表单的类型:create - 新增;update - 修改
 const formData = ref<DataBridgeVO>({
-  status: 0,
-  direction: 1, // TODO @puhui999:枚举类
-  type: 1, // TODO @puhui999:枚举类
+  status: CommonStatusEnum.ENABLE,
+  direction: IotDataBridgeDirectionEnum.INPUT,
+  type: IoTDataBridgeConfigType.HTTP,
   config: {} as any
 })
 const formRules = reactive({
@@ -139,9 +145,9 @@ const formRules = reactive({
 })
 
 const formRef = ref() // 表单 Ref
-const showConfig = computed(() => (val: string) => {
+const showConfig = computed(() => (val: number) => {
   const dict = getDictObj(DICT_TYPE.IOT_DATA_BRIDGE_TYPE_ENUM, formData.value.type)
-  return dict && dict.value + '' === val
+  return dict && dict.value + '' === val + ''
 }) // 显示对应的 Config 配置项
 
 /** 打开弹窗 */
@@ -196,10 +202,9 @@ const handleTypeChange = (val: number) => {
 /** 重置表单 */
 const resetForm = () => {
   formData.value = {
-    // TODO @puhui999:换成枚举值哈
-    status: 0,
-    direction: 1,
-    type: 1,
+    status: CommonStatusEnum.ENABLE,
+    direction: IotDataBridgeDirectionEnum.INPUT,
+    type: IoTDataBridgeConfigType.HTTP,
     config: {} as any
   }
   formRef.value?.resetFields()

+ 1 - 1
src/views/iot/rule/databridge/config/HttpConfigForm.vue

@@ -73,7 +73,7 @@ onMounted(() => {
   }
 
   config.value = {
-    type: IoTDataBridgeConfigType.HTTP,
+    type: IoTDataBridgeConfigType.HTTP + '', // 序列化成对应类型时使用
     url: '',
     method: 'POST',
     headers: {},

+ 1 - 1
src/views/iot/rule/databridge/config/KafkaMQConfigForm.vue

@@ -34,7 +34,7 @@ onMounted(() => {
     return
   }
   config.value = {
-    type: IoTDataBridgeConfigType.KAFKA,
+    type: IoTDataBridgeConfigType.KAFKA + '', // 序列化成对应类型时使用
     bootstrapServers: '',
     username: '',
     password: '',

+ 1 - 1
src/views/iot/rule/databridge/config/MqttConfigForm.vue

@@ -34,7 +34,7 @@ onMounted(() => {
     return
   }
   config.value = {
-    type: IoTDataBridgeConfigType.MQTT,
+    type: IoTDataBridgeConfigType.MQTT + '', // 序列化成对应类型时使用
     url: '',
     username: '',
     password: '',

+ 1 - 1
src/views/iot/rule/databridge/config/RabbitMQConfigForm.vue

@@ -49,7 +49,7 @@ onMounted(() => {
     return
   }
   config.value = {
-    type: IoTDataBridgeConfigType.RABBITMQ,
+    type: IoTDataBridgeConfigType.RABBITMQ + '', // 序列化成对应类型时使用
     host: '',
     port: 5672,
     virtualHost: '/',

+ 1 - 2
src/views/iot/rule/databridge/config/RedisStreamMQConfigForm.vue → src/views/iot/rule/databridge/config/RedisStreamConfigForm.vue

@@ -1,4 +1,3 @@
-<!-- TODO @puhui999:去掉 MQ 关键字哈 -->
 <template>
   <el-form-item label="主机地址" prop="config.host">
     <el-input v-model="config.host" placeholder="请输入主机地址,如:localhost" />
@@ -47,7 +46,7 @@ onMounted(() => {
     return
   }
   config.value = {
-    type: IoTDataBridgeConfigType.REDIS_STREAM,
+    type: IoTDataBridgeConfigType.REDIS_STREAM + '', // 序列化成对应类型时使用
     host: '',
     port: 6379,
     password: '',

+ 1 - 1
src/views/iot/rule/databridge/config/RocketMQConfigForm.vue

@@ -45,7 +45,7 @@ onMounted(() => {
     return
   }
   config.value = {
-    type: IoTDataBridgeConfigType.ROCKETMQ,
+    type: IoTDataBridgeConfigType.ROCKETMQ + '', // 序列化成对应类型时使用
     nameServer: '',
     accessKey: '',
     secretKey: '',

+ 2 - 2
src/views/iot/rule/databridge/config/index.ts

@@ -3,7 +3,7 @@ import MqttConfigForm from './MqttConfigForm.vue'
 import RocketMQConfigForm from './RocketMQConfigForm.vue'
 import KafkaMQConfigForm from './KafkaMQConfigForm.vue'
 import RabbitMQConfigForm from './RabbitMQConfigForm.vue'
-import RedisStreamMQConfigForm from './RedisStreamMQConfigForm.vue'
+import RedisStreamConfigForm from './RedisStreamConfigForm.vue'
 
 export {
   HttpConfigForm,
@@ -11,5 +11,5 @@ export {
   RocketMQConfigForm,
   KafkaMQConfigForm,
   RabbitMQConfigForm,
-  RedisStreamMQConfigForm
+  RedisStreamConfigForm
 }

+ 28 - 25
src/views/iot/rule/scene/IoTRuleSceneForm.vue → src/views/iot/rule/scene/RuleSceneForm.vue

@@ -40,16 +40,13 @@
             @update:model-value="(val) => (formData.triggers[index] = val)"
             class="mb-10px"
           >
-            <el-button
-              type="danger"
-              round
-              :icon="Delete"
-              size="small"
-              @click="removeTrigger(index)"
-            />
+            <el-button type="danger" round size="small" @click="removeTrigger(index)">
+              <Icon icon="ep:delete" />
+            </el-button>
           </device-listener>
-          <!-- TODO @puhui999:可以使用 el-button,然后选个合适的样式哇 -->
-          <el-text class="ml-10px!" type="primary" @click="addTrigger">添加触发器</el-text>
+          <el-button class="ml-10px!" type="primary" size="small" @click="addTrigger">
+            添加触发器
+          </el-button>
         </el-col>
         <el-col :span="24">
           <el-divider content-position="left">执行动作配置</el-divider>
@@ -67,14 +64,19 @@
 </template>
 <script setup lang="ts">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
-import { RuleSceneApi, RuleSceneVO } from '@/api/iot/rule/scene'
+import { RuleSceneApi } from '@/api/iot/rule/scene'
 import DeviceListener from './components/DeviceListener.vue'
-// TODO @puhui999:尽量用 icon 组件哈,项目里的
-import { Delete } from '@element-plus/icons-vue'
-import { IotRuleSceneTriggerConfig } from '@/api/iot/rule/scene/scene.types'
+import { CommonStatusEnum } from '@/utils/constants'
+import {
+  IotDeviceMessageIdentifierEnum,
+  IotDeviceMessageTypeEnum,
+  IotRuleScene,
+  IotRuleSceneTriggerTypeEnum,
+  TriggerConfig
+} from '@/api/iot/rule/scene/scene.types'
 
-/** IoT 规则场景(场景联动) 表单 */
-defineOptions({ name: 'RuleSceneForm' })
+/** IoT 场景联动表单 */
+defineOptions({ name: 'IotRuleSceneForm' })
 
 const { t } = useI18n() // 国际化
 const message = useMessage() // 消息弹窗
@@ -83,10 +85,10 @@ const dialogVisible = ref(false) // 弹窗的是否展示
 const dialogTitle = ref('') // 弹窗的标题
 const formLoading = ref(false) // 表单的加载中:1)修改时的数据加载;2)提交的按钮禁用
 const formType = ref('') // 表单的类型:create - 新增;update - 修改
-const formData = ref<RuleSceneVO>({
-  status: 0, // TODO @puhui999:使用枚举值
-  triggers: [] as IotRuleSceneTriggerConfig[]
-} as RuleSceneVO)
+const formData = ref<IotRuleScene>({
+  status: CommonStatusEnum.ENABLE,
+  triggers: [] as TriggerConfig[]
+} as IotRuleScene)
 const formRules = reactive({
   name: [{ required: true, message: '场景名称不能为空', trigger: 'blur' }],
   status: [{ required: true, message: '场景状态不能为空', trigger: 'blur' }],
@@ -98,12 +100,13 @@ const formRef = ref() // 表单 Ref
 /** 添加触发器 */
 const addTrigger = () => {
   formData.value.triggers.push({
-    type: 1, // TODO @puhui999:使用枚举值
+    type: IotRuleSceneTriggerTypeEnum.DEVICE,
     productKey: '',
     deviceNames: [],
     conditions: [
       {
-        type: 'property',
+        type: IotDeviceMessageTypeEnum.PROPERTY,
+        identifier: IotDeviceMessageIdentifierEnum.PROPERTY_SET,
         parameters: []
       }
     ]
@@ -142,7 +145,7 @@ const submitForm = async () => {
   // 提交请求
   formLoading.value = true
   try {
-    const data = formData.value as unknown as RuleSceneVO
+    const data = formData.value as unknown as IotRuleScene
     if (formType.value === 'create') {
       await RuleSceneApi.createRuleScene(data)
       message.success(t('common.createSuccess'))
@@ -161,9 +164,9 @@ const submitForm = async () => {
 /** 重置表单 */
 const resetForm = () => {
   formData.value = {
-    status: 0, // TODO @puhui999:使用枚举值
-    triggers: [] as IotRuleSceneTriggerConfig[]
-  } as RuleSceneVO
+    status: CommonStatusEnum.ENABLE,
+    triggers: [] as TriggerConfig[]
+  } as IotRuleScene
   formRef.value?.resetFields()
 }
 </script>

+ 79 - 27
src/views/iot/rule/scene/components/ConditionSelector.vue

@@ -1,29 +1,18 @@
 <template>
-  <el-select
-    v-model="selectedOperator"
-    class="condition-selector"
-    clearable
-    :placeholder="placeholder"
-  >
-    <!-- TODO puhui999: 考虑根据属性类型不同展示不同的可选条件 -->
-    <!-- TODO @puhui999:可以在 scene.types.ts IotRuleSceneTriggerConditionParameterOperatorEnum 枚举下 -->
-    <el-option label="等于" value="=" />
-    <el-option label="不等于" value="!=" />
-    <el-option label="大于" value=">" />
-    <el-option label="大于等于" value=">=" />
-    <el-option label="小于" value="<" />
-    <el-option label="小于等于" value="<=" />
-    <el-option label="在列表中" value="in" />
-    <el-option label="不在列表中" value="not in" />
-    <el-option label="在范围内" value="between" />
-    <el-option label="不在范围内" value="not between" />
-    <el-option label="包含" value="like" />
-    <el-option label="非空" value="not null" />
+  <el-select v-model="selectedOperator" class="w-1/1" clearable :placeholder="placeholder">
+    <!-- 根据属性类型展示不同的可选条件 -->
+    <el-option
+      v-for="(item, key) in filteredOperators"
+      :key="key"
+      :label="item.name"
+      :value="item.value"
+    />
   </el-select>
 </template>
 
 <script setup lang="ts">
 import { computed } from 'vue'
+import { IotRuleSceneTriggerConditionParameterOperatorEnum } from '@/api/iot/rule/scene/scene.types'
 
 /** 条件选择器 */
 defineOptions({ name: 'ConditionSelector' })
@@ -35,6 +24,10 @@ const props = defineProps({
   modelValue: {
     type: String,
     default: ''
+  },
+  dataType: {
+    type: String,
+    default: ''
   }
 })
 
@@ -44,11 +37,70 @@ const selectedOperator = computed({
   get: () => props.modelValue,
   set: (value) => emit('update:modelValue', value)
 })
-</script>
 
-<!-- TODO @puhui999:尽量用 unocss -->
-<style scoped>
-.condition-selector {
-  width: 100%;
-}
-</style>
+// 根据数据类型过滤可用的操作符
+const filteredOperators = computed(() => {
+  // 如果没有指定数据类型,返回所有操作符
+  if (!props.dataType) {
+    return IotRuleSceneTriggerConditionParameterOperatorEnum
+  }
+
+  const operatorMap = new Map()
+  
+  // 添加通用的操作符(所有类型都有非空操作符)
+  operatorMap.set('NOT_NULL', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_NULL)
+  
+  // 根据数据类型添加特定的操作符
+  switch (props.dataType) {
+    case 'int':
+    case 'float':
+    case 'double':
+      // 数值类型支持的所有操作符
+      operatorMap.set('EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS)
+      operatorMap.set('NOT_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS)
+      operatorMap.set('GREATER_THAN', IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN)
+      operatorMap.set('GREATER_THAN_OR_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN_OR_EQUALS)
+      operatorMap.set('LESS_THAN', IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN)
+      operatorMap.set('LESS_THAN_OR_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN_OR_EQUALS)
+      operatorMap.set('IN', IotRuleSceneTriggerConditionParameterOperatorEnum.IN)
+      operatorMap.set('NOT_IN', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_IN)
+      operatorMap.set('BETWEEN', IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN)
+      operatorMap.set('NOT_BETWEEN', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_BETWEEN)
+      break
+    case 'enum':
+      // 枚举类型支持的操作符
+      operatorMap.set('EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS)
+      operatorMap.set('NOT_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS)
+      operatorMap.set('IN', IotRuleSceneTriggerConditionParameterOperatorEnum.IN)
+      operatorMap.set('NOT_IN', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_IN)
+      break
+    case 'bool':
+      // 布尔类型支持的操作符
+      operatorMap.set('EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS)
+      operatorMap.set('NOT_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS)
+      break
+    case 'text':
+      // 文本类型支持的操作符
+      operatorMap.set('EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS)
+      operatorMap.set('NOT_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS)
+      operatorMap.set('LIKE', IotRuleSceneTriggerConditionParameterOperatorEnum.LIKE)
+      break
+    case 'date':
+      // 日期类型支持的操作符
+      operatorMap.set('EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.EQUALS)
+      operatorMap.set('NOT_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_EQUALS)
+      operatorMap.set('GREATER_THAN', IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN)
+      operatorMap.set('GREATER_THAN_OR_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.GREATER_THAN_OR_EQUALS)
+      operatorMap.set('LESS_THAN', IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN)
+      operatorMap.set('LESS_THAN_OR_EQUALS', IotRuleSceneTriggerConditionParameterOperatorEnum.LESS_THAN_OR_EQUALS)
+      operatorMap.set('BETWEEN', IotRuleSceneTriggerConditionParameterOperatorEnum.BETWEEN)
+      operatorMap.set('NOT_BETWEEN', IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_BETWEEN)
+      break
+    // struct 和 array 类型只支持非空操作符,已在通用部分添加
+    default:
+      return IotRuleSceneTriggerConditionParameterOperatorEnum
+  }
+  
+  return Object.fromEntries(operatorMap)
+})
+</script>

+ 117 - 116
src/views/iot/rule/scene/components/DeviceListener.vue

@@ -1,6 +1,6 @@
 <template>
-  <div class="device-listener m-10px">
-    <div class="device-listener-header h-50px flex items-center px-10px">
+  <div class="m-10px">
+    <div class="relative bg-[#eff3f7] h-50px flex items-center px-10px">
       <div class="flex items-center mr-60px">
         <span class="mr-10px">触发条件</span>
         <el-select
@@ -17,99 +17,119 @@
           />
         </el-select>
       </div>
-      <div class="flex items-center mr-60px">
+      <div
+        v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
+        class="flex items-center mr-60px"
+      >
         <span class="mr-10px">产品</span>
         <el-button type="primary" @click="productTableSelectRef?.open()" size="small" plain>
-          <!-- TODO @puhui999:最终最好是,product ? product.name : '选择产品',减少取反 -->
-          {{ !product ? '选择产品' : product.name }}
+          {{ product ? product.name : '选择产品' }}
         </el-button>
       </div>
-      <div class="flex items-center mr-60px">
+      <div
+        v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
+        class="flex items-center mr-60px"
+      >
         <span class="mr-10px">设备</span>
         <el-button type="primary" @click="openDeviceSelect" size="small" plain>
           {{ isEmpty(deviceList) ? '选择设备' : triggerConfig.deviceNames.join(',') }}
         </el-button>
       </div>
       <!-- 删除触发器 -->
-      <div class="device-listener-delete">
+      <div class="absolute top-auto right-16px bottom-auto">
         <el-tooltip content="删除触发器" placement="top">
           <slot></slot>
         </el-tooltip>
       </div>
     </div>
-    <!-- 触发器条件 -->
-    <div
-      class="device-listener-condition flex p-10px"
-      v-for="(condition, index) in triggerConfig.conditions"
-      :key="index"
-    >
-      <div class="flex flex-col items-center justify-center mr-10px h-a">
-        <el-select
-          v-model="condition.type"
-          @change="condition.parameters = []"
-          class="!w-160px"
-          clearable
-          placeholder=""
-        >
-          <!--          <el-option-->
-          <!--            v-for="dict in getStrDictOptions(DICT_TYPE.IOT_DEVICE_MESSAGE_TYPE_ENUM)"-->
-          <!--            :key="dict.value"-->
-          <!--            :label="dict.label"-->
-          <!--            :value="dict.value"-->
-          <!--          />-->
-          <el-option label="属性" value="property" />
-          <el-option label="服务" value="service" />
-          <el-option label="事件" value="event" />
-        </el-select>
-      </div>
-      <div class="">
-        <DeviceListenerCondition
-          v-for="(parameter, index2) in condition.parameters"
-          :key="index2"
-          :model-value="parameter"
-          :thingModels="thingModels(condition)"
-          @update:model-value="(val) => (condition.parameters[index2] = val)"
-          class="mb-10px last:mb-0"
-        >
-          <el-tooltip content="删除参数" placement="top">
+    <!-- 设备触发器条件 -->
+    <template v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE">
+      <div
+        class="bg-[#dbe5f6] flex p-10px"
+        v-for="(condition, index) in triggerConfig.conditions"
+        :key="index"
+      >
+        <div class="flex flex-col items-center justify-center mr-10px h-a">
+          <el-select
+            v-model="condition.type"
+            @change="condition.parameters = []"
+            class="!w-160px"
+            clearable
+            placeholder=""
+          >
+            <el-option label="属性" :value="IotDeviceMessageTypeEnum.PROPERTY" />
+            <el-option label="服务" :value="IotDeviceMessageTypeEnum.SERVICE" />
+            <el-option label="事件" :value="IotDeviceMessageTypeEnum.EVENT" />
+          </el-select>
+        </div>
+        <div class="">
+          <DeviceListenerCondition
+            v-for="(parameter, index2) in condition.parameters"
+            :key="index2"
+            :model-value="parameter"
+            :thingModels="thingModels(condition)"
+            @update:model-value="(val) => (condition.parameters[index2] = val)"
+            class="mb-10px last:mb-0"
+          >
+            <el-tooltip content="删除参数" placement="top">
+              <el-button
+                type="danger"
+                circle
+                size="small"
+                @click="removeConditionParameter(condition.parameters, index2)"
+              >
+                <Icon icon="ep:delete" />
+              </el-button>
+            </el-tooltip>
+          </DeviceListenerCondition>
+        </div>
+        <!-- 添加参数 -->
+        <div class="flex flex-1 flex-col items-center justify-center w-60px h-a">
+          <el-tooltip content="添加参数" placement="top">
             <el-button
-              class="device-listener-delete"
-              type="danger"
+              type="primary"
               circle
-              :icon="Delete"
               size="small"
-              @click="removeConditionParameter(condition.parameters, index2)"
-            />
+              @click="addConditionParameter(condition.parameters)"
+            >
+              <Icon icon="ep:plus" />
+            </el-button>
           </el-tooltip>
-        </DeviceListenerCondition>
-      </div>
-      <!-- 添加参数 -->
-      <div class="flex flex-1 flex-col items-center justify-center w-60px h-a">
-        <el-tooltip content="添加参数" placement="top">
-          <el-button
-            type="primary"
-            circle
-            :icon="Plus"
-            size="small"
-            @click="addConditionParameter(condition.parameters)"
-          />
-        </el-tooltip>
-      </div>
-      <!-- 删除条件 -->
-      <div
-        class="device-listener-condition flex flex-1 flex-col items-center justify-center w-a h-a"
-      >
-        <el-tooltip content="删除条件" placement="top">
-          <el-button type="danger" :icon="Delete" size="small" @click="removeCondition(index)" />
-        </el-tooltip>
+        </div>
+        <!-- 删除条件 -->
+        <div
+          class="device-listener-condition flex flex-1 flex-col items-center justify-center w-a h-a"
+        >
+          <el-tooltip content="删除条件" placement="top">
+            <el-button type="danger" size="small" @click="removeCondition(index)">
+              <Icon icon="ep:delete" />
+            </el-button>
+          </el-tooltip>
+        </div>
       </div>
+    </template>
+    <!-- 定时触发 -->
+    <div
+      v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.TIMER"
+      class="bg-[#dbe5f6] flex items-center justify-between p-10px"
+    >
+      <span class="w-120px">CRON 表达式</span>
+      <crontab v-model="triggerConfig.cronExpression" />
     </div>
-    <el-text class="ml-10px!" type="primary" @click="addCondition">添加触发条件</el-text>
+    <!-- 设备触发才可以设置多个触发条件 -->
+    <el-text
+      v-if="triggerConfig.type === IotRuleSceneTriggerTypeEnum.DEVICE"
+      class="ml-10px!"
+      type="primary"
+      @click="addCondition"
+    >
+      添加触发条件
+    </el-text>
   </div>
 
   <!-- 产品、设备的选择 -->
-  <IoTProductTableSelect ref="productTableSelectRef" @success="handleProductSelect" />
-  <IoTDeviceTableSelect
+  <ProductTableSelect ref="productTableSelectRef" @success="handleProductSelect" />
+  <DeviceTableSelect
     ref="deviceTableSelectRef"
     multiple
     :product-id="product?.id"
@@ -118,61 +138,64 @@
 </template>
 
 <script setup lang="ts">
-import { Delete, Plus } from '@element-plus/icons-vue'
+import { useVModel } from '@vueuse/core'
+import { isEmpty } from '@/utils/is'
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import DeviceListenerCondition from './DeviceListenerCondition.vue'
-import IoTProductTableSelect from '@/views/iot/product/product/components/IoTProductTableSelect.vue'
-import IoTDeviceTableSelect from '@/views/iot/device/device/components/IoTDeviceTableSelect.vue'
-import {
-  IotRuleSceneTriggerCondition,
-  IotRuleSceneTriggerConditionParameter,
-  IotRuleSceneTriggerConfig
-} from '@/api/iot/rule/scene/scene.types'
+import ProductTableSelect from '@/views/iot/product/product/components/ProductTableSelect.vue'
+import DeviceTableSelect from '@/views/iot/device/device/components/DeviceTableSelect.vue'
 import { ProductVO } from '@/api/iot/product/product'
 import { DeviceVO } from '@/api/iot/device/device'
-import { useVModel } from '@vueuse/core'
-import { isEmpty } from '@/utils/is'
 import { ThingModelApi } from '@/api/iot/thingmodel'
+import {
+  IotDeviceMessageIdentifierEnum,
+  IotDeviceMessageTypeEnum,
+  IotRuleSceneTriggerTypeEnum,
+  TriggerCondition,
+  TriggerConditionParameter,
+  TriggerConfig
+} from '@/api/iot/rule/scene/scene.types'
 
 /** 场景联动之监听器组件 */
 defineOptions({ name: 'DeviceListener' })
 
 const props = defineProps<{ modelValue: any }>()
 const emits = defineEmits(['update:modelValue'])
-const triggerConfig = useVModel(props, 'modelValue', emits) as Ref<IotRuleSceneTriggerConfig>
+const triggerConfig = useVModel(props, 'modelValue', emits) as Ref<TriggerConfig>
 
 const message = useMessage()
 
 /** 添加触发条件 */
 const addCondition = () => {
-  triggerConfig.value.conditions.push({
-    type: 'property',
+  triggerConfig.value.conditions?.push({
+    type: IotDeviceMessageTypeEnum.PROPERTY,
+    identifier: IotDeviceMessageIdentifierEnum.PROPERTY_SET,
     parameters: []
   })
 }
 /** 移除触发条件 */
 const removeCondition = (index: number) => {
-  triggerConfig.value.conditions.splice(index, 1)
+  triggerConfig.value.conditions?.splice(index, 1)
 }
 
 /** 添加参数 */
-const addConditionParameter = (conditionParameters: IotRuleSceneTriggerConditionParameter[]) => {
+const addConditionParameter = (conditionParameters: TriggerConditionParameter[]) => {
   if (!product.value) {
     message.warning('请先选择一个产品')
     return
   }
-  conditionParameters.push({} as IotRuleSceneTriggerConditionParameter)
+  conditionParameters.push({} as TriggerConditionParameter)
 }
 /** 移除参数 */
 const removeConditionParameter = (
-  conditionParameters: IotRuleSceneTriggerConditionParameter[],
+  conditionParameters: TriggerConditionParameter[],
   index: number
 ) => {
   conditionParameters.splice(index, 1)
 }
 
-const productTableSelectRef = ref<InstanceType<typeof IoTProductTableSelect>>()
-const deviceTableSelectRef = ref<InstanceType<typeof IoTDeviceTableSelect>>()
+const productTableSelectRef = ref<InstanceType<typeof ProductTableSelect>>()
+const deviceTableSelectRef = ref<InstanceType<typeof DeviceTableSelect>>()
 const product = ref<ProductVO>()
 const deviceList = ref<DeviceVO[]>([])
 /** 处理产品选择 */
@@ -198,15 +221,14 @@ const openDeviceSelect = () => {
 
 /** 获取产品物模型 */
 const thingModelTSL = ref<any>()
-const thingModels = computed(() => (condition: IotRuleSceneTriggerCondition) => {
-  // TODO @puhui999:这里最好也用枚举
+const thingModels = computed(() => (condition: TriggerCondition) => {
   switch (condition.type) {
-    case 'property':
+    case IotDeviceMessageTypeEnum.PROPERTY:
       return thingModelTSL.value.properties
     // TODO puhui999: 服务和事件后续考虑
-    case 'service':
+    case IotDeviceMessageTypeEnum.SERVICE:
       return thingModelTSL.value.services
-    case 'event':
+    case IotDeviceMessageTypeEnum.EVENT:
       return thingModelTSL.value.events
   }
   return []
@@ -218,24 +240,3 @@ const getThingModelTSL = async () => {
   thingModelTSL.value = await ThingModelApi.getThingModelTSLByProductId(product.value.id)
 }
 </script>
-
-<!-- TODO @puhui999:建议使用 unocss 哈 -->
-<style lang="scss" scoped>
-.device-listener {
-  .device-listener-header {
-    position: relative;
-    background-color: #eff3f7;
-
-    .device-listener-delete {
-      position: absolute;
-      top: auto;
-      right: 16px;
-      bottom: auto;
-    }
-  }
-
-  .device-listener-condition {
-    background-color: #dbe5f6;
-  }
-}
-</style>

+ 33 - 10
src/views/iot/rule/scene/components/DeviceListenerCondition.vue

@@ -13,10 +13,22 @@
         :value="thingModel.identifier"
       />
     </el-select>
-    <ConditionSelector v-model="conditionParameter.operator" class="!w-180px mr-10px" />
+    <ConditionSelector
+      v-model="conditionParameter.operator"
+      :data-type="getDataType"
+      class="!w-180px mr-10px"
+    />
     <!-- TODO puhui999: 输入值范围校验? -->
-    <el-input v-model="conditionParameter.value" class="!w-240px mr-10px" placeholder="请输入值">
-      <template #append> {{ getUnitName }} </template>
+    <el-input
+      v-if="
+        conditionParameter.operator !==
+        IotRuleSceneTriggerConditionParameterOperatorEnum.NOT_NULL.value
+      "
+      v-model="conditionParameter.value"
+      class="!w-240px mr-10px"
+      placeholder="请输入值"
+    >
+      <template v-if="getUnitName" #append> {{ getUnitName }} </template>
     </el-input>
     <!-- 按钮插槽 -->
     <slot></slot>
@@ -25,18 +37,28 @@
 
 <script setup lang="ts">
 import ConditionSelector from './ConditionSelector.vue'
-import { IotRuleSceneTriggerConditionParameter } from '@/api/iot/rule/scene/scene.types'
+import {
+  IotRuleSceneTriggerConditionParameterOperatorEnum,
+  TriggerConditionParameter
+} from '@/api/iot/rule/scene/scene.types'
 import { useVModel } from '@vueuse/core'
 
 defineOptions({ name: 'DeviceListenerCondition' })
 const props = defineProps<{ modelValue: any; thingModels: any }>()
 const emits = defineEmits(['update:modelValue'])
-const conditionParameter = useVModel(
-  props,
-  'modelValue',
-  emits
-) as Ref<IotRuleSceneTriggerConditionParameter>
+const conditionParameter = useVModel(props, 'modelValue', emits) as Ref<TriggerConditionParameter>
 
+/** 获得物模型属性类型 */
+const getDataType = computed(() => {
+  const model = props.thingModels?.find(
+    (item: any) => item.identifier === conditionParameter.value.identifier
+  )
+  // 属性
+  if (model?.dataSpecs) {
+    return model.dataSpecs.dataType
+  }
+  return ''
+})
 /** 获得属性单位 */
 const getUnitName = computed(() => {
   const model = props.thingModels?.find(
@@ -46,11 +68,12 @@ const getUnitName = computed(() => {
   if (model?.dataSpecs) {
     return model.dataSpecs.unitName
   }
+  // TODO puhui999: 先不考虑服务和事件的情况
   // 服务和事件
   // if (model?.outputParams) {
   //   return model.dataSpecs.unitName
   // }
-  return '单位'
+  return ''
 })
 </script>
 

+ 6 - 5
src/views/iot/rule/scene/index.vue

@@ -110,23 +110,24 @@
   </ContentWrap>
 
   <!-- 表单弹窗:添加/修改 -->
-  <IoTRuleSceneForm ref="formRef" @success="getList" />
+  <RuleSceneForm ref="formRef" @success="getList" />
 </template>
 
 <script setup lang="ts">
 import { DICT_TYPE, getIntDictOptions } from '@/utils/dict'
 import { dateFormatter } from '@/utils/formatTime'
-import { RuleSceneApi, RuleSceneVO } from '@/api/iot/rule/scene'
-import IoTRuleSceneForm from './IoTRuleSceneForm.vue'
+import { RuleSceneApi } from '@/api/iot/rule/scene'
+import RuleSceneForm from './RuleSceneForm.vue'
+import { IotRuleScene } from '@/api/iot/rule/scene/scene.types'
 
-/** IoT 规则场景(场景联动 列表 */
+/** IoT 场景联动 列表 */
 defineOptions({ name: 'IotRuleScene' })
 
 const message = useMessage() // 消息弹窗
 const { t } = useI18n() // 国际化
 
 const loading = ref(true) // 列表的加载中
-const list = ref<RuleSceneVO[]>([]) // 列表的数据
+const list = ref<IotRuleScene[]>([]) // 列表的数据
 const total = ref(0) // 列表的总页数
 const queryParams = reactive({
   pageNo: 1,

+ 49 - 0
src/views/iot/thingmodel/ThingModelTSL.vue

@@ -0,0 +1,49 @@
+<template>
+  <Dialog v-model="dialogVisible" :title="dialogTitle">
+    <el-scrollbar height="600px">
+      <pre><code v-dompurify-html="highlightedCode()" class="hljs"></code></pre>
+    </el-scrollbar>
+  </Dialog>
+</template>
+
+<script setup lang="ts">
+import hljs from 'highlight.js' // 导入代码高亮文件
+import 'highlight.js/styles/github.css' // 导入代码高亮样式
+import json from 'highlight.js/lib/languages/json'
+import { ThingModelApi } from '@/api/iot/thingmodel'
+import { ProductVO } from '@/api/iot/product/product'
+import { IOT_PROVIDE_KEY } from '@/views/iot/utils/constants'
+
+defineOptions({ name: 'ThingModelTSL' })
+
+const dialogVisible = ref(false) // 弹窗的是否展示
+const dialogTitle = ref('物模型 TSL') // 弹窗的标题
+const product = inject<Ref<ProductVO>>(IOT_PROVIDE_KEY.PRODUCT) // 注入产品信息
+/** 打开弹窗 */
+const open = () => {
+  dialogVisible.value = true
+}
+defineExpose({ open })
+
+const thingModelTSL = ref('')
+/** 获取 TSL */
+const getTsl = async () => {
+  const res = await ThingModelApi.getThingModelTSLByProductId(product?.value?.id || 0)
+  thingModelTSL.value = JSON.stringify(res, null, 2)
+}
+
+/**
+ * 代码高亮
+ */
+const highlightedCode = () => {
+  const result = hljs.highlight('json', thingModelTSL.value, true)
+  return result.value || '&nbsp;'
+}
+
+/** 初始化 **/
+onMounted(async () => {
+  // 注册代码高亮的各种语言
+  hljs.registerLanguage('json', json)
+  await getTsl()
+})
+</script>

+ 10 - 0
src/views/iot/thingmodel/index.vue

@@ -42,6 +42,9 @@
           <Icon class="mr-5px" icon="ep:plus" />
           添加功能
         </el-button>
+        <el-button v-hasPermi="[`iot:thing-model:query`]" plain type="primary" @click="openTSL">
+          TSL
+        </el-button>
       </el-form-item>
     </el-form>
   </ContentWrap>
@@ -99,6 +102,7 @@
   </ContentWrap>
   <!-- 表单弹窗:添加/修改 -->
   <ThingModelForm ref="formRef" @success="getList" />
+  <ThingModelTSL ref="thingModelTSLRef" />
 </template>
 <script lang="ts" setup>
 import { ThingModelApi, ThingModelData } from '@/api/iot/thingmodel'
@@ -160,6 +164,12 @@ const openForm = (type: string, id?: number) => {
   formRef.value.open(type, id)
 }
 
+/** 展示物模型 TSL */
+const thingModelTSLRef = ref()
+const openTSL = () => {
+  thingModelTSLRef.value?.open()
+}
+
 /** 删除按钮操作 */
 const handleDelete = async (id: number) => {
   try {