From a1d7e81859f554f3a53680cc35f0f49bf1f77098 Mon Sep 17 00:00:00 2001
From: wwf <1971391498@qq.com>
Date: 星期四, 14 五月 2026 14:37:02 +0800
Subject: [PATCH] 导入项目

---
 src/views/iot/rule/scene/index.vue |  492 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 492 insertions(+), 0 deletions(-)

diff --git a/src/views/iot/rule/scene/index.vue b/src/views/iot/rule/scene/index.vue
new file mode 100644
index 0000000..f4a7b55
--- /dev/null
+++ b/src/views/iot/rule/scene/index.vue
@@ -0,0 +1,492 @@
+<template>
+  <ContentWrap>
+    <!-- 椤甸潰澶撮儴 -->
+    <div class="flex justify-between items-start mb-20px">
+      <div class="flex-1">
+        <h2 class="flex items-center m-0 mb-8px text-24px font-600 text-[#303133]">
+          <Icon icon="ep:connection" class="ml-5px mr-12px text-[#409eff]" />
+          鍦烘櫙鑱斿姩瑙勫垯
+        </h2>
+        <p class="m-0 text-[#606266] text-14px">
+          閫氳繃閰嶇疆瑙﹀彂鏉′欢鍜屾墽琛屽姩浣滐紝瀹炵幇璁惧闂寸殑鏅鸿兘鑱斿姩鎺у埗
+        </p>
+      </div>
+      <div>
+        <el-button type="primary" @click="handleAdd">
+          <Icon icon="ep:plus" />
+          鏂板瑙勫垯
+        </el-button>
+      </div>
+    </div>
+
+    <!-- 鎼滅储鍜岀瓫閫� -->
+    <el-card class="mb-16px" shadow="never">
+      <el-form
+        ref="queryFormRef"
+        :model="queryParams"
+        :inline="true"
+        label-width="80px"
+        @submit.prevent
+      >
+        <el-form-item label="瑙勫垯鍚嶇О">
+          <el-input
+            v-model="queryParams.name"
+            placeholder="璇疯緭鍏ヨ鍒欏悕绉�"
+            clearable
+            @keyup.enter="handleQuery"
+            class="!w-240px"
+          />
+        </el-form-item>
+        <el-form-item label="瑙勫垯鐘舵��">
+          <el-select
+            v-model="queryParams.status"
+            placeholder="璇烽�夋嫨鐘舵��"
+            clearable
+            class="!w-240px"
+          >
+            <el-option
+              v-for="dict in getIntDictOptions(DICT_TYPE.COMMON_STATUS)"
+              :key="dict.value"
+              :label="dict.label"
+              :value="dict.value"
+            />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleQuery">
+            <Icon icon="ep:search" />
+            鎼滅储
+          </el-button>
+          <el-button @click="resetQuery">
+            <Icon icon="ep:refresh" />
+            閲嶇疆
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <!-- 缁熻鍗$墖 -->
+    <el-row :gutter="16" class="mb-16px">
+      <el-col :span="6">
+        <el-card
+          class="cursor-pointer transition-all duration-300 hover:transform hover:-translate-y-2px"
+          shadow="hover"
+        >
+          <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]"
+            >
+              <Icon icon="ep:document" />
+            </div>
+            <div>
+              <div class="text-24px font-600 text-[#303133] leading-none">
+                {{ statistics.total }}
+              </div>
+              <div class="text-14px text-[#909399] mt-4px">鎬昏鍒欐暟</div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card
+          class="cursor-pointer transition-all duration-300 hover:transform hover:-translate-y-2px"
+          shadow="hover"
+        >
+          <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]"
+            >
+              <Icon icon="ep:check" />
+            </div>
+            <div>
+              <div class="text-24px font-600 text-[#303133] leading-none">
+                {{ statistics.enabled }}
+              </div>
+              <div class="text-14px text-[#909399] mt-4px">鍚敤瑙勫垯</div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card
+          class="cursor-pointer transition-all duration-300 hover:transform hover:-translate-y-2px"
+          shadow="hover"
+        >
+          <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]"
+            >
+              <Icon icon="ep:close" />
+            </div>
+            <div>
+              <div class="text-24px font-600 text-[#303133] leading-none">
+                {{ statistics.disabled }}
+              </div>
+              <div class="text-14px text-[#909399] mt-4px">绂佺敤瑙勫垯</div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+      <el-col :span="6">
+        <el-card
+          class="cursor-pointer transition-all duration-300 hover:transform hover:-translate-y-2px"
+          shadow="hover"
+        >
+          <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]"
+            >
+              <Icon icon="ep:timer" />
+            </div>
+            <div>
+              <div class="text-24px font-600 text-[#303133] leading-none">
+                {{ statistics.timerRules }}
+              </div>
+              <div class="text-14px text-[#909399] mt-4px">瀹氭椂瑙勫垯</div>
+            </div>
+          </div>
+        </el-card>
+      </el-col>
+    </el-row>
+
+    <!-- 鏁版嵁琛ㄦ牸 -->
+    <el-card class="mb-20px" shadow="never">
+      <el-table v-loading="loading" :data="list" stripe @selection-change="handleSelectionChange">
+        <el-table-column type="selection" width="55" />
+        <el-table-column label="瑙勫垯鍚嶇О" prop="name" min-width="200">
+          <template #default="{ row }">
+            <div class="flex items-center gap-8px">
+              <span class="font-500 text-[#303133]">{{ row.name }}</span>
+              <dict-tag :type="DICT_TYPE.COMMON_STATUS" :value="row.status" />
+            </div>
+            <div v-if="row.description" class="text-12px text-[#909399] mt-4px">
+              {{ row.description }}
+            </div>
+          </template>
+        </el-table-column>
+        <!-- 瑙﹀彂鏉′欢鍒� -->
+        <el-table-column label="瑙﹀彂鏉′欢" min-width="280">
+          <template #default="{ row }">
+            <div class="space-y-4px">
+              <div class="flex flex-wrap gap-4px">
+                <el-tag type="primary" size="small" class="m-0">
+                  {{ getTriggerSummary(row) }}
+                </el-tag>
+              </div>
+              <!-- 鏄剧ず瀹氭椂瑙﹀彂鍣ㄧ殑棰濆淇℃伅 -->
+              <div v-if="hasTimerTrigger(row)" class="mt-4px">
+                <el-tooltip :content="getCronExpression(row)" placement="top">
+                  <el-tag size="small" type="info" class="mr-4px">
+                    <Icon icon="ep:timer" class="mr-2px" />
+                    {{ getCronFrequency(row) }}
+                  </el-tag>
+                </el-tooltip>
+                <div v-if="getNextExecutionTime(row)" class="text-12px text-[#909399] mt-2px">
+                  <Icon icon="ep:clock" class="mr-2px" />
+                  涓嬫鎵ц: {{ formatDate(getNextExecutionTime(row)!) }}
+                </div>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+        <!-- 鎵ц鍔ㄤ綔鍒� -->
+        <el-table-column label="鎵ц鍔ㄤ綔" min-width="250">
+          <template #default="{ row }">
+            <div class="flex flex-wrap gap-4px">
+              <el-tag type="success" size="small" class="m-0">
+                {{ getActionSummary(row) }}
+              </el-tag>
+            </div>
+          </template>
+        </el-table-column>
+        <el-table-column label="鏈�杩戣Е鍙�" prop="lastTriggeredTime" width="180">
+          <template #default="{ row }">
+            <span v-if="row.lastTriggeredTime">
+              {{ formatDate(row.lastTriggeredTime) }}
+            </span>
+            <span v-else class="text-gray-400">鏈Е鍙�</span>
+          </template>
+        </el-table-column>
+        <el-table-column label="鍒涘缓鏃堕棿" prop="createTime" width="180">
+          <template #default="{ row }">
+            {{ formatDate(row.createTime) }}
+          </template>
+        </el-table-column>
+        <el-table-column label="鎿嶄綔" width="210" fixed="right">
+          <template #default="{ row }">
+            <div>
+              <el-button type="primary" link @click="handleEdit(row)">
+                <Icon icon="ep:edit" />
+                缂栬緫
+              </el-button>
+              <el-button
+                :type="row.status === 0 ? 'warning' : 'success'"
+                link
+                @click="handleToggleStatus(row)"
+              >
+                <Icon :icon="row.status === 0 ? 'ep:video-pause' : 'ep:video-play'" />
+                {{ getDictLabel(DICT_TYPE.COMMON_STATUS, row.status) }}
+              </el-button>
+              <el-button type="danger" class="!mr-10px" link @click="handleDelete(row.id)">
+                <Icon icon="ep:delete" />
+                鍒犻櫎
+              </el-button>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 鍒嗛〉 -->
+      <Pagination
+        :total="total"
+        v-model:page="queryParams.pageNo"
+        v-model:limit="queryParams.pageSize"
+        @pagination="getList"
+      />
+    </el-card>
+
+    <!-- 琛ㄥ崟瀵硅瘽妗� -->
+    <RuleSceneForm v-model="formVisible" :rule-scene="currentRule" @success="getList" />
+  </ContentWrap>
+</template>
+
+<script setup lang="ts">
+import { DICT_TYPE, getDictLabel, getIntDictOptions } from '@/utils/dict'
+import { ContentWrap } from '@/components/ContentWrap'
+import RuleSceneForm from './form/RuleSceneForm.vue'
+import { IotSceneRule, RuleSceneApi } from '@/api/iot/rule/scene'
+import {
+  getActionTypeLabel,
+  getTriggerTypeLabel,
+  IotRuleSceneTriggerTypeEnum
+} from '@/views/iot/utils/constants'
+import { formatDate } from '@/utils/formatTime'
+import { CommonStatusEnum } from '@/utils/constants'
+import { CronUtils } from '@/utils/cron'
+
+/** 鍦烘櫙鑱斿姩瑙勫垯绠$悊椤甸潰 */
+defineOptions({ name: 'IoTSceneRule' })
+
+const message = useMessage() // 娑堟伅寮圭獥
+const { t } = useI18n() // 鍥介檯鍖�
+
+/** 鏌ヨ鍙傛暟 */
+const queryParams = reactive({
+  pageNo: 1,
+  pageSize: 10,
+  name: '',
+  status: undefined
+})
+
+const loading = ref(true) // 鍒楄〃鐨勫姞杞戒腑
+const list = ref<IotSceneRule[]>([]) // 鍒楄〃鐨勬暟鎹�
+const total = ref(0) // 鍒楄〃鐨勬�婚〉鏁�
+const selectedRows = ref<IotSceneRule[]>([]) // 閫変腑鐨勮鏁版嵁
+const queryFormRef = ref() // 鎼滅储鐨勮〃鍗�
+
+/** 琛ㄥ崟鐘舵�� */
+const formVisible = ref(false) // 鏄惁鍙
+const currentRule = ref<IotSceneRule>() // 琛ㄥ崟鏁版嵁
+
+/** 缁熻鏁版嵁 */
+const statistics = ref({
+  total: 0,
+  enabled: 0,
+  disabled: 0,
+  timerRules: 0 // 瀹氭椂瑙勫垯鏁伴噺
+})
+
+/** 鑾峰彇瑙勫垯鎽樿淇℃伅 */
+const getRuleSceneSummary = (rule: IotSceneRule) => {
+  const triggerSummary =
+    rule.triggers?.map((trigger: any) => {
+      // 鏋勫缓鍩虹鎻忚堪
+      let description = getTriggerTypeLabel(trigger.type)
+      switch (trigger.type) {
+        case IotRuleSceneTriggerTypeEnum.DEVICE_STATE_UPDATE:
+          break
+        case IotRuleSceneTriggerTypeEnum.DEVICE_PROPERTY_POST:
+        case IotRuleSceneTriggerTypeEnum.DEVICE_EVENT_POST:
+        case IotRuleSceneTriggerTypeEnum.DEVICE_SERVICE_INVOKE:
+          if (trigger.identifier) {
+            description += ` (${trigger.identifier})`
+          }
+          break
+        case IotRuleSceneTriggerTypeEnum.TIMER:
+          description = `${getTriggerTypeLabel(trigger.type)} (${CronUtils.format(trigger.cronExpression || '')})`
+          break
+        default:
+          description = getTriggerTypeLabel(trigger.type)
+      }
+      // 娣诲姞璁惧淇℃伅锛堝鏋滄湁锛�
+      if (trigger.deviceId) {
+        description += ` [璁惧 ID: ${trigger.deviceId}]`
+      } else if (trigger.productId) {
+        description += ` [浜у搧 ID: ${trigger.productId}]`
+      }
+      return description
+    }) || []
+
+  const actionSummary =
+    rule.actions?.map((action: any) => {
+      // 鏋勫缓鍩虹鎻忚堪
+      let description = getActionTypeLabel(action.type)
+      // 娣诲姞璁惧淇℃伅锛堝鏋滄湁锛�
+      if (action.deviceId) {
+        description += ` [璁惧 ID: ${action.deviceId}]`
+      } else if (action.productId) {
+        description += ` [浜у搧 ID: ${action.productId}]`
+      }
+      // 娣诲姞鍛婅閰嶇疆淇℃伅锛堝鏋滄湁锛�
+      if (action.alertConfigId) {
+        description += ` [鍛婅閰嶇疆 ID: ${action.alertConfigId}]`
+      }
+      return description
+    }) || []
+
+  return {
+    triggerSummary: triggerSummary.join(', ') || '鏃犺Е鍙戝櫒',
+    actionSummary: actionSummary.join(', ') || '鏃犳墽琛屽櫒'
+  }
+}
+
+/** 鏌ヨ鍒楄〃 */
+const getList = async () => {
+  loading.value = true
+  try {
+    const data = await RuleSceneApi.getRuleScenePage(queryParams)
+    list.value = data.list
+    total.value = data.total
+  } finally {
+    // 鏇存柊缁熻鏁版嵁
+    updateStatistics()
+    loading.value = false
+  }
+}
+
+/** 鏇存柊缁熻鏁版嵁 */
+const updateStatistics = () => {
+  statistics.value = {
+    total: list.value.length,
+    enabled: list.value.filter((item) => item.status === CommonStatusEnum.ENABLE).length,
+    disabled: list.value.filter((item) => item.status === CommonStatusEnum.DISABLE).length,
+    timerRules: list.value.filter((item) => hasTimerTrigger(item)).length
+  }
+}
+
+/** 鑾峰彇瑙﹀彂鍣ㄦ憳瑕� */
+const getTriggerSummary = (rule: IotSceneRule) => {
+  return getRuleSceneSummary(rule).triggerSummary
+}
+
+/** 鑾峰彇鎵ц鍣ㄦ憳瑕� */
+const getActionSummary = (rule: IotSceneRule) => {
+  return getRuleSceneSummary(rule).actionSummary
+}
+
+/** 妫�鏌ヨ鍒欐槸鍚﹀寘鍚畾鏃惰Е鍙戝櫒 */
+const hasTimerTrigger = (rule: IotSceneRule): boolean => {
+  return (
+    rule.triggers?.some((trigger) => trigger.type === IotRuleSceneTriggerTypeEnum.TIMER) || false
+  )
+}
+
+/** 鑾峰彇 CRON 琛ㄨ揪寮忕殑鎵ц棰戠巼鎻忚堪 */
+const getCronFrequency = (rule: IotSceneRule): string => {
+  const timerTrigger = rule.triggers?.find(
+    (trigger) => trigger.type === IotRuleSceneTriggerTypeEnum.TIMER
+  )
+  if (timerTrigger?.cronExpression) {
+    return CronUtils.getFrequencyDescription(timerTrigger.cronExpression)
+  }
+  return ''
+}
+
+/** 鑾峰彇涓嬫鎵ц鏃堕棿 */
+const getNextExecutionTime = (rule: IotSceneRule): Date | null => {
+  const timerTrigger = rule.triggers?.find(
+    (trigger) => trigger.type === IotRuleSceneTriggerTypeEnum.TIMER
+  )
+  if (timerTrigger?.cronExpression) {
+    return CronUtils.getNextExecutionTime(timerTrigger.cronExpression)
+  }
+  return null
+}
+
+/** 鑾峰彇 CRON 琛ㄨ揪寮忓師濮嬪�� */
+const getCronExpression = (rule: IotSceneRule): string => {
+  const timerTrigger = rule.triggers?.find(
+    (trigger) => trigger.type === IotRuleSceneTriggerTypeEnum.TIMER
+  )
+  return timerTrigger?.cronExpression || ''
+}
+
+/** 鎼滅储鎸夐挳鎿嶄綔 */
+const handleQuery = () => {
+  queryParams.pageNo = 1
+  getList()
+}
+
+/** 閲嶇疆鎸夐挳鎿嶄綔 */
+const resetQuery = () => {
+  queryParams.name = ''
+  queryParams.status = undefined
+  handleQuery()
+}
+
+/** 娣诲姞鎿嶄綔 */
+const handleAdd = () => {
+  currentRule.value = undefined
+  formVisible.value = true
+}
+
+/** 淇敼鎿嶄綔 */
+const handleEdit = (row: IotSceneRule) => {
+  currentRule.value = row
+  formVisible.value = true
+}
+
+/** 鍒犻櫎鎸夐挳鎿嶄綔 */
+const handleDelete = async (id: number) => {
+  try {
+    // 鍒犻櫎鐨勪簩娆$‘璁�
+    await message.delConfirm()
+    // 鍙戣捣鍒犻櫎
+    await RuleSceneApi.deleteRuleScene(id)
+    message.success(t('common.delSuccess'))
+    // 鍒锋柊鍒楄〃
+    await getList()
+  } catch (error) {}
+}
+
+/** 淇敼鐘舵�� */
+const handleToggleStatus = async (row: IotSceneRule) => {
+  try {
+    // 淇敼鐘舵�佺殑浜屾纭
+    const text = row.status === CommonStatusEnum.ENABLE ? '绂佺敤' : '鍚敤'
+    await message.confirm('纭瑕�' + text + '"' + row.name + '"鍚�?')
+    // 鍙戣捣淇敼鐘舵��
+    await RuleSceneApi.updateRuleSceneStatus(
+      row.id!,
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
+    )
+    message.success(text + '鎴愬姛')
+    // 鍒锋柊
+    await getList()
+  } catch {
+    // 鍙栨秷鍚庯紝杩涜鎭㈠鎸夐挳
+    row.status =
+      row.status === CommonStatusEnum.ENABLE ? CommonStatusEnum.DISABLE : CommonStatusEnum.ENABLE
+  }
+}
+
+/** 澶氶�夋閫変腑鏁版嵁 */
+const handleSelectionChange = (selection: IotSceneRule[]) => {
+  selectedRows.value = selection
+}
+
+/** 鍒濆鍖� */
+onMounted(() => {
+  getList()
+})
+</script>

--
Gitblit v1.8.0