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