From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目

---
 src/views/iot/rule/scene/form/selectors/PropertySelector.vue |  437 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 437 insertions(+), 0 deletions(-)

diff --git a/src/views/iot/rule/scene/form/selectors/PropertySelector.vue b/src/views/iot/rule/scene/form/selectors/PropertySelector.vue
new file mode 100644
index 0000000..51f2117
--- /dev/null
+++ b/src/views/iot/rule/scene/form/selectors/PropertySelector.vue
@@ -0,0 +1,437 @@
+<!-- 灞炴�ч�夋嫨鍣ㄧ粍浠� -->
+<template>
+  <div class="flex items-center gap-8px">
+    <el-select
+      v-model="localValue"
+      placeholder="璇烽�夋嫨鐩戞帶椤�"
+      filterable
+      clearable
+      @change="handleChange"
+      class="!w-150px"
+      :loading="loading"
+    >
+      <el-option-group v-for="group in propertyGroups" :key="group.label" :label="group.label">
+        <el-option
+          v-for="property in group.options"
+          :key="property.identifier"
+          :label="property.name"
+          :value="property.identifier"
+        >
+          <div class="flex items-center justify-between w-full py-2px">
+            <span class="text-14px font-500 text-[var(--el-text-color-primary)] flex-1 truncate">
+              {{ property.name }}
+            </span>
+            <el-tag
+              :type="getDataTypeTagType(property.dataType)"
+              size="small"
+              class="ml-8px flex-shrink-0"
+            >
+              {{ property.identifier }}
+            </el-tag>
+          </div>
+        </el-option>
+      </el-option-group>
+    </el-select>
+
+    <!-- 灞炴�ц鎯呭脊鍑哄眰 -->
+    <el-popover
+      v-if="selectedProperty"
+      placement="right-start"
+      :width="350"
+      trigger="click"
+      :show-arrow="true"
+      :offset="8"
+      popper-class="property-detail-popover"
+    >
+      <template #reference>
+        <el-button
+          type="info"
+          :icon="InfoFilled"
+          circle
+          size="small"
+          class="flex-shrink-0"
+          title="鏌ョ湅灞炴�ц鎯�"
+        />
+      </template>
+
+      <!-- 寮瑰嚭灞傚唴瀹� -->
+      <div class="property-detail-content">
+        <div class="flex items-center gap-8px mb-12px">
+          <Icon icon="ep:info-filled" class="text-[var(--el-color-info)] text-16px" />
+          <span class="text-14px font-500 text-[var(--el-text-color-primary)]">
+            {{ selectedProperty.name }}
+          </span>
+          <el-tag :type="getDataTypeTagType(selectedProperty.dataType)" size="small">
+            {{ getDataTypeName(selectedProperty.dataType) }}
+          </el-tag>
+        </div>
+
+        <div class="space-y-8px ml-24px">
+          <div class="flex items-start gap-8px">
+            <span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0">
+              鏍囪瘑绗︼細
+            </span>
+            <span class="text-12px text-[var(--el-text-color-primary)] flex-1">
+              {{ selectedProperty.identifier }}
+            </span>
+          </div>
+
+          <div v-if="selectedProperty.description" class="flex items-start gap-8px">
+            <span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0">
+              鎻忚堪锛�
+            </span>
+            <span class="text-12px text-[var(--el-text-color-primary)] flex-1">
+              {{ selectedProperty.description }}
+            </span>
+          </div>
+
+          <div v-if="selectedProperty.unit" class="flex items-start gap-8px">
+            <span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0">
+              鍗曚綅锛�
+            </span>
+            <span class="text-12px text-[var(--el-text-color-primary)] flex-1">
+              {{ selectedProperty.unit }}
+            </span>
+          </div>
+
+          <div v-if="selectedProperty.range" class="flex items-start gap-8px">
+            <span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0">
+              鍙栧�艰寖鍥达細
+            </span>
+            <span class="text-12px text-[var(--el-text-color-primary)] flex-1">
+              {{ selectedProperty.range }}
+            </span>
+          </div>
+
+          <!-- 鏍规嵁灞炴�х被鍨嬫樉绀洪澶栦俊鎭� -->
+          <div
+            v-if="
+              selectedProperty.type === IoTThingModelTypeEnum.PROPERTY &&
+              selectedProperty.accessMode
+            "
+            class="flex items-start gap-8px"
+          >
+            <span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0">
+              璁块棶妯″紡锛�
+            </span>
+            <span class="text-12px text-[var(--el-text-color-primary)] flex-1">
+              {{ getAccessModeLabel(selectedProperty.accessMode) }}
+            </span>
+          </div>
+
+          <div
+            v-if="
+              selectedProperty.type === IoTThingModelTypeEnum.EVENT && selectedProperty.eventType
+            "
+            class="flex items-start gap-8px"
+          >
+            <span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0">
+              浜嬩欢绫诲瀷锛�
+            </span>
+            <span class="text-12px text-[var(--el-text-color-primary)] flex-1">
+              {{ getEventTypeLabel(selectedProperty.eventType) }}
+            </span>
+          </div>
+
+          <div
+            v-if="
+              selectedProperty.type === IoTThingModelTypeEnum.SERVICE && selectedProperty.callType
+            "
+            class="flex items-start gap-8px"
+          >
+            <span class="text-12px text-[var(--el-text-color-secondary)] min-w-60px flex-shrink-0">
+              璋冪敤绫诲瀷锛�
+            </span>
+            <span class="text-12px text-[var(--el-text-color-primary)] flex-1">
+              {{ getThingModelServiceCallTypeLabel(selectedProperty.callType) }}
+            </span>
+          </div>
+        </div>
+      </div>
+    </el-popover>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import { InfoFilled } from '@element-plus/icons-vue'
+import {
+  IotRuleSceneTriggerTypeEnum,
+  IoTThingModelTypeEnum,
+  getAccessModeLabel,
+  getEventTypeLabel,
+  getThingModelServiceCallTypeLabel,
+  getDataTypeName,
+  getDataTypeTagType,
+  THING_MODEL_GROUP_LABELS
+} from '@/views/iot/utils/constants'
+import type {
+  IotThingModelTSLResp,
+  ThingModelEvent,
+  ThingModelParam,
+  ThingModelProperty,
+  ThingModelService
+} from '@/api/iot/thingmodel'
+import { ThingModelApi } from '@/api/iot/thingmodel'
+
+/** 灞炴�ч�夋嫨鍣ㄧ粍浠� */
+defineOptions({ name: 'PropertySelector' })
+
+/** 灞炴�ч�夋嫨鍣ㄥ唴閮ㄤ娇鐢ㄧ殑缁熶竴鏁版嵁缁撴瀯 */
+interface PropertySelectorItem {
+  identifier: string
+  name: string
+  description?: string
+  dataType: string
+  type: number // IoTThingModelTypeEnum
+  accessMode?: string
+  required?: boolean
+  unit?: string
+  range?: string
+  eventType?: string
+  callType?: string
+  inputParams?: ThingModelParam[]
+  outputParams?: ThingModelParam[]
+  property?: ThingModelProperty
+  event?: ThingModelEvent
+  service?: ThingModelService
+}
+
+const props = defineProps<{
+  modelValue?: string
+  triggerType: number
+  productId?: number
+  deviceId?: number
+}>()
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', value: string): void
+  (e: 'change', value: { type: string; config: any }): void
+}>()
+
+const localValue = useVModel(props, 'modelValue', emit)
+
+const loading = ref(false) // 鍔犺浇鐘舵��
+const propertyList = ref<PropertySelectorItem[]>([]) // 灞炴�у垪琛�
+const thingModelTSL = ref<IotThingModelTSLResp | null>(null) // 鐗╂ā鍨婽SL鏁版嵁
+
+// 璁$畻灞炴�э細灞炴�у垎缁�
+const propertyGroups = computed(() => {
+  const groups: { label: string; options: any[] }[] = []
+
+  if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST) {
+    groups.push({
+      label: THING_MODEL_GROUP_LABELS.PROPERTY,
+      options: propertyList.value.filter((p) => p.type === IoTThingModelTypeEnum.PROPERTY)
+    })
+  }
+
+  if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST) {
+    groups.push({
+      label: THING_MODEL_GROUP_LABELS.EVENT,
+      options: propertyList.value.filter((p) => p.type === IoTThingModelTypeEnum.EVENT)
+    })
+  }
+
+  if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE) {
+    groups.push({
+      label: THING_MODEL_GROUP_LABELS.SERVICE,
+      options: propertyList.value.filter((p) => p.type === IoTThingModelTypeEnum.SERVICE)
+    })
+  }
+
+  return groups.filter((group) => group.options.length > 0)
+})
+
+// 璁$畻灞炴�э細褰撳墠閫変腑鐨勫睘鎬�
+const selectedProperty = computed(() => {
+  return propertyList.value.find((p) => p.identifier === localValue.value)
+})
+
+/**
+ * 澶勭悊閫夋嫨鍙樺寲浜嬩欢
+ * @param value 閫変腑鐨勫睘鎬ф爣璇嗙
+ */
+const handleChange = (value: string) => {
+  const property = propertyList.value.find((p) => p.identifier === value)
+  if (property) {
+    emit('change', {
+      type: property.dataType,
+      config: property
+    })
+  }
+}
+
+/**
+ * 鑾峰彇鐗╂ā鍨婽SL鏁版嵁
+ */
+const getThingModelTSL = async () => {
+  if (!props.productId) {
+    thingModelTSL.value = null
+    propertyList.value = []
+    return
+  }
+
+  loading.value = true
+  try {
+    const tslData = await ThingModelApi.getThingModelTSLByProductId(props.productId)
+
+    if (tslData) {
+      thingModelTSL.value = tslData
+      parseThingModelData()
+    } else {
+      console.error('鑾峰彇鐗╂ā鍨婽SL澶辫触: 杩斿洖鏁版嵁涓虹┖')
+      propertyList.value = []
+    }
+  } catch (error) {
+    console.error('鑾峰彇鐗╂ā鍨婽SL澶辫触:', error)
+    propertyList.value = []
+  } finally {
+    loading.value = false
+  }
+}
+
+/** 瑙f瀽鐗╂ā鍨� TSL 鏁版嵁 */
+const parseThingModelData = () => {
+  const tsl = thingModelTSL.value
+  const properties: PropertySelectorItem[] = []
+
+  if (!tsl) {
+    propertyList.value = properties
+    return
+  }
+  // 瑙f瀽灞炴��
+  if (tsl.properties && Array.isArray(tsl.properties)) {
+    tsl.properties.forEach((prop) => {
+      properties.push({
+        identifier: prop.identifier,
+        name: prop.name,
+        description: prop.description,
+        dataType: prop.dataType,
+        type: IoTThingModelTypeEnum.PROPERTY,
+        accessMode: prop.accessMode,
+        required: prop.required,
+        unit: getPropertyUnit(prop),
+        range: getPropertyRange(prop),
+        property: prop
+      })
+    })
+  }
+
+  // 瑙f瀽浜嬩欢
+  if (tsl.events && Array.isArray(tsl.events)) {
+    tsl.events.forEach((event) => {
+      properties.push({
+        identifier: event.identifier,
+        name: event.name,
+        description: event.description,
+        dataType: 'struct',
+        type: IoTThingModelTypeEnum.EVENT,
+        eventType: event.type,
+        required: event.required,
+        outputParams: event.outputParams,
+        event: event
+      })
+    })
+  }
+
+  // 瑙f瀽鏈嶅姟
+  if (tsl.services && Array.isArray(tsl.services)) {
+    tsl.services.forEach((service) => {
+      properties.push({
+        identifier: service.identifier,
+        name: service.name,
+        description: service.description,
+        dataType: 'struct',
+        type: IoTThingModelTypeEnum.SERVICE,
+        callType: service.callType,
+        required: service.required,
+        inputParams: service.inputParams,
+        outputParams: service.outputParams,
+        service: service
+      })
+    })
+  }
+  propertyList.value = properties
+}
+
+/**
+ * 鑾峰彇灞炴�у崟浣�
+ * @param property 灞炴�у璞�
+ * @returns 灞炴�у崟浣�
+ */
+const getPropertyUnit = (property: any) => {
+  if (!property) return undefined
+
+  // 鏁板�煎瀷鏁版嵁鐨勫崟浣�
+  if (property.dataSpecs && property.dataSpecs.unit) {
+    return property.dataSpecs.unit
+  }
+
+  return undefined
+}
+
+/**
+ * 鑾峰彇灞炴�ц寖鍥存弿杩�
+ * @param property 灞炴�у璞�
+ * @returns 灞炴�ц寖鍥存弿杩�
+ */
+const getPropertyRange = (property: any) => {
+  if (!property) return undefined
+
+  // 鏁板�煎瀷鏁版嵁鐨勮寖鍥�
+  if (property.dataSpecs) {
+    const specs = property.dataSpecs
+    if (specs.min !== undefined && specs.max !== undefined) {
+      return `${specs.min}~${specs.max}`
+    }
+  }
+
+  // 鏋氫妇鍨嬪拰甯冨皵鍨嬫暟鎹殑閫夐」
+  if (property.dataSpecsList && Array.isArray(property.dataSpecsList)) {
+    return property.dataSpecsList.map((item: any) => `${item.name}(${item.value})`).join(', ')
+  }
+
+  return undefined
+}
+
+/** 鐩戝惉浜у搧鍙樺寲 */
+watch(
+  () => props.productId,
+  () => {
+    getThingModelTSL()
+  },
+  { immediate: true }
+)
+
+/** 鐩戝惉瑙﹀彂绫诲瀷鍙樺寲 */
+watch(
+  () => props.triggerType,
+  () => {
+    localValue.value = ''
+  }
+)
+</script>
+
+<style scoped>
+/* 涓嬫媺閫夐」鏍峰紡 */
+:deep(.el-select-dropdown__item) {
+  height: auto;
+  padding: 6px 20px;
+}
+
+/* 寮瑰嚭灞傚唴瀹规牱寮� */
+.property-detail-content {
+  padding: 4px 0;
+}
+
+/* 寮瑰嚭灞傝嚜瀹氫箟鏍峰紡 */
+:global(.property-detail-popover) {
+  /* 鍙互鍦ㄨ繖閲屾坊鍔犲叏灞�寮瑰嚭灞傛牱寮� */
+  max-width: 400px !important;
+}
+
+:global(.property-detail-popover .el-popover__content) {
+  padding: 16px !important;
+}
+</style>

--
Gitblit v1.8.0