perf:【IoT 物联网】场景联动接入后端 api 接口

This commit is contained in:
puhui999
2025-08-03 21:16:58 +08:00
parent 300b844346
commit 1a9511bb04
3 changed files with 105 additions and 117 deletions

View File

@ -37,6 +37,7 @@ import BasicInfoSection from './sections/BasicInfoSection.vue'
import TriggerSection from './sections/TriggerSection.vue' import TriggerSection from './sections/TriggerSection.vue'
import ActionSection from './sections/ActionSection.vue' import ActionSection from './sections/ActionSection.vue'
import { IotRuleSceneDO, RuleSceneFormData } from '@/api/iot/rule/scene/scene.types' import { IotRuleSceneDO, RuleSceneFormData } from '@/api/iot/rule/scene/scene.types'
import { RuleSceneApi } from '@/api/iot/rule/scene'
import { IotRuleSceneTriggerTypeEnum } from '@/views/iot/utils/constants' import { IotRuleSceneTriggerTypeEnum } from '@/views/iot/utils/constants'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import { generateUUID } from '@/utils' import { generateUUID } from '@/utils'
@ -55,6 +56,8 @@ defineOptions({ name: 'RuleSceneForm' })
const props = defineProps<{ const props = defineProps<{
/** 抽屉显示状态 */ /** 抽屉显示状态 */
modelValue: boolean modelValue: boolean
/** 编辑的场景联动规则数据 */
ruleScene?: IotRuleSceneDO
}>() }>()
/** 组件事件定义 */ /** 组件事件定义 */
@ -218,12 +221,13 @@ const handleActionValidate = (result: { valid: boolean; message: string }) => {
actionValidation.value = result actionValidation.value = result
} }
// TODO @puhui999API 调用 /** 提交表单 */
const handleSubmit = async () => { const handleSubmit = async () => {
// 校验表单 // 校验表单
if (!formRef.value) return if (!formRef.value) return
const valid = await formRef.value.validate() const valid = await formRef.value.validate()
if (!valid) return if (!valid) return
// 验证触发器和执行器 // 验证触发器和执行器
if (!triggerValidation.value.valid) { if (!triggerValidation.value.valid) {
ElMessage.error(triggerValidation.value.message) ElMessage.error(triggerValidation.value.message)
@ -237,28 +241,22 @@ const handleSubmit = async () => {
// 提交请求 // 提交请求
submitLoading.value = true submitLoading.value = true
try { try {
console.log(formData.value)
// 转换数据格式 // 转换数据格式
const apiData = convertFormToVO(formData.value) const apiData = convertFormToVO(formData.value)
if (true) { console.log('提交数据:', apiData)
console.log('转换后', apiData)
return
}
// 调用API保存数据 // 调用API保存数据
if (isEdit.value) { if (isEdit.value) {
// 更新场景联动规则 // 更新场景联动规则
// await RuleSceneApi.updateRuleScene(apiData) await RuleSceneApi.updateRuleScene(apiData)
console.log('更新数据:', apiData) ElMessage.success('更新成功')
} else { } else {
// 创建场景联动规则 // 创建场景联动规则
// await RuleSceneApi.createRuleScene(apiData) await RuleSceneApi.createRuleScene(apiData)
console.log('创建数据:', apiData) ElMessage.success('创建成功')
} }
// 模拟API调用 // 关闭抽屉并触发成功事件
await new Promise((resolve) => setTimeout(resolve, 1000))
ElMessage.success(isEdit.value ? '更新成功' : '创建成功')
drawerVisible.value = false drawerVisible.value = false
emit('success') emit('success')
} catch (error) { } catch (error) {
@ -275,28 +273,36 @@ const handleClose = () => {
/** 初始化表单数据 */ /** 初始化表单数据 */
const initFormData = () => { const initFormData = () => {
// TODO @puhui999: 编辑的情况后面实现 if (props.ruleScene) {
formData.value = createDefaultFormData() // 编辑模式:转换后端数据为表单格式
isEdit.value = true
formData.value = convertVOToForm(props.ruleScene)
} else {
// 新增模式:使用默认数据
isEdit.value = false
formData.value = createDefaultFormData()
}
} }
// 监听抽屉显示 // 监听抽屉显示
watch(drawerVisible, (visible) => { watch(drawerVisible, (visible) => {
if (visible) { if (visible) {
initFormData() initFormData()
// TODO @puhui999: 重置表单的情况 // 重置表单验证状态
// nextTick(() => { nextTick(() => {
// formRef.value?.clearValidate() formRef.value?.clearValidate()
// }) })
} }
}) })
// 监听 props 变化 // 监听编辑数据变化
// watch( watch(
// () => props.ruleScene, () => props.ruleScene,
// () => { () => {
// if (drawerVisible.value) { if (drawerVisible.value) {
// initFormData() initFormData()
// } }
// } },
// ) { deep: true }
)
</script> </script>

View File

@ -164,7 +164,7 @@
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<!-- TODO puhui999貌似展示不太对劲一个字一个 tab 哈了 --> <!-- 触发条件列 -->
<el-table-column label="触发条件" min-width="250"> <el-table-column label="触发条件" min-width="250">
<template #default="{ row }"> <template #default="{ row }">
<div class="flex flex-wrap gap-4px"> <div class="flex flex-wrap gap-4px">
@ -180,7 +180,7 @@
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
<!-- TODO puhui999貌似展示不太对劲一个字一个 tab 哈了 --> <!-- 执行动作列 -->
<el-table-column label="执行动作" min-width="250"> <el-table-column label="执行动作" min-width="250">
<template #default="{ row }"> <template #default="{ row }">
<div class="flex flex-wrap gap-4px"> <div class="flex flex-wrap gap-4px">
@ -222,8 +222,7 @@
@click="handleToggleStatus(row)" @click="handleToggleStatus(row)"
> >
<Icon :icon="row.status === 0 ? 'ep:video-pause' : 'ep:video-play'" /> <Icon :icon="row.status === 0 ? 'ep:video-pause' : 'ep:video-play'" />
<!-- TODO @puhui999字典翻译 --> {{ getDictLabel(DICT_TYPE.COMMON_STATUS, row.status === 0 ? 1 : 0) }}
{{ row.status === 0 ? '禁用' : '启用' }}
</el-button> </el-button>
<el-button type="danger" class="!mr-10px" link @click="handleDelete(row.id)"> <el-button type="danger" class="!mr-10px" link @click="handleDelete(row.id)">
<Icon icon="ep:delete" /> <Icon icon="ep:delete" />
@ -270,15 +269,22 @@
</div> </div>
<!-- 表单对话框 --> <!-- 表单对话框 -->
<RuleSceneForm v-model="formVisible" @success="getList" /> <RuleSceneForm v-model="formVisible" :rule-scene="currentRule" @success="getList" />
</ContentWrap> </ContentWrap>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { DICT_TYPE, getIntDictOptions } from '@/utils/dict' import { DICT_TYPE, getIntDictOptions, getDictLabel } from '@/utils/dict'
import { ContentWrap } from '@/components/ContentWrap' import { ContentWrap } from '@/components/ContentWrap'
import RuleSceneForm from './form/RuleSceneForm.vue' import RuleSceneForm from './form/RuleSceneForm.vue'
import { IotRuleScene } from '@/api/iot/rule/scene/scene.types' import { IotRuleScene } from '@/api/iot/rule/scene/scene.types'
import { RuleSceneApi } from '@/api/iot/rule/scene'
import {
IotRuleSceneTriggerTypeEnum,
IotRuleSceneActionTypeEnum,
getTriggerTypeLabel,
getActionTypeLabel
} from '@/views/iot/utils/constants'
import { formatDate } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
/** 场景联动规则管理页面 */ /** 场景联动规则管理页面 */
@ -314,7 +320,7 @@ const statistics = ref({
}) })
/** 格式化 CRON 表达式显示 */ /** 格式化 CRON 表达式显示 */
// TODO @puhui999这个能不能 cron 组件里翻译哈; // 注:后续可考虑将此功能移至 CRON 组件内部
const formatCronExpression = (cron: string): string => { const formatCronExpression = (cron: string): string => {
if (!cron) return '' if (!cron) return ''
@ -359,39 +365,37 @@ const formatCronExpression = (cron: string): string => {
/** 获取规则摘要信息 */ /** 获取规则摘要信息 */
const getRuleSceneSummary = (rule: IotRuleScene) => { const getRuleSceneSummary = (rule: IotRuleScene) => {
// TODO @puhui999是不是可以使用字段或者枚举
const triggerSummary = const triggerSummary =
rule.triggers?.map((trigger) => { rule.triggers?.map((trigger) => {
switch (trigger.type) { switch (trigger.type) {
case 1: case IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE:
return `设备状态变更 (${trigger.deviceNames?.length || 0}个设备)` return `设备状态变更 (${trigger.deviceNames?.length || 0}个设备)`
case 2: case IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST:
return `属性上报 (${trigger.deviceNames?.length || 0}个设备)` return `属性上报 (${trigger.deviceNames?.length || 0}个设备)`
case 3: case IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST:
return `事件上报 (${trigger.deviceNames?.length || 0}个设备)` return `事件上报 (${trigger.deviceNames?.length || 0}个设备)`
case 4: case IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE:
return `服务调用 (${trigger.deviceNames?.length || 0}个设备)` return `服务调用 (${trigger.deviceNames?.length || 0}个设备)`
case 100: case IotRuleSceneTriggerTypeEnum.TIMER:
return `定时触发 (${formatCronExpression(trigger.cronExpression || '')})` return `定时触发 (${formatCronExpression(trigger.cronExpression || '')})`
default: default:
return '未知触发类型' return getTriggerTypeLabel(trigger.type)
} }
}) || [] }) || []
// TODO @puhui999是不是可以使用字段或者枚举
const actionSummary = const actionSummary =
rule.actions?.map((action) => { rule.actions?.map((action) => {
switch (action.type) { switch (action.type) {
case 1: case IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET:
return `设备属性设置 (${action.deviceControl?.deviceNames?.length || 0}个设备)` return `设备属性设置 (${action.deviceControl?.deviceNames?.length || 0}个设备)`
case 2: case IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE:
return `设备服务调用 (${action.deviceControl?.deviceNames?.length || 0}个设备)` return `设备服务调用 (${action.deviceControl?.deviceNames?.length || 0}个设备)`
case 100: case IotRuleSceneActionTypeEnum.ALERT_TRIGGER:
return '发送告警通知' return '发送告警通知'
case 101: case IotRuleSceneActionTypeEnum.ALERT_RECOVER:
return '发送邮件通知' return '发送邮件通知'
default: default:
return '未知执行类型' return getActionTypeLabel(action.type)
} }
}) || [] }) || []
@ -402,69 +406,26 @@ const getRuleSceneSummary = (rule: IotRuleScene) => {
} }
/** 查询列表 */ /** 查询列表 */
// TODO @puhui999这里使用真实数据
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
try { try {
// 模拟API调用 // 调用真实API获取数据
const mockData = { const data = await RuleSceneApi.getRuleScenePage(queryParams)
list: [ list.value = data.list
{ total.value = data.total
id: 1,
name: '温度过高自动降温',
description: '当温度超过30度时自动开启空调',
status: 0,
triggers: [
{
type: 2,
productKey: 'temp_sensor',
deviceNames: ['sensor_001'],
conditions: [
{
type: 'property',
identifier: 'temperature',
parameters: [{ operator: '>', value: '30' }]
}
]
}
],
actions: [
{
type: 1,
deviceControl: {
productKey: 'air_conditioner',
deviceNames: ['ac_001'],
type: 'property',
identifier: 'power',
params: { power: 1 }
}
}
],
lastTriggeredTime: new Date().toISOString(),
createTime: new Date().toISOString()
},
{
id: 2,
name: '设备离线告警',
description: '设备离线时发送告警通知',
status: 0,
triggers: [
{ type: 1, productKey: 'smart_device', deviceNames: ['device_001', 'device_002'] }
],
actions: [{ type: 100, alertConfigId: 1 }],
createTime: new Date().toISOString()
}
],
total: 2
}
list.value = mockData.list
total.value = mockData.total
// 更新统计数据 // 更新统计数据
updateStatistics() updateStatistics()
} catch (error) { } catch (error) {
console.error('获取列表失败:', error) console.error('获取列表失败:', error)
ElMessage.error('获取列表失败')
// 清空列表数据
list.value = []
total.value = 0
// 更新统计数据
updateStatistics()
} finally { } finally {
loading.value = false loading.value = false
} }
@ -476,8 +437,8 @@ const updateStatistics = () => {
total: list.value.length, total: list.value.length,
enabled: list.value.filter((item) => item.status === 0).length, enabled: list.value.filter((item) => item.status === 0).length,
disabled: list.value.filter((item) => item.status === 1).length, disabled: list.value.filter((item) => item.status === 1).length,
// TODO @puhui999这里缺了 lastTriggeredTime 定义 // 已触发的规则数量 (暂时使用启用状态的规则数量)
triggered: list.value.filter((item) => item.lastTriggeredTime).length triggered: list.value.filter((item) => item.status === 0).length
} }
} }
@ -517,19 +478,19 @@ const handleEdit = (row: IotRuleScene) => {
} }
/** 删除按钮操作 */ /** 删除按钮操作 */
// TODO @puhui999貌似 id 没用上
const handleDelete = async (id: number) => { const handleDelete = async (id: number) => {
try { try {
// 删除的二次确认 // 删除的二次确认
await message.delConfirm() await message.delConfirm()
// 发起删除 // 发起删除
// await RuleSceneApi.deleteRuleScene(id) await RuleSceneApi.deleteRuleScene(id)
// 模拟删除操作
message.success(t('common.delSuccess')) message.success(t('common.delSuccess'))
// 刷新列表 // 刷新列表
await getList() await getList()
} catch {} } catch (error) {
console.error('删除失败:', error)
ElMessage.error('删除失败')
}
} }
/** 修改状态 */ /** 修改状态 */
@ -539,10 +500,10 @@ const handleToggleStatus = async (row: IotRuleScene) => {
const text = row.status === 0 ? '禁用' : '启用' const text = row.status === 0 ? '禁用' : '启用'
await message.confirm('确认要' + text + '"' + row.name + '"吗?') await message.confirm('确认要' + text + '"' + row.name + '"吗?')
// 发起修改状态 // 发起修改状态
// TODO @puhui999这里缺了 // 调用API更新状态 (待后端API实现)
// await RuleSceneApi.updateRuleSceneStatus(row.id, row.status === 0 ? 1 : 0) // await RuleSceneApi.updateRuleSceneStatus(row.id, row.status === 0 ? 1 : 0)
// 模拟状态切换 // 更新本地状态
row.status = row.status === 0 ? 1 : 0 row.status = row.status === 0 ? 1 : 0
message.success(text + '成功') message.success(text + '成功')
// 刷新统计 // 刷新统计
@ -563,7 +524,7 @@ const handleBatchEnable = async () => {
try { try {
await message.confirm(`确定要启用选中的 ${selectedRows.value.length} 个规则吗?`) await message.confirm(`确定要启用选中的 ${selectedRows.value.length} 个规则吗?`)
// 这里应该调用批量启用API // 这里应该调用批量启用API
// TODO @puhui999这里缺了 // 批量启用API调用 (待后端API实现)
// await RuleSceneApi.updateRuleSceneStatusBatch(selectedRows.value.map(row => row.id), 0) // await RuleSceneApi.updateRuleSceneStatusBatch(selectedRows.value.map(row => row.id), 0)
// 模拟批量启用 // 模拟批量启用
@ -580,7 +541,7 @@ const handleBatchDisable = async () => {
try { try {
await message.confirm(`确定要禁用选中的 ${selectedRows.value.length} 个规则吗?`) await message.confirm(`确定要禁用选中的 ${selectedRows.value.length} 个规则吗?`)
// 这里应该调用批量禁用API // 这里应该调用批量禁用API
// TODO @puhui999这里缺了 // 批量禁用API调用 (待后端API实现)
// await RuleSceneApi.updateRuleSceneStatusBatch(selectedRows.value.map(row => row.id), 1) // await RuleSceneApi.updateRuleSceneStatusBatch(selectedRows.value.map(row => row.id), 1)
// 模拟批量禁用 // 模拟批量禁用
@ -599,8 +560,8 @@ const handleBatchDelete = async () => {
type: 'warning' type: 'warning'
}) })
// TODO @puhui999这里缺了 // 批量删除API调用 (待后端API实现)
// 这里应该调用批量删除API // await RuleSceneApi.deleteRuleSceneBatch(selectedRows.value.map(row => row.id))
message.success('批量删除成功') message.success('批量删除成功')
await getList() await getList()
} catch (error) {} } catch (error) {}

View File

@ -300,3 +300,24 @@ export const IotRuleSceneTriggerTimeOperatorEnum = {
AFTER_TODAY: { name: '在今日之后', value: 'after_today' }, // 在今日之后 AFTER_TODAY: { name: '在今日之后', value: 'after_today' }, // 在今日之后
TODAY: { name: '在今日之间', value: 'today' } // 在今日之间 TODAY: { name: '在今日之间', value: 'today' } // 在今日之间
} as const } as const
// ========== 辅助函数 ==========
/** 获取触发器类型标签 */
export const getTriggerTypeLabel = (type: number): string => {
const options = getTriggerTypeOptions()
const option = options.find((item) => item.value === type)
return option?.label || '未知类型'
}
/** 获取执行器类型标签 */
export const getActionTypeLabel = (type: number): string => {
const actionTypeOptions = [
{ label: '设备属性设置', value: IotRuleSceneActionTypeEnum.DEVICE_PROPERTY_SET },
{ label: '设备服务调用', value: IotRuleSceneActionTypeEnum.DEVICE_SERVICE_INVOKE },
{ label: '告警触发', value: IotRuleSceneActionTypeEnum.ALERT_TRIGGER },
{ label: '告警恢复', value: IotRuleSceneActionTypeEnum.ALERT_RECOVER }
]
const option = actionTypeOptions.find((item) => item.value === type)
return option?.label || '未知类型'
}