DeviceControlConfig.vue 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. <!-- 设备控制配置组件 -->
  2. <template>
  3. <div class="flex flex-col gap-16px">
  4. <!-- 产品和设备选择 - 与触发器保持一致的分离式选择器 -->
  5. <el-row :gutter="16">
  6. <el-col :span="12">
  7. <el-form-item label="产品" required>
  8. <ProductSelector v-model="action.productId" @change="handleProductChange" />
  9. </el-form-item>
  10. </el-col>
  11. <el-col :span="12">
  12. <el-form-item label="设备" required>
  13. <DeviceSelector
  14. v-model="action.deviceId"
  15. :product-id="action.productId"
  16. @change="handleDeviceChange"
  17. />
  18. </el-form-item>
  19. </el-col>
  20. </el-row>
  21. <!-- 服务选择 - 服务调用类型时显示 -->
  22. <div v-if="action.productId && isServiceInvokeAction" class="space-y-16px">
  23. <el-form-item label="服务" required>
  24. <el-select
  25. v-model="action.identifier"
  26. placeholder="请选择服务"
  27. filterable
  28. clearable
  29. class="w-full"
  30. :loading="loadingServices"
  31. @change="handleServiceChange"
  32. >
  33. <el-option
  34. v-for="service in serviceList"
  35. :key="service.identifier"
  36. :label="service.name"
  37. :value="service.identifier"
  38. >
  39. <div class="flex items-center justify-between">
  40. <span>{{ service.name }}</span>
  41. <el-tag :type="service.callType === 'sync' ? 'primary' : 'success'" size="small">
  42. {{ service.callType === 'sync' ? '同步' : '异步' }}
  43. </el-tag>
  44. </div>
  45. </el-option>
  46. </el-select>
  47. </el-form-item>
  48. <!-- 服务参数配置 -->
  49. <div v-if="action.identifier" class="space-y-16px">
  50. <el-form-item label="服务参数" required>
  51. <JsonParamsInput
  52. v-model="paramsValue"
  53. type="service"
  54. :config="{ service: selectedService } as any"
  55. placeholder="请输入 JSON 格式的服务参数"
  56. />
  57. </el-form-item>
  58. </div>
  59. </div>
  60. <!-- 控制参数配置 - 属性设置类型时显示 -->
  61. <div v-if="action.productId && isPropertySetAction" class="space-y-16px">
  62. <!-- 参数配置 -->
  63. <el-form-item label="参数" required>
  64. <JsonParamsInput
  65. v-model="paramsValue"
  66. type="property"
  67. :config="{ properties: thingModelProperties }"
  68. placeholder="请输入 JSON 格式的控制参数"
  69. />
  70. </el-form-item>
  71. </div>
  72. </div>
  73. </template>
  74. <script setup lang="ts">
  75. import { useVModel } from '@vueuse/core'
  76. import ProductSelector from '../selectors/ProductSelector.vue'
  77. import DeviceSelector from '../selectors/DeviceSelector.vue'
  78. import JsonParamsInput from '../inputs/JsonParamsInput.vue'
  79. import type { Action } from '@/api/iot/rule/scene'
  80. import type { ThingModelProperty, ThingModelService } from '@/api/iot/thingmodel'
  81. import {
  82. IotRuleSceneActionTypeEnum,
  83. IoTThingModelAccessModeEnum,
  84. IoTDataSpecsDataTypeEnum
  85. } from '@/views/iot/utils/constants'
  86. import { ThingModelApi } from '@/api/iot/thingmodel'
  87. /** 设备控制配置组件 */
  88. defineOptions({ name: 'DeviceControlConfig' })
  89. const props = defineProps<{
  90. modelValue: Action
  91. }>()
  92. const emit = defineEmits<{
  93. (e: 'update:modelValue', value: Action): void
  94. }>()
  95. const action = useVModel(props, 'modelValue', emit)
  96. const thingModelProperties = ref<ThingModelProperty[]>([]) // 物模型属性列表
  97. const loadingThingModel = ref(false) // 物模型加载状态
  98. const selectedService = ref<ThingModelService | null>(null) // 选中的服务对象
  99. const serviceList = ref<ThingModelService[]>([]) // 服务列表
  100. const loadingServices = ref(false) // 服务加载状态
  101. // 参数值的计算属性,用于双向绑定
  102. const paramsValue = computed({
  103. get: () => {
  104. // 如果 params 是对象,转换为 JSON 字符串(兼容旧数据)
  105. if (action.value.params && typeof action.value.params === 'object') {
  106. return JSON.stringify(action.value.params, null, 2)
  107. }
  108. // 如果 params 已经是字符串,直接返回
  109. return action.value.params || ''
  110. },
  111. set: (value: string) => {
  112. // 直接保存为 JSON 字符串,不进行解析转换
  113. action.value.params = value.trim() || ''
  114. }
  115. })
  116. // 计算属性:是否为属性设置类型
  117. const isPropertySetAction = computed(() => {
  118. return action.value.type === IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET
  119. })
  120. // 计算属性:是否为服务调用类型
  121. const isServiceInvokeAction = computed(() => {
  122. return action.value.type === IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE
  123. })
  124. /**
  125. * 处理产品变化事件
  126. * @param productId 产品ID
  127. */
  128. const handleProductChange = (productId?: number) => {
  129. // 当产品变化时,清空设备选择和参数配置
  130. if (action.value.productId !== productId) {
  131. action.value.deviceId = undefined
  132. action.value.identifier = undefined // 清空服务标识符
  133. action.value.params = '' // 清空参数,保存为空字符串
  134. selectedService.value = null // 清空选中的服务
  135. serviceList.value = [] // 清空服务列表
  136. }
  137. // 加载新产品的物模型属性或服务列表
  138. if (productId) {
  139. if (isPropertySetAction.value) {
  140. loadThingModelProperties(productId)
  141. } else if (isServiceInvokeAction.value) {
  142. loadServiceList(productId)
  143. }
  144. }
  145. }
  146. /**
  147. * 处理设备变化事件
  148. * @param deviceId 设备ID
  149. */
  150. const handleDeviceChange = (deviceId?: number) => {
  151. // 当设备变化时,清空参数配置
  152. if (action.value.deviceId !== deviceId) {
  153. action.value.params = '' // 清空参数,保存为空字符串
  154. }
  155. }
  156. /**
  157. * 处理服务变化事件
  158. * @param serviceIdentifier 服务标识符
  159. */
  160. const handleServiceChange = (serviceIdentifier?: string) => {
  161. // 根据服务标识符找到对应的服务对象
  162. const service = serviceList.value.find((s) => s.identifier === serviceIdentifier) || null
  163. selectedService.value = service
  164. // 当服务变化时,清空参数配置
  165. action.value.params = ''
  166. // 如果选择了服务且有输入参数,生成默认参数结构
  167. if (service && service.inputParams && service.inputParams.length > 0) {
  168. const defaultParams = {}
  169. service.inputParams.forEach((param) => {
  170. defaultParams[param.identifier] = getDefaultValueForParam(param)
  171. })
  172. // 将默认参数转换为 JSON 字符串保存
  173. action.value.params = JSON.stringify(defaultParams, null, 2)
  174. }
  175. }
  176. /**
  177. * 获取物模型TSL数据
  178. * @param productId 产品ID
  179. * @returns 物模型TSL数据
  180. */
  181. const getThingModelTSL = async (productId: number) => {
  182. if (!productId) return null
  183. try {
  184. return await ThingModelApi.getThingModelTSLByProductId(productId)
  185. } catch (error) {
  186. console.error('获取物模型TSL数据失败:', error)
  187. return null
  188. }
  189. }
  190. /**
  191. * 加载物模型属性(可写属性)
  192. * @param productId 产品ID
  193. */
  194. const loadThingModelProperties = async (productId: number) => {
  195. if (!productId) {
  196. thingModelProperties.value = []
  197. return
  198. }
  199. try {
  200. loadingThingModel.value = true
  201. const tslData = await getThingModelTSL(productId)
  202. if (!tslData?.properties) {
  203. thingModelProperties.value = []
  204. return
  205. }
  206. // 过滤出可写的属性(accessMode 包含 'w')
  207. thingModelProperties.value = tslData.properties.filter(
  208. (property: ThingModelProperty) =>
  209. property.accessMode &&
  210. (property.accessMode === IoTThingModelAccessModeEnum.READ_WRITE.value ||
  211. property.accessMode === IoTThingModelAccessModeEnum.WRITE_ONLY.value)
  212. )
  213. } catch (error) {
  214. console.error('加载物模型属性失败:', error)
  215. thingModelProperties.value = []
  216. } finally {
  217. loadingThingModel.value = false
  218. }
  219. }
  220. /**
  221. * 加载服务列表
  222. * @param productId 产品ID
  223. */
  224. const loadServiceList = async (productId: number) => {
  225. if (!productId) {
  226. serviceList.value = []
  227. return
  228. }
  229. try {
  230. loadingServices.value = true
  231. const tslData = await getThingModelTSL(productId)
  232. if (!tslData?.services) {
  233. serviceList.value = []
  234. return
  235. }
  236. serviceList.value = tslData.services
  237. } catch (error) {
  238. console.error('加载服务列表失败:', error)
  239. serviceList.value = []
  240. } finally {
  241. loadingServices.value = false
  242. }
  243. }
  244. /**
  245. * 从TSL加载服务信息(用于编辑模式回显)
  246. * @param productId 产品ID
  247. * @param serviceIdentifier 服务标识符
  248. */
  249. const loadServiceFromTSL = async (productId: number, serviceIdentifier: string) => {
  250. // 先加载服务列表
  251. await loadServiceList(productId)
  252. // 然后设置选中的服务
  253. const service = serviceList.value.find((s: any) => s.identifier === serviceIdentifier)
  254. if (service) {
  255. selectedService.value = service
  256. }
  257. }
  258. /**
  259. * 根据参数类型获取默认值
  260. * @param param 参数对象
  261. * @returns 默认值
  262. */
  263. const getDefaultValueForParam = (param: any) => {
  264. switch (param.dataType) {
  265. case IoTDataSpecsDataTypeEnum.INT:
  266. return 0
  267. case IoTDataSpecsDataTypeEnum.FLOAT:
  268. case IoTDataSpecsDataTypeEnum.DOUBLE:
  269. return 0.0
  270. case IoTDataSpecsDataTypeEnum.BOOL:
  271. return false
  272. case IoTDataSpecsDataTypeEnum.TEXT:
  273. return ''
  274. case IoTDataSpecsDataTypeEnum.ENUM:
  275. // 如果有枚举值,使用第一个
  276. if (param.dataSpecs?.dataSpecsList && param.dataSpecs.dataSpecsList.length > 0) {
  277. return param.dataSpecs.dataSpecsList[0].value
  278. }
  279. return ''
  280. default:
  281. return ''
  282. }
  283. }
  284. const isInitialized = ref(false) // 防止重复初始化的标志
  285. /**
  286. * 初始化组件数据
  287. */
  288. const initializeComponent = async () => {
  289. if (isInitialized.value) return
  290. const currentAction = action.value
  291. if (!currentAction) return
  292. // 如果已经选择了产品且是属性设置类型,加载物模型
  293. if (currentAction.productId && isPropertySetAction.value) {
  294. await loadThingModelProperties(currentAction.productId)
  295. }
  296. // 如果是服务调用类型且已有标识符,初始化服务选择
  297. if (currentAction.productId && isServiceInvokeAction.value && currentAction.identifier) {
  298. // 加载物模型TSL以获取服务信息
  299. await loadServiceFromTSL(currentAction.productId, currentAction.identifier)
  300. }
  301. isInitialized.value = true
  302. }
  303. /**
  304. * 组件初始化
  305. */
  306. onMounted(() => {
  307. initializeComponent()
  308. })
  309. // 监听关键字段的变化,避免深度监听导致的性能问题
  310. watch(
  311. () => [action.value.productId, action.value.type, action.value.identifier],
  312. async ([newProductId, , newIdentifier], [oldProductId, , oldIdentifier]) => {
  313. // 避免初始化时的重复调用
  314. if (!isInitialized.value) return
  315. // 产品变化时重新加载数据
  316. if (newProductId !== oldProductId) {
  317. if (newProductId && isPropertySetAction.value) {
  318. await loadThingModelProperties(newProductId as number)
  319. } else if (newProductId && isServiceInvokeAction.value) {
  320. await loadServiceList(newProductId as number)
  321. }
  322. }
  323. // 服务标识符变化时更新选中的服务
  324. if (
  325. newIdentifier !== oldIdentifier &&
  326. newProductId &&
  327. isServiceInvokeAction.value &&
  328. newIdentifier
  329. ) {
  330. const service = serviceList.value.find((s: any) => s.identifier === newIdentifier)
  331. if (service) {
  332. selectedService.value = service
  333. }
  334. }
  335. }
  336. )
  337. </script>