review:【iot 物联网】场景联动的 review

pull/802/MERGE
YunaiV 2025-07-28 23:25:54 +08:00
parent 9b88826a17
commit 5021c711a7
15 changed files with 145 additions and 66 deletions

View File

@ -144,7 +144,7 @@ interface RuleSceneFormData {
name: string name: string
description?: string description?: string
status: number status: number
trigger: TriggerFormData // 改为单个触发器 trigger: TriggerFormData
actions: ActionFormData[] actions: ActionFormData[]
} }

View File

@ -1,5 +1,5 @@
<!-- IoT场景联动规则表单 - 主表单组件 -->
<template> <template>
<!-- TODO @puhui999这个抽屉的高度太高了 -->
<el-drawer <el-drawer
v-model="drawerVisible" v-model="drawerVisible"
:title="drawerTitle" :title="drawerTitle"
@ -28,7 +28,6 @@
<ActionSection v-model:actions="formData.actions" @validate="handleActionValidate" /> <ActionSection v-model:actions="formData.actions" @validate="handleActionValidate" />
</el-form> </el-form>
</div> </div>
<template #footer> <template #footer>
<el-button :disabled="submitLoading" type="primary" @click="handleSubmit"> </el-button> <el-button :disabled="submitLoading" type="primary" @click="handleSubmit"> </el-button>
<el-button @click="handleClose"> </el-button> <el-button @click="handleClose"> </el-button>
@ -55,11 +54,13 @@ import { generateUUID } from '@/utils'
/** IoT 场景联动规则表单 - 主表单组件 */ /** IoT 场景联动规则表单 - 主表单组件 */
defineOptions({ name: 'RuleSceneForm' }) defineOptions({ name: 'RuleSceneForm' })
// TODO @puhui999 props
interface Props { interface Props {
modelValue: boolean modelValue: boolean
ruleScene?: IotRuleScene ruleScene?: IotRuleScene
} }
// TODO @puhui999Emits emit
interface Emits { interface Emits {
(e: 'update:modelValue', value: boolean): void (e: 'update:modelValue', value: boolean): void
(e: 'success'): void (e: 'success'): void
@ -68,11 +69,11 @@ interface Emits {
const props = defineProps<Props>() const props = defineProps<Props>()
const emit = defineEmits<Emits>() const emit = defineEmits<Emits>()
const drawerVisible = useVModel(props, 'modelValue', emit) const drawerVisible = useVModel(props, 'modelValue', emit) //
/** // TODO @puhui999使 /** */
* 创建默认的表单数据
*/ /** 创建默认的表单数据 */
const createDefaultFormData = (): RuleSceneFormData => { const createDefaultFormData = (): RuleSceneFormData => {
return { return {
name: '', name: '',
@ -93,8 +94,9 @@ const createDefaultFormData = (): RuleSceneFormData => {
} }
} }
// TODO @puhui999使 convertFormToVO
/** /**
* 将表单数据转换为API请求格式 * 将表单数据转换为 API 请求格式
*/ */
const transformFormToApi = (formData: RuleSceneFormData): IotRuleScene => { const transformFormToApi = (formData: RuleSceneFormData): IotRuleScene => {
return { return {
@ -196,7 +198,6 @@ 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)
@ -214,6 +215,7 @@ const handleSubmit = async () => {
const apiData = transformFormToApi(formData.value) const apiData = transformFormToApi(formData.value)
// API // API
// TODO @puhui999
console.log('提交数据:', apiData) console.log('提交数据:', apiData)
// API // API
@ -250,7 +252,7 @@ watch(drawerVisible, (visible) => {
} }
}) })
// props // props
watch( watch(
() => props.ruleScene, () => props.ruleScene,
() => { () => {
@ -261,6 +263,7 @@ watch(
) )
</script> </script>
<!-- TODO @puhui999看看下面样式哪些是必要添加的 -->
<style scoped> <style scoped>
/* 滚动条样式 */ /* 滚动条样式 */
.h-\[calc\(100vh-120px\)\]::-webkit-scrollbar { .h-\[calc\(100vh-120px\)\]::-webkit-scrollbar {

View File

@ -91,6 +91,7 @@
</el-row> </el-row>
<!-- 条件预览 --> <!-- 条件预览 -->
<!-- TODO puhui999可以去掉因为表单选择了可以看懂的呀 -->
<div <div
v-if="conditionPreview" v-if="conditionPreview"
class="p-12px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]" class="p-12px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]"

View File

@ -1,7 +1,7 @@
<!-- 条件组容器配置组件 -->
<template> <template>
<div class="flex flex-col gap-16px"> <div class="flex flex-col gap-16px">
<!-- 条件组容器头部 --> <!-- 条件组容器头部 -->
<!-- TODO @puhui999这个是不是删除不然有两个附件条件组 header -->
<div <div
class="flex items-center justify-between p-16px bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200 rounded-8px" class="flex items-center justify-between p-16px bg-gradient-to-r from-green-50 to-emerald-50 border border-green-200 rounded-8px"
> >
@ -39,6 +39,7 @@
<!-- 子条件组列表 --> <!-- 子条件组列表 -->
<div v-if="modelValue.subGroups && modelValue.subGroups.length > 0" class="space-y-16px"> <div v-if="modelValue.subGroups && modelValue.subGroups.length > 0" class="space-y-16px">
<!-- 逻辑关系说明 --> <!-- 逻辑关系说明 -->
<!-- TODO @puhui999这个可以去掉提示有点太多了 -->
<div v-if="modelValue.subGroups.length > 1" class="flex items-center justify-center"> <div v-if="modelValue.subGroups.length > 1" class="flex items-center justify-center">
<div <div
class="flex items-center gap-8px px-12px py-6px bg-orange-50 border border-orange-200 rounded-full text-12px text-orange-600" class="flex items-center gap-8px px-12px py-6px bg-orange-50 border border-orange-200 rounded-full text-12px text-orange-600"

View File

@ -1,7 +1,9 @@
<!-- 当前时间条件配置组件 --> <!-- 当前时间条件配置组件 -->
<template> <template>
<div class="flex flex-col gap-16px"> <div class="flex flex-col gap-16px">
<div class="flex items-center gap-8px p-12px px-16px bg-orange-50 rounded-6px border border-orange-200"> <div
class="flex items-center gap-8px p-12px px-16px bg-orange-50 rounded-6px border border-orange-200"
>
<Icon icon="ep:timer" class="text-orange-500 text-18px" /> <Icon icon="ep:timer" class="text-orange-500 text-18px" />
<span class="text-14px font-500 text-orange-700">当前时间条件配置</span> <span class="text-14px font-500 text-orange-700">当前时间条件配置</span>
</div> </div>
@ -89,13 +91,20 @@
</el-row> </el-row>
<!-- 条件预览 --> <!-- 条件预览 -->
<div v-if="conditionPreview" class="p-12px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]"> <!-- puhui999可以去掉因为表单选择了可以看懂的呀 -->
<div
v-if="conditionPreview"
class="p-12px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]"
>
<div class="flex items-center gap-8px mb-8px"> <div class="flex items-center gap-8px mb-8px">
<Icon icon="ep:view" class="text-[var(--el-color-info)] text-16px" /> <Icon icon="ep:view" class="text-[var(--el-color-info)] text-16px" />
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">条件预览</span> <span class="text-14px font-500 text-[var(--el-text-color-primary)]">条件预览</span>
</div> </div>
<div class="pl-24px"> <div class="pl-24px">
<code class="text-12px text-[var(--el-color-primary)] bg-[var(--el-fill-color-blank)] p-8px rounded-4px font-mono">{{ conditionPreview }}</code> <code
class="text-12px text-[var(--el-color-primary)] bg-[var(--el-fill-color-blank)] p-8px rounded-4px font-mono"
>{{ conditionPreview }}</code
>
</div> </div>
</div> </div>
@ -113,7 +122,10 @@
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core' import { useVModel } from '@vueuse/core'
import { ConditionFormData, IotRuleSceneTriggerTimeOperatorEnum } from '@/api/iot/rule/scene/scene.types' import {
ConditionFormData,
IotRuleSceneTriggerTimeOperatorEnum
} from '@/api/iot/rule/scene/scene.types'
/** 当前时间条件配置组件 */ /** 当前时间条件配置组件 */
defineOptions({ name: 'CurrentTimeConditionConfig' }) defineOptions({ name: 'CurrentTimeConditionConfig' })
@ -204,7 +216,7 @@ const conditionPreview = computed(() => {
return '' return ''
} }
const operatorOption = timeOperatorOptions.find(opt => opt.value === condition.value.operator) const operatorOption = timeOperatorOptions.find((opt) => opt.value === condition.value.operator)
const operatorLabel = operatorOption?.label || condition.value.operator const operatorLabel = operatorOption?.label || condition.value.operator
if (condition.value.operator === IotRuleSceneTriggerTimeOperatorEnum.TODAY.value) { if (condition.value.operator === IotRuleSceneTriggerTimeOperatorEnum.TODAY.value) {

View File

@ -79,6 +79,7 @@
</el-row> </el-row>
<!-- 条件预览 --> <!-- 条件预览 -->
<!-- TODO puhui999可以去掉因为表单选择了可以看懂的呀 -->
<div <div
v-if="conditionPreview" v-if="conditionPreview"
class="p-12px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]" class="p-12px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]"

View File

@ -17,7 +17,7 @@
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">附加条件组</span> <span class="text-14px font-500 text-[var(--el-text-color-primary)]">附加条件组</span>
<el-tag size="small" type="success">与主条件为且关系</el-tag> <el-tag size="small" type="success">与主条件为且关系</el-tag>
<el-tag size="small" type="info"> <el-tag size="small" type="info">
{{ trigger.conditionGroup?.subGroups?.length || 0 }}个子条件组 {{ trigger.conditionGroup?.subGroups?.length || 0 }} 个子条件组
</el-tag> </el-tag>
</div> </div>
<el-button <el-button
@ -70,6 +70,7 @@ import {
/** 设备触发配置组件 */ /** 设备触发配置组件 */
defineOptions({ name: 'DeviceTriggerConfig' }) defineOptions({ name: 'DeviceTriggerConfig' })
// TODO @puhui999 PropsEmits
interface Props { interface Props {
modelValue: TriggerFormData modelValue: TriggerFormData
} }
@ -92,8 +93,6 @@ const mainConditionValidation = ref<{ valid: boolean; message: string }>({
const validationMessage = ref('') const validationMessage = ref('')
const isValid = ref(true) const isValid = ref(true)
//
// //
const initMainCondition = () => { const initMainCondition = () => {
if (!trigger.value.mainCondition) { if (!trigger.value.mainCondition) {

View File

@ -20,6 +20,7 @@
</div> </div>
<!-- 主条件配置 --> <!-- 主条件配置 -->
<!-- TODO @puhui999这里可以简化下主条件是不能删除的 -->
<div v-else class="space-y-16px"> <div v-else class="space-y-16px">
<div class="flex items-center justify-between"> <div class="flex items-center justify-between">
<div class="flex items-center gap-8px"> <div class="flex items-center gap-8px">

View File

@ -1,4 +1,3 @@
<!-- 主条件内部配置组件 - 不显示条件类型选择 -->
<template> <template>
<div class="space-y-16px"> <div class="space-y-16px">
<!-- 触发事件类型显示 --> <!-- 触发事件类型显示 -->
@ -76,6 +75,7 @@
</el-row> </el-row>
<!-- 条件预览 --> <!-- 条件预览 -->
<!-- TODO puhui999可以去掉因为表单选择了可以看懂的呀 -->
<div v-if="conditionPreview" class="mt-12px"> <div v-if="conditionPreview" class="mt-12px">
<div class="text-12px text-[var(--el-text-color-secondary)]"> <div class="text-12px text-[var(--el-text-color-secondary)]">
预览{{ conditionPreview }} 预览{{ conditionPreview }}
@ -133,6 +133,7 @@ const emit = defineEmits<Emits>()
// //
const condition = useVModel(props, 'modelValue', emit) const condition = useVModel(props, 'modelValue', emit)
// TODO @puhui999 validationMessage
const isValid = ref(true) const isValid = ref(true)
const validationMessage = ref('') const validationMessage = ref('')
const propertyType = ref('') const propertyType = ref('')
@ -159,6 +160,7 @@ const conditionPreview = computed(() => {
}) })
// //
// TODO @puhui999
const getTriggerTypeText = (type: number) => { const getTriggerTypeText = (type: number) => {
switch (type) { switch (type) {
case IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST: case IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST:

View File

@ -1,4 +1,3 @@
<!-- 子条件组配置组件 -->
<template> <template>
<div class="p-16px"> <div class="p-16px">
<!-- 空状态 --> <!-- 空状态 -->
@ -66,6 +65,7 @@
</div> </div>
<!-- 条件间的"且"连接符 --> <!-- 条件间的"且"连接符 -->
<!-- TODO @puhu999建议去掉有点元素太丰富了 -->
<div <div
v-if="conditionIndex < subGroup.conditions!.length - 1" v-if="conditionIndex < subGroup.conditions!.length - 1"
class="flex items-center justify-center py-8px" class="flex items-center justify-center py-8px"

View File

@ -1,13 +1,16 @@
<!-- 定时触发配置组件 -->
<template> <template>
<div class="flex flex-col gap-16px"> <div class="flex flex-col gap-16px">
<div class="flex items-center gap-8px p-12px px-16px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]"> <div
class="flex items-center gap-8px p-12px px-16px bg-[var(--el-fill-color-light)] rounded-6px border border-[var(--el-border-color-lighter)]"
>
<Icon icon="ep:timer" class="text-[var(--el-color-danger)] text-18px" /> <Icon icon="ep:timer" class="text-[var(--el-color-danger)] text-18px" />
<span class="text-14px font-500 text-[var(--el-text-color-primary)]">定时触发配置</span> <span class="text-14px font-500 text-[var(--el-text-color-primary)]">定时触发配置</span>
</div> </div>
<!-- CRON表达式配置 --> <!-- CRON 表达式配置 -->
<div class="p-16px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]"> <div
class="p-16px border border-[var(--el-border-color-lighter)] rounded-6px bg-[var(--el-fill-color-blank)]"
>
<el-form-item label="CRON表达式" required> <el-form-item label="CRON表达式" required>
<Crontab v-model="localValue" /> <Crontab v-model="localValue" />
</el-form-item> </el-form-item>
@ -22,6 +25,7 @@ import { Crontab } from '@/components/Crontab'
/** 定时触发配置组件 */ /** 定时触发配置组件 */
defineOptions({ name: 'TimerTriggerConfig' }) defineOptions({ name: 'TimerTriggerConfig' })
// TODO @puhui999 PropsEmits
interface Props { interface Props {
modelValue?: string modelValue?: string
} }

View File

@ -8,6 +8,7 @@
<span class="text-16px font-600 text-[var(--el-text-color-primary)]">基础信息</span> <span class="text-16px font-600 text-[var(--el-text-color-primary)]">基础信息</span>
</div> </div>
<div class="flex items-center gap-8px"> <div class="flex items-center gap-8px">
<!-- TODO @puhui999dict-tag 可以哇 -->
<el-tag :type="formData.status === 0 ? 'success' : 'danger'" size="small"> <el-tag :type="formData.status === 0 ? 'success' : 'danger'" size="small">
{{ formData.status === 0 ? '启用' : '禁用' }} {{ formData.status === 0 ? '启用' : '禁用' }}
</el-tag> </el-tag>
@ -65,6 +66,8 @@ import { RuleSceneFormData } from '@/api/iot/rule/scene/scene.types'
/** 基础信息配置组件 */ /** 基础信息配置组件 */
defineOptions({ name: 'BasicInfoSection' }) defineOptions({ name: 'BasicInfoSection' })
// TODO @puhui999 PropsEmits
interface Props { interface Props {
modelValue: RuleSceneFormData modelValue: RuleSceneFormData
rules?: any rules?: any

View File

@ -1,6 +1,6 @@
<!-- 场景触发器配置组件 -->
<template> <template>
<el-card class="border border-[var(--el-border-color-light)] rounded-8px" shadow="never"> <el-card class="border border-[var(--el-border-color-light)] rounded-8px" shadow="never">
<!-- TODO @puhui999触发器还是多个每个触发器里面有事件类型 + 附加条件组最好文案上和阿里 iot 保持相对一致 -->
<template #header> <template #header>
<div class="flex items-center gap-8px"> <div class="flex items-center gap-8px">
<Icon icon="ep:lightning" class="text-[var(--el-color-primary)] text-18px" /> <Icon icon="ep:lightning" class="text-[var(--el-color-primary)] text-18px" />
@ -36,6 +36,7 @@
/> />
<!-- 定时触发配置 --> <!-- 定时触发配置 -->
<!-- TODO @puhui999这里要不 v-else 好了 -->
<TimerTriggerConfig <TimerTriggerConfig
v-if="trigger.type === TriggerTypeEnum.TIMER" v-if="trigger.type === TriggerTypeEnum.TIMER"
:model-value="trigger.cronExpression" :model-value="trigger.cronExpression"
@ -57,6 +58,7 @@ import {
/** 触发器配置组件 */ /** 触发器配置组件 */
defineOptions({ name: 'TriggerSection' }) defineOptions({ name: 'TriggerSection' })
// TODO @puhui999 PropsEmits
interface Props { interface Props {
trigger: TriggerFormData trigger: TriggerFormData
} }
@ -71,6 +73,7 @@ const emit = defineEmits<Emits>()
const trigger = useVModel(props, 'trigger', emit) const trigger = useVModel(props, 'trigger', emit)
// //
// TODO @puhui999/Users/yunai/Java/yudao-ui-admin-vue3/src/views/iot/utils/constants.ts
const triggerTypeOptions = [ const triggerTypeOptions = [
{ {
value: TriggerTypeEnum.DEVICE_STATE_UPDATE, value: TriggerTypeEnum.DEVICE_STATE_UPDATE,
@ -95,6 +98,7 @@ const triggerTypeOptions = [
] ]
// //
// TODO @puhui999/Users/yunai/Java/yudao-ui-admin-vue3/src/views/iot/utils/constants.ts
const isDeviceTrigger = (type: number) => { const isDeviceTrigger = (type: number) => {
const deviceTriggerTypes = [ const deviceTriggerTypes = [
TriggerTypeEnum.DEVICE_STATE_UPDATE, TriggerTypeEnum.DEVICE_STATE_UPDATE,
@ -111,10 +115,12 @@ const updateTriggerType = (type: number) => {
onTriggerTypeChange(type) onTriggerTypeChange(type)
} }
// TODO @puhui999updateTriggerDeviceConfig
const updateTrigger = (newTrigger: TriggerFormData) => { const updateTrigger = (newTrigger: TriggerFormData) => {
trigger.value = newTrigger trigger.value = newTrigger
} }
// TODO @puhui999updateTriggerCronConfig
const updateTriggerCronExpression = (cronExpression?: string) => { const updateTriggerCronExpression = (cronExpression?: string) => {
trigger.value.cronExpression = cronExpression trigger.value.cronExpression = cronExpression
} }

View File

@ -4,10 +4,12 @@
<div class="flex justify-between items-start mb-20px"> <div class="flex justify-between items-start mb-20px">
<div class="flex-1"> <div class="flex-1">
<h2 class="flex items-center m-0 mb-8px text-24px font-600 text-[#303133]"> <h2 class="flex items-center m-0 mb-8px text-24px font-600 text-[#303133]">
<Icon icon="ep:connection" class="mr-12px text-[#409eff]" /> <Icon icon="ep:connection" class="ml-5px mr-12px text-[#409eff]" />
场景联动规则 场景联动规则
</h2> </h2>
<p class="m-0 text-[#606266] text-14px"> 通过配置触发条件和执行动作实现设备间的智能联动控制 </p> <p class="m-0 text-[#606266] text-14px">
通过配置触发条件和执行动作实现设备间的智能联动控制
</p>
</div> </div>
<div> <div>
<el-button type="primary" @click="handleAdd"> <el-button type="primary" @click="handleAdd">
@ -66,52 +68,80 @@
<!-- 统计卡片 --> <!-- 统计卡片 -->
<el-row :gutter="16" class="mb-16px"> <el-row :gutter="16" class="mb-16px">
<el-col :span="6"> <el-col :span="6">
<el-card class="cursor-pointer transition-all duration-300 hover:transform hover:-translate-y-2px" shadow="hover"> <el-card
class="cursor-pointer transition-all duration-300 hover:transform hover:-translate-y-2px"
shadow="hover"
>
<div class="flex items-center"> <div class="flex items-center">
<div class="w-48px h-48px rounded-8px flex items-center justify-center text-24px text-white mr-16px bg-gradient-to-br from-[#667eea] to-[#764ba2]"> <div
class="w-48px h-48px rounded-8px flex items-center justify-center text-24px text-white mr-16px bg-gradient-to-br from-[#667eea] to-[#764ba2]"
>
<Icon icon="ep:document" /> <Icon icon="ep:document" />
</div> </div>
<div> <div>
<div class="text-24px font-600 text-[#303133] leading-none">{{ statistics.total }}</div> <div class="text-24px font-600 text-[#303133] leading-none">{{
statistics.total
}}</div>
<div class="text-14px text-[#909399] mt-4px">总规则数</div> <div class="text-14px text-[#909399] mt-4px">总规则数</div>
</div> </div>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<el-card class="cursor-pointer transition-all duration-300 hover:transform hover:-translate-y-2px" shadow="hover"> <el-card
class="cursor-pointer transition-all duration-300 hover:transform hover:-translate-y-2px"
shadow="hover"
>
<div class="flex items-center"> <div class="flex items-center">
<div class="w-48px h-48px rounded-8px flex items-center justify-center text-24px text-white mr-16px bg-gradient-to-br from-[#f093fb] to-[#f5576c]"> <div
class="w-48px h-48px rounded-8px flex items-center justify-center text-24px text-white mr-16px bg-gradient-to-br from-[#f093fb] to-[#f5576c]"
>
<Icon icon="ep:check" /> <Icon icon="ep:check" />
</div> </div>
<div> <div>
<div class="text-24px font-600 text-[#303133] leading-none">{{ statistics.enabled }}</div> <div class="text-24px font-600 text-[#303133] leading-none">{{
statistics.enabled
}}</div>
<div class="text-14px text-[#909399] mt-4px">启用规则</div> <div class="text-14px text-[#909399] mt-4px">启用规则</div>
</div> </div>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<el-card class="cursor-pointer transition-all duration-300 hover:transform hover:-translate-y-2px" shadow="hover"> <el-card
class="cursor-pointer transition-all duration-300 hover:transform hover:-translate-y-2px"
shadow="hover"
>
<div class="flex items-center"> <div class="flex items-center">
<div class="w-48px h-48px rounded-8px flex items-center justify-center text-24px text-white mr-16px bg-gradient-to-br from-[#4facfe] to-[#00f2fe]"> <div
class="w-48px h-48px rounded-8px flex items-center justify-center text-24px text-white mr-16px bg-gradient-to-br from-[#4facfe] to-[#00f2fe]"
>
<Icon icon="ep:close" /> <Icon icon="ep:close" />
</div> </div>
<div> <div>
<div class="text-24px font-600 text-[#303133] leading-none">{{ statistics.disabled }}</div> <div class="text-24px font-600 text-[#303133] leading-none">{{
statistics.disabled
}}</div>
<div class="text-14px text-[#909399] mt-4px">禁用规则</div> <div class="text-14px text-[#909399] mt-4px">禁用规则</div>
</div> </div>
</div> </div>
</el-card> </el-card>
</el-col> </el-col>
<el-col :span="6"> <el-col :span="6">
<el-card class="cursor-pointer transition-all duration-300 hover:transform hover:-translate-y-2px" shadow="hover"> <el-card
class="cursor-pointer transition-all duration-300 hover:transform hover:-translate-y-2px"
shadow="hover"
>
<div class="flex items-center"> <div class="flex items-center">
<div class="w-48px h-48px rounded-8px flex items-center justify-center text-24px text-white mr-16px bg-gradient-to-br from-[#43e97b] to-[#38f9d7]"> <div
class="w-48px h-48px rounded-8px flex items-center justify-center text-24px text-white mr-16px bg-gradient-to-br from-[#43e97b] to-[#38f9d7]"
>
<Icon icon="ep:lightning" /> <Icon icon="ep:lightning" />
</div> </div>
<div> <div>
<div class="text-24px font-600 text-[#303133] leading-none">{{ statistics.triggered }}</div> <div class="text-24px font-600 text-[#303133] leading-none">{{
statistics.triggered
}}</div>
<div class="text-14px text-[#909399] mt-4px">今日触发</div> <div class="text-14px text-[#909399] mt-4px">今日触发</div>
</div> </div>
</div> </div>
@ -134,6 +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">
@ -149,6 +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">
@ -177,7 +209,7 @@
{{ formatDate(row.createTime) }} {{ formatDate(row.createTime) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="200" fixed="right"> <el-table-column label="操作" width="210" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<div class="flex gap-8px"> <div class="flex gap-8px">
<el-button type="primary" link @click="handleEdit(row)"> <el-button type="primary" link @click="handleEdit(row)">
@ -190,9 +222,10 @@
@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.id)"> <el-button type="danger" class="!mr-10px" link @click="handleDelete(row.id)">
<Icon icon="ep:delete" /> <Icon icon="ep:delete" />
删除 删除
</el-button> </el-button>
@ -211,7 +244,10 @@
</el-card> </el-card>
<!-- 批量操作 --> <!-- 批量操作 -->
<div v-if="selectedRows.length > 0" class="fixed bottom-20px left-1/2 transform -translate-x-1/2 z-1000"> <div
v-if="selectedRows.length > 0"
class="fixed bottom-20px left-1/2 transform -translate-x-1/2 z-1000"
>
<el-card shadow="always"> <el-card shadow="always">
<div class="flex items-center gap-16px"> <div class="flex items-center gap-16px">
<span class="font-500 text-[#303133]"> 已选择 {{ selectedRows.length }} </span> <span class="font-500 text-[#303133]"> 已选择 {{ selectedRows.length }} </span>
@ -266,8 +302,8 @@ const selectedRows = ref<IotRuleScene[]>([]) // 选中的行数据
const queryFormRef = ref() // const queryFormRef = ref() //
// //
const formVisible = ref(false) const formVisible = ref(false) //
const currentRule = ref<IotRuleScene>() const currentRule = ref<IotRuleScene>() //
// //
const statistics = ref({ const statistics = ref({
@ -277,13 +313,12 @@ const statistics = ref({
triggered: 0 triggered: 0
}) })
/** /** 格式化 CRON 表达式显示 */
* 格式化CRON表达式显示 // TODO @puhui999 cron
*/
const formatCronExpression = (cron: string): string => { const formatCronExpression = (cron: string): string => {
if (!cron) return '' if (!cron) return ''
// CRON // CRON
const parts = cron.trim().split(' ') const parts = cron.trim().split(' ')
if (parts.length < 5) return cron if (parts.length < 5) return cron
@ -322,10 +357,9 @@ const formatCronExpression = (cron: string): string => {
return description || cron return description || cron
} }
/** /** 获取规则摘要信息 */
* 获取规则摘要信息
*/
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) {
@ -344,6 +378,7 @@ const getRuleSceneSummary = (rule: IotRuleScene) => {
} }
}) || [] }) || []
// TODO @puhui999使
const actionSummary = const actionSummary =
rule.actions?.map((action) => { rule.actions?.map((action) => {
switch (action.type) { switch (action.type) {
@ -367,6 +402,7 @@ const getRuleSceneSummary = (rule: IotRuleScene) => {
} }
/** 查询列表 */ /** 查询列表 */
// TODO @puhui999使
const getList = async () => { const getList = async () => {
loading.value = true loading.value = true
try { try {
@ -440,6 +476,7 @@ 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.lastTriggeredTime).length
} }
} }
@ -467,18 +504,20 @@ const resetQuery = () => {
handleQuery() handleQuery()
} }
/** 添加/修改操作 */ /** 添加操作 */
const handleAdd = () => { const handleAdd = () => {
currentRule.value = undefined currentRule.value = undefined
formVisible.value = true formVisible.value = true
} }
/** 修改操作 */
const handleEdit = (row: IotRuleScene) => { const handleEdit = (row: IotRuleScene) => {
currentRule.value = row currentRule.value = row
formVisible.value = true formVisible.value = true
} }
/** 删除按钮操作 */ /** 删除按钮操作 */
// TODO @puhui999 id
const handleDelete = async (id: number) => { const handleDelete = async (id: number) => {
try { try {
// //
@ -500,6 +539,7 @@ 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
// await RuleSceneApi.updateRuleSceneStatus(row.id, row.status === 0 ? 1 : 0) // await RuleSceneApi.updateRuleSceneStatus(row.id, row.status === 0 ? 1 : 0)
// //
@ -523,6 +563,7 @@ const handleBatchEnable = async () => {
try { try {
await message.confirm(`确定要启用选中的 ${selectedRows.value.length} 个规则吗?`) await message.confirm(`确定要启用选中的 ${selectedRows.value.length} 个规则吗?`)
// API // API
// TODO @puhui999
// await RuleSceneApi.updateRuleSceneStatusBatch(selectedRows.value.map(row => row.id), 0) // await RuleSceneApi.updateRuleSceneStatusBatch(selectedRows.value.map(row => row.id), 0)
// //
@ -539,6 +580,7 @@ const handleBatchDisable = async () => {
try { try {
await message.confirm(`确定要禁用选中的 ${selectedRows.value.length} 个规则吗?`) await message.confirm(`确定要禁用选中的 ${selectedRows.value.length} 个规则吗?`)
// API // API
// TODO @puhui999
// await RuleSceneApi.updateRuleSceneStatusBatch(selectedRows.value.map(row => row.id), 1) // await RuleSceneApi.updateRuleSceneStatusBatch(selectedRows.value.map(row => row.id), 1)
// //
@ -550,18 +592,18 @@ const handleBatchDisable = async () => {
} catch {} } catch {}
} }
/** 批量删除操作 */
const handleBatchDelete = async () => { const handleBatchDelete = async () => {
try { try {
await ElMessageBox.confirm(`确定要删除选中的 ${selectedRows.value.length} 个规则吗?`, '提示', { await ElMessageBox.confirm(`确定要删除选中的 ${selectedRows.value.length} 个规则吗?`, '提示', {
type: 'warning' type: 'warning'
}) })
// TODO @puhui999
// API // API
message.success('批量删除成功') message.success('批量删除成功')
getList() await getList()
} catch (error) { } catch (error) {}
//
}
} }
// //
@ -569,5 +611,3 @@ onMounted(() => {
getList() getList()
}) })
</script> </script>

View File

@ -1,3 +1,4 @@
// TODO @puhui999貌似很多地方都用不到啦这个文件
/** /**
* IoT * IoT
*/ */
@ -37,9 +38,10 @@ export const getBaseValidationRules = (): FormValidationRules => ({
}) })
/** 验证CRON表达式格式 */ /** 验证CRON表达式格式 */
// TODO @puhui999这个可以拿到 cron 组件里哇?
export function validateCronExpression(cron: string): boolean { export function validateCronExpression(cron: string): boolean {
if (!cron || cron.trim().length === 0) return false if (!cron || cron.trim().length === 0) return false
// 基础的 CRON 表达式正则验证(支持6位和7位格式 // 基础的 CRON 表达式正则验证(支持 6 位和 7 位格式)
const cronRegex = const cronRegex =
/^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])|\*\/([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\*\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])|\*\/([1-9]|1[0-2])) (\*|([0-6])|\*\/([0-6]))( (\*|([1-9][0-9]{3})|\*\/([1-9][0-9]{3})))?$/ /^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])|\*\/([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\*\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])|\*\/([1-9]|1[0-2])) (\*|([0-6])|\*\/([0-6]))( (\*|([1-9][0-9]{3})|\*\/([1-9][0-9]{3})))?$/
return cronRegex.test(cron.trim()) return cronRegex.test(cron.trim())
@ -58,6 +60,7 @@ export function validateDeviceNames(deviceNames: string[]): boolean {
export function validateCompareValue(operator: string, value: string): boolean { export function validateCompareValue(operator: string, value: string): boolean {
if (!value || value.trim().length === 0) return false if (!value || value.trim().length === 0) return false
const trimmedValue = value.trim() const trimmedValue = value.trim()
// TODO @puhui999这里要用下枚举哇
switch (operator) { switch (operator) {
case 'between': case 'between':
case 'not between': case 'not between':
@ -81,11 +84,13 @@ export function validateCompareValue(operator: string, value: string): boolean {
case '!=': case '!=':
case 'like': case 'like':
case 'not null': case 'not null':
// TODO @puhui999这里要不加个 default 抛出异常?
default: default:
return true return true
} }
} }
// TODO @puhui999貌似没用到
/** 验证触发器配置 */ /** 验证触发器配置 */
export function validateTriggerConfig(trigger: TriggerConfig): { export function validateTriggerConfig(trigger: TriggerConfig): {
valid: boolean valid: boolean
@ -136,6 +141,7 @@ export function validateTriggerConfig(trigger: TriggerConfig): {
return { valid: true } return { valid: true }
} }
// TODO @puhui999貌似没用到
/** 验证执行器配置 */ /** 验证执行器配置 */
export function validateActionConfig(action: ActionConfig): { valid: boolean; message?: string } { export function validateActionConfig(action: ActionConfig): { valid: boolean; message?: string } {
if (!action.type) { if (!action.type) {