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/configs/DeviceControlConfig.vue |  376 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 376 insertions(+), 0 deletions(-)

diff --git a/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue b/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue
new file mode 100644
index 0000000..2cc89c9
--- /dev/null
+++ b/src/views/iot/rule/scene/form/configs/DeviceControlConfig.vue
@@ -0,0 +1,376 @@
+<!-- 璁惧鎺у埗閰嶇疆缁勪欢 -->
+<template>
+  <div class="flex flex-col gap-16px">
+    <!-- 浜у搧鍜岃澶囬�夋嫨 - 涓庤Е鍙戝櫒淇濇寔涓�鑷寸殑鍒嗙寮忛�夋嫨鍣� -->
+    <el-row :gutter="16">
+      <el-col :span="12">
+        <el-form-item label="浜у搧" required>
+          <ProductSelector v-model="action.productId" @change="handleProductChange" />
+        </el-form-item>
+      </el-col>
+      <el-col :span="12">
+        <el-form-item label="璁惧" required>
+          <DeviceSelector
+            v-model="action.deviceId"
+            :product-id="action.productId"
+            @change="handleDeviceChange"
+          />
+        </el-form-item>
+      </el-col>
+    </el-row>
+
+    <!-- 鏈嶅姟閫夋嫨 - 鏈嶅姟璋冪敤绫诲瀷鏃舵樉绀� -->
+    <div v-if="action.productId && isServiceInvokeAction" class="space-y-16px">
+      <el-form-item label="鏈嶅姟" required>
+        <el-select
+          v-model="action.identifier"
+          placeholder="璇烽�夋嫨鏈嶅姟"
+          filterable
+          clearable
+          class="w-full"
+          :loading="loadingServices"
+          @change="handleServiceChange"
+        >
+          <el-option
+            v-for="service in serviceList"
+            :key="service.identifier"
+            :label="service.name"
+            :value="service.identifier"
+          >
+            <div class="flex items-center justify-between">
+              <span>{{ service.name }}</span>
+              <el-tag :type="service.callType === 'sync' ? 'primary' : 'success'" size="small">
+                {{ service.callType === 'sync' ? '鍚屾' : '寮傛' }}
+              </el-tag>
+            </div>
+          </el-option>
+        </el-select>
+      </el-form-item>
+
+      <!-- 鏈嶅姟鍙傛暟閰嶇疆 -->
+      <div v-if="action.identifier" class="space-y-16px">
+        <el-form-item label="鏈嶅姟鍙傛暟" required>
+          <JsonParamsInput
+            v-model="paramsValue"
+            type="service"
+            :config="{ service: selectedService } as any"
+            placeholder="璇疯緭鍏� JSON 鏍煎紡鐨勬湇鍔″弬鏁�"
+          />
+        </el-form-item>
+      </div>
+    </div>
+
+    <!-- 鎺у埗鍙傛暟閰嶇疆 - 灞炴�ц缃被鍨嬫椂鏄剧ず -->
+    <div v-if="action.productId && isPropertySetAction" class="space-y-16px">
+      <!-- 鍙傛暟閰嶇疆 -->
+      <el-form-item label="鍙傛暟" required>
+        <JsonParamsInput
+          v-model="paramsValue"
+          type="property"
+          :config="{ properties: thingModelProperties }"
+          placeholder="璇疯緭鍏� JSON 鏍煎紡鐨勬帶鍒跺弬鏁�"
+        />
+      </el-form-item>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { useVModel } from '@vueuse/core'
+import ProductSelector from '../selectors/ProductSelector.vue'
+import DeviceSelector from '../selectors/DeviceSelector.vue'
+import JsonParamsInput from '../inputs/JsonParamsInput.vue'
+import type { Action } from '@/api/iot/rule/scene'
+import type { ThingModelProperty, ThingModelService } from '@/api/iot/thingmodel'
+import {
+  IotRuleSceneActionTypeEnum,
+  IoTThingModelAccessModeEnum,
+  IoTDataSpecsDataTypeEnum
+} from '@/views/iot/utils/constants'
+import { ThingModelApi } from '@/api/iot/thingmodel'
+
+/** 璁惧鎺у埗閰嶇疆缁勪欢 */
+defineOptions({ name: 'DeviceControlConfig' })
+
+const props = defineProps<{
+  modelValue: Action
+}>()
+
+const emit = defineEmits<{
+  (e: 'update:modelValue', value: Action): void
+}>()
+
+const action = useVModel(props, 'modelValue', emit)
+
+const thingModelProperties = ref<ThingModelProperty[]>([]) // 鐗╂ā鍨嬪睘鎬у垪琛�
+const loadingThingModel = ref(false) // 鐗╂ā鍨嬪姞杞界姸鎬�
+const selectedService = ref<ThingModelService | null>(null) // 閫変腑鐨勬湇鍔″璞�
+const serviceList = ref<ThingModelService[]>([]) // 鏈嶅姟鍒楄〃
+const loadingServices = ref(false) // 鏈嶅姟鍔犺浇鐘舵��
+
+// 鍙傛暟鍊肩殑璁$畻灞炴�э紝鐢ㄤ簬鍙屽悜缁戝畾
+const paramsValue = computed({
+  get: () => {
+    // 濡傛灉 params 鏄璞★紝杞崲涓� JSON 瀛楃涓诧紙鍏煎鏃ф暟鎹級
+    if (action.value.params && typeof action.value.params === 'object') {
+      return JSON.stringify(action.value.params, null, 2)
+    }
+    // 濡傛灉 params 宸茬粡鏄瓧绗︿覆锛岀洿鎺ヨ繑鍥�
+    return action.value.params || ''
+  },
+  set: (value: string) => {
+    // 鐩存帴淇濆瓨涓� JSON 瀛楃涓诧紝涓嶈繘琛岃В鏋愯浆鎹�
+    action.value.params = value.trim() || ''
+  }
+})
+
+// 璁$畻灞炴�э細鏄惁涓哄睘鎬ц缃被鍨�
+const isPropertySetAction = computed(() => {
+  return action.value.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET
+})
+
+// 璁$畻灞炴�э細鏄惁涓烘湇鍔¤皟鐢ㄧ被鍨�
+const isServiceInvokeAction = computed(() => {
+  return action.value.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
+})
+
+/**
+ * 澶勭悊浜у搧鍙樺寲浜嬩欢
+ * @param productId 浜у搧 ID
+ */
+const handleProductChange = (productId?: number) => {
+  // 褰撲骇鍝佸彉鍖栨椂锛屾竻绌鸿澶囬�夋嫨鍜屽弬鏁伴厤缃�
+  if (action.value.productId !== productId) {
+    action.value.deviceId = undefined
+    action.value.identifier = undefined // 娓呯┖鏈嶅姟鏍囪瘑绗�
+    action.value.params = '' // 娓呯┖鍙傛暟锛屼繚瀛樹负绌哄瓧绗︿覆
+    selectedService.value = null // 娓呯┖閫変腑鐨勬湇鍔�
+    serviceList.value = [] // 娓呯┖鏈嶅姟鍒楄〃
+  }
+
+  // 鍔犺浇鏂颁骇鍝佺殑鐗╂ā鍨嬪睘鎬ф垨鏈嶅姟鍒楄〃
+  if (productId) {
+    if (isPropertySetAction.value) {
+      loadThingModelProperties(productId)
+    } else if (isServiceInvokeAction.value) {
+      loadServiceList(productId)
+    }
+  }
+}
+
+/**
+ * 澶勭悊璁惧鍙樺寲浜嬩欢
+ * @param deviceId 璁惧 ID
+ */
+const handleDeviceChange = (deviceId?: number) => {
+  // 褰撹澶囧彉鍖栨椂锛屾竻绌哄弬鏁伴厤缃�
+  if (action.value.deviceId !== deviceId) {
+    action.value.params = '' // 娓呯┖鍙傛暟锛屼繚瀛樹负绌哄瓧绗︿覆
+  }
+}
+
+/**
+ * 澶勭悊鏈嶅姟鍙樺寲浜嬩欢
+ * @param serviceIdentifier 鏈嶅姟鏍囪瘑绗�
+ */
+const handleServiceChange = (serviceIdentifier?: string) => {
+  // 鏍规嵁鏈嶅姟鏍囪瘑绗︽壘鍒板搴旂殑鏈嶅姟瀵硅薄
+  const service = serviceList.value.find((s) => s.identifier === serviceIdentifier) || null
+  selectedService.value = service
+
+  // 褰撴湇鍔″彉鍖栨椂锛屾竻绌哄弬鏁伴厤缃�
+  action.value.params = ''
+
+  // 濡傛灉閫夋嫨浜嗘湇鍔′笖鏈夎緭鍏ュ弬鏁帮紝鐢熸垚榛樿鍙傛暟缁撴瀯
+  if (service && service.inputParams && service.inputParams.length > 0) {
+    const defaultParams = {}
+    service.inputParams.forEach((param) => {
+      defaultParams[param.identifier] = getDefaultValueForParam(param)
+    })
+    // 灏嗛粯璁ゅ弬鏁拌浆鎹负 JSON 瀛楃涓蹭繚瀛�
+    action.value.params = JSON.stringify(defaultParams, null, 2)
+  }
+}
+
+/**
+ * 鑾峰彇鐗╂ā鍨婽SL鏁版嵁
+ * @param productId 浜у搧ID
+ * @returns 鐗╂ā鍨婽SL鏁版嵁
+ */
+const getThingModelTSL = async (productId: number) => {
+  if (!productId) return null
+
+  try {
+    return await ThingModelApi.getThingModelTSLByProductId(productId)
+  } catch (error) {
+    console.error('鑾峰彇鐗╂ā鍨婽SL鏁版嵁澶辫触:', error)
+    return null
+  }
+}
+
+/**
+ * 鍔犺浇鐗╂ā鍨嬪睘鎬э紙鍙啓灞炴�э級
+ * @param productId 浜у搧ID
+ */
+const loadThingModelProperties = async (productId: number) => {
+  if (!productId) {
+    thingModelProperties.value = []
+    return
+  }
+
+  try {
+    loadingThingModel.value = true
+    const tslData = await getThingModelTSL(productId)
+
+    if (!tslData?.properties) {
+      thingModelProperties.value = []
+      return
+    }
+
+    // 杩囨护鍑哄彲鍐欑殑灞炴�э紙accessMode 鍖呭惈 'w'锛�
+    thingModelProperties.value = tslData.properties.filter(
+      (property: ThingModelProperty) =>
+        property.accessMode &&
+        (property.accessMode === IoTThingModelAccessModeEnum.READ_WRITE.value ||
+          property.accessMode === IoTThingModelAccessModeEnum.WRITE_ONLY.value)
+    )
+  } catch (error) {
+    console.error('鍔犺浇鐗╂ā鍨嬪睘鎬уけ璐�:', error)
+    thingModelProperties.value = []
+  } finally {
+    loadingThingModel.value = false
+  }
+}
+
+/**
+ * 鍔犺浇鏈嶅姟鍒楄〃
+ * @param productId 浜у搧ID
+ */
+const loadServiceList = async (productId: number) => {
+  if (!productId) {
+    serviceList.value = []
+    return
+  }
+
+  try {
+    loadingServices.value = true
+    const tslData = await getThingModelTSL(productId)
+
+    if (!tslData?.services) {
+      serviceList.value = []
+      return
+    }
+
+    serviceList.value = tslData.services
+  } catch (error) {
+    console.error('鍔犺浇鏈嶅姟鍒楄〃澶辫触:', error)
+    serviceList.value = []
+  } finally {
+    loadingServices.value = false
+  }
+}
+
+/**
+ * 浠嶵SL鍔犺浇鏈嶅姟淇℃伅锛堢敤浜庣紪杈戞ā寮忓洖鏄撅級
+ * @param productId 浜у搧ID
+ * @param serviceIdentifier 鏈嶅姟鏍囪瘑绗�
+ */
+const loadServiceFromTSL = async (productId: number, serviceIdentifier: string) => {
+  // 鍏堝姞杞芥湇鍔″垪琛�
+  await loadServiceList(productId)
+
+  // 鐒跺悗璁剧疆閫変腑鐨勬湇鍔�
+  const service = serviceList.value.find((s: any) => s.identifier === serviceIdentifier)
+  if (service) {
+    selectedService.value = service
+  }
+}
+
+/**
+ * 鏍规嵁鍙傛暟绫诲瀷鑾峰彇榛樿鍊�
+ * @param param 鍙傛暟瀵硅薄
+ * @returns 榛樿鍊�
+ */
+const getDefaultValueForParam = (param: any) => {
+  switch (param.dataType) {
+    case IoTDataSpecsDataTypeEnum.INT:
+      return 0
+    case IoTDataSpecsDataTypeEnum.FLOAT:
+    case IoTDataSpecsDataTypeEnum.DOUBLE:
+      return 0.0
+    case IoTDataSpecsDataTypeEnum.BOOL:
+      return false
+    case IoTDataSpecsDataTypeEnum.TEXT:
+      return ''
+    case IoTDataSpecsDataTypeEnum.ENUM:
+      // 濡傛灉鏈夋灇涓惧�硷紝浣跨敤绗竴涓�
+      if (param.dataSpecs?.dataSpecsList && param.dataSpecs.dataSpecsList.length > 0) {
+        return param.dataSpecs.dataSpecsList[0].value
+      }
+      return ''
+    default:
+      return ''
+  }
+}
+
+const isInitialized = ref(false) // 闃叉閲嶅鍒濆鍖栫殑鏍囧織
+
+/**
+ * 鍒濆鍖栫粍浠舵暟鎹�
+ */
+const initializeComponent = async () => {
+  if (isInitialized.value) return
+
+  const currentAction = action.value
+  if (!currentAction) return
+
+  // 濡傛灉宸茬粡閫夋嫨浜嗕骇鍝佷笖鏄睘鎬ц缃被鍨嬶紝鍔犺浇鐗╂ā鍨�
+  if (currentAction.productId && isPropertySetAction.value) {
+    await loadThingModelProperties(currentAction.productId)
+  }
+
+  // 濡傛灉鏄湇鍔¤皟鐢ㄧ被鍨嬩笖宸叉湁鏍囪瘑绗︼紝鍒濆鍖栨湇鍔¢�夋嫨
+  if (currentAction.productId && isServiceInvokeAction.value && currentAction.identifier) {
+    // 鍔犺浇鐗╂ā鍨婽SL浠ヨ幏鍙栨湇鍔′俊鎭�
+    await loadServiceFromTSL(currentAction.productId, currentAction.identifier)
+  }
+
+  isInitialized.value = true
+}
+
+/** 缁勪欢鍒濆鍖� */
+onMounted(() => {
+  initializeComponent()
+})
+
+/** 鐩戝惉鍏抽敭瀛楁鐨勫彉鍖栵紝閬垮厤娣卞害鐩戝惉瀵艰嚧鐨勬�ц兘闂 */
+watch(
+  () => [action.value.productId, action.value.type, action.value.identifier],
+  async ([newProductId, , newIdentifier], [oldProductId, , oldIdentifier]) => {
+    // 閬垮厤鍒濆鍖栨椂鐨勯噸澶嶈皟鐢�
+    if (!isInitialized.value) return
+
+    // 浜у搧鍙樺寲鏃堕噸鏂板姞杞芥暟鎹�
+    if (newProductId !== oldProductId) {
+      if (newProductId && isPropertySetAction.value) {
+        await loadThingModelProperties(newProductId as number)
+      } else if (newProductId && isServiceInvokeAction.value) {
+        await loadServiceList(newProductId as number)
+      }
+    }
+
+    // 鏈嶅姟鏍囪瘑绗﹀彉鍖栨椂鏇存柊閫変腑鐨勬湇鍔�
+    if (
+      newIdentifier !== oldIdentifier &&
+      newProductId &&
+      isServiceInvokeAction.value &&
+      newIdentifier
+    ) {
+      const service = serviceList.value.find((s: any) => s.identifier === newIdentifier)
+      if (service) {
+        selectedService.value = service
+      }
+    }
+  }
+)
+</script>

--
Gitblit v1.8.0