wwf
10 小时以前 a1d7e81859f554f3a53680cc35f0f49bf1f77098
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
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)
  }
}
 
/**
 * 获取物模型TSL数据
 * @param productId 产品ID
 * @returns 物模型TSL数据
 */
const getThingModelTSL = async (productId: number) => {
  if (!productId) return null
 
  try {
    return await ThingModelApi.getThingModelTSLByProductId(productId)
  } catch (error) {
    console.error('获取物模型TSL数据失败:', 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
  }
}
 
/**
 * 从TSL加载服务信息(用于编辑模式回显)
 * @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) {
    // 加载物模型TSL以获取服务信息
    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>