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