review:【IoT 物联网】场景联动的部分 review

pull/794/MERGE
YunaiV 2025-07-18 23:55:51 +08:00
parent a72c297e86
commit bbd38e026b
23 changed files with 383 additions and 397 deletions

View File

@ -1,4 +1,5 @@
<!-- IoT场景联动规则表单 - 主表单组件 --> <!-- IoT场景联动规则表单 - 主表单组件 -->
<!-- TODO @puhui999要不搞个 form 目录不用 components保持和别的模块风格一致哈 -->
<template> <template>
<el-drawer <el-drawer
v-model="drawerVisible" v-model="drawerVisible"
@ -37,6 +38,7 @@
</div> </div>
<!-- 抽屉底部操作栏 --> <!-- 抽屉底部操作栏 -->
<!-- TODO @puhui999这个按钮逻辑和别的模块一致 -->
<template #footer> <template #footer>
<div class="drawer-footer"> <div class="drawer-footer">
<el-button @click="handleClose" size="large">取消</el-button> <el-button @click="handleClose" size="large">取消</el-button>
@ -65,7 +67,7 @@ import { getBaseValidationRules } from '../utils/validation'
import { transformFormToApi, transformApiToForm, createDefaultFormData } from '../utils/transform' import { transformFormToApi, transformApiToForm, createDefaultFormData } from '../utils/transform'
import { handleValidationError, showSuccess, withErrorHandling } from '../utils/errorHandler' import { handleValidationError, showSuccess, withErrorHandling } from '../utils/errorHandler'
/** IoT场景联动规则表单 - 主表单组件 */ /** IoT 场景联动规则表单 - 主表单组件 */
defineOptions({ name: 'RuleSceneForm' }) defineOptions({ name: 'RuleSceneForm' })
interface Props { interface Props {
@ -96,7 +98,7 @@ const actionValidation = ref({ valid: true, message: '' })
// //
const isEdit = computed(() => !!props.ruleScene?.id) const isEdit = computed(() => !!props.ruleScene?.id)
const drawerTitle = computed(() => (isEdit.value ? '编辑场景联动规则' : '新增场景联动规则')) const drawerTitle = computed(() => (isEdit.value ? '编辑场景联动规则' : '新增场景联动规则')) // TODO @puhui999
const canSubmit = computed(() => { const canSubmit = computed(() => {
return ( return (
@ -140,6 +142,7 @@ const handleValidate = async () => {
} }
} }
// TODO @puhui999
const handleSubmit = async () => { const handleSubmit = async () => {
const result = await withErrorHandling( const result = await withErrorHandling(
async () => { async () => {

View File

@ -1,6 +1,7 @@
<!-- 告警配置组件 --> <!-- 告警配置组件 -->
<template> <template>
<div class="alert-config"> <div class="alert-config">
<!-- TODO @puhui999触发告警时不用选择配置哈 -->
<el-form-item label="告警配置" required> <el-form-item label="告警配置" required>
<el-select <el-select
v-model="localValue" v-model="localValue"
@ -95,7 +96,7 @@ const isValid = ref(true)
// //
const selectedConfig = computed(() => { const selectedConfig = computed(() => {
return alertConfigs.value.find(config => config.id === localValue.value) return alertConfigs.value.find((config) => config.id === localValue.value)
}) })
// //
@ -121,7 +122,7 @@ const updateValidationResult = () => {
emit('validate', { valid: false, message: validationMessage.value }) emit('validate', { valid: false, message: validationMessage.value })
return return
} }
const config = selectedConfig.value const config = selectedConfig.value
if (!config) { if (!config) {
isValid.value = false isValid.value = false
@ -129,14 +130,14 @@ const updateValidationResult = () => {
emit('validate', { valid: false, message: validationMessage.value }) emit('validate', { valid: false, message: validationMessage.value })
return return
} }
if (!config.enabled) { if (!config.enabled) {
isValid.value = false isValid.value = false
validationMessage.value = '选择的告警配置已禁用' validationMessage.value = '选择的告警配置已禁用'
emit('validate', { valid: false, message: validationMessage.value }) emit('validate', { valid: false, message: validationMessage.value })
return return
} }
// //
isValid.value = true isValid.value = true
validationMessage.value = '告警配置验证通过' validationMessage.value = '告警配置验证通过'
@ -149,6 +150,7 @@ const getAlertConfigs = async () => {
try { try {
// API // API
// 使 // 使
// TODO @puhui999
alertConfigs.value = [ alertConfigs.value = [
{ {
id: 1, id: 1,
@ -183,9 +185,12 @@ const getAlertConfigs = async () => {
} }
// //
watch(() => localValue.value, () => { watch(
updateValidationResult() () => localValue.value,
}) () => {
updateValidationResult()
}
)
// //
onMounted(() => { onMounted(() => {

View File

@ -1,4 +1,5 @@
<!-- 单个条件配置组件 --> <!-- 单个条件配置组件 -->
<!-- TODO @puhui999这里需要在对下阿里云 IoT不太对它是条件类型然后选择产品设备接着选条件类型对应的比较 -->
<template> <template>
<div class="condition-config"> <div class="condition-config">
<el-row :gutter="16"> <el-row :gutter="16">
@ -105,11 +106,11 @@ const conditionPreview = computed(() => {
if (!condition.value.identifier || !condition.value.operator || !condition.value.param) { if (!condition.value.identifier || !condition.value.operator || !condition.value.param) {
return '' return ''
} }
const propertyName = propertyConfig.value?.name || condition.value.identifier const propertyName = propertyConfig.value?.name || condition.value.identifier
const operatorText = getOperatorText(condition.value.operator) const operatorText = getOperatorText(condition.value.operator)
const value = condition.value.param const value = condition.value.param
return `${propertyName} ${operatorText} ${value} 时触发` return `${propertyName} ${operatorText} ${value} 时触发`
}) })
@ -122,8 +123,8 @@ const getOperatorText = (operator: string) => {
'>=': '大于等于', '>=': '大于等于',
'<': '小于', '<': '小于',
'<=': '小于等于', '<=': '小于等于',
'in': '包含于', in: '包含于',
'between': '介于' between: '介于'
} }
return operatorMap[operator] || operator return operatorMap[operator] || operator
} }
@ -137,11 +138,11 @@ const updateConditionField = (field: keyof ConditionFormData, value: any) => {
const handlePropertyChange = (propertyInfo: { type: string; config: any }) => { const handlePropertyChange = (propertyInfo: { type: string; config: any }) => {
propertyType.value = propertyInfo.type propertyType.value = propertyInfo.type
propertyConfig.value = propertyInfo.config propertyConfig.value = propertyInfo.config
// //
condition.value.operator = '=' condition.value.operator = '='
condition.value.param = '' condition.value.param = ''
updateValidationResult() updateValidationResult()
} }
@ -164,21 +165,21 @@ const updateValidationResult = () => {
emit('validate', { valid: false, message: validationMessage.value }) emit('validate', { valid: false, message: validationMessage.value })
return return
} }
if (!condition.value.operator) { if (!condition.value.operator) {
isValid.value = false isValid.value = false
validationMessage.value = '请选择操作符' validationMessage.value = '请选择操作符'
emit('validate', { valid: false, message: validationMessage.value }) emit('validate', { valid: false, message: validationMessage.value })
return return
} }
if (!condition.value.param) { if (!condition.value.param) {
isValid.value = false isValid.value = false
validationMessage.value = '请输入比较值' validationMessage.value = '请输入比较值'
emit('validate', { valid: false, message: validationMessage.value }) emit('validate', { valid: false, message: validationMessage.value })
return return
} }
// //
if (!valueValidation.value.valid) { if (!valueValidation.value.valid) {
isValid.value = false isValid.value = false
@ -186,7 +187,7 @@ const updateValidationResult = () => {
emit('validate', { valid: false, message: validationMessage.value }) emit('validate', { valid: false, message: validationMessage.value })
return return
} }
// //
isValid.value = true isValid.value = true
validationMessage.value = '条件配置验证通过' validationMessage.value = '条件配置验证通过'
@ -194,9 +195,13 @@ const updateValidationResult = () => {
} }
// //
watch(() => [condition.value.identifier, condition.value.operator, condition.value.param], () => { watch(
updateValidationResult() () => [condition.value.identifier, condition.value.operator, condition.value.param],
}, { deep: true }) () => {
updateValidationResult()
},
{ deep: true }
)
// //
onMounted(() => { onMounted(() => {

View File

@ -40,15 +40,9 @@
</div> </div>
<!-- 逻辑连接符 --> <!-- 逻辑连接符 -->
<div <!-- TODO @puhui999不用这个哈 -->
v-if="index < group.conditions!.length - 1" <div v-if="index < group.conditions!.length - 1" class="logic-connector">
class="logic-connector" <el-select v-model="group.logicOperator" size="small" style="width: 80px">
>
<el-select
v-model="group.logicOperator"
size="small"
style="width: 80px;"
>
<el-option label="且" value="AND" /> <el-option label="且" value="AND" />
<el-option label="或" value="OR" /> <el-option label="或" value="OR" />
</el-select> </el-select>
@ -67,23 +61,22 @@
</div> </div>
<!-- 添加条件按钮 --> <!-- 添加条件按钮 -->
<div v-if="group.conditions && group.conditions.length > 0 && group.conditions.length < maxConditions" class="add-condition"> <div
<el-button v-if="
type="primary" group.conditions && group.conditions.length > 0 && group.conditions.length < maxConditions
plain "
@click="addCondition" class="add-condition"
class="add-condition-btn" >
> <el-button type="primary" plain @click="addCondition" class="add-condition-btn">
<Icon icon="ep:plus" /> <Icon icon="ep:plus" />
继续添加条件 继续添加条件
</el-button> </el-button>
<span class="add-condition-text"> <span class="add-condition-text"> 最多可添加 {{ maxConditions }} 个条件 </span>
最多可添加 {{ maxConditions }} 个条件
</span>
</div> </div>
</div> </div>
<!-- 验证结果 --> <!-- 验证结果 -->
<!-- TODO @puhui999是不是不用这种提示只要 validator rules 能展示出来就好了呀 -->
<div v-if="validationMessage" class="validation-result"> <div v-if="validationMessage" class="validation-result">
<el-alert <el-alert
:title="validationMessage" :title="validationMessage"
@ -98,10 +91,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import ConditionConfig from './ConditionConfig.vue' import ConditionConfig from './ConditionConfig.vue'
import { import {
ConditionGroupFormData, ConditionGroupFormData,
ConditionFormData, ConditionFormData,
IotRuleSceneTriggerTypeEnum IotRuleSceneTriggerTypeEnum
} from '@/api/iot/rule/scene/scene.types' } from '@/api/iot/rule/scene/scene.types'
/** 条件组配置组件 */ /** 条件组配置组件 */
@ -155,11 +148,11 @@ const addCondition = () => {
if (!group.value.conditions) { if (!group.value.conditions) {
group.value.conditions = [] group.value.conditions = []
} }
if (group.value.conditions.length >= maxConditions) { if (group.value.conditions.length >= maxConditions) {
return return
} }
const newCondition: ConditionFormData = { const newCondition: ConditionFormData = {
type: props.triggerType, type: props.triggerType,
productId: props.productId || 0, productId: props.productId || 0,
@ -168,7 +161,7 @@ const addCondition = () => {
operator: '=', operator: '=',
param: '' param: ''
} }
group.value.conditions.push(newCondition) group.value.conditions.push(newCondition)
} }
@ -176,10 +169,10 @@ const removeCondition = (index: number) => {
if (group.value.conditions) { if (group.value.conditions) {
group.value.conditions.splice(index, 1) group.value.conditions.splice(index, 1)
delete conditionValidations.value[index] delete conditionValidations.value[index]
// //
const newValidations: { [key: number]: { valid: boolean; message: string } } = {} const newValidations: { [key: number]: { valid: boolean; message: string } } = {}
Object.keys(conditionValidations.value).forEach(key => { Object.keys(conditionValidations.value).forEach((key) => {
const numKey = parseInt(key) const numKey = parseInt(key)
if (numKey > index) { if (numKey > index) {
newValidations[numKey - 1] = conditionValidations.value[numKey] newValidations[numKey - 1] = conditionValidations.value[numKey]
@ -188,7 +181,7 @@ const removeCondition = (index: number) => {
} }
}) })
conditionValidations.value = newValidations conditionValidations.value = newValidations
updateValidationResult() updateValidationResult()
} }
} }
@ -205,28 +198,29 @@ const updateValidationResult = () => {
emit('validate', { valid: false, message: validationMessage.value }) emit('validate', { valid: false, message: validationMessage.value })
return return
} }
const validations = Object.values(conditionValidations.value) const validations = Object.values(conditionValidations.value)
const allValid = validations.every(v => v.valid) const allValid = validations.every((v) => v.valid)
if (allValid) { if (allValid) {
isValid.value = true isValid.value = true
validationMessage.value = '条件组配置验证通过' validationMessage.value = '条件组配置验证通过'
} else { } else {
isValid.value = false isValid.value = false
const errorMessages = validations const errorMessages = validations.filter((v) => !v.valid).map((v) => v.message)
.filter(v => !v.valid)
.map(v => v.message)
validationMessage.value = `条件配置错误: ${errorMessages.join('; ')}` validationMessage.value = `条件配置错误: ${errorMessages.join('; ')}`
} }
emit('validate', { valid: isValid.value, message: validationMessage.value }) emit('validate', { valid: isValid.value, message: validationMessage.value })
} }
// //
watch(() => group.value.conditions?.length, () => { watch(
updateValidationResult() () => group.value.conditions?.length,
}) () => {
updateValidationResult()
}
)
// //
onMounted(() => { onMounted(() => {

View File

@ -1,4 +1,5 @@
<!-- 设备控制配置组件 --> <!-- 设备控制配置组件 -->
<!-- TODO @puhui999貌似没生效~~~ -->
<template> <template>
<div class="device-control-config"> <div class="device-control-config">
<!-- 产品和设备选择 --> <!-- 产品和设备选择 -->
@ -19,15 +20,10 @@
@input="handleParamsChange" @input="handleParamsChange"
/> />
</el-form-item> </el-form-item>
<!-- 参数示例 --> <!-- 参数示例 -->
<div class="params-example"> <div class="params-example">
<el-alert <el-alert title="参数格式示例" type="info" :closable="false" show-icon>
title="参数格式示例"
type="info"
:closable="false"
show-icon
>
<template #default> <template #default>
<div class="example-content"> <div class="example-content">
<p>属性设置示例</p> <p>属性设置示例</p>
@ -109,14 +105,14 @@ const updateValidationResult = () => {
emit('validate', { valid: false, message: validationMessage.value }) emit('validate', { valid: false, message: validationMessage.value })
return return
} }
if (!action.value.params || Object.keys(action.value.params).length === 0) { if (!action.value.params || Object.keys(action.value.params).length === 0) {
isValid.value = false isValid.value = false
validationMessage.value = '请配置控制参数' validationMessage.value = '请配置控制参数'
emit('validate', { valid: false, message: validationMessage.value }) emit('validate', { valid: false, message: validationMessage.value })
return return
} }
// //
isValid.value = true isValid.value = true
validationMessage.value = '设备控制配置验证通过' validationMessage.value = '设备控制配置验证通过'
@ -132,11 +128,15 @@ onMounted(() => {
}) })
// //
watch(() => action.value.params, (newParams) => { watch(
if (newParams && typeof newParams === 'object') { () => action.value.params,
paramsJson.value = JSON.stringify(newParams, null, 2) (newParams) => {
} if (newParams && typeof newParams === 'object') {
}, { deep: true }) paramsJson.value = JSON.stringify(newParams, null, 2)
}
},
{ deep: true }
)
</script> </script>
<style scoped> <style scoped>

View File

@ -8,17 +8,10 @@
@change="handleDeviceChange" @change="handleDeviceChange"
/> />
<!-- TODO @puhui999这里有点冗余建议去掉 -->
<!-- 设备状态变更提示 --> <!-- 设备状态变更提示 -->
<div <div v-if="trigger.type === TriggerTypeEnum.DEVICE_STATE_UPDATE" class="state-update-notice">
v-if="trigger.type === TriggerTypeEnum.DEVICE_STATE_UPDATE" <el-alert title="设备状态变更触发" type="info" :closable="false" show-icon>
class="state-update-notice"
>
<el-alert
title="设备状态变更触发"
type="info"
:closable="false"
show-icon
>
<template #default> <template #default>
<p>当选中的设备上线或离线时将自动触发场景规则</p> <p>当选中的设备上线或离线时将自动触发场景规则</p>
<p class="notice-tip">无需配置额外的触发条件</p> <p class="notice-tip">无需配置额外的触发条件</p>
@ -27,13 +20,11 @@
</div> </div>
<!-- 条件组配置 --> <!-- 条件组配置 -->
<div <div v-else-if="needsConditions" class="condition-groups">
v-else-if="needsConditions"
class="condition-groups"
>
<div class="condition-groups-header"> <div class="condition-groups-header">
<div class="header-left"> <div class="header-left">
<span class="header-title">触发条件</span> <span class="header-title">触发条件</span>
<!-- TODO @puhui999去掉数量限制 -->
<el-tag size="small" type="info"> <el-tag size="small" type="info">
{{ trigger.conditionGroups?.length || 0 }}/{{ maxConditionGroups }} {{ trigger.conditionGroups?.length || 0 }}/{{ maxConditionGroups }}
</el-tag> </el-tag>
@ -52,7 +43,10 @@
</div> </div>
<!-- 条件组列表 --> <!-- 条件组列表 -->
<div v-if="trigger.conditionGroups && trigger.conditionGroups.length > 0" class="condition-groups-list"> <div
v-if="trigger.conditionGroups && trigger.conditionGroups.length > 0"
class="condition-groups-list"
>
<div <div
v-for="(group, groupIndex) in trigger.conditionGroups" v-for="(group, groupIndex) in trigger.conditionGroups"
:key="`group-${groupIndex}`" :key="`group-${groupIndex}`"
@ -61,10 +55,11 @@
<div class="group-header"> <div class="group-header">
<div class="group-title"> <div class="group-title">
<span>条件组 {{ groupIndex + 1 }}</span> <span>条件组 {{ groupIndex + 1 }}</span>
<!-- TODO @puhui999不用条件组之间就是或条件之间就是且 -->
<el-select <el-select
v-model="group.logicOperator" v-model="group.logicOperator"
size="small" size="small"
style="width: 80px; margin-left: 12px;" style="width: 80px; margin-left: 12px"
> >
<el-option label="且" value="AND" /> <el-option label="且" value="AND" />
<el-option label="或" value="OR" /> <el-option label="或" value="OR" />
@ -120,10 +115,10 @@
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import ProductDeviceSelector from '../selectors/ProductDeviceSelector.vue' import ProductDeviceSelector from '../selectors/ProductDeviceSelector.vue'
import ConditionGroupConfig from './ConditionGroupConfig.vue' import ConditionGroupConfig from './ConditionGroupConfig.vue'
import { import {
TriggerFormData, TriggerFormData,
ConditionGroupFormData, ConditionGroupFormData,
IotRuleSceneTriggerTypeEnum as TriggerTypeEnum IotRuleSceneTriggerTypeEnum as TriggerTypeEnum
} from '@/api/iot/rule/scene/scene.types' } from '@/api/iot/rule/scene/scene.types'
/** 设备触发配置组件 */ /** 设备触发配置组件 */
@ -173,16 +168,16 @@ const addConditionGroup = () => {
if (!trigger.value.conditionGroups) { if (!trigger.value.conditionGroups) {
trigger.value.conditionGroups = [] trigger.value.conditionGroups = []
} }
if (trigger.value.conditionGroups.length >= maxConditionGroups) { if (trigger.value.conditionGroups.length >= maxConditionGroups) {
return return
} }
const newGroup: ConditionGroupFormData = { const newGroup: ConditionGroupFormData = {
conditions: [], conditions: [],
logicOperator: 'AND' logicOperator: 'AND'
} }
trigger.value.conditionGroups.push(newGroup) trigger.value.conditionGroups.push(newGroup)
} }
@ -190,10 +185,10 @@ const removeConditionGroup = (index: number) => {
if (trigger.value.conditionGroups) { if (trigger.value.conditionGroups) {
trigger.value.conditionGroups.splice(index, 1) trigger.value.conditionGroups.splice(index, 1)
delete groupValidations.value[index] delete groupValidations.value[index]
// //
const newValidations: { [key: number]: { valid: boolean; message: string } } = {} const newValidations: { [key: number]: { valid: boolean; message: string } } = {}
Object.keys(groupValidations.value).forEach(key => { Object.keys(groupValidations.value).forEach((key) => {
const numKey = parseInt(key) const numKey = parseInt(key)
if (numKey > index) { if (numKey > index) {
newValidations[numKey - 1] = groupValidations.value[numKey] newValidations[numKey - 1] = groupValidations.value[numKey]
@ -202,7 +197,7 @@ const removeConditionGroup = (index: number) => {
} }
}) })
groupValidations.value = newValidations groupValidations.value = newValidations
updateValidationResult() updateValidationResult()
} }
} }
@ -220,7 +215,7 @@ const updateValidationResult = () => {
emit('validate', { valid: false, message: validationMessage.value }) emit('validate', { valid: false, message: validationMessage.value })
return return
} }
// //
if (trigger.value.type === TriggerTypeEnum.DEVICE_STATE_UPDATE) { if (trigger.value.type === TriggerTypeEnum.DEVICE_STATE_UPDATE) {
isValid.value = true isValid.value = true
@ -228,7 +223,7 @@ const updateValidationResult = () => {
emit('validate', { valid: true, message: validationMessage.value }) emit('validate', { valid: true, message: validationMessage.value })
return return
} }
// //
if (!trigger.value.conditionGroups || trigger.value.conditionGroups.length === 0) { if (!trigger.value.conditionGroups || trigger.value.conditionGroups.length === 0) {
isValid.value = false isValid.value = false
@ -236,33 +231,38 @@ const updateValidationResult = () => {
emit('validate', { valid: false, message: validationMessage.value }) emit('validate', { valid: false, message: validationMessage.value })
return return
} }
const validations = Object.values(groupValidations.value) const validations = Object.values(groupValidations.value)
const allValid = validations.every(v => v.valid) const allValid = validations.every((v) => v.valid)
if (allValid) { if (allValid) {
isValid.value = true isValid.value = true
validationMessage.value = '设备触发配置验证通过' validationMessage.value = '设备触发配置验证通过'
} else { } else {
isValid.value = false isValid.value = false
const errorMessages = validations const errorMessages = validations.filter((v) => !v.valid).map((v) => v.message)
.filter(v => !v.valid)
.map(v => v.message)
validationMessage.value = `条件组配置错误: ${errorMessages.join('; ')}` validationMessage.value = `条件组配置错误: ${errorMessages.join('; ')}`
} }
emit('validate', { valid: isValid.value, message: validationMessage.value }) emit('validate', { valid: isValid.value, message: validationMessage.value })
} }
// //
watch(() => trigger.value.type, () => { watch(
updateValidationResult() () => trigger.value.type,
}) () => {
updateValidationResult()
}
)
// //
watch(() => [trigger.value.productId, trigger.value.deviceId], () => { watch(
updateValidationResult() () => [trigger.value.productId, trigger.value.deviceId],
}) () => {
updateValidationResult()
}
)
// TODO @puhui999unocss
</script> </script>
<style scoped> <style scoped>

View File

@ -7,11 +7,7 @@
<span class="header-title">定时触发配置</span> <span class="header-title">定时触发配置</span>
</div> </div>
<div class="header-right"> <div class="header-right">
<el-button <el-button type="text" size="small" @click="showBuilder = !showBuilder">
type="text"
size="small"
@click="showBuilder = !showBuilder"
>
<Icon :icon="showBuilder ? 'ep:edit' : 'ep:setting'" /> <Icon :icon="showBuilder ? 'ep:edit' : 'ep:setting'" />
{{ showBuilder ? '手动编辑' : '可视化编辑' }} {{ showBuilder ? '手动编辑' : '可视化编辑' }}
</el-button> </el-button>
@ -19,6 +15,7 @@
</div> </div>
<!-- 可视化编辑器 --> <!-- 可视化编辑器 -->
<!-- TODO @puhui999是不是复用现有的 cron 组件不然有点重复哈维护比较复杂 -->
<div v-if="showBuilder" class="visual-builder"> <div v-if="showBuilder" class="visual-builder">
<CronBuilder v-model="localValue" @validate="handleValidate" /> <CronBuilder v-model="localValue" @validate="handleValidate" />
</div> </div>

View File

@ -1,10 +1,11 @@
<!-- CRON 可视化构建器组件 --> <!-- CRON 可视化构建器组件 -->
<!-- TODO @puhui999看看能不能复用全局的 cron 组件 -->
<template> <template>
<div class="cron-builder"> <div class="cron-builder">
<div class="builder-header"> <div class="builder-header">
<span class="header-title">可视化 CRON 编辑器</span> <span class="header-title">可视化 CRON 编辑器</span>
</div> </div>
<div class="builder-content"> <div class="builder-content">
<!-- 快捷选项 --> <!-- 快捷选项 -->
<div class="quick-options"> <div class="quick-options">
@ -36,9 +37,9 @@
<el-option label="每分钟" value="*" /> <el-option label="每分钟" value="*" />
<el-option <el-option
v-for="i in 60" v-for="i in 60"
:key="i-1" :key="i - 1"
:label="`${i-1}分`" :label="`${i - 1}分`"
:value="String(i-1)" :value="String(i - 1)"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -49,9 +50,9 @@
<el-option label="每小时" value="*" /> <el-option label="每小时" value="*" />
<el-option <el-option
v-for="i in 24" v-for="i in 24"
:key="i-1" :key="i - 1"
:label="`${i-1}时`" :label="`${i - 1}时`"
:value="String(i-1)" :value="String(i - 1)"
/> />
</el-select> </el-select>
</el-form-item> </el-form-item>
@ -60,12 +61,7 @@
<el-form-item label="日"> <el-form-item label="日">
<el-select v-model="cronParts.day" @change="updateCronExpression"> <el-select v-model="cronParts.day" @change="updateCronExpression">
<el-option label="每日" value="*" /> <el-option label="每日" value="*" />
<el-option <el-option v-for="i in 31" :key="i" :label="`${i}日`" :value="String(i)" />
v-for="i in 31"
:key="i"
:label="`${i}日`"
:value="String(i)"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -132,7 +128,20 @@ const cronParts = reactive({
}) })
// //
const months = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'] const months = [
'1月',
'2月',
'3月',
'4月',
'5月',
'6月',
'7月',
'8月',
'9月',
'10月',
'11月',
'12月'
]
const weeks = ['周日', '周一', '周二', '周三', '周四', '周五', '周六'] const weeks = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
// //
@ -159,7 +168,7 @@ const applyQuickOption = (option: any) => {
const parseCronExpression = () => { const parseCronExpression = () => {
if (!localValue.value) return if (!localValue.value) return
const parts = localValue.value.split(' ') const parts = localValue.value.split(' ')
if (parts.length >= 6) { if (parts.length >= 6) {
cronParts.second = parts[0] || '0' cronParts.second = parts[0] || '0'

View File

@ -1,4 +1,5 @@
<!-- CRON 表达式输入组件 --> <!-- CRON 表达式输入组件 -->
<!-- TODO @puhui999看看能不能复用全局的 cron 组件 -->
<template> <template>
<div class="cron-input"> <div class="cron-input">
<el-input <el-input
@ -13,15 +14,10 @@
</el-tooltip> </el-tooltip>
</template> </template>
</el-input> </el-input>
<!-- 帮助信息 --> <!-- 帮助信息 -->
<div v-if="showHelp" class="cron-help"> <div v-if="showHelp" class="cron-help">
<el-alert <el-alert title="CRON 表达式格式:秒 分 时 日 月 周" type="info" :closable="false" show-icon>
title="CRON 表达式格式:秒 分 时 日 月 周"
type="info"
:closable="false"
show-icon
>
<template #default> <template #default>
<div class="help-content"> <div class="help-content">
<p><strong>示例</strong></p> <p><strong>示例</strong></p>
@ -83,7 +79,7 @@ const validateExpression = () => {
emit('validate', { valid: false, message: '请输入CRON表达式' }) emit('validate', { valid: false, message: '请输入CRON表达式' })
return return
} }
const isValid = validateCronExpression(localValue.value) const isValid = validateCronExpression(localValue.value)
if (isValid) { if (isValid) {
emit('validate', { valid: true, message: 'CRON表达式验证通过' }) emit('validate', { valid: true, message: 'CRON表达式验证通过' })
@ -93,9 +89,12 @@ const validateExpression = () => {
} }
// //
watch(() => localValue.value, () => { watch(
validateExpression() () => localValue.value,
}) () => {
validateExpression()
}
)
// //
onMounted(() => { onMounted(() => {

View File

@ -15,19 +15,10 @@
<!-- 描述模板 --> <!-- 描述模板 -->
<teleport to="body"> <teleport to="body">
<div <div v-if="showTemplates" ref="templateDropdownRef" class="templates" :style="dropdownStyle">
v-if="showTemplates"
ref="templateDropdownRef"
class="templates"
:style="dropdownStyle"
>
<div class="templates-header"> <div class="templates-header">
<span class="templates-title">描述模板</span> <span class="templates-title">描述模板</span>
<el-button <el-button type="text" size="small" @click="showTemplates = false">
type="text"
size="small"
@click="showTemplates = false"
>
<Icon icon="ep:close" /> <Icon icon="ep:close" />
</el-button> </el-button>
</div> </div>
@ -45,13 +36,10 @@
</div> </div>
</teleport> </teleport>
<!-- TODO @puhui999不用模版哈简单点 -->
<!-- 模板按钮 --> <!-- 模板按钮 -->
<div v-if="!localValue && !showTemplates" class="template-trigger"> <div v-if="!localValue && !showTemplates" class="template-trigger">
<el-button <el-button type="text" size="small" @click="toggleTemplates">
type="text"
size="small"
@click="toggleTemplates"
>
<Icon icon="ep:document" class="mr-1" /> <Icon icon="ep:document" class="mr-1" />
使用模板 使用模板
</el-button> </el-button>
@ -163,8 +151,12 @@ const toggleTemplates = () => {
// //
const handleClickOutside = (event: Event) => { const handleClickOutside = (event: Event) => {
if (templateDropdownRef.value && !templateDropdownRef.value.contains(event.target as Node) && if (
inputRef.value && !inputRef.value.$el.contains(event.target as Node)) { templateDropdownRef.value &&
!templateDropdownRef.value.contains(event.target as Node) &&
inputRef.value &&
!inputRef.value.$el.contains(event.target as Node)
) {
showTemplates.value = false showTemplates.value = false
} }
} }

View File

@ -14,8 +14,9 @@
<Icon icon="ep:edit" class="input-icon" /> <Icon icon="ep:edit" class="input-icon" />
</template> </template>
</el-input> </el-input>
<!-- 智能提示 --> <!-- 智能提示 -->
<!-- TODO @puhui999暂时不用考虑智能推荐哈用途不大 -->
<div v-if="showSuggestions && suggestions.length > 0" class="suggestions"> <div v-if="showSuggestions && suggestions.length > 0" class="suggestions">
<div class="suggestions-header"> <div class="suggestions-header">
<span class="suggestions-title">推荐名称</span> <span class="suggestions-title">推荐名称</span>
@ -72,9 +73,12 @@ const nameTemplates = [
const handleInput = (value: string) => { const handleInput = (value: string) => {
if (value.length > 0 && value.length < 10) { if (value.length > 0 && value.length < 10) {
// //
suggestions.value = nameTemplates.filter(template => suggestions.value = nameTemplates
template.includes(value) || value.includes('温度') && template.includes('温度') .filter(
).slice(0, 5) (template) =>
template.includes(value) || (value.includes('温度') && template.includes('温度'))
)
.slice(0, 5)
showSuggestions.value = suggestions.value.length > 0 showSuggestions.value = suggestions.value.length > 0
} else { } else {
showSuggestions.value = false showSuggestions.value = false

View File

@ -1,4 +1,5 @@
<!-- 值输入组件 --> <!-- 值输入组件 -->
<!-- TODO @yunai这个需要在看看 -->
<template> <template>
<div class="value-input"> <div class="value-input">
<!-- 布尔值选择 --> <!-- 布尔值选择 -->
@ -64,12 +65,7 @@
</el-input> </el-input>
<div v-if="listPreview.length > 0" class="list-preview"> <div v-if="listPreview.length > 0" class="list-preview">
<span class="preview-label">解析结果</span> <span class="preview-label">解析结果</span>
<el-tag <el-tag v-for="(item, index) in listPreview" :key="index" size="small" class="preview-tag">
v-for="(item, index) in listPreview"
:key="index"
size="small"
class="preview-tag"
>
{{ item }} {{ item }}
</el-tag> </el-tag>
</div> </div>
@ -110,7 +106,11 @@
class="w-full" class="w-full"
> >
<template #suffix> <template #suffix>
<el-tooltip v-if="propertyConfig?.unit" :content="`单位:${propertyConfig.unit}`" placement="top"> <el-tooltip
v-if="propertyConfig?.unit"
:content="`单位:${propertyConfig.unit}`"
placement="top"
>
<span class="input-unit">{{ propertyConfig.unit }}</span> <span class="input-unit">{{ propertyConfig.unit }}</span>
</el-tooltip> </el-tooltip>
</template> </template>
@ -172,7 +172,10 @@ const enumOptions = computed(() => {
const listPreview = computed(() => { const listPreview = computed(() => {
if (props.operator === 'in' && localValue.value) { if (props.operator === 'in' && localValue.value) {
return localValue.value.split(',').map(item => item.trim()).filter(item => item) return localValue.value
.split(',')
.map((item) => item.trim())
.filter((item) => item)
} }
return [] return []
}) })
@ -195,12 +198,12 @@ const getInputType = () => {
const getPlaceholder = () => { const getPlaceholder = () => {
const typeMap = { const typeMap = {
'string': '请输入字符串', string: '请输入字符串',
'int': '请输入整数', int: '请输入整数',
'float': '请输入浮点数', float: '请输入浮点数',
'double': '请输入双精度数', double: '请输入双精度数',
'struct': '请输入JSON格式数据', struct: '请输入JSON格式数据',
'array': '请输入数组格式数据' array: '请输入数组格式数据'
} }
return typeMap[props.propertyType || ''] || '请输入值' return typeMap[props.propertyType || ''] || '请输入值'
} }
@ -325,18 +328,24 @@ const validateValue = () => {
} }
// //
watch(() => localValue.value, () => { watch(
validateValue() () => localValue.value,
}) () => {
validateValue()
}
)
// //
watch(() => props.operator, () => { watch(
localValue.value = '' () => props.operator,
rangeStart.value = '' () => {
rangeEnd.value = '' localValue.value = ''
dateValue.value = '' rangeStart.value = ''
numberValue.value = undefined rangeEnd.value = ''
}) dateValue.value = ''
numberValue.value = undefined
}
)
// //
onMounted(() => { onMounted(() => {

View File

@ -1,4 +1,5 @@
<!-- 配置预览组件 --> <!-- 配置预览组件 -->
<!-- TODO @puhui999应该暂时不用预览哈 -->
<template> <template>
<div class="config-preview"> <div class="config-preview">
<div class="preview-items"> <div class="preview-items">

View File

@ -1,4 +1,5 @@
<!-- 执行器配置组件 --> <!-- 执行器配置组件 -->
<!-- todo @puhui999参考触发器配置简化下 -->
<template> <template>
<el-card class="action-section" shadow="never"> <el-card class="action-section" shadow="never">
<template #header> <template #header>
@ -35,19 +36,12 @@
<!-- 执行器列表 --> <!-- 执行器列表 -->
<div v-else class="actions-list"> <div v-else class="actions-list">
<div <div v-for="(action, index) in actions" :key="`action-${index}`" class="action-item">
v-for="(action, index) in actions"
:key="`action-${index}`"
class="action-item"
>
<div class="action-header"> <div class="action-header">
<div class="action-title"> <div class="action-title">
<Icon icon="ep:setting" class="action-icon" /> <Icon icon="ep:setting" class="action-icon" />
<span>执行器 {{ index + 1 }}</span> <span>执行器 {{ index + 1 }}</span>
<el-tag <el-tag :type="getActionTypeTag(action.type)" size="small">
:type="getActionTypeTag(action.type)"
size="small"
>
{{ getActionTypeName(action.type) }} {{ getActionTypeName(action.type) }}
</el-tag> </el-tag>
</div> </div>
@ -94,18 +88,11 @@
<!-- 添加提示 --> <!-- 添加提示 -->
<div v-if="actions.length > 0 && actions.length < maxActions" class="add-more"> <div v-if="actions.length > 0 && actions.length < maxActions" class="add-more">
<el-button <el-button type="primary" plain @click="addAction" class="add-more-btn">
type="primary"
plain
@click="addAction"
class="add-more-btn"
>
<Icon icon="ep:plus" /> <Icon icon="ep:plus" />
继续添加执行器 继续添加执行器
</el-button> </el-button>
<span class="add-more-text"> <span class="add-more-text"> 最多可添加 {{ maxActions }} 个执行器 </span>
最多可添加 {{ maxActions }} 个执行器
</span>
</div> </div>
<!-- 验证结果 --> <!-- 验证结果 -->
@ -126,9 +113,9 @@ import { useVModel } from '@vueuse/core'
import ActionTypeSelector from '../selectors/ActionTypeSelector.vue' import ActionTypeSelector from '../selectors/ActionTypeSelector.vue'
import DeviceControlConfig from '../configs/DeviceControlConfig.vue' import DeviceControlConfig from '../configs/DeviceControlConfig.vue'
import AlertConfig from '../configs/AlertConfig.vue' import AlertConfig from '../configs/AlertConfig.vue'
import { import {
ActionFormData, ActionFormData,
IotRuleSceneActionTypeEnum as ActionTypeEnum IotRuleSceneActionTypeEnum as ActionTypeEnum
} from '@/api/iot/rule/scene/scene.types' } from '@/api/iot/rule/scene/scene.types'
import { createDefaultActionData } from '../../utils/transform' import { createDefaultActionData } from '../../utils/transform'
@ -174,17 +161,11 @@ const actionTypeTags = {
// //
const isDeviceAction = (type: number) => { const isDeviceAction = (type: number) => {
return [ return [ActionTypeEnum.DEVICE_PROPERTY_SET, ActionTypeEnum.DEVICE_SERVICE_INVOKE].includes(type)
ActionTypeEnum.DEVICE_PROPERTY_SET,
ActionTypeEnum.DEVICE_SERVICE_INVOKE
].includes(type)
} }
const isAlertAction = (type: number) => { const isAlertAction = (type: number) => {
return [ return [ActionTypeEnum.ALERT_TRIGGER, ActionTypeEnum.ALERT_RECOVER].includes(type)
ActionTypeEnum.ALERT_TRIGGER,
ActionTypeEnum.ALERT_RECOVER
].includes(type)
} }
const getActionTypeName = (type: number) => { const getActionTypeName = (type: number) => {
@ -200,7 +181,7 @@ const addAction = () => {
if (actions.value.length >= maxActions) { if (actions.value.length >= maxActions) {
return return
} }
const newAction = createDefaultActionData() const newAction = createDefaultActionData()
actions.value.push(newAction) actions.value.push(newAction)
} }
@ -208,10 +189,10 @@ const addAction = () => {
const removeAction = (index: number) => { const removeAction = (index: number) => {
actions.value.splice(index, 1) actions.value.splice(index, 1)
delete actionValidations.value[index] delete actionValidations.value[index]
// //
const newValidations: { [key: number]: { valid: boolean; message: string } } = {} const newValidations: { [key: number]: { valid: boolean; message: string } } = {}
Object.keys(actionValidations.value).forEach(key => { Object.keys(actionValidations.value).forEach((key) => {
const numKey = parseInt(key) const numKey = parseInt(key)
if (numKey > index) { if (numKey > index) {
newValidations[numKey - 1] = actionValidations.value[numKey] newValidations[numKey - 1] = actionValidations.value[numKey]
@ -220,7 +201,7 @@ const removeAction = (index: number) => {
} }
}) })
actionValidations.value = newValidations actionValidations.value = newValidations
updateValidationResult() updateValidationResult()
} }
@ -258,9 +239,9 @@ const handleActionValidate = (index: number, result: { valid: boolean; message:
const updateValidationResult = () => { const updateValidationResult = () => {
const validations = Object.values(actionValidations.value) const validations = Object.values(actionValidations.value)
const allValid = validations.every(v => v.valid) const allValid = validations.every((v) => v.valid)
const hasValidations = validations.length > 0 const hasValidations = validations.length > 0
if (!hasValidations) { if (!hasValidations) {
isValid.value = true isValid.value = true
validationMessage.value = '' validationMessage.value = ''
@ -269,19 +250,20 @@ const updateValidationResult = () => {
validationMessage.value = '所有执行器配置验证通过' validationMessage.value = '所有执行器配置验证通过'
} else { } else {
isValid.value = false isValid.value = false
const errorMessages = validations const errorMessages = validations.filter((v) => !v.valid).map((v) => v.message)
.filter(v => !v.valid)
.map(v => v.message)
validationMessage.value = `执行器配置错误: ${errorMessages.join('; ')}` validationMessage.value = `执行器配置错误: ${errorMessages.join('; ')}`
} }
emit('validate', { valid: isValid.value, message: validationMessage.value }) emit('validate', { valid: isValid.value, message: validationMessage.value })
} }
// //
watch(() => actions.value.length, () => { watch(
updateValidationResult() () => actions.value.length,
}) () => {
updateValidationResult()
}
)
</script> </script>
<style scoped> <style scoped>

View File

@ -17,11 +17,13 @@
<div class="section-content"> <div class="section-content">
<el-row :gutter="24"> <el-row :gutter="24">
<!-- TODO @puhui999NameInputStatusRadioDescriptionInput 是不是直接写在当前界面哈有点散 -->
<el-col :span="12"> <el-col :span="12">
<el-form-item label="场景名称" prop="name" required> <el-form-item label="场景名称" prop="name" required>
<NameInput v-model="formData.name" /> <NameInput v-model="formData.name" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<!-- TODO @puhui999每个一行会好点 -->
<el-col :span="12"> <el-col :span="12">
<el-form-item label="场景状态" prop="status" required> <el-form-item label="场景状态" prop="status" required>
<StatusRadio v-model="formData.status" /> <StatusRadio v-model="formData.status" />
@ -59,6 +61,7 @@ const props = defineProps<Props>()
const emit = defineEmits<Emits>() const emit = defineEmits<Emits>()
const formData = useVModel(props, 'modelValue', emit) const formData = useVModel(props, 'modelValue', emit)
// TODO @puhui999 unocss
</script> </script>
<style scoped> <style scoped>

View File

@ -1,4 +1,5 @@
<!-- 预览区域组件 --> <!-- 预览区域组件 -->
<!-- TODO @puhui999是不是不用这个哈 -->
<template> <template>
<el-card class="preview-section" shadow="never"> <el-card class="preview-section" shadow="never">
<template #header> <template #header>
@ -8,12 +9,7 @@
<span class="section-title">配置预览</span> <span class="section-title">配置预览</span>
</div> </div>
<div class="header-right"> <div class="header-right">
<el-button <el-button type="primary" size="small" @click="handleValidate" :loading="validating">
type="primary"
size="small"
@click="handleValidate"
:loading="validating"
>
<Icon icon="ep:check" /> <Icon icon="ep:check" />
验证配置 验证配置
</el-button> </el-button>
@ -101,7 +97,7 @@ const handleValidate = async () => {
validating.value = true validating.value = true
try { try {
// //
await new Promise(resolve => setTimeout(resolve, 500)) await new Promise((resolve) => setTimeout(resolve, 500))
emit('validate') emit('validate')
} finally { } finally {
validating.value = false validating.value = false

View File

@ -6,6 +6,7 @@
<div class="header-left"> <div class="header-left">
<Icon icon="ep:lightning" class="section-icon" /> <Icon icon="ep:lightning" class="section-icon" />
<span class="section-title">触发器配置</span> <span class="section-title">触发器配置</span>
<!-- TODO @puhui999是不是去掉 maxTriggers计数 -->
<el-tag size="small" type="info">{{ triggers.length }}/{{ maxTriggers }}</el-tag> <el-tag size="small" type="info">{{ triggers.length }}/{{ maxTriggers }}</el-tag>
</div> </div>
<div class="header-right"> <div class="header-right">
@ -26,6 +27,7 @@
<!-- 空状态 --> <!-- 空状态 -->
<div v-if="triggers.length === 0" class="empty-state"> <div v-if="triggers.length === 0" class="empty-state">
<el-empty description="暂无触发器配置"> <el-empty description="暂无触发器配置">
<!-- TODO @puhui999这个要不要去掉哈入口统一点 -->
<el-button type="primary" @click="addTrigger"> <el-button type="primary" @click="addTrigger">
<Icon icon="ep:plus" /> <Icon icon="ep:plus" />
添加第一个触发器 添加第一个触发器
@ -35,19 +37,12 @@
<!-- 触发器列表 --> <!-- 触发器列表 -->
<div v-else class="triggers-list"> <div v-else class="triggers-list">
<div <div v-for="(trigger, index) in triggers" :key="`trigger-${index}`" class="trigger-item">
v-for="(trigger, index) in triggers"
:key="`trigger-${index}`"
class="trigger-item"
>
<div class="trigger-header"> <div class="trigger-header">
<div class="trigger-title"> <div class="trigger-title">
<Icon icon="ep:lightning" class="trigger-icon" /> <Icon icon="ep:lightning" class="trigger-icon" />
<span>触发器 {{ index + 1 }}</span> <span>触发器 {{ index + 1 }}</span>
<el-tag <el-tag :type="getTriggerTypeTag(trigger.type)" size="small">
:type="getTriggerTypeTag(trigger.type)"
size="small"
>
{{ getTriggerTypeName(trigger.type) }} {{ getTriggerTypeName(trigger.type) }}
</el-tag> </el-tag>
</div> </div>
@ -93,19 +88,13 @@
</div> </div>
<!-- 添加提示 --> <!-- 添加提示 -->
<!-- TODO @puhui999这个要不要去掉哈入口统一点 -->
<div v-if="triggers.length > 0 && triggers.length < maxTriggers" class="add-more"> <div v-if="triggers.length > 0 && triggers.length < maxTriggers" class="add-more">
<el-button <el-button type="primary" plain @click="addTrigger" class="add-more-btn">
type="primary"
plain
@click="addTrigger"
class="add-more-btn"
>
<Icon icon="ep:plus" /> <Icon icon="ep:plus" />
继续添加触发器 继续添加触发器
</el-button> </el-button>
<span class="add-more-text"> <span class="add-more-text"> 最多可添加 {{ maxTriggers }} 个触发器 </span>
最多可添加 {{ maxTriggers }} 个触发器
</span>
</div> </div>
<!-- 验证结果 --> <!-- 验证结果 -->
@ -126,9 +115,9 @@ import { useVModel } from '@vueuse/core'
import TriggerTypeSelector from '../selectors/TriggerTypeSelector.vue' import TriggerTypeSelector from '../selectors/TriggerTypeSelector.vue'
import DeviceTriggerConfig from '../configs/DeviceTriggerConfig.vue' import DeviceTriggerConfig from '../configs/DeviceTriggerConfig.vue'
import TimerTriggerConfig from '../configs/TimerTriggerConfig.vue' import TimerTriggerConfig from '../configs/TimerTriggerConfig.vue'
import { import {
TriggerFormData, TriggerFormData,
IotRuleSceneTriggerTypeEnum as TriggerTypeEnum IotRuleSceneTriggerTypeEnum as TriggerTypeEnum
} from '@/api/iot/rule/scene/scene.types' } from '@/api/iot/rule/scene/scene.types'
import { createDefaultTriggerData } from '../../utils/transform' import { createDefaultTriggerData } from '../../utils/transform'
@ -197,7 +186,7 @@ const addTrigger = () => {
if (triggers.value.length >= maxTriggers) { if (triggers.value.length >= maxTriggers) {
return return
} }
const newTrigger = createDefaultTriggerData() const newTrigger = createDefaultTriggerData()
triggers.value.push(newTrigger) triggers.value.push(newTrigger)
} }
@ -205,10 +194,10 @@ const addTrigger = () => {
const removeTrigger = (index: number) => { const removeTrigger = (index: number) => {
triggers.value.splice(index, 1) triggers.value.splice(index, 1)
delete triggerValidations.value[index] delete triggerValidations.value[index]
// //
const newValidations: { [key: number]: { valid: boolean; message: string } } = {} const newValidations: { [key: number]: { valid: boolean; message: string } } = {}
Object.keys(triggerValidations.value).forEach(key => { Object.keys(triggerValidations.value).forEach((key) => {
const numKey = parseInt(key) const numKey = parseInt(key)
if (numKey > index) { if (numKey > index) {
newValidations[numKey - 1] = triggerValidations.value[numKey] newValidations[numKey - 1] = triggerValidations.value[numKey]
@ -217,7 +206,7 @@ const removeTrigger = (index: number) => {
} }
}) })
triggerValidations.value = newValidations triggerValidations.value = newValidations
updateValidationResult() updateValidationResult()
} }
@ -263,9 +252,9 @@ const handleTriggerValidate = (index: number, result: { valid: boolean; message:
const updateValidationResult = () => { const updateValidationResult = () => {
const validations = Object.values(triggerValidations.value) const validations = Object.values(triggerValidations.value)
const allValid = validations.every(v => v.valid) const allValid = validations.every((v) => v.valid)
const hasValidations = validations.length > 0 const hasValidations = validations.length > 0
if (!hasValidations) { if (!hasValidations) {
isValid.value = true isValid.value = true
validationMessage.value = '' validationMessage.value = ''
@ -274,19 +263,20 @@ const updateValidationResult = () => {
validationMessage.value = '所有触发器配置验证通过' validationMessage.value = '所有触发器配置验证通过'
} else { } else {
isValid.value = false isValid.value = false
const errorMessages = validations const errorMessages = validations.filter((v) => !v.valid).map((v) => v.message)
.filter(v => !v.valid)
.map(v => v.message)
validationMessage.value = `触发器配置错误: ${errorMessages.join('; ')}` validationMessage.value = `触发器配置错误: ${errorMessages.join('; ')}`
} }
emit('validate', { valid: isValid.value, message: validationMessage.value }) emit('validate', { valid: isValid.value, message: validationMessage.value })
} }
// //
watch(() => triggers.value.length, () => { watch(
updateValidationResult() () => triggers.value.length,
}) () => {
updateValidationResult()
}
)
</script> </script>
<style scoped> <style scoped>

View File

@ -1,6 +1,7 @@
<!-- 执行器类型选择组件 --> <!-- 执行器类型选择组件 -->
<template> <template>
<div class="action-type-selector"> <div class="action-type-selector">
<!-- TODO @puhui9991设备属性设置时貌似没选属性2服务调用时貌似也没的设置哈 -->
<el-form-item label="执行类型" required> <el-form-item label="执行类型" required>
<el-select <el-select
v-model="localValue" v-model="localValue"

View File

@ -24,6 +24,7 @@
</el-select> </el-select>
<!-- 操作符说明 --> <!-- 操作符说明 -->
<!-- TODO @puhui999这个去掉 -->
<div v-if="selectedOperator" class="operator-description"> <div v-if="selectedOperator" class="operator-description">
<div class="desc-content"> <div class="desc-content">
<Icon icon="ep:info-filled" class="desc-icon" /> <Icon icon="ep:info-filled" class="desc-icon" />
@ -155,14 +156,12 @@ const availableOperators = computed(() => {
if (!props.propertyType) { if (!props.propertyType) {
return allOperators return allOperators
} }
return allOperators.filter(op => return allOperators.filter((op) => op.supportedTypes.includes(props.propertyType!))
op.supportedTypes.includes(props.propertyType!)
)
}) })
const selectedOperator = computed(() => { const selectedOperator = computed(() => {
return allOperators.find(op => op.value === localValue.value) return allOperators.find((op) => op.value === localValue.value)
}) })
// //
@ -171,14 +170,17 @@ const handleChange = (value: string) => {
} }
// //
watch(() => props.propertyType, () => { watch(
// () => props.propertyType,
if (localValue.value && selectedOperator.value) { () => {
if (!selectedOperator.value.supportedTypes.includes(props.propertyType || '')) { //
localValue.value = '' if (localValue.value && selectedOperator.value) {
if (!selectedOperator.value.supportedTypes.includes(props.propertyType || '')) {
localValue.value = ''
}
} }
} }
}) )
</script> </script>
<style scoped> <style scoped>

View File

@ -25,6 +25,7 @@
<div class="option-name">{{ product.name }}</div> <div class="option-name">{{ product.name }}</div>
<div class="option-key">{{ product.productKey }}</div> <div class="option-key">{{ product.productKey }}</div>
</div> </div>
<!-- TODO @puhui999是不是用字典 -->
<el-tag size="small" :type="product.status === 0 ? 'success' : 'danger'"> <el-tag size="small" :type="product.status === 0 ? 'success' : 'danger'">
{{ product.status === 0 ? '正常' : '禁用' }} {{ product.status === 0 ? '正常' : '禁用' }}
</el-tag> </el-tag>
@ -38,8 +39,8 @@
<el-col :span="12"> <el-col :span="12">
<el-form-item label="设备选择模式" required> <el-form-item label="设备选择模式" required>
<el-radio-group v-model="deviceSelectionMode" @change="handleDeviceSelectionModeChange"> <el-radio-group v-model="deviceSelectionMode" @change="handleDeviceSelectionModeChange">
<el-radio value="specific">选择设备</el-radio>
<el-radio value="all">全部设备</el-radio> <el-radio value="all">全部设备</el-radio>
<el-radio value="specific">选择设备</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
</el-col> </el-col>
@ -48,7 +49,9 @@
<!-- 具体设备选择 --> <!-- 具体设备选择 -->
<el-row v-if="deviceSelectionMode === 'specific'" :gutter="16"> <el-row v-if="deviceSelectionMode === 'specific'" :gutter="16">
<el-col :span="24"> <el-col :span="24">
<!-- TODO @puhui999貌似产品选择不上 -->
<el-form-item label="选择设备" required> <el-form-item label="选择设备" required>
<!-- TODO @puhui999请先选择产品是不是改成请选择设备然后上面localProductId 为空未选择的时候禁用 deviceSelectionMode -->
<el-select <el-select
v-model="localDeviceId" v-model="localDeviceId"
placeholder="请先选择产品" placeholder="请先选择产品"
@ -70,10 +73,7 @@
<div class="option-name">{{ device.deviceName }}</div> <div class="option-name">{{ device.deviceName }}</div>
<div class="option-nickname">{{ device.nickname || '无备注' }}</div> <div class="option-nickname">{{ device.nickname || '无备注' }}</div>
</div> </div>
<el-tag <el-tag size="small" :type="getDeviceStatusTag(device.state)">
size="small"
:type="getDeviceStatusTag(device.state)"
>
{{ getDeviceStatusText(device.state) }} {{ getDeviceStatusText(device.state) }}
</el-tag> </el-tag>
</div> </div>
@ -84,7 +84,7 @@
</el-row> </el-row>
<!-- 选择结果展示 --> <!-- 选择结果展示 -->
<div v-if="localProductId && (localDeviceId !== undefined)" class="selection-result"> <div v-if="localProductId && localDeviceId !== undefined" class="selection-result">
<div class="result-header"> <div class="result-header">
<Icon icon="ep:check" class="result-icon" /> <Icon icon="ep:check" class="result-icon" />
<span class="result-title">已选择设备</span> <span class="result-title">已选择设备</span>
@ -99,18 +99,8 @@
<span class="result-label">设备</span> <span class="result-label">设备</span>
<span v-if="deviceSelectionMode === 'all'" class="result-value"></span> <span v-if="deviceSelectionMode === 'all'" class="result-value"></span>
<span v-else class="result-value">{{ selectedDevice?.deviceName }}</span> <span v-else class="result-value">{{ selectedDevice?.deviceName }}</span>
<el-tag <el-tag v-if="deviceSelectionMode === 'all'" size="small" type="warning"> </el-tag>
v-if="deviceSelectionMode === 'all'" <el-tag v-else size="small" :type="getDeviceStatusTag(selectedDevice?.state)">
size="small"
type="warning"
>
全部
</el-tag>
<el-tag
v-else
size="small"
:type="getDeviceStatusTag(selectedDevice?.state)"
>
{{ getDeviceStatusText(selectedDevice?.state) }} {{ getDeviceStatusText(selectedDevice?.state) }}
</el-tag> </el-tag>
</div> </div>
@ -145,7 +135,8 @@ const localProductId = useVModel(props, 'productId', emit)
const localDeviceId = useVModel(props, 'deviceId', emit) const localDeviceId = useVModel(props, 'deviceId', emit)
// //
const deviceSelectionMode = ref<'specific' | 'all'>('specific') // TODO @puhui999 all
const deviceSelectionMode = ref<'specific' | 'all'>('all')
// //
const productLoading = ref(false) const productLoading = ref(false)
@ -155,29 +146,38 @@ const deviceList = ref<any[]>([])
// //
const selectedProduct = computed(() => { const selectedProduct = computed(() => {
return productList.value.find(p => p.id === localProductId.value) return productList.value.find((p) => p.id === localProductId.value)
}) })
const selectedDevice = computed(() => { const selectedDevice = computed(() => {
return deviceList.value.find(d => d.id === localDeviceId.value) return deviceList.value.find((d) => d.id === localDeviceId.value)
}) })
// TODO @puhui999
// //
const getDeviceStatusText = (state?: number) => { const getDeviceStatusText = (state?: number) => {
switch (state) { switch (state) {
case 0: return '未激活' case 0:
case 1: return '在线' return '未激活'
case 2: return '离线' case 1:
default: return '未知' return '在线'
case 2:
return '离线'
default:
return '未知'
} }
} }
const getDeviceStatusTag = (state?: number) => { const getDeviceStatusTag = (state?: number) => {
switch (state) { switch (state) {
case 0: return 'info' case 0:
case 1: return 'success' return 'info'
case 2: return 'danger' case 1:
default: return 'info' return 'success'
case 2:
return 'danger'
default:
return 'info'
} }
} }
@ -203,10 +203,10 @@ const handleDeviceSelectionModeChange = (mode: 'specific' | 'all') => {
deviceSelectionMode.value = mode deviceSelectionMode.value = mode
if (mode === 'all') { if (mode === 'all') {
// ID0 // ID 0
localDeviceId.value = 0 localDeviceId.value = 0
} else { } else {
// ID // ID
localDeviceId.value = undefined localDeviceId.value = undefined
} }
@ -229,6 +229,7 @@ const getProductList = async () => {
} catch (error) { } catch (error) {
console.error('获取产品列表失败:', error) console.error('获取产品列表失败:', error)
// //
// TODO @puhui999
productList.value = [ productList.value = [
{ id: 1, name: '智能温度传感器', productKey: 'temp_sensor_001', status: 0 }, { id: 1, name: '智能温度传感器', productKey: 'temp_sensor_001', status: 0 },
{ id: 2, name: '智能空调控制器', productKey: 'ac_controller_001', status: 0 }, { id: 2, name: '智能空调控制器', productKey: 'ac_controller_001', status: 0 },
@ -247,6 +248,7 @@ const getDeviceList = async (productId: number) => {
} catch (error) { } catch (error) {
console.error('获取设备列表失败:', error) console.error('获取设备列表失败:', error)
// //
// TODO @puhui999
deviceList.value = [ deviceList.value = [
{ id: 1, deviceName: 'sensor_001', nickname: '客厅温度传感器', state: 1, productId }, { id: 1, deviceName: 'sensor_001', nickname: '客厅温度传感器', state: 1, productId },
{ id: 2, deviceName: 'sensor_002', nickname: '卧室温度传感器', state: 2, productId }, { id: 2, deviceName: 'sensor_002', nickname: '卧室温度传感器', state: 2, productId },
@ -261,7 +263,7 @@ const getDeviceList = async (productId: number) => {
onMounted(async () => { onMounted(async () => {
await getProductList() await getProductList()
// ID // ID
if (localDeviceId.value === 0) { if (localDeviceId.value === 0) {
deviceSelectionMode.value = 'all' deviceSelectionMode.value = 'all'
} else if (localDeviceId.value) { } else if (localDeviceId.value) {
@ -274,11 +276,15 @@ onMounted(async () => {
}) })
// //
watch(() => localProductId.value, async (newProductId) => { watch(
if (newProductId && deviceList.value.length === 0) { () => localProductId.value,
await getDeviceList(newProductId) async (newProductId) => {
if (newProductId && deviceList.value.length === 0) {
await getDeviceList(newProductId)
}
} }
}) )
// TODO @puhui999 unocss
</script> </script>
<style scoped> <style scoped>

View File

@ -1,4 +1,5 @@
<!-- 属性选择器组件 --> <!-- 属性选择器组件 -->
<!-- TODO @yunai可能要在 review -->
<template> <template>
<div class="property-selector"> <div class="property-selector">
<el-select <el-select
@ -10,11 +11,7 @@
class="w-full" class="w-full"
:loading="loading" :loading="loading"
> >
<el-option-group <el-option-group v-for="group in propertyGroups" :key="group.label" :label="group.label">
v-for="group in propertyGroups"
:key="group.label"
:label="group.label"
>
<el-option <el-option
v-for="property in group.options" v-for="property in group.options"
:key="property.identifier" :key="property.identifier"
@ -70,7 +67,7 @@
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { IotRuleSceneTriggerTypeEnum } from '@/api/iot/rule/scene/scene.types' import { IotRuleSceneTriggerTypeEnum } from '@/api/iot/rule/scene/scene.types'
import { ThingModelApi, ThingModelData } from '@/api/iot/thingmodel' import { ThingModelApi } from '@/api/iot/thingmodel'
import { IoTThingModelTypeEnum } from '@/views/iot/utils/constants' import { IoTThingModelTypeEnum } from '@/views/iot/utils/constants'
import type { IotThingModelTSLRespVO, PropertySelectorItem } from './types' import type { IotThingModelTSLRespVO, PropertySelectorItem } from './types'
@ -106,65 +103,65 @@ const propertyGroups = computed(() => {
if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST) { if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST) {
groups.push({ groups.push({
label: '设备属性', label: '设备属性',
options: propertyList.value.filter(p => p.type === IoTThingModelTypeEnum.PROPERTY) options: propertyList.value.filter((p) => p.type === IoTThingModelTypeEnum.PROPERTY)
}) })
} }
if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST) { if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST) {
groups.push({ groups.push({
label: '设备事件', label: '设备事件',
options: propertyList.value.filter(p => p.type === IoTThingModelTypeEnum.EVENT) options: propertyList.value.filter((p) => p.type === IoTThingModelTypeEnum.EVENT)
}) })
} }
if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE) { if (props.triggerType === IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE) {
groups.push({ groups.push({
label: '设备服务', label: '设备服务',
options: propertyList.value.filter(p => p.type === IoTThingModelTypeEnum.SERVICE) options: propertyList.value.filter((p) => p.type === IoTThingModelTypeEnum.SERVICE)
}) })
} }
return groups.filter(group => group.options.length > 0) return groups.filter((group) => group.options.length > 0)
}) })
const selectedProperty = computed(() => { const selectedProperty = computed(() => {
return propertyList.value.find(p => p.identifier === localValue.value) return propertyList.value.find((p) => p.identifier === localValue.value)
}) })
// //
const getPropertyTypeName = (dataType: string) => { const getPropertyTypeName = (dataType: string) => {
const typeMap = { const typeMap = {
'int': '整数', int: '整数',
'float': '浮点数', float: '浮点数',
'double': '双精度', double: '双精度',
'text': '字符串', text: '字符串',
'bool': '布尔值', bool: '布尔值',
'enum': '枚举', enum: '枚举',
'date': '日期', date: '日期',
'struct': '结构体', struct: '结构体',
'array': '数组' array: '数组'
} }
return typeMap[dataType] || dataType return typeMap[dataType] || dataType
} }
const getPropertyTypeTag = (dataType: string) => { const getPropertyTypeTag = (dataType: string) => {
const tagMap = { const tagMap = {
'int': 'primary', int: 'primary',
'float': 'success', float: 'success',
'double': 'success', double: 'success',
'text': 'info', text: 'info',
'bool': 'warning', bool: 'warning',
'enum': 'danger', enum: 'danger',
'date': 'primary', date: 'primary',
'struct': 'info', struct: 'info',
'array': 'warning' array: 'warning'
} }
return tagMap[dataType] || 'info' return tagMap[dataType] || 'info'
} }
// //
const handleChange = (value: string) => { const handleChange = (value: string) => {
const property = propertyList.value.find(p => p.identifier === value) const property = propertyList.value.find((p) => p.identifier === value)
if (property) { if (property) {
emit('change', { emit('change', {
type: property.dataType, type: property.dataType,
@ -321,14 +318,21 @@ const getDataRange = (dataSpecs: any) => {
} }
// //
watch(() => props.productId, () => { watch(
getThingModelTSL() () => props.productId,
}, { immediate: true }) () => {
getThingModelTSL()
},
{ immediate: true }
)
// //
watch(() => props.triggerType, () => { watch(
localValue.value = '' () => props.triggerType,
}) () => {
localValue.value = ''
}
)
</script> </script>
<style scoped> <style scoped>

View File

@ -16,12 +16,14 @@
> >
<div class="trigger-option"> <div class="trigger-option">
<div class="option-content"> <div class="option-content">
<!-- TODO @puhui999貌似没对齐 -->
<Icon :icon="option.icon" class="option-icon" /> <Icon :icon="option.icon" class="option-icon" />
<div class="option-info"> <div class="option-info">
<div class="option-label">{{ option.label }}</div> <div class="option-label">{{ option.label }}</div>
<div class="option-desc">{{ option.description }}</div> <div class="option-desc">{{ option.description }}</div>
</div> </div>
</div> </div>
<!-- TODO @puhui999这个要不去掉 -->
<el-tag :type="option.tag" size="small"> <el-tag :type="option.tag" size="small">
{{ option.category }} {{ option.category }}
</el-tag> </el-tag>
@ -31,6 +33,7 @@
</el-form-item> </el-form-item>
<!-- 类型说明 --> <!-- 类型说明 -->
<!-- TODO @puhui999这个去掉感觉没啥内容哈 -->
<div v-if="selectedOption" class="type-description"> <div v-if="selectedOption" class="type-description">
<div class="desc-header"> <div class="desc-header">
<Icon :icon="selectedOption.icon" class="desc-icon" /> <Icon :icon="selectedOption.icon" class="desc-icon" />
@ -39,11 +42,7 @@
<div class="desc-content"> <div class="desc-content">
<p class="desc-text">{{ selectedOption.description }}</p> <p class="desc-text">{{ selectedOption.description }}</p>
<div class="desc-features"> <div class="desc-features">
<div <div v-for="feature in selectedOption.features" :key="feature" class="feature-item">
v-for="feature in selectedOption.features"
:key="feature"
class="feature-item"
>
<Icon icon="ep:check" class="feature-icon" /> <Icon icon="ep:check" class="feature-icon" />
<span class="feature-text">{{ feature }}</span> <span class="feature-text">{{ feature }}</span>
</div> </div>
@ -83,11 +82,7 @@ const triggerTypeOptions = [
icon: 'ep:connection', icon: 'ep:connection',
tag: 'warning', tag: 'warning',
category: '设备状态', category: '设备状态',
features: [ features: ['监控设备连接状态', '实时响应设备变化', '无需配置额外条件']
'监控设备连接状态',
'实时响应设备变化',
'无需配置额外条件'
]
}, },
{ {
value: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST, value: IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST,
@ -96,11 +91,7 @@ const triggerTypeOptions = [
icon: 'ep:data-line', icon: 'ep:data-line',
tag: 'primary', tag: 'primary',
category: '数据监控', category: '数据监控',
features: [ features: ['监控设备属性变化', '支持多种比较条件', '可配置阈值范围']
'监控设备属性变化',
'支持多种比较条件',
'可配置阈值范围'
]
}, },
{ {
value: IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST, value: IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST,
@ -109,11 +100,7 @@ const triggerTypeOptions = [
icon: 'ep:bell', icon: 'ep:bell',
tag: 'success', tag: 'success',
category: '事件监控', category: '事件监控',
features: [ features: ['监控设备事件', '支持事件参数过滤', '实时事件响应']
'监控设备事件',
'支持事件参数过滤',
'实时事件响应'
]
}, },
{ {
value: IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE, value: IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE,
@ -122,11 +109,7 @@ const triggerTypeOptions = [
icon: 'ep:service', icon: 'ep:service',
tag: 'info', tag: 'info',
category: '服务监控', category: '服务监控',
features: [ features: ['监控服务调用', '支持参数条件', '服务执行跟踪']
'监控服务调用',
'支持参数条件',
'服务执行跟踪'
]
}, },
{ {
value: IotRuleSceneTriggerTypeEnum.TIMER, value: IotRuleSceneTriggerTypeEnum.TIMER,
@ -135,17 +118,13 @@ const triggerTypeOptions = [
icon: 'ep:timer', icon: 'ep:timer',
tag: 'danger', tag: 'danger',
category: '定时任务', category: '定时任务',
features: [ features: ['支持CRON表达式', '灵活的时间配置', '可视化时间设置']
'支持CRON表达式',
'灵活的时间配置',
'可视化时间设置'
]
} }
] ]
// //
const selectedOption = computed(() => { const selectedOption = computed(() => {
return triggerTypeOptions.find(option => option.value === localValue.value) return triggerTypeOptions.find((option) => option.value === localValue.value)
}) })
// //
@ -155,6 +134,7 @@ const handleChange = (value: number) => {
</script> </script>
<style scoped> <style scoped>
/** TODO @puhui999unocss 哈 */
.trigger-type-selector { .trigger-type-selector {
width: 100%; width: 100%;
} }

View File

@ -1,4 +1,3 @@
<!-- 改进的场景联动规则管理页面 -->
<template> <template>
<ContentWrap> <ContentWrap>
<!-- 页面头部 --> <!-- 页面头部 -->
@ -186,6 +185,7 @@
</el-table-column> </el-table-column>
<el-table-column label="操作" width="200" fixed="right"> <el-table-column label="操作" width="200" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<!-- TODO @puhui999间隙大了点 -->
<div class="action-buttons"> <div class="action-buttons">
<el-button type="primary" link @click="handleEdit(row)"> <el-button type="primary" link @click="handleEdit(row)">
<Icon icon="ep:edit" /> <Icon icon="ep:edit" />
@ -197,6 +197,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翻译字典 -->
{{ row.status === 0 ? '禁用' : '启用' }} {{ row.status === 0 ? '禁用' : '启用' }}
</el-button> </el-button>
<el-button type="danger" link @click="handleDelete(row)"> <el-button type="danger" link @click="handleDelete(row)">
@ -241,7 +242,7 @@
</div> </div>
<!-- 表单对话框 --> <!-- 表单对话框 -->
<RuleSceneForm v-model="formVisible" :rule-scene="currentRule" @success="handleFormSuccess" /> <RuleSceneForm v-model="formVisible" :rule-scene="currentRule" @success="getList" />
</ContentWrap> </ContentWrap>
</template> </template>
@ -252,11 +253,11 @@ import { IotRuleScene } from '@/api/iot/rule/scene/scene.types'
import { getRuleSceneSummary } from './utils/transform' import { getRuleSceneSummary } from './utils/transform'
import { formatDate } from '@/utils/formatTime' import { formatDate } from '@/utils/formatTime'
/** 改进的场景联动规则管理页面 */ /** 场景联动规则管理页面 */
defineOptions({ name: 'ImprovedRuleSceneIndex' }) defineOptions({ name: 'IoTSceneRule' })
const message = useMessage() const message = useMessage()
// const { t } = useI18n() // const { t } = useI18n() // TODO @puhui999
// //
const queryParams = reactive({ const queryParams = reactive({
@ -267,6 +268,7 @@ const queryParams = reactive({
}) })
// //
// TODO @puhui999
const loading = ref(true) const loading = ref(true)
const list = ref<IotRuleScene[]>([]) const list = ref<IotRuleScene[]>([])
const total = ref(0) const total = ref(0)
@ -285,6 +287,7 @@ const statistics = ref({
}) })
// //
// TODO @puhui999
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
try { try {
@ -352,6 +355,8 @@ const getList = async () => {
} }
} }
// TODO @puhui999使 /** */
// //
const updateStatistics = () => { const updateStatistics = () => {
statistics.value = { statistics.value = {
@ -384,6 +389,7 @@ const resetQuery = () => {
handleQuery() handleQuery()
} }
// TODO @puhui999使 open
const handleAdd = () => { const handleAdd = () => {
currentRule.value = undefined currentRule.value = undefined
formVisible.value = true formVisible.value = true
@ -394,6 +400,7 @@ const handleEdit = (row: IotRuleScene) => {
formVisible.value = true formVisible.value = true
} }
// TODO @puhui999handleDeletehandleToggleStatus
const handleDelete = async (row: IotRuleScene) => { const handleDelete = async (row: IotRuleScene) => {
try { try {
await ElMessageBox.confirm('确定要删除这个规则吗?', '提示', { await ElMessageBox.confirm('确定要删除这个规则吗?', '提示', {
@ -481,10 +488,6 @@ const handleBatchDelete = async () => {
} }
} }
const handleFormSuccess = () => {
getList()
}
// //
onMounted(() => { onMounted(() => {
getList() getList()
@ -492,6 +495,7 @@ onMounted(() => {
</script> </script>
<style scoped> <style scoped>
/** TODO @puhui999看看下面的是不是可以用 unocss 替代 */
.page-header { .page-header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;