فهرست منبع

修改页面 照明系统数据列表 接口对接 能源管理数据列表系统接口对接 空调系统数据列表接口对接 修复公告 显示美化界面 修复时间表管理显示

bzd_lxf 6 روز پیش
والد
کامیت
7ae609c446

+ 1246 - 249
pm_ui/src/layout/components/Navbar.vue

@@ -1,122 +1,112 @@
 <template>
   <div class="navbar">
-    <hamburger id="hamburger-container" :is-active="appStore.sidebar.opened" class="hamburger-container"
-               @toggleClick="toggleSideBar"/>
-    <breadcrumb v-if="!settingsStore.topNav" id="breadcrumb-container" class="breadcrumb-container"/>
-    <top-nav v-if="settingsStore.topNav" id="topmenu-container" class="topmenu-container"/>
+    <!-- 左侧区域 -->
+    <div class="navbar-left">
+      <hamburger
+          id="hamburger-container"
+          :is-active="appStore.sidebar.opened"
+          class="hamburger-container"
+          @toggleClick="toggleSideBar"
+      />
+      <breadcrumb
+          v-if="!settingsStore.topNav"
+          id="breadcrumb-container"
+          class="breadcrumb-container"
+      />
+      <top-nav
+          v-if="settingsStore.topNav"
+          id="topmenu-container"
+          class="topmenu-container"
+      />
+    </div>
 
+    <!-- 右侧菜单区域 -->
     <div class="right-menu">
       <template v-if="appStore.device !== 'mobile'">
-        <!-- 使用 el-badge 包裹图标 -->
-        <el-tooltip :content="noticeContent" effect="dark" placement="bottom">
-          <div class="right-menu-item" style="height: 70%">
-            <el-badge :value="noticeCount" :max="99" :offset="[0, 10]" :hidden="noticeCount == 0 ? true : false"
-                      class="badge-container">
-              <el-icon @click="showDetail" style="cursor: pointer;">
-                <BellFilled/>
-              </el-icon>
-            </el-badge>
-          </div>
-        </el-tooltip>
-        <!-- 弹框组件 -->
-        <el-dialog v-model="dialogVisible2" title="详情" width="30%" :before-close="closeDetailDialog">
-          <el-row :gutter="20">
-            <el-col :span="24">
-              <div class="dialog-item">
-                <span class="label">标题:</span>
-                <el-input v-model="noticeTitle" readonly placeholder="请输入公告标题" style="width: 90%;"></el-input>
-              </div>
-            </el-col>
-            <el-col :span="24">
-              <div class="dialog-item">
-                <span class="label">类型:</span>
-                <el-select v-model="noticeType" disabled placeholder="请选择公告类型" style="width: 90%;">
-                  <el-option
-                      v-for="dict in sys_notice_type"
-                      :key="dict.value"
-                      :label="dict.label"
-                      :value="dict.value"
-                  ></el-option>
-                </el-select>
-              </div>
-            </el-col>
-            <el-col :span="24">
-              <div class="dialog-item">
-                <span class="label">内容:</span>
-                <editor v-model="noticeContentV" readonly disabled :min-height="192" style="width: 100%;"></editor>
-              </div>
-            </el-col>
-          </el-row>
-          <template #footer>
-            <div class="dialog-footer">
-              <el-button type="primary" @click="readInfo2">已 读</el-button>
+        <!-- 通知铃铛 -->
+        <div class="notification-wrapper">
+          <el-tooltip :content="noticeContent" effect="dark" placement="bottom">
+            <div class="right-menu-item notification-item" @click="showDetail">
+              <el-badge
+                  :value="noticeCount"
+                  :max="99"
+                  :offset="[5, 5]"
+                  :hidden="noticeCount == 0"
+                  class="badge-container"
+              >
+                <div class="notification-bell">
+                  <el-icon class="bell-icon" :class="{ 'bell-shake': noticeCount > 0 }">
+                    <BellFilled/>
+                  </el-icon>
+<!--                  <div v-if="noticeCount > 0" class="notification-dot"></div>-->
+                </div>
+              </el-badge>
             </div>
-          </template>
-        </el-dialog>
-        <el-dialog v-model="dialogVisible" title="通知" width="50%" :before-close="closeListDialog">
-          <el-table v-loading="loading" :data="noticeList" :default-sort="defaultSort" @sort-change="handleSortChange">
-            <el-table-column type="selection" width="55" align="center"/>
-            <!--            <el-table-column label="序号" align="center" prop="notice_id" width="100" />-->
-            <el-table-column
-                label="公告标题"
-                align="center"
-                prop="notice_title"
-                :show-overflow-tooltip="true"
-            />
-            <el-table-column label="公告类型" align="center" prop="notice_type" width="100">
-              <template #default="scope">
-                <dict-tag :options="sys_notice_type" :value="scope.row.notice_type"/>
-              </template>
-            </el-table-column>
-            <!--            <el-table-column label="状态" align="center" prop="status" width="100">
-                          <template #default="scope">
-                            <dict-tag :options="sys_notice_status" :value="scope.row.status" />
-                          </template>
-                        </el-table-column>-->
-            <el-table-column label="创建者" align="center" prop="create_by" width="100"/>
-            <el-table-column label="创建时间" align="center" prop="create_time" width="100" sortable="custom">
-              <template #default="scope">
-                <span>{{ parseTime(scope.row.create_time, '{y}-{m}-{d}') }}</span>
-              </template>
-            </el-table-column>
-            <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="180px">
-              <template #default="scope">
-                <el-button link type="primary" icon="View" @click="handleUpdate(scope.row)" v-hasPermi="['system:notice:query']">查看</el-button>
-                <el-button link type="primary" icon="Check" @click="readInfo(scope.row)" >已读</el-button>
-              </template>
-            </el-table-column>
-          </el-table>
-        </el-dialog>
+          </el-tooltip>
+        </div>
+
+        <!-- 搜索 -->
         <header-search id="header-search" class="right-menu-item"/>
+
+        <!-- 全屏 -->
         <screenfull id="screenfull" class="right-menu-item hover-effect"/>
+
+        <!-- 主题切换 -->
         <el-tooltip content="主题模式" effect="dark" placement="bottom">
           <div class="right-menu-item hover-effect theme-switch-wrapper" @click="toggleTheme">
-            <svg-icon v-if="settingsStore.isDark" icon-class="sunny"/>
-            <svg-icon v-if="!settingsStore.isDark" icon-class="moon"/>
+            <transition name="theme-rotate" mode="out-in">
+              <svg-icon
+                  v-if="settingsStore.isDark"
+                  icon-class="sunny"
+                  key="light"
+                  class="theme-icon"
+              />
+              <svg-icon
+                  v-else
+                  icon-class="moon"
+                  key="dark"
+                  class="theme-icon"
+              />
+            </transition>
           </div>
         </el-tooltip>
 
+        <!-- 布局大小 -->
         <el-tooltip content="布局大小" effect="dark" placement="bottom">
           <size-select id="size-select" class="right-menu-item hover-effect"/>
         </el-tooltip>
       </template>
+
+      <!-- 用户头像下拉菜单 -->
       <div class="avatar-container">
-        <el-dropdown @command="handleCommand" class="right-menu-item hover-effect" trigger="click">
+        <el-dropdown @command="handleCommand" class="right-menu-item hover-effect user-dropdown" trigger="click">
           <div class="avatar-wrapper">
-            <img :src="userStore.avatar" class="user-avatar"/>
-            <el-icon>
+            <div class="avatar-image-wrapper">
+              <img :src="userStore.avatar" class="user-avatar" alt="用户头像"/>
+              <div class="avatar-status"></div>
+            </div>
+            <div class="user-info" v-if="appStore.device !== 'mobile'">
+              <span class="user-name">{{ userStore.name || '用户' }}</span>
+              <span class="user-role">管理员</span>
+            </div>
+            <el-icon class="dropdown-arrow">
               <caret-bottom/>
             </el-icon>
           </div>
           <template #dropdown>
-            <el-dropdown-menu>
-              <router-link to="/user/profile">
-                <el-dropdown-item>个人中心</el-dropdown-item>
-              </router-link>
-              <el-dropdown-item command="setLayout" v-if="settingsStore.showSettings">
+            <el-dropdown-menu class="user-dropdown-menu">
+              <el-dropdown-item class="dropdown-item-custom">
+                <router-link to="/user/profile" class="dropdown-link">
+                  <el-icon class="dropdown-icon"><User /></el-icon>
+                  <span>个人中心</span>
+                </router-link>
+              </el-dropdown-item>
+              <el-dropdown-item command="setLayout" v-if="settingsStore.showSettings" class="dropdown-item-custom">
+                <el-icon class="dropdown-icon"><Setting /></el-icon>
                 <span>布局设置</span>
               </el-dropdown-item>
-              <el-dropdown-item divided command="logout">
+              <el-dropdown-item divided command="logout" class="dropdown-item-custom logout-item">
+                <el-icon class="dropdown-icon"><SwitchButton /></el-icon>
                 <span>退出登录</span>
               </el-dropdown-item>
             </el-dropdown-menu>
@@ -124,105 +114,294 @@
         </el-dropdown>
       </div>
     </div>
+
+    <!-- 通知列表弹框 -->
+    <el-dialog
+        v-model="dialogVisible"
+        title="通知列表"
+        width="70%"
+        :before-close="closeListDialog"
+        class="notice-list-dialog"
+        destroy-on-close
+    >
+      <div class="dialog-header">
+        <div class="header-info">
+          <el-icon class="info-icon"><InfoFilled /></el-icon>
+          <span>共有 {{ noticeList.length }} 条通知</span>
+        </div>
+        <div class="header-actions">
+          <el-button type="primary" size="small" @click="markAllAsRead" :disabled="noticeCount === 0">
+            全部已读
+          </el-button>
+        </div>
+      </div>
+
+      <el-table
+          v-loading="loading"
+          :data="noticeList"
+          :default-sort="defaultSort"
+          @sort-change="handleSortChange"
+          class="notice-table"
+          stripe
+          highlight-current-row
+          height="600"
+          style="width: 100%"
+      >
+        <el-table-column type="selection" width="55" align="center" />
+
+        <el-table-column
+            label="公告标题"
+            align="left"
+            prop="notice_title"
+            :show-overflow-tooltip="true"
+            min-width="200"
+        >
+          <template #default="scope">
+            <div class="notice-title-cell">
+<!--              <el-tag v-if="!scope.row.isRead" type="danger" size="small" class="unread-tag">
+                NEW
+              </el-tag>-->
+              <span class="title-text" :class="{ 'unread-title': !scope.row.isRead }">
+                {{ scope.row.notice_title }}
+              </span>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="公告类型" align="center" prop="notice_type" width="120">
+          <template #default="scope">
+            <dict-tag :options="sys_notice_type" :value="scope.row.notice_type"/>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="创建者" align="center" prop="create_by" width="120">
+          <template #default="scope">
+            <div class="creator-cell">
+              <el-avatar :size="24" class="creator-avatar">
+                {{ scope.row.create_by?.charAt(0) || 'U' }}
+              </el-avatar>
+              <span>{{ scope.row.create_by }}</span>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column
+            label="创建时间"
+            align="center"
+            prop="create_time"
+            width="160"
+            sortable="custom"
+        >
+          <template #default="scope">
+            <div class="time-cell">
+              <el-icon class="time-icon"><Clock /></el-icon>
+              <span>{{ parseTime(scope.row.create_time, '{y}-{m}-{d}') }}</span>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
+          <template #default="scope">
+            <div class="action-buttons">
+              <el-button
+                  link
+                  type="primary"
+                  size="small"
+                  @click="handleUpdate(scope.row)"
+                  v-hasPermi="['system:notice:query']"
+              >
+                <el-icon><View /></el-icon>
+                查看
+              </el-button>
+              <el-button
+                  link
+                  type="success"
+                  size="small"
+                  @click="readInfo(scope.row)"
+                  :disabled="scope.row.isRead"
+              >
+                <el-icon><Check /></el-icon>
+                {{ scope.row.isRead ? '已读' : '标记已读' }}
+              </el-button>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeListDialog">关闭</el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 通知详情弹框 -->
+    <el-dialog
+        v-model="dialogVisible2"
+        title="通知详情"
+        width="50%"
+        :before-close="closeDetailDialog"
+        class="notice-detail-dialog"
+        destroy-on-close
+    >
+      <div class="detail-content">
+        <el-form :model="noticeForm" label-width="80px" class="notice-form">
+          <el-form-item label="标题">
+            <div class="form-item-content">
+              <el-icon class="form-icon"><Document /></el-icon>
+              <el-input
+                  v-model="noticeTitle"
+                  readonly
+                  class="readonly-input"
+              />
+            </div>
+          </el-form-item>
+
+          <el-form-item label="类型">
+            <div class="form-item-content">
+              <el-icon class="form-icon"><Collection /></el-icon>
+              <el-select
+                  v-model="noticeType"
+                  disabled
+                  style="width: 195px;"
+                  class="readonly-select"
+              >
+                <el-option
+                    v-for="dict in sys_notice_type"
+                    :key="dict.value"
+                    :label="dict.label"
+                    :value="dict.value"
+                />
+              </el-select>
+            </div>
+          </el-form-item>
+
+          <el-form-item label="内容">
+            <div class="content-wrapper">
+              <editor
+                  v-model="noticeContentV"
+                  readonly
+                  disabled
+                  :min-height="200"
+                  style="width: 100%;"
+                  class="content-editor"
+              />
+            </div>
+          </el-form-item>
+        </el-form>
+      </div>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="closeDetailDialog">关闭</el-button>
+          <el-button type="primary" @click="readInfo2">
+            <el-icon><Check /></el-icon>
+            标记已读
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
   </div>
 </template>
 
 <script setup>
-import {ElMessageBox} from 'element-plus'
+import { ElMessageBox, ElNotification } from 'element-plus'
+import {
+  BellFilled,
+  CaretBottom,
+  User,
+  Setting,
+  SwitchButton,
+  InfoFilled,
+  Clock,
+  View,
+  Check,
+  Document,
+  Collection
+} from '@element-plus/icons-vue'
 import Breadcrumb from '@/components/Breadcrumb'
 import TopNav from '@/components/TopNav'
 import Hamburger from '@/components/Hamburger'
 import Screenfull from '@/components/Screenfull'
 import SizeSelect from '@/components/SizeSelect'
 import HeaderSearch from '@/components/HeaderSearch'
-import pmGit from '@/components/pm/Git'
-import pmDoc from '@/components/pm/Doc'
-import {listNoticeRead} from "@/api/system/menu";
-import {getNotice, show} from "@/api/system/notice";
+import { listNoticeRead } from "@/api/system/menu"
+import { getNotice, show } from "@/api/system/notice"
 import useAppStore from '@/store/modules/app'
 import useUserStore from '@/store/modules/user'
 import useSettingsStore from '@/store/modules/settings'
-import { h } from 'vue';
+import { h, ref, computed, getCurrentInstance } from 'vue'
+import { connectToWebSocket } from "@/layout/components/websocket"
 
-const {proxy} = getCurrentInstance();
-const {sys_notice_status, sys_notice_type} = proxy.useDict("sys_notice_status", "sys_notice_type");
+const { proxy } = getCurrentInstance()
+const { sys_notice_status, sys_notice_type } = proxy.useDict("sys_notice_status", "sys_notice_type")
 
-import {connectToWebSocket} from "@/layout/components/websocket";
-import {ElNotification} from 'element-plus'
+const appStore = useAppStore()
+const userStore = useUserStore()
+const settingsStore = useSettingsStore()
 
+// 响应式数据
 const announcements = ref([])
+const noticeList = ref([])
+const loading = ref(true)
+const noticeContentV = ref('暂无消息')
+const noticeContent = ref('')
+const noticeCount = ref(0)
+const intervalId = ref(null)
+const notice_id = ref(null)
+const dialogVisible = ref(false)
+const dialogVisible2 = ref(false)
+const noticeTitle = ref('')
+const noticeType = ref('')
+const uniqueKey = ref(0)
+const defaultSort = ref({ prop: 'create_time', order: 'descending' })
+
+const queryParams = ref({
+  pageNum: 1,
+  pageSize: 10000,
+  orderByColumn: 'create_time',
+  isAsc: 'desc'
+})
+
+const noticeForm = computed(() => ({
+  title: noticeTitle.value,
+  type: noticeType.value,
+  content: noticeContentV.value
+}))
 
+// WebSocket 连接
 connectToWebSocket((newAnnounce) => {
   announcements.value.unshift(newAnnounce)
   if (newAnnounce) {
-    // 显示右侧提示信息
-    // ElNotification({
-    //   title: '新公告',
-    //   message: '您有新的公告,请注意查收',
-    //   type: 'warning',
-    //   duration: 5000,
-    //   position: 'top-right'
-    // });
-    debugger
-    if (newAnnounce !== "del" && newAnnounce !== "update"){ // 如果删除公告则不提示信息
-      txt2("新公告", "您有新的公告,请注意查收",'warning',3000);
+    if (newAnnounce !== "del" && newAnnounce !== "update") {
+      txt2("新公告", "您有新的公告,请注意查收", 'warning', 3000)
     }
     getlistNotice()
   }
 })
 
-function txt2(title,msg,type,time) {
+// 方法定义
+function txt2(title, msg, type, time) {
   ElNotification({
     title: title,
     message: h('div', { style: 'display: flex; align-items: center;' }, [
       msg,
       h('a', {
-        style: 'margin-left: 5px; color: #409eff; text-decoration: underline;',
+        style: 'margin-left: 5px; color: #409eff; text-decoration: underline; cursor: pointer;',
         onClick: () => showDetail()
       }, '查看')
     ]),
     type: type,
     duration: time,
-    position: 'top-right'
-  });
-}
-
-const getNoticeTypeLabel = (type) => {
-  const dict = sys_notice_type.value.find(item => item.value === type)
-  return dict ? dict.label : type
-}
-
-const markAsRead = (noticeId) => {
-  show({noticeId}).then(response => {
-    getlistNotice()
+    position: 'top-right',
+    customClass: 'custom-notification'
   })
 }
 
 function showDetail() {
-  //getlistNotice()
   dialogVisible.value = true
 }
 
-
-const appStore = useAppStore()
-const userStore = useUserStore()
-const settingsStore = useSettingsStore()
-
-const noticeList = ref([]);
-const loading = ref(true);
-// 定义响应式变量
-const noticeContentV = ref('暂无消息'); // 通知内容
-const noticeContent = ref(''); // 通知内容
-const noticeCount = ref(0); // 通知数量
-const intervalId = ref(null); // 定时器 ID
-const notice_id = ref(null); // 通知 ID
-const dialogVisible = ref(false); // 控制弹框显示/隐藏
-const dialogVisible2 = ref(false); // 控制弹框显示/隐藏
-const noticeTitle = ref(''); // 通知标题
-const noticeType = ref(''); // 通知类型
-const uniqueKey = ref(0); // 新增的唯一键属性
-const defaultSort = ref({prop: 'create_time', order: 'descending'}) // 默认排序
-
 function toggleSideBar() {
   appStore.toggleSideBar()
 }
@@ -230,13 +409,13 @@ function toggleSideBar() {
 function handleCommand(command) {
   switch (command) {
     case "setLayout":
-      setLayout();
-      break;
+      setLayout()
+      break
     case "logout":
-      logout();
-      break;
+      logout()
+      break
     default:
-      break;
+      break
   }
 }
 
@@ -244,122 +423,147 @@ function logout() {
   ElMessageBox.confirm('确定注销并退出系统吗?', '提示', {
     confirmButtonText: '确定',
     cancelButtonText: '取消',
-    type: 'warning'
+    type: 'warning',
+    customClass: 'custom-message-box'
   }).then(() => {
     userStore.logOut().then(() => {
-      location.href = '/index';
+      location.href = '/index'
     })
-  }).catch(() => {
-  });
+  }).catch(() => {})
 }
 
-// function close() {
-//   dialogVisible.value = false;
-//   show({ noticeId: notice_id.value }).then(response => {
-//     getlistNotice();
-//   })
-// }
 function readInfo(row) {
-  // dialogVisible.value = false;     // 关闭公告列表弹框
-  // dialogVisible2.value = false;    // 同时关闭公告详情弹框
-  show({noticeId: row.notice_id}).then(response => {
-    getlistNotice();
-  });
-  txt("已读公告信息", "您已成功读取公告信息",'success',3000);
+  show({ noticeId: row.notice_id }).then(response => {
+    getlistNotice()
+    txt("操作成功", "已标记为已读", 'success', 2000)
+  })
 }
+
 function readInfo2() {
-  //dialogVisible.value = false;     // 关闭公告列表弹框
-  dialogVisible2.value = false;    // 同时关闭公告详情弹框
-  show({noticeId: notice_id.value}).then(response => {
-    getlistNotice();
-  });
-  txt("已读公告信息", "您已成功读取公告信息",'success',3000);
+  dialogVisible2.value = false
+  show({ noticeId: notice_id.value }).then(response => {
+    getlistNotice()
+    txt("操作成功", "已标记为已读", 'success', 2000)
+  })
+}
+
+function markAllAsRead() {
+  const unreadNotices = noticeList.value.filter(item => !item.isRead)
+  if (unreadNotices.length === 0) {
+    txt("提示", "没有未读消息", 'info', 2000)
+    return
+  }
+
+  ElMessageBox.confirm(`确定要将所有 ${unreadNotices.length} 条未读消息标记为已读吗?`, '批量操作确认', {
+    confirmButtonText: '确定',
+    cancelButtonText: '取消',
+    type: 'warning'
+  }).then(() => {
+    // 这里应该调用批量标记已读的API,暂时用循环模拟
+    unreadNotices.forEach(notice => {
+      show({ noticeId: notice.notice_id })
+    })
+    getlistNotice()
+    txt("操作成功", `已将 ${unreadNotices.length} 条消息标记为已读`, 'success', 3000)
+  })
 }
 
 function closeListDialog() {
-  dialogVisible.value = false;
+  dialogVisible.value = false
 }
 
 function closeDetailDialog() {
-  dialogVisible2.value = false;
+  dialogVisible2.value = false
 }
-const queryParams = ref({
-  pageNum: 1,
-  pageSize: 10000,
-  orderByColumn: 'create_time',
-  isAsc: 'desc'
-});
-getlistNotice()
 
 function getlistNotice() {
-  loading.value = true;
+  loading.value = true
   listNoticeRead(queryParams.value).then((response) => {
-    // 更新唯一键以强制重新渲染
-    noticeCount.value = response.data.length;
-
-    loading.value = false;
-    noticeList.value = response.data;
-    noticeContent.value = "您有" + noticeCount.value + "条未读的信息 (点击铃铛查看消息)";
-  });
+    noticeCount.value = response.data.length
+    loading.value = false
+    noticeList.value = response.data
+    noticeContent.value = noticeCount.value > 0
+        ? `您有 ${noticeCount.value} 条未读消息 (点击查看详情)`
+        : "暂无未读消息"
+  }).catch(() => {
+    loading.value = false
+  })
 }
-function handleSortChange({column, prop, order}) {
-  queryParams.value.orderByColumn = prop;
-  queryParams.value.isAsc = order === 'ascending' ? 'asc' : 'desc';
+
+function handleSortChange({ column, prop, order }) {
+  queryParams.value.orderByColumn = prop
+  queryParams.value.isAsc = order === 'ascending' ? 'asc' : 'desc'
   getlistNotice()
 }
 
 function handleUpdate(row) {
-  const noticeId = row.notice_id || ids.value;
+  const noticeId = row.notice_id
   getNotice(noticeId).then(response => {
-    //form.value = response.data;
-    dialogVisible2.value = true;
-    noticeContentV.value = response.data.noticeContent;
-    noticeTitle.value = response.data.noticeTitle;
-    noticeType.value = response.data.noticeType;
-    notice_id.value = response.data.noticeId;
-  });
+    dialogVisible2.value = true
+    noticeContentV.value = response.data.noticeContent
+    noticeTitle.value = response.data.noticeTitle
+    noticeType.value = response.data.noticeType
+    notice_id.value = response.data.noticeId
+  })
 }
 
-
 const emits = defineEmits(['setLayout'])
 
 function setLayout() {
-  emits('setLayout');
+  emits('setLayout')
 }
 
 function toggleTheme() {
   settingsStore.toggleTheme()
 }
 
-function txt(title,msg,type,time) {
+function txt(title, msg, type, time) {
   ElNotification({
     title: title,
     message: msg,
     type: type,
     duration: time,
     position: 'top-right'
-  });
+  })
 }
+
+// 初始化
+getlistNotice()
 </script>
 
 <style lang='scss' scoped>
 .navbar {
-  height: 50px;
+  height: 60px;
   overflow: hidden;
   position: relative;
-  background: var(--navbar-bg);
-  box-shadow: 0 1px 4px rgba(0, 21, 41, 0.08);
+  background: linear-gradient(135deg, var(--el-bg-color) 0%, var(--el-bg-color-page) 100%);
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+  border-bottom: 1px solid var(--el-border-color-lighter);
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 0 20px;
+  transition: all 0.3s ease;
+
+  .navbar-left {
+    display: flex;
+    align-items: center;
+    gap: 16px;
+  }
 
   .hamburger-container {
     line-height: 46px;
     height: 100%;
     float: left;
     cursor: pointer;
-    transition: background 0.3s;
+    transition: all 0.3s ease;
+    border-radius: 8px;
+    padding: 0 8px;
     -webkit-tap-highlight-color: transparent;
 
     &:hover {
-      background: rgba(0, 0, 0, 0.025);
+      background: var(--el-color-primary-light-9);
+      transform: scale(1.05);
     }
   }
 
@@ -372,75 +576,868 @@ function txt(title,msg,type,time) {
     left: 50px;
   }
 
-  .errLog-container {
-    display: inline-block;
-    vertical-align: top;
-  }
-
   .right-menu {
     float: right;
     height: 100%;
-    line-height: 50px;
+    line-height: 0px;
     display: flex;
+    align-items: center;
+    gap: 1px;
 
     &:focus {
       outline: none;
     }
 
     .right-menu-item {
-      display: inline-block;
-      padding: 0 8px;
-      height: 100%;
-      font-size: 18px;
-      color: var(--navbar-text);
+      display: inline-flex;
+      align-items: center;
+      justify-content: center;
+      padding: 8px 12px;
+      height: 40px;
+      font-size: 16px;
+      color: var(--el-text-color-regular);
       vertical-align: text-bottom;
+      border-radius: 8px;
+      transition: all 0.3s ease;
+      position: relative;
 
       &.hover-effect {
         cursor: pointer;
-        transition: background 0.3s;
 
         &:hover {
-          background: rgba(0, 0, 0, 0.025);
+          background: var(--el-color-primary-light-9);
+          color: var(--el-color-primary);
+          transform: translateY(-1px);
+          box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
         }
       }
+    }
 
-      &.theme-switch-wrapper {
-        display: flex;
-        align-items: center;
+    .notification-wrapper {
+      .notification-item {
+        cursor: pointer;
+        position: relative;
 
-        svg {
-          transition: transform 0.3s;
+        .notification-bell {
+          position: relative;
+          display: flex;
+          align-items: center;
+          justify-content: center;
 
-          &:hover {
-            transform: scale(1.15);
+          .bell-icon {
+            font-size: 18px;
+            transition: all 0.3s ease;
+
+            &.bell-shake {
+              animation: bellShake 2s infinite;
+            }
+          }
+
+          .notification-dot {
+            position: absolute;
+            top: -2px;
+            right: -2px;
+            width: 8px;
+            height: 8px;
+            background: var(--el-color-danger);
+            border-radius: 50%;
+            animation: pulse 2s infinite;
+          }
+        }
+
+        &:hover {
+          .bell-icon {
+            color: var(--el-color-primary);
+            transform: scale(1.1);
           }
         }
       }
+
+      .badge-container {
+        // 修复徽章位置问题
+        :deep(.el-badge) {
+          display: inline-block;
+          position: relative;
+        }
+
+        :deep(.el-badge__content) {
+          background: linear-gradient(45deg, var(--el-color-danger), #ff6b6b);
+          border: 2px solid var(--el-bg-color);
+          font-weight: 600;
+          font-size: 10px;
+          min-width: 18px;
+          height: 18px;
+          line-height: 14px;
+          // 关键修复:直接定位到右上角,禁用动画
+          position: absolute !important;
+          margin-top: 0px !important;
+          margin-right: -10px !important;
+          transform: translate(50%, -50%) !important;
+          transition: none !important; // 禁用过渡动画
+          animation: none !important; // 禁用初始动画
+
+          // 如果你想要数字变化时的动画,可以只保留这个
+          &.el-badge__content--is-fixed {
+            animation: badgeBounceIn 0.3s ease-out;
+          }
+        }
+
+        // 当徽章内容更新时的动画(可选)
+        :deep(.el-badge__content.is-dot) {
+          width: 8px;
+          height: 8px;
+          right: 5px;
+          top: 5px;
+        }
+      }
     }
 
-    .avatar-container {
-      margin-right: 40px;
+    .theme-switch-wrapper {
+      .theme-icon {
+        transition: all 0.3s ease;
+      }
 
-      .avatar-wrapper {
-        margin-top: 5px;
-        position: relative;
+      .theme-rotate-enter-active,
+      .theme-rotate-leave-active {
+        transition: all 0.3s ease;
+      }
+
+      .theme-rotate-enter-from {
+        opacity: 0;
+        transform: rotate(-180deg) scale(0.8);
+      }
 
-        .user-avatar {
+      .theme-rotate-leave-to {
+        opacity: 0;
+        transform: rotate(180deg) scale(0.8);
+      }
+
+      &:hover .theme-icon {
+        transform: scale(1.15) rotate(15deg);
+      }
+    }
+
+    .avatar-container {
+      margin-left: 16px;
+
+      .user-dropdown {
+        .avatar-wrapper {
+          display: flex;
+          align-items: center;
+          gap: 12px;
+          padding: 6px 12px;
+          border-radius: 20px;
           cursor: pointer;
-          width: 40px;
-          height: 40px;
-          border-radius: 10px;
+          transition: all 0.3s ease;
+          border: 1px solid transparent;
+
+          &:hover {
+            background: var(--el-color-primary-light-9);
+            border-color: var(--el-color-primary-light-7);
+            transform: translateY(-1px);
+            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+          }
+
+          .avatar-image-wrapper {
+            position: relative;
+
+            .user-avatar {
+              width: 36px;
+              height: 36px;
+              border-radius: 50%;
+              border: 2px solid var(--el-color-primary-light-8);
+              transition: all 0.3s ease;
+              object-fit: cover;
+            }
+
+            .avatar-status {
+              position: absolute;
+              bottom: 0;
+              right: 0;
+              width: 10px;
+              height: 10px;
+              background: var(--el-color-success);
+              border: 2px solid var(--el-bg-color);
+              border-radius: 50%;
+            }
+          }
+
+          .user-info {
+            display: flex;
+            flex-direction: column;
+            align-items: flex-start;
+
+            .user-name {
+              font-size: 14px;
+              font-weight: 600;
+              color: var(--el-text-color-primary);
+              line-height: 1.2;
+            }
+
+            .user-role {
+              font-size: 12px;
+              color: var(--el-text-color-secondary);
+              line-height: 1.2;
+            }
+          }
+
+          .dropdown-arrow {
+            font-size: 12px;
+            color: var(--el-text-color-secondary);
+            transition: transform 0.3s ease;
+          }
+
+          &:hover {
+            .user-avatar {
+              border-color: var(--el-color-primary);
+              transform: scale(1.05);
+            }
+
+            .dropdown-arrow {
+              transform: rotate(180deg);
+            }
+          }
         }
+      }
+    }
+  }
+}
 
-        i {
-          cursor: pointer;
-          position: absolute;
-          right: -20px;
-          top: 25px;
-          font-size: 12px;
+// 下拉菜单样式
+:deep(.user-dropdown-menu) {
+  min-width: 180px;
+  border-radius: 12px;
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
+  border: 1px solid var(--el-border-color-lighter);
+  padding: 8px 0;
+
+  .dropdown-item-custom {
+    padding: 12px 16px;
+    margin: 2px 8px;
+    border-radius: 8px;
+    transition: all 0.3s ease;
+
+    .dropdown-link {
+      display: flex;
+      align-items: center;
+      gap: 10px;
+      color: inherit;
+      text-decoration: none;
+      width: 100%;
+    }
+
+    .dropdown-icon {
+      font-size: 16px;
+      color: var(--el-text-color-secondary);
+    }
+
+    &:hover {
+      background: var(--el-color-primary-light-9);
+      color: var(--el-color-primary);
+
+      .dropdown-icon {
+        color: var(--el-color-primary);
+      }
+    }
+
+    &.logout-item {
+      &:hover {
+        background: var(--el-color-danger-light-9);
+        color: var(--el-color-danger);
+
+        .dropdown-icon {
+          color: var(--el-color-danger);
         }
       }
     }
   }
 }
+
+// 通知列表弹窗样式
+:deep(.notice-list-dialog) {
+  .el-dialog {
+    border-radius: 16px;
+    overflow: hidden;
+    // 设置弹窗最大高度
+    max-height: 90vh;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .el-dialog__header {
+    background: linear-gradient(135deg, var(--el-color-primary-light-9), var(--el-color-primary-light-8));
+    padding: 20px 24px;
+    border-bottom: 1px solid var(--el-border-color-lighter);
+    flex-shrink: 0; // 防止头部被压缩
+
+    .el-dialog__title {
+      font-size: 18px;
+      font-weight: 600;
+      color: var(--el-color-primary);
+    }
+  }
+
+  .el-dialog__body {
+    padding: 24px;
+    flex: 1;
+    overflow: hidden;
+    display: flex;
+    flex-direction: column;
+  }
+
+  .el-dialog__footer {
+    flex-shrink: 0; // 防止底部被压缩
+  }
+
+  .dialog-header {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    margin-bottom: 20px;
+    padding: 16px;
+    background: var(--el-bg-color-page);
+    border-radius: 12px;
+    border: 1px solid var(--el-border-color-lighter);
+    flex-shrink: 0; // 防止被压缩
+
+    .header-info {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      color: var(--el-text-color-regular);
+
+      .info-icon {
+        color: var(--el-color-primary);
+        font-size: 16px;
+      }
+    }
+
+    .header-actions {
+      .el-button {
+        border-radius: 8px;
+      }
+    }
+  }
+
+  // 关键修改:设置表格容器的固定高度和滚动
+  .notice-table {
+    border-radius: 12px;
+    overflow: hidden;
+    border: 1px solid var(--el-border-color-lighter);
+    flex: 1;
+    display: flex;
+    flex-direction: column;
+
+    // 设置表格的最大高度和滚动
+    :deep(.el-table) {
+      height: 100%;
+      max-height: 500px; // 设置最大高度
+      display: flex;
+      flex-direction: column;
+    }
+
+    :deep(.el-table__header-wrapper) {
+      flex-shrink: 0;
+    }
+
+    :deep(.el-table__body-wrapper) {
+      flex: 1;
+      overflow-y: auto; // 垂直滚动
+      overflow-x: auto; // 水平滚动(如果需要)
+      max-height: 400px; // 表格内容区域最大高度
+
+      // 自定义滚动条样式
+      &::-webkit-scrollbar {
+        width: 8px;
+        height: 8px;
+      }
+
+      &::-webkit-scrollbar-track {
+        background: var(--el-fill-color-lighter);
+        border-radius: 4px;
+        margin: 2px;
+      }
+
+      &::-webkit-scrollbar-thumb {
+        background: var(--el-border-color-dark);
+        border-radius: 4px;
+        transition: background 0.3s ease;
+
+        &:hover {
+          background: var(--el-border-color-darker);
+        }
+      }
+
+      &::-webkit-scrollbar-corner {
+        background: var(--el-fill-color-lighter);
+      }
+
+      // 滚动条在暗色主题下的样式
+      @media (prefers-color-scheme: dark) {
+        &::-webkit-scrollbar-track {
+          background: rgba(255, 255, 255, 0.1);
+        }
+
+        &::-webkit-scrollbar-thumb {
+          background: rgba(255, 255, 255, 0.3);
+
+          &:hover {
+            background: rgba(255, 255, 255, 0.5);
+          }
+        }
+      }
+    }
+
+    // 表格行样式保持不变
+    .notice-title-cell {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+
+      .unread-tag {
+        flex-shrink: 0;
+        font-size: 10px;
+        padding: 2px 6px;
+        border-radius: 4px;
+        font-weight: 600;
+      }
+
+      .title-text {
+        &.unread-title {
+          font-weight: 600;
+          color: var(--el-text-color-primary);
+        }
+      }
+    }
+
+    .creator-cell {
+      display: flex;
+      align-items: center;
+      gap: 8px;
+      justify-content: center;
+
+      .creator-avatar {
+        font-size: 12px;
+        font-weight: 600;
+        background: var(--el-color-primary-light-8);
+        color: var(--el-color-primary);
+      }
+    }
+
+    .time-cell {
+      display: flex;
+      align-items: center;
+      gap: 6px;
+      justify-content: center;
+
+      .time-icon {
+        color: var(--el-text-color-secondary);
+        font-size: 14px;
+      }
+    }
+
+    .action-buttons {
+      display: flex;
+      gap: 8px;
+      justify-content: center;
+
+      .el-button {
+        border-radius: 6px;
+        padding: 4px 8px;
+
+        .el-icon {
+          margin-right: 4px;
+        }
+      }
+    }
+  }
+
+  .dialog-footer {
+    display: flex;
+    justify-content: flex-end;
+    padding: 16px 0 0;
+    border-top: 1px solid var(--el-border-color-lighter);
+    margin-top: 16px;
+  }
+}
+
+// 通知详情弹窗样式
+:deep(.notice-detail-dialog) {
+  .el-dialog {
+    border-radius: 16px;
+    overflow: hidden;
+  }
+
+  .el-dialog__header {
+    background: linear-gradient(135deg, var(--el-color-success-light-9), var(--el-color-success-light-8));
+    padding: 20px 24px;
+    border-bottom: 1px solid var(--el-border-color-lighter);
+
+    .el-dialog__title {
+      font-size: 18px;
+      font-weight: 600;
+      color: var(--el-color-success);
+    }
+  }
+
+  .el-dialog__body {
+    padding: 24px;
+  }
+
+  .detail-content {
+    .notice-form {
+      .el-form-item {
+        margin-bottom: 24px;
+
+        .el-form-item__label {
+          font-weight: 600;
+          color: var(--el-text-color-primary);
+        }
+
+        .form-item-content {
+          display: flex;
+          align-items: center;
+          gap: 12px;
+
+          .form-icon {
+            color: var(--el-color-primary);
+            font-size: 16px;
+            flex-shrink: 0;
+          }
+
+          .readonly-input {
+            flex: 1;
+
+            :deep(.el-input__wrapper) {
+              background-color: var(--el-fill-color-light);
+              border: 1px solid var(--el-border-color-light);
+              border-radius: 8px;
+              box-shadow: none;
+
+              .el-input__inner {
+                color: var(--el-text-color-primary);
+                font-weight: 500;
+              }
+            }
+          }
+
+          .readonly-select {
+            flex: 1;
+
+            :deep(.el-select__wrapper) {
+              background-color: var(--el-fill-color-light);
+              border: 1px solid var(--el-border-color-light);
+              border-radius: 8px;
+              box-shadow: none;
+            }
+          }
+        }
+
+        .content-wrapper {
+          .content-editor {
+            border: 1px solid var(--el-border-color-light);
+            border-radius: 8px;
+            overflow: hidden;
+            background-color: var(--el-fill-color-light);
+
+            :deep(.ql-editor) {
+              padding: 16px;
+              min-height: 200px;
+              font-size: 14px;
+              line-height: 1.6;
+              color: var(--el-text-color-primary);
+            }
+          }
+        }
+      }
+    }
+  }
+
+  .dialog-footer {
+    display: flex;
+    justify-content: flex-end;
+    gap: 12px;
+    padding: 16px 0 0;
+    border-top: 1px solid var(--el-border-color-lighter);
+
+    .el-button {
+      border-radius: 8px;
+      padding: 8px 20px;
+
+      .el-icon {
+        margin-right: 6px;
+      }
+    }
+  }
+}
+
+// 动画效果
+@keyframes bellShake {
+  0%, 50%, 100% {
+    transform: rotate(0deg);
+  }
+  10%, 30% {
+    transform: rotate(-10deg);
+  }
+  20%, 40% {
+    transform: rotate(10deg);
+  }
+}
+
+@keyframes pulse {
+  0% {
+    transform: scale(1);
+    opacity: 1;
+  }
+  50% {
+    transform: scale(1.2);
+    opacity: 0.7;
+  }
+  100% {
+    transform: scale(1);
+    opacity: 1;
+  }
+}
+
+// 自定义通知样式
+:deep(.custom-notification) {
+  border-radius: 12px;
+  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12);
+  border: 1px solid var(--el-border-color-lighter);
+
+  .el-notification__title {
+    font-weight: 600;
+  }
+
+  .el-notification__content {
+    font-size: 14px;
+    line-height: 1.5;
+  }
+}
+
+// 自定义消息框样式
+:deep(.custom-message-box) {
+  border-radius: 16px;
+  overflow: hidden;
+
+  .el-message-box__header {
+    background: var(--el-bg-color-page);
+    padding: 20px 24px 16px;
+  }
+
+  .el-message-box__title {
+    font-weight: 600;
+    color: var(--el-text-color-primary);
+  }
+
+  .el-message-box__content {
+    padding: 16px 24px 20px;
+  }
+
+  .el-message-box__btns {
+    padding: 16px 24px 20px;
+    border-top: 1px solid var(--el-border-color-lighter);
+
+    .el-button {
+      border-radius: 8px;
+      padding: 8px 20px;
+    }
+  }
+}
+
+// 响应式设计
+@media (max-width: 768px) {
+  .navbar {
+    height: 50px;
+    padding: 0 12px;
+
+    .right-menu {
+      gap: 4px;
+
+      .right-menu-item {
+        padding: 6px 8px;
+        height: 36px;
+        font-size: 14px;
+      }
+
+      .avatar-container {
+        margin-left: 8px;
+
+        .user-dropdown .avatar-wrapper {
+          padding: 4px 8px;
+          gap: 8px;
+
+          .avatar-image-wrapper .user-avatar {
+            width: 32px;
+            height: 32px;
+          }
+
+          .user-info {
+            display: none;
+          }
+        }
+      }
+    }
+  }
+
+  :deep(.notice-list-dialog) {
+    .el-dialog {
+      width: 95% !important;
+      margin: 5vh auto;
+    }
+
+    .dialog-header {
+      flex-direction: column;
+      gap: 12px;
+      align-items: stretch;
+    }
+
+    .notice-table {
+      .el-table__body-wrapper {
+        overflow-x: auto;
+      }
+    }
+  }
+
+  :deep(.notice-detail-dialog) {
+    .el-dialog {
+      width: 95% !important;
+      margin: 5vh auto;
+    }
+  }
+}
+
+// 暗色主题适配
+@media (prefers-color-scheme: dark) {
+  .navbar {
+    background: linear-gradient(135deg, var(--el-bg-color) 0%, var(--el-bg-color-page) 100%);
+    border-bottom-color: var(--el-border-color);
+    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.2);
+  }
+
+  :deep(.notice-list-dialog) {
+    .el-dialog__header {
+      background: linear-gradient(135deg, var(--el-color-primary-dark-2), var(--el-color-primary-light-3));
+    }
+  }
+
+  :deep(.notice-detail-dialog) {
+    .el-dialog__header {
+      background: linear-gradient(135deg, var(--el-color-success-dark-2), var(--el-color-success-light-3));
+    }
+  }
+}
+
+// 表格行悬停效果
+:deep(.notice-table) {
+  .el-table__row {
+    transition: all 0.3s ease;
+
+    &:hover {
+      background-color: var(--el-color-primary-light-9) !important;
+      transform: translateY(-1px);
+      box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
+    }
+  }
+
+  .el-table__header {
+    .el-table__cell {
+      background-color: var(--el-bg-color-page);
+      color: var(--el-text-color-primary);
+      font-weight: 600;
+    }
+  }
+}
+
+// 加载状态优化
+:deep(.el-loading-mask) {
+  background-color: rgba(255, 255, 255, 0.8);
+  backdrop-filter: blur(4px);
+
+  .el-loading-spinner {
+    .el-loading-text {
+      color: var(--el-color-primary);
+      font-weight: 500;
+    }
+  }
+}
+
+// 滚动条样式
+:deep(.el-table__body-wrapper) {
+  &::-webkit-scrollbar {
+    width: 6px;
+    height: 6px;
+  }
+
+  &::-webkit-scrollbar-track {
+    background: var(--el-fill-color-lighter);
+    border-radius: 3px;
+  }
+
+  &::-webkit-scrollbar-thumb {
+    background: var(--el-border-color-dark);
+    border-radius: 3px;
+
+    &:hover {
+      background: var(--el-border-color-darker);
+    }
+  }
+}
+
+// 徽章数字动画
+:deep(.el-badge__content) {
+  animation: badgeBounce 0.5s ease-out;
+}
+
+@keyframes badgeBounce {
+  0% {
+    transform: scale(0);
+  }
+  50% {
+    transform: scale(1.2);
+  }
+  100% {
+    transform: scale(1);
+  }
+}
+
+// 按钮悬停效果增强
+.el-button {
+  transition: all 0.3s ease;
+
+  &:hover {
+    transform: translateY(-1px);
+    box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
+  }
+
+  &:active {
+    transform: translateY(0);
+  }
+}
+
+// 输入框聚焦效果
+:deep(.el-input__wrapper) {
+  transition: all 0.3s ease;
+
+  &:hover {
+    box-shadow: 0 0 0 1px var(--el-color-primary-light-7);
+  }
+
+  &.is-focus {
+    box-shadow: 0 0 0 2px var(--el-color-primary-light-3);
+  }
+}
+
+// 选择器悬停效果
+:deep(.el-select__wrapper) {
+  transition: all 0.3s ease;
+
+  &:hover {
+    box-shadow: 0 0 0 1px var(--el-color-primary-light-7);
+  }
+
+  &.is-focus {
+    box-shadow: 0 0 0 2px var(--el-color-primary-light-3);
+  }
+}
 </style>

+ 620 - 141
pm_ui/src/views/device/scheduleManagement/index.vue

@@ -2,25 +2,27 @@
   <div class="timetable-management-container">
     <!-- 顶部导航栏 -->
     <el-card class="header-card">
-      <el-tabs v-model="viewMode" type="card" @tab-click="handleTabClick">
-        <el-tab-pane label="日历视图" name="calendar"></el-tab-pane>
-        <el-tab-pane label="列表视图" name="list"></el-tab-pane>
-        <el-tab-pane label="执行历史" name="history"></el-tab-pane>
-      </el-tabs>
-      <el-button type="primary" @click="addTimetable">新增时间表</el-button>
+      <div class="header-content">
+        <el-tabs v-model="viewMode" type="card" @tab-click="handleTabClick">
+          <el-tab-pane label="日历视图" name="calendar"></el-tab-pane>
+          <el-tab-pane label="列表视图" name="list"></el-tab-pane>
+          <el-tab-pane label="执行历史" name="history"></el-tab-pane>
+        </el-tabs>
+        <el-button type="primary" @click="addTimetable">新增时间表</el-button>
+      </div>
     </el-card>
 
     <!-- 日历视图 -->
     <div v-if="viewMode === 'calendar'" class="calendar-view">
       <el-calendar v-model="currentDate">
-        <template #dateCell="{ data }">
+        <template #date-cell="{ data }">
           <div class="date-cell" @click="handleDateClick(data.day)" @contextmenu.prevent="showContextMenu(data.day, $event)">
             <p :class="{'highlight': isToday(data.day), 'event-day': hasEvents(data.day)}">
               {{ data.day.split('-').slice(2).join('-') }}
             </p>
             <div v-if="hasEvents(data.day)" class="events">
               <el-tag
-                  v-for="(event, index) in getEvents(data.day)"
+                  v-for="(event, index) in getEvents(data.day).slice(0, 3)"
                   :key="index"
                   :type="getTagType(event.status)"
                   size="small"
@@ -30,10 +32,13 @@
               >
                 {{ event.title }}
               </el-tag>
+              <el-tag v-if="getEvents(data.day).length > 3" size="small" type="info">
+                +{{ getEvents(data.day).length - 3 }}
+              </el-tag>
             </div>
             <el-button
                 v-if="!hasEvents(data.day)"
-                size="mini"
+                size="small"
                 type="text"
                 @click.stop="addTimetableForDay(data.day)"
             >
@@ -42,195 +47,345 @@
           </div>
         </template>
       </el-calendar>
+
       <!-- 右键菜单 -->
-      <el-dropdown
+      <div
           v-if="contextMenuVisible"
-          trigger="manual"
-          :visible="contextMenuVisible"
-          @command="handleContextCommand"
-          @hide="contextMenuVisible = false"
-          :style="{ position: 'absolute', left: contextMenuX + 'px', top: contextMenuY + 'px' }"
+          class="context-menu"
+          :style="{ left: contextMenuX + 'px', top: contextMenuY + 'px' }"
+          @click.stop
       >
-        <template #dropdown>
-          <el-dropdown-menu>
-            <el-dropdown-item command="add">新增时间表</el-dropdown-item>
-            <el-dropdown-item v-if="selectedDayEvents.length > 0" command="view">查看事件</el-dropdown-item>
-          </el-dropdown-menu>
-        </template>
-      </el-dropdown>
+        <div class="menu-item" @click="handleContextCommand('add')">新增时间表</div>
+        <div v-if="selectedDayEvents.length > 0" class="menu-item" @click="handleContextCommand('viewAll')">
+          查看所有事件 ({{ selectedDayEvents.length }})
+        </div>
+      </div>
     </div>
 
-    <!-- 列表图 -->
+    <!-- 列表图 -->
     <div v-if="viewMode === 'list'" class="list-view">
-      <el-table :data="timetableList" border style="width: 100%" @row-click="viewEvent">
+      <!-- 搜索栏 -->
+      <el-form :inline="true" class="search-form">
+        <el-form-item label="标题">
+          <el-input v-model="searchParams.title" placeholder="请输入标题" clearable />
+        </el-form-item>
+        <el-form-item label="时间范围">
+          <el-date-picker
+              v-model="searchParams.dateRange"
+              type="datetimerange"
+              range-separator="至"
+              start-placeholder="开始时间"
+              end-placeholder="结束时间"
+              value-format="YYYY-MM-DD HH:mm:ss"
+              clearable
+          />
+        </el-form-item>
+        <el-form-item label="状态">
+          <el-select v-model="searchParams.status" placeholder="请选择状态" clearable>
+            <el-option label="成功" value="success" />
+            <el-option label="失败" value="failed" />
+            <el-option label="待执行" value="pending" />
+          </el-select>
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleSearch">搜索</el-button>
+          <el-button @click="resetSearch">重置</el-button>
+        </el-form-item>
+      </el-form>
+
+      <el-table :data="timetableList" border style="width: 100%" v-loading="loading">
         <el-table-column prop="id" label="ID" align="center" width="80" />
         <el-table-column prop="title" label="标题" align="center" />
-        <el-table-column prop="startTime" label="开始时间" align="center" width="180" />
-        <el-table-column prop="endTime" label="结束时间" align="center" width="180" />
-        <el-table-column prop="devices" label="设备" align="center" show-overflow-tooltip />
-        <el-table-column prop="commands" label="指令" align="center" show-overflow-tooltip />
-        <el-table-column label="操作" align="center" width="150">
+        <el-table-column prop="startTime" label="开始时间" align="center" width="180" sortable />
+        <el-table-column prop="endTime" label="结束时间" align="center" width="180" sortable />
+        <el-table-column prop="devices" label="设备" align="center">
+          <template #default="{ row }">
+            <el-tag v-for="device in row.devices" :key="device" size="small" style="margin: 0 2px;">
+              {{ getDeviceName(device) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column prop="status" label="状态" align="center" width="100">
+          <template #default="{ row }">
+            <el-tag :type="getTagType(row.status)">{{ getStatusText(row.status) }}</el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="200" fixed="right">
           <template #default="{ row }">
+            <el-button size="small" @click.stop="viewEvent(row)">查看</el-button>
             <el-button size="small" @click.stop="editTimetable(row)">编辑</el-button>
             <el-button size="small" type="danger" @click.stop="deleteTimetable(row)">删除</el-button>
           </template>
         </el-table-column>
       </el-table>
+
       <!-- 分页 -->
       <el-pagination
           v-if="total > 0"
           background
-          layout="prev, pager, next"
+          layout="total, sizes, prev, pager, next, jumper"
           :total="total"
+          :page-sizes="[10, 20, 50, 100]"
           :page-size="queryParams.pageSize"
           :current-page="queryParams.pageNumber"
+          @size-change="handleSizeChange"
           @current-change="handlePageChange"
       />
     </div>
 
     <!-- 执行历史视图 -->
     <div v-if="viewMode === 'history'" class="history-view">
-      <el-table :data="executionHistory" border style="width: 100%" @row-click="viewExecutionDetail">
+      <el-table :data="executionHistory" border style="width: 100%" v-loading="historyLoading">
         <el-table-column prop="id" label="ID" align="center" width="80" />
-        <el-table-column prop="timetableId" label="时间表ID" align="center" />
-        <el-table-column prop="executionTime" label="执行时间" align="center" width="180" />
-        <el-table-column prop="status" label="状态" align="center" />
+        <el-table-column prop="timetableId" label="时间表ID" align="center" width="100" />
+        <el-table-column prop="timetableTitle" label="时间表标题" align="center" />
+        <el-table-column prop="executionTime" label="执行时间" align="center" width="180" sortable />
+        <el-table-column prop="status" label="状态" align="center" width="100">
+          <template #default="{ row }">
+            <el-tag :type="row.status === '成功' ? 'success' : 'danger'">{{ row.status }}</el-tag>
+          </template>
+        </el-table-column>
         <el-table-column prop="result" label="结果" align="center" show-overflow-tooltip />
+        <el-table-column label="操作" align="center" width="100">
+          <template #default="{ row }">
+            <el-button size="small" @click="viewExecutionDetail(row)">详情</el-button>
+          </template>
+        </el-table-column>
       </el-table>
+
       <!-- 分页 -->
       <el-pagination
           v-if="historyTotal > 0"
           background
-          layout="prev, pager, next"
+          layout="total, sizes, prev, pager, next, jumper"
           :total="historyTotal"
+          :page-sizes="[10, 20, 50, 100]"
           :page-size="historyQueryParams.pageSize"
           :current-page="historyQueryParams.pageNumber"
+          @size-change="handleHistorySizeChange"
           @current-change="handleHistoryPageChange"
       />
     </div>
 
     <!-- 新增/编辑时间表对话框 -->
-    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="50%">
-      <el-form :model="timetableForm" label-width="120px">
-        <el-form-item label="标题">
-          <el-input v-model="timetableForm.title" placeholder="请输入标题" />
-        </el-form-item>
-        <el-form-item label="开始时间">
-          <el-date-picker
-              v-model="timetableForm.startTime"
-              type="datetime"
-              placeholder="选择开始时间"
-              value-format="YYYY-MM-DD HH:mm:ss"
-          />
+    <el-dialog v-model="dialogVisible" :title="dialogTitle" width="600px" :close-on-click-modal="false">
+      <el-form ref="timetableFormRef" :model="timetableForm" :rules="timetableRules" label-width="120px">
+        <el-form-item label="标题" prop="title">
+          <el-input v-model="timetableForm.title" placeholder="请输入标题" maxlength="50" show-word-limit />
         </el-form-item>
-        <el-form-item label="结束时间">
+        <el-form-item label="时间范围" prop="timeRange">
           <el-date-picker
-              v-model="timetableForm.endTime"
-              type="datetime"
-              placeholder="选择结束时间"
+              v-model="timeRange"
+              type="datetimerange"
+              range-separator="至"
+              start-placeholder="开始时间"
+              end-placeholder="结束时间"
               value-format="YYYY-MM-DD HH:mm:ss"
+              @change="handleTimeRangeChange"
           />
         </el-form-item>
-        <el-form-item label="设备">
-          <el-select v-model="timetableForm.devices" multiple placeholder="请选择设备">
+        <el-form-item label="设备" prop="devices">
+          <el-select v-model="timetableForm.devices" multiple placeholder="请选择设备" style="width: 100%">
             <el-option label="设备A" value="deviceA" />
             <el-option label="设备B" value="deviceB" />
             <el-option label="设备C" value="deviceC" />
           </el-select>
         </el-form-item>
-        <el-form-item label="指令">
-          <el-input v-model="timetableForm.commands" type="textarea" :rows="4" placeholder="请输入指令" />
+        <el-form-item label="指令" prop="commands">
+          <el-input v-model="timetableForm.commands" type="textarea" :rows="4" placeholder="请输入指令" maxlength="500" show-word-limit />
+        </el-form-item>
+        <el-form-item label="循环执行">
+          <el-switch v-model="timetableForm.isRecurring" />
+        </el-form-item>
+        <el-form-item v-if="timetableForm.isRecurring" label="循环类型" prop="recurringType">
+          <el-radio-group v-model="timetableForm.recurringType">
+            <el-radio label="daily">每天</el-radio>
+            <el-radio label="weekly">每周</el-radio>
+            <el-radio label="monthly">每月</el-radio>
+          </el-radio-group>
         </el-form-item>
       </el-form>
       <template #footer>
         <span class="dialog-footer">
           <el-button @click="dialogVisible = false">取消</el-button>
-          <el-button type="primary" @click="saveTimetable">保存</el-button>
+          <el-button type="primary" @click="saveTimetable" :loading="saveLoading">保存</el-button>
         </span>
       </template>
     </el-dialog>
 
     <!-- 查看时间表详情对话框 -->
-    <el-dialog v-model="detailDialogVisible" title="时间表详情" width="50%">
+    <el-dialog v-model="detailDialogVisible" title="时间表详情" width="600px">
       <el-descriptions :column="2" border>
         <el-descriptions-item label="ID">{{ selectedTimetable.id }}</el-descriptions-item>
         <el-descriptions-item label="标题">{{ selectedTimetable.title }}</el-descriptions-item>
         <el-descriptions-item label="开始时间">{{ selectedTimetable.startTime }}</el-descriptions-item>
         <el-descriptions-item label="结束时间">{{ selectedTimetable.endTime }}</el-descriptions-item>
-        <el-descriptions-item label="设备">{{ selectedTimetable.devices.join(', ') }}</el-descriptions-item>
-        <el-descriptions-item label="指令">{{ selectedTimetable.commands }}</el-descriptions-item>
+        <el-descriptions-item label="设备" :span="2">
+          <el-tag v-for="device in selectedTimetable.devices" :key="device" size="small" style="margin: 0 2px;">
+            {{ getDeviceName(device) }}
+          </el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="指令" :span="2">{{ selectedTimetable.commands }}</el-descriptions-item>
+        <el-descriptions-item label="状态">
+          <el-tag :type="getTagType(selectedTimetable.status)">{{ getStatusText(selectedTimetable.status) }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="创建时间">{{ selectedTimetable.createTime || '2025-07-01 08:00:00' }}</el-descriptions-item>
       </el-descriptions>
+      <template #footer>
+        <el-button @click="detailDialogVisible = false">关闭</el-button>
+        <el-button type="primary" @click="editFromDetail">编辑</el-button>
+      </template>
     </el-dialog>
 
     <!-- 查看执行历史详情对话框 -->
-    <el-dialog v-model="historyDetailDialogVisible" title="执行历史详情" width="50%">
+    <el-dialog v-model="historyDetailDialogVisible" title="执行历史详情" width="600px">
       <el-descriptions :column="2" border>
         <el-descriptions-item label="ID">{{ selectedExecution.id }}</el-descriptions-item>
         <el-descriptions-item label="时间表ID">{{ selectedExecution.timetableId }}</el-descriptions-item>
+        <el-descriptions-item label="时间表标题">{{ selectedExecution.timetableTitle }}</el-descriptions-item>
         <el-descriptions-item label="执行时间">{{ selectedExecution.executionTime }}</el-descriptions-item>
-        <el-descriptions-item label="状态">{{ selectedExecution.status }}</el-descriptions-item>
-        <el-descriptions-item label="结果">{{ selectedExecution.result }}</el-descriptions-item>
+        <el-descriptions-item label="状态">
+          <el-tag :type="selectedExecution.status === '成功' ? 'success' : 'danger'">{{ selectedExecution.status }}</el-tag>
+        </el-descriptions-item>
+        <el-descriptions-item label="耗时">{{ selectedExecution.duration || '120ms' }}</el-descriptions-item>
+        <el-descriptions-item label="结果" :span="2">{{ selectedExecution.result }}</el-descriptions-item>
+        <el-descriptions-item label="错误信息" :span="2" v-if="selectedExecution.status === '失败'">
+          <span style="color: #F56C6C;">{{ selectedExecution.errorMsg || '设备连接超时' }}</span>
+        </el-descriptions-item>
       </el-descriptions>
+      <template #footer>
+        <el-button @click="historyDetailDialogVisible = false">关闭</el-button>
+      </template>
+    </el-dialog>
+
+    <!-- 查看某天所有事件对话框 -->
+    <el-dialog v-model="dayEventsDialogVisible" :title="`${selectedDay} 的所有事件`" width="800px">
+      <el-table :data="selectedDayEvents" border style="width: 100%">
+        <el-table-column prop="title" label="标题" align="center" />
+        <el-table-column prop="startTime" label="开始时间" align="center" width="180" />
+        <el-table-column prop="endTime" label="结束时间" align="center" width="180" />
+        <el-table-column prop="devices" label="设备" align="center">
+          <template #default="{ row }">
+            <el-tag v-for="device in row.devices" :key="device" size="small" style="margin: 0 2px;">
+              {{ getDeviceName(device) }}
+            </el-tag>
+          </template>
+        </el-table-column>
+        <el-table-column label="操作" align="center" width="150">
+          <template #default="{ row }">
+            <el-button size="small" @click="viewEvent(row)">查看</el-button>
+            <el-button size="small" @click="editTimetable(row)">编辑</el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+      <template #footer>
+        <el-button @click="dayEventsDialogVisible = false">关闭</el-button>
+      </template>
     </el-dialog>
   </div>
 </template>
 
 <script setup>
-import { ref, onMounted } from 'vue'
-import { ElMessage } from 'element-plus'
+import { ref, onMounted, computed, watch, nextTick } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
 
-// 视图模式(日历视图或列表视图或执行历史)
+// 视图模式
 const viewMode = ref('calendar')
 // 当前日期
 const currentDate = ref(new Date())
 // 时间表列表数据
 const timetableList = ref([])
 const total = ref(0)
+const loading = ref(false)
 // 查询参数
 const queryParams = ref({
   pageNumber: 1,
   pageSize: 10
 })
+// 搜索参数
+const searchParams = ref({
+  title: '',
+  dateRange: null,
+  status: ''
+})
 // 执行历史数据
 const executionHistory = ref([])
 const historyTotal = ref(0)
+const historyLoading = ref(false)
 // 执行历史查询参数
 const historyQueryParams = ref({
   pageNumber: 1,
   pageSize: 10
 })
-// 控制弹窗显示和内容
+// 控制弹窗显示
 const dialogVisible = ref(false)
 const detailDialogVisible = ref(false)
 const historyDetailDialogVisible = ref(false)
+const dayEventsDialogVisible = ref(false)
 const dialogTitle = ref('新增时间表')
+const saveLoading = ref(false)
+// 表单引用
+const timetableFormRef = ref(null)
+// 时间范围
+const timeRange = ref([])
+// 表单数据
 const timetableForm = ref({
   id: '',
   title: '',
   startTime: '',
   endTime: '',
   devices: [],
-  commands: ''
+  commands: '',
+  isRecurring: false,
+  recurringType: 'daily'
 })
+// 表单验证规则
+const timetableRules = {
+  title: [
+    { required: true, message: '请输入标题', trigger: 'blur' },
+    { min: 2, max: 50, message: '标题长度在 2 到 50 个字符', trigger: 'blur' }
+  ],
+  timeRange: [
+    { required: true, message: '请选择时间范围', trigger: 'change' }
+  ],
+  devices: [
+    { required: true, message: '请选择设备', trigger: 'change' },
+    { type: 'array', min: 1, message: '至少选择一个设备', trigger: 'change' }
+  ],
+  commands: [
+    { required: true, message: '请输入指令', trigger: 'blur' },
+    { min: 1, max: 500, message: '指令长度在 1 到 500 个字符', trigger: 'blur' }
+  ],
+  recurringType: [
+    { required: true, message: '请选择循环类型', trigger: 'change' }
+  ]
+}
+// 选中的数据
 const selectedTimetable = ref({})
 const selectedExecution = ref({})
 // 模拟时间表数据
 const timetableMap = ref({
-  '2025-04-01': [
-    { id: 'T001', title: '设备A维护', startTime: '2025-04-01 08:00:00', endTime: '2025-04-01 10:00:00', devices: ['deviceA'], commands: '维护指令', status: 'success' },
-    { id: 'T002', title: '设备B检查', startTime: '2025-04-01 12:00:00', endTime: '2025-04-01 14:00:00', devices: ['deviceB'], commands: '检查指令', status: 'failed' }
+  '2025-07-01': [
+    { id: 'T001', title: '设备A维护', startTime: '2025-07-01 08:00:00', endTime: '2025-07-01 10:00:00', devices: ['deviceA'], commands: '维护指令', status: 'success' },
+    { id: 'T002', title: '设备B检查', startTime: '2025-07-01 12:00:00', endTime: '2025-07-01 14:00:00', devices: ['deviceB'], commands: '检查指令', status: 'failed' }
+  ],
+  '2025-07-02': [
+    { id: 'T003', title: '设备C清洗', startTime: '2025-07-02 09:00:00', endTime: '2025-07-02 11:00:00', devices: ['deviceC'], commands: '清洗指令', status: 'success' }
   ],
-  '2025-04-02': [
-    { id: 'T003', title: '设备C清洗', startTime: '2025-04-02 09:00:00', endTime: '2025-04-02 11:00:00', devices: ['deviceC'], commands: '清洗指令', status: 'success' }
+  '2025-07-15': [
+    { id: 'T006', title: '设备A例行检查', startTime: '2025-07-15 09:00:00', endTime: '2025-07-15 10:00:00', devices: ['deviceA'], commands: '检查指令', status: 'pending' },
+    { id: 'T007', title: '设备B维护', startTime: '2025-07-15 14:00:00', endTime: '2025-07-15 16:00:00', devices: ['deviceB'], commands: '维护指令', status: 'pending' },
+    { id: 'T008', title: '设备C测试', startTime: '2025-07-15 16:30:00', endTime: '2025-07-15 17:30:00', devices: ['deviceC'], commands: '测试指令', status: 'pending' },
+    { id: 'T009', title: '全设备检查', startTime: '2025-07-15 18:00:00', endTime: '2025-07-15 20:00:00', devices: ['deviceA', 'deviceB', 'deviceC'], commands: '全面检查', status: 'pending' }
   ]
 })
 // 模拟执行历史数据
 const executionHistoryMap = ref([
-  { id: 'H001', timetableId: 'T001', executionTime: '2025-04-01 08:00:00', status: '成功', result: '设备A维护完成' },
-  { id: 'H002', timetableId: 'T002', executionTime: '2025-04-01 12:00:00', status: '失败', result: '设备B检查异常' },
-  { id: 'H003', timetableId: 'T003', executionTime: '2025-04-02 09:00:00', status: '成功', result: '设备C清洗完成' },
-  { id: 'H004', timetableId: 'T001', executionTime: '2025-04-03 10:00:00', status: '成功', result: '设备A测试完成' },
-  { id: 'H005', timetableId: 'T002', executionTime: '2025-04-04 14:00:00', status: '成功', result: '设备B升级完成' }
+  { id: 'H001', timetableId: 'T001', timetableTitle: '设备A维护', executionTime: '2025-07-01 08:00:00', status: '成功', result: '设备A维护完成', duration: '120ms' },
+  { id: 'H002', timetableId: 'T002', timetableTitle: '设备B检查', executionTime: '2025-07-01 12:00:00', status: '失败', result: '设备B检查异常', errorMsg: '设备连接超时', duration: '5000ms' },
+  { id: 'H003', timetableId: 'T003', timetableTitle: '设备C清洗', executionTime: '2025-07-02 09:00:00', status: '成功', result: '设备C清洗完成', duration: '200ms' },
+  { id: 'H004', timetableId: 'T001', timetableTitle: '设备A维护', executionTime: '2025-07-03 10:00:00', status: '成功', result: '设备A测试完成', duration: '150ms' },
+  { id: 'H005', timetableId: 'T002', timetableTitle: '设备B检查', executionTime: '2025-07-04 14:00:00', status: '成功', result: '设备B升级完成', duration: '300ms' }
 ])
 // 右键菜单相关变量
 const contextMenuVisible = ref(false)
@@ -239,6 +394,30 @@ const contextMenuY = ref(0)
 const selectedDay = ref('')
 const selectedDayEvents = ref([])
 
+// 设备名称映射
+const deviceNameMap = {
+  'deviceA': '设备A',
+  'deviceB': '设备B',
+  'deviceC': '设备C'
+}
+
+// 状态文本映射
+const statusTextMap = {
+  'success': '成功',
+  'failed': '失败',
+  'pending': '待执行'
+}
+
+// 获取设备名称
+const getDeviceName = (deviceCode) => {
+  return deviceNameMap[deviceCode] || deviceCode
+}
+
+// 获取状态文本
+const getStatusText = (status) => {
+  return statusTextMap[status] || status
+}
+
 // 获取当前日期的时间表
 const getEvents = (day) => {
   return timetableMap.value[day] || []
@@ -262,6 +441,8 @@ const getTagType = (status) => {
       return 'success'
     case 'failed':
       return 'danger'
+    case 'pending':
+      return 'warning'
     default:
       return 'info'
   }
@@ -269,7 +450,7 @@ const getTagType = (status) => {
 
 // 获取事件提示信息
 const getTooltip = (event) => {
-  return `标题: ${event.title}\n开始时间: ${event.startTime}\n结束时间: ${event.endTime}\n设备: ${event.devices.join(', ')}\n指令: ${event.commands}`
+  return `标题: ${event.title}\n开始时间: ${event.startTime}\n结束时间: ${event.endTime}\n设备: ${event.devices.map(d => getDeviceName(d)).join(', ')}\n指令: ${event.commands}`
 }
 
 // 切换视图模式
@@ -282,34 +463,83 @@ const handleTabClick = () => {
 }
 
 // 获取时间表列表
-const fetchTimetables = () => {
-  // 模拟异步请求
-  setTimeout(() => {
-    const mockData = [
-      { id: 'T001', title: '设备A维护', startTime: '2025-04-01 08:00:00', endTime: '2025-04-01 10:00:00', devices: ['deviceA'], commands: '维护指令', status: 'success' },
-      { id: 'T002', title: '设备B检查', startTime: '2025-04-01 12:00:00', endTime: '2025-04-01 14:00:00', devices: ['deviceB'], commands: '检查指令', status: 'failed' },
-      { id: 'T003', title: '设备C清洗', startTime: '2025-04-02 09:00:00', endTime: '2025-04-02 11:00:00', devices: ['deviceC'], commands: '清洗指令', status: 'success' },
-      { id: 'T004', title: '设备A测试', startTime: '2025-04-03 10:00:00', endTime: '2025-04-03 12:00:00', devices: ['deviceA'], commands: '测试指令', status: 'success' },
-      { id: 'T005', title: '设备B升级', startTime: '2025-04-04 14:00:00', endTime: '2025-04-04 16:00:00', devices: ['deviceB'], commands: '升级指令', status: 'success' }
+const fetchTimetables = async () => {
+  loading.value = true
+  try {
+    // 模拟异步请求
+    await new Promise(resolve => setTimeout(resolve, 300))
+
+    let mockData = [
+      { id: 'T001', title: '设备A维护', startTime: '2025-07-01 08:00:00', endTime: '2025-07-01 10:00:00', devices: ['deviceA'], commands: '维护指令', status: 'success', createTime: '2025-03-25 10:00:00' },
+      { id: 'T002', title: '设备B检查', startTime: '2025-07-01 12:00:00', endTime: '2025-07-01 14:00:00', devices: ['deviceB'], commands: '检查指令', status: 'failed', createTime: '2025-03-25 11:00:00' },
+      { id: 'T003', title: '设备C清洗', startTime: '2025-07-02 09:00:00', endTime: '2025-07-02 11:00:00', devices: ['deviceC'], commands: '清洗指令', status: 'success', createTime: '2025-03-26 09:00:00' },
+      { id: 'T004', title: '设备A测试', startTime: '2025-07-03 10:00:00', endTime: '2025-07-03 12:00:00', devices: ['deviceA'], commands: '测试指令', status: 'success', createTime: '2025-03-27 14:00:00' },
+      { id: 'T005', title: '设备B升级', startTime: '2025-07-04 14:00:00', endTime: '2025-07-04 16:00:00', devices: ['deviceB'], commands: '升级指令', status: 'success', createTime: '2025-03-28 15:00:00' },
+      { id: 'T006', title: '设备A例行检查', startTime: '2025-07-15 09:00:00', endTime: '2025-07-15 10:00:00', devices: ['deviceA'], commands: '检查指令', status: 'pending', createTime: '2025-07-01 10:00:00' },
+      { id: 'T007', title: '设备B维护', startTime: '2025-07-15 14:00:00', endTime: '2025-07-15 16:00:00', devices: ['deviceB'], commands: '维护指令', status: 'pending', createTime: '2025-07-01 11:00:00' },
+      { id: 'T008', title: '设备C测试', startTime: '2025-07-15 16:30:00', endTime: '2025-07-15 17:30:00', devices: ['deviceC'], commands: '测试指令', status: 'pending', createTime: '2025-07-01 12:00:00' },
+      { id: 'T009', title: '全设备检查', startTime: '2025-07-15 18:00:00', endTime: '2025-07-15 20:00:00', devices: ['deviceA', 'deviceB', 'deviceC'], commands: '全面检查', status: 'pending', createTime: '2025-07-01 13:00:00' }
     ]
+
+    // 应用搜索过滤
+    if (searchParams.value.title) {
+      mockData = mockData.filter(item => item.title.includes(searchParams.value.title))
+    }
+    if (searchParams.value.status) {
+      mockData = mockData.filter(item => item.status === searchParams.value.status)
+    }
+    if (searchParams.value.dateRange && searchParams.value.dateRange.length === 2) {
+      const [start, end] = searchParams.value.dateRange
+      mockData = mockData.filter(item => {
+        return item.startTime >= start && item.endTime <= end
+      })
+    }
+
     // 模拟分页
+    total.value = mockData.length
     const start = (queryParams.value.pageNumber - 1) * queryParams.value.pageSize
     const end = start + queryParams.value.pageSize
     timetableList.value = mockData.slice(start, end)
-    total.value = mockData.length
-  }, 300)
+  } catch (error) {
+    ElMessage.error('获取时间表列表失败')
+  } finally {
+    loading.value = false
+  }
 }
 
 // 获取执行历史
-const fetchExecutionHistory = () => {
-  // 模拟异步请求
-  setTimeout(() => {
+const fetchExecutionHistory = async () => {
+  historyLoading.value = true
+  try {
+    // 模拟异步请求
+    await new Promise(resolve => setTimeout(resolve, 300))
+
     // 模拟分页
     const start = (historyQueryParams.value.pageNumber - 1) * historyQueryParams.value.pageSize
     const end = start + historyQueryParams.value.pageSize
     executionHistory.value = executionHistoryMap.value.slice(start, end)
     historyTotal.value = executionHistoryMap.value.length
-  }, 300)
+  } catch (error) {
+    ElMessage.error('获取执行历史失败')
+  } finally {
+    historyLoading.value = false
+  }
+}
+
+// 搜索
+const handleSearch = () => {
+  queryParams.value.pageNumber = 1
+  fetchTimetables()
+}
+
+// 重置搜索
+const resetSearch = () => {
+  searchParams.value = {
+    title: '',
+    dateRange: null,
+    status: ''
+  }
+  handleSearch()
 }
 
 // 新增时间表
@@ -321,98 +551,218 @@ const addTimetable = () => {
     startTime: '',
     endTime: '',
     devices: [],
-    commands: ''
+    commands: '',
+    isRecurring: false,
+    recurringType: 'daily'
   }
+  timeRange.value = []
   dialogVisible.value = true
+  // 清除表单验证
+  nextTick(() => {
+    timetableFormRef.value?.clearValidate()
+  })
 }
 
 // 新增某一天的时间表
 const addTimetableForDay = (day) => {
   dialogTitle.value = '新增时间表'
+  const startTime = `${day} 08:00:00`
+  const endTime = `${day} 10:00:00`
   timetableForm.value = {
     id: '',
     title: '',
-    startTime: `${day} 08:00:00`,
-    endTime: `${day} 10:00:00`,
+    startTime,
+    endTime,
     devices: [],
-    commands: ''
+    commands: '',
+    isRecurring: false,
+    recurringType: 'daily'
   }
+  timeRange.value = [startTime, endTime]
   dialogVisible.value = true
+  // 清除表单验证
+  nextTick(() => {
+    timetableFormRef.value?.clearValidate()
+  })
 }
 
 // 编辑时间表
 const editTimetable = (timetable) => {
   dialogTitle.value = '编辑时间表'
   timetableForm.value = { ...timetable }
+  timeRange.value = [timetable.startTime, timetable.endTime]
   dialogVisible.value = true
+  // 清除表单验证
+  nextTick(() => {
+    timetableFormRef.value?.clearValidate()
+  })
+}
+
+// 从详情页编辑
+const editFromDetail = () => {
+  detailDialogVisible.value = false
+  editTimetable(selectedTimetable.value)
 }
 
 // 删除时间表
 const deleteTimetable = (timetable) => {
-  ElMessage.confirm('确定要删除该时间表吗?', '提示', {
-    confirmButtonText: '确定',
-    cancelButtonText: '取消',
-    type: 'warning'
-  }).then(() => {
-    // 模拟删除操作
-    setTimeout(() => {
-      ElMessage.success('时间表删除成功')
+  ElMessageBox.confirm(
+      `确定要删除时间表"${timetable.title}"吗?`,
+      '提示',
+      {
+        confirmButtonText: '确定',
+        cancelButtonText: '取消',
+        type: 'warning'
+      }
+  ).then(async () => {
+    try {
+      // 模拟删除操作
+      await new Promise(resolve => setTimeout(resolve, 300))
+
+      // 从日历数据中删除
+      const day = timetable.startTime.split(' ')[0]
+      if (timetableMap.value[day]) {
+        timetableMap.value[day] = timetableMap.value[day].filter(item => item.id !== timetable.id)
+        if (timetableMap.value[day].length === 0) {
+          delete timetableMap.value[day]
+        }
+      }
+
+      ElMessage.success('删除成功')
       fetchTimetables()
-    }, 300)
+    } catch (error) {
+      ElMessage.error('删除失败')
+    }
   }).catch(() => {})
 }
 
-// 保存时间表
-const saveTimetable = () => {
-  if (!timetableForm.value.title || !timetableForm.value.startTime || !timetableForm.value.endTime || timetableForm.value.devices.length === 0 || !timetableForm.value.commands) {
-    ElMessage.warning('请填写完整的时间表信息')
-    return
+// 处理时间范围变化
+const handleTimeRangeChange = (value) => {
+  if (value && value.length === 2) {
+    timetableForm.value.startTime = value[0]
+    timetableForm.value.endTime = value[1]
   }
-  // 模拟保存操作
-  setTimeout(() => {
-    ElMessage.success('时间表保存成功')
+}
+
+// 保存时间表
+const saveTimetable = async () => {
+  try {
+    // 表单验证
+    const valid = await timetableFormRef.value.validate()
+    if (!valid) return
+
+    // 时间验证
+    if (new Date(timetableForm.value.startTime) >= new Date(timetableForm.value.endTime)) {
+      ElMessage.warning('开始时间必须小于结束时间')
+      return
+    }
+
+    saveLoading.value = true
+    // 模拟保存操作
+    await new Promise(resolve => setTimeout(resolve, 500))
+
+    // 生成ID(编辑时保持原ID)
+    if (!timetableForm.value.id) {
+      timetableForm.value.id = 'T' + Date.now().toString().slice(-3)
+    }
+
+    // 添加到日历数据
+    const day = timetableForm.value.startTime.split(' ')[0]
+    if (!timetableMap.value[day]) {
+      timetableMap.value[day] = []
+    }
+
+    // 如果是编辑,先删除原数据
+    if (dialogTitle.value === '编辑时间表') {
+      // 可能日期改变了,需要从原日期删除
+      Object.keys(timetableMap.value).forEach(date => {
+        timetableMap.value[date] = timetableMap.value[date].filter(item => item.id !== timetableForm.value.id)
+        if (timetableMap.value[date].length === 0) {
+          delete timetableMap.value[date]
+        }
+      })
+    }
+
+    // 添加新数据
+    const newTimetable = {
+      ...timetableForm.value,
+      status: 'pending',
+      createTime: new Date().toISOString().replace('T', ' ').slice(0, 19)
+    }
+
+    if (!timetableMap.value[day]) {
+      timetableMap.value[day] = []
+    }
+    timetableMap.value[day].push(newTimetable)
+
+    ElMessage.success(dialogTitle.value === '新增时间表' ? '新增成功' : '编辑成功')
     dialogVisible.value = false
-    fetchTimetables()
-  }, 300)
+
+    // 如果在列表视图,刷新列表
+    if (viewMode.value === 'list') {
+      fetchTimetables()
+    }
+  } catch (error) {
+    console.error('保存失败:', error)
+  } finally {
+    saveLoading.value = false
+  }
 }
 
 // 查看时间表详情
 const viewEvent = (event) => {
-  selectedTimetable.value = event
+  selectedTimetable.value = { ...event }
   detailDialogVisible.value = true
 }
 
 // 查看执行历史详情
 const viewExecutionDetail = (execution) => {
-  selectedExecution.value = execution
+  selectedExecution.value = { ...execution }
   historyDetailDialogVisible.value = true
 }
 
 // 处理日期点击事件
 const handleDateClick = (day) => {
-  if (!hasEvents(day)) {
+  const events = getEvents(day)
+  if (events.length === 0) {
     addTimetableForDay(day)
+  } else if (events.length === 1) {
+    viewEvent(events[0])
+  } else {
+    // 多个事件时显示列表
+    selectedDay.value = day
+    selectedDayEvents.value = events
+    dayEventsDialogVisible.value = true
   }
 }
 
 // 显示右键菜单
 const showContextMenu = (day, event) => {
+  event.preventDefault()
   contextMenuVisible.value = true
   contextMenuX.value = event.clientX
   contextMenuY.value = event.clientY
   selectedDay.value = day
   selectedDayEvents.value = getEvents(day)
+
+  // 点击其他地方关闭菜单
+  document.addEventListener('click', closeContextMenu)
+}
+
+// 关闭右键菜单
+const closeContextMenu = () => {
+  contextMenuVisible.value = false
+  document.removeEventListener('click', closeContextMenu)
 }
 
 // 处理右键菜单命令
 const handleContextCommand = (command) => {
   if (command === 'add') {
     addTimetableForDay(selectedDay.value)
-  } else if (command === 'view') {
-    if (selectedDayEvents.value.length > 0) {
-      viewEvent(selectedDayEvents.value[0]) // 默认查看第一个事件
-    }
+  } else if (command === 'viewAll') {
+    dayEventsDialogVisible.value = true
   }
+  closeContextMenu()
 }
 
 // 分页变化处理
@@ -421,76 +771,205 @@ const handlePageChange = (page) => {
   fetchTimetables()
 }
 
+// 每页条数变化处理
+const handleSizeChange = (size) => {
+  queryParams.value.pageSize = size
+  queryParams.value.pageNumber = 1
+  fetchTimetables()
+}
+
 // 执行历史分页变化处理
 const handleHistoryPageChange = (page) => {
   historyQueryParams.value.pageNumber = page
   fetchExecutionHistory()
 }
 
+// 执行历史每页条数变化处理
+const handleHistorySizeChange = (size) => {
+  historyQueryParams.value.pageSize = size
+  historyQueryParams.value.pageNumber = 1
+  fetchExecutionHistory()
+}
+
+// 监听时间范围变化,同步到表单
+watch(timeRange, (newVal) => {
+  if (newVal && newVal.length === 2) {
+    timetableForm.value.startTime = newVal[0]
+    timetableForm.value.endTime = newVal[1]
+  }
+})
+
 // 页面加载时初始化数据
 onMounted(() => {
   fetchTimetables()
 })
+
+// 组件卸载时清理
+onUnmounted(() => {
+  document.removeEventListener('click', closeContextMenu)
+})
 </script>
 
 <style scoped lang="scss">
 .timetable-management-container {
   padding: 20px;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
 }
+
 .header-card {
   margin-bottom: 20px;
-  display: flex;
-  justify-content: space-between;
-  align-items: center;
+
+  .header-content {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+  }
 }
+
+.search-form {
+  margin-bottom: 20px;
+  padding: 20px;
+  background-color: #f5f7fa;
+  border-radius: 4px;
+}
+
 .calendar-view {
-  .el-calendar {
-    --el-calendar-day-height: 80px;
+  flex: 1;
+  overflow: auto;
+  position: relative;
+
+  :deep(.el-calendar) {
+    --el-calendar-day-height: 100px;
+
+    .el-calendar__body {
+      padding: 12px 20px 35px;
+    }
   }
+
   .date-cell {
     height: 100%;
+    padding: 8px;
     display: flex;
     flex-direction: column;
-    justify-content: center;
-    align-items: center;
     cursor: pointer;
+    transition: background-color 0.3s;
+
+    &:hover {
+      background-color: #f5f7fa;
+    }
+
     p {
       font-size: 14px;
+      margin-bottom: 4px;
+
+      &.highlight {
+        font-weight: bold;
+        color: #409EFF;
+      }
+
+      &.event-day {
+        color: #67C23A;
+        font-weight: 600;
+      }
     }
-    .highlight {
-      font-weight: bold;
-      color: #409EFF;
-    }
-    .event-day {
-      color: #67C23A;
-    }
+
     .events {
-      margin-top: 5px;
+      flex: 1;
       display: flex;
-      flex-wrap: wrap;
-      gap: 5px;
+      flex-direction: column;
+      gap: 4px;
+      overflow: hidden;
+
       .el-tag {
         max-width: 100%;
         white-space: nowrap;
         overflow: hidden;
         text-overflow: ellipsis;
+        cursor: pointer;
+
+        &:hover {
+          opacity: 0.8;
+        }
       }
     }
+
     .el-button--text {
       padding: 0;
       font-size: 12px;
+      margin-top: auto;
+    }
+  }
+}
+
+.context-menu {
+  position: fixed;
+  background: white;
+  border: 1px solid #e4e7ed;
+  border-radius: 4px;
+  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
+  z-index: 3000;
+  padding: 4px 0;
+
+  .menu-item {
+    padding: 8px 16px;
+    cursor: pointer;
+    font-size: 14px;
+    color: #606266;
+
+    &:hover {
+      background-color: #f5f7fa;
+      color: #409EFF;
     }
   }
 }
+
 .list-view, .history-view {
+  flex: 1;
+  display: flex;
+  flex-direction: column;
+
   .el-table {
-    margin-bottom: 20px;
+    flex: 1;
   }
+
   .el-pagination {
+    margin-top: 20px;
     text-align: right;
   }
 }
+
 .dialog-footer {
   text-align: right;
 }
+
+// 响应式布局
+@media (max-width: 768px) {
+  .search-form {
+    :deep(.el-form-item) {
+      margin-bottom: 10px;
+    }
+  }
+
+  .calendar-view {
+    :deep(.el-calendar) {
+      --el-calendar-day-height: 80px;
+    }
+
+    .date-cell {
+      padding: 4px;
+
+      p {
+        font-size: 12px;
+      }
+
+      .events {
+        .el-tag {
+          font-size: 10px;
+        }
+      }
+    }
+  }
+}
 </style>

+ 1525 - 0
pm_ui/src/views/ktxt/index.vue

@@ -0,0 +1,1525 @@
+<template>
+  <div class="temperature-device-page">
+
+    <!-- 查询条件表单 -->
+    <div class="search-form">
+      <el-form :model="searchForm" inline>
+        <el-form-item label="设备名称">
+          <el-input
+              v-model="searchForm.query"
+              placeholder="请输入设备名称"
+              clearable
+              prefix-icon="Search"
+          />
+        </el-form-item>
+
+        <el-form-item label="设备状态">
+          <el-select
+              v-model="searchForm.devices_enabled"
+              placeholder="请选择设备状态"
+              clearable
+          >
+            <el-option label="全部" :value="-1" />
+            <el-option label="在线" :value="1" />
+            <el-option label="离线" :value="0" />
+          </el-select>
+        </el-form-item>
+
+        <el-form-item label="网关">
+          <el-select
+              v-model="searchForm.gateway_id"
+              placeholder="请选择网关"
+              clearable
+              filterable
+          >
+            <el-option
+                v-for="item in gatewayList"
+                :key="item.gateway_id"
+                :label="item.gateway_name"
+                :value="item.gateway_id"
+            >
+              <div class="gateway-option">
+                <span class="gateway-name">{{ item.gateway_name }}</span>
+                <el-tag
+                    size="small"
+                    :type="item.gateway_status === 1 ? 'success' : 'danger'"
+                    class="gateway-status"
+                >
+                  {{ item.gateway_status === 1 ? '在线' : '离线' }}
+                </el-tag>
+              </div>
+            </el-option>
+          </el-select>
+        </el-form-item>
+
+        <el-form-item>
+          <el-button type="primary" @click="handleSearch" icon="Search">查询</el-button>
+          <el-button @click="handleReset" icon="Refresh">重置</el-button>
+        </el-form-item>
+      </el-form>
+    </div>
+
+    <!-- 数据表格 -->
+    <div class="table-container">
+      <el-table
+          :data="deviceList"
+          v-loading="loading"
+          border
+          stripe
+          style="width: 100%"
+          :row-class-name="tableRowClassName"
+      >
+        <el-table-column prop="devices_id" label="设备ID" width="120" align="center" />
+
+        <el-table-column label="设备信息" width="300">
+          <template #default="scope">
+            <div class="device-info">
+              <div class="device-name">{{ scope.row.devices_name }}</div>
+              <div class="device-type">{{ scope.row.devices_type_name }}</div>
+              <div class="device-udid">{{ scope.row.devices_udid }}</div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="位置信息" width="250">
+          <template #default="scope">
+            <div class="location-info">
+              <div class="building">
+                <el-icon><OfficeBuilding /></el-icon>
+                {{ scope.row.building_name }}
+              </div>
+              <div class="region">{{ scope.row.full_region_name }}</div>
+              <div class="room">房间: {{ scope.row.room_name }}</div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="网关信息" width="160">
+          <template #default="scope">
+            <div class="gateway-info">
+              <div class="gateway-name">{{ scope.row.gateway_name }}</div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="设备状态" width="120" align="center">
+          <template #default="scope">
+            <div class="device-status">
+              <el-tag
+                  :type="scope.row.devices_enabled ? 'success' : 'info'"
+                  effect="dark"
+              >
+                {{ scope.row.devices_enabled ? '在线' : '离线' }}
+              </el-tag>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="温度信息" width="140" align="center">
+          <template #default="scope">
+            <div class="temperature-info">
+              <div class="current-temp">
+                <span class="temp-label">当前:</span>
+                <span class="temp-value" :class="getTempClass(getDeviceData(scope.row).dis_temp)">
+                  {{ getDeviceData(scope.row).dis_temp || '--' }}°C
+                </span>
+              </div>
+              <div class="set-temp">
+                <span class="temp-label">设定:</span>
+                <span class="temp-value">
+                  {{ getDeviceData(scope.row).temp_setting || '--' }}°C
+                </span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="运行状态" width="120" align="center">
+          <template #default="scope">
+            <div class="run-status">
+              <div class="switch-status">
+                <el-tag
+                    :type="getDeviceData(scope.row).switch_status ? 'success' : 'info'"
+                    size="small"
+                >
+                  {{ getDeviceData(scope.row).switch_status ? '开启' : '关闭' }}
+                </el-tag>
+              </div>
+              <div class="run-mode">
+                <el-tag
+                    size="small"
+                    :type="getRunModeType(getDeviceData(scope.row).run_mode)"
+                >
+                  {{ getRunModeText(getDeviceData(scope.row).run_mode) }}
+                </el-tag>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="设备详情" width="160" align="center">
+          <template #default="scope">
+            <div class="device-details">
+              <div class="fan-info">
+                <span class="detail-label">风扇:</span>
+                <el-tag
+                    size="small"
+                    :type="getDeviceData(scope.row).fan_status ? 'success' : 'info'"
+                >
+                  {{ getDeviceData(scope.row).fan_status ? '运行' : '停止' }}
+                </el-tag>
+              </div>
+              <div class="valve-info">
+                <span class="detail-label">阀门:</span>
+                <el-tag
+                    size="small"
+                    :type="getDeviceData(scope.row).valve_status ? 'success' : 'info'"
+                >
+                  {{ getDeviceData(scope.row).valve_status ? '开启' : '关闭' }}
+                </el-tag>
+              </div>
+              <div class="fan-setting">
+                <span class="detail-label">风速:</span>
+                <span class="detail-value">{{ getDeviceData(scope.row).fan_setting || '--' }}</span>
+              </div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="温度范围" width="120" align="center">
+          <template #default="scope">
+            <div class="temp-range">
+              <div class="temp-min">最低: {{ getDeviceData(scope.row).temp_min || '--' }}°C</div>
+              <div class="temp-max">最高: {{ getDeviceData(scope.row).temp_max || '--' }}°C</div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="devices_last_request_time" label="最后请求时间"  align="center">
+          <template #default="scope">
+            <div class="time-info">
+              <div class="date">{{ parseTime(scope.row.devices_last_request_time) }}</div>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column label="操作" width="120"  align="center">
+          <template #default="scope">
+            <el-button
+                type="primary"
+                size="small"
+                @click="handleView(scope.row)"
+                icon="View"
+            >
+              详情
+            </el-button>
+          </template>
+        </el-table-column>
+      </el-table>
+    </div>
+
+    <!-- 分页 -->
+    <div class="pagination-container">
+      <el-pagination
+          v-model:current-page="searchForm.currentPage"
+          v-model:page-size="searchForm.pageSize"
+          :page-sizes="[10, 20, 50, 100]"
+          :total="total"
+          layout="total, sizes, prev, pager, next, jumper"
+          @size-change="handleSizeChange"
+          @current-change="handleCurrentChange"
+      />
+    </div>
+
+    <!-- 设备详情弹窗 -->
+    <el-dialog
+        v-model="dialogVisible"
+        title="设备详情"
+        width="900px"
+        :before-close="handleCloseDialog"
+    >
+      <div v-if="selectedDevice" class="device-detail">
+        <!-- 基本信息 -->
+        <div class="detail-section">
+          <h3>基本信息</h3>
+          <el-descriptions :column="3" border>
+            <el-descriptions-item label="设备ID">{{ selectedDevice.devices_id }}</el-descriptions-item>
+            <el-descriptions-item label="设备名称">{{ selectedDevice.devices_name }}</el-descriptions-item>
+            <el-descriptions-item label="设备类型">{{ selectedDevice.devices_type_name }}</el-descriptions-item>
+            <el-descriptions-item label="设备UDID">{{ selectedDevice.devices_udid }}</el-descriptions-item>
+            <el-descriptions-item label="网关名称">{{ selectedDevice.gateway_name }}</el-descriptions-item>
+            <el-descriptions-item label="请求间隔">{{ selectedDevice.devices_req_interval }}秒</el-descriptions-item>
+            <el-descriptions-item label="建筑">{{ selectedDevice.building_name }}</el-descriptions-item>
+            <el-descriptions-item label="区域">{{ selectedDevice.full_region_name }}</el-descriptions-item>
+            <el-descriptions-item label="房间">{{ selectedDevice.room_name }}</el-descriptions-item>
+          </el-descriptions>
+        </div>
+
+        <!-- 状态信息 -->
+        <div class="detail-section">
+          <h3>状态信息</h3>
+          <el-descriptions :column="3" border>
+            <el-descriptions-item label="设备状态">
+              <el-tag :type="selectedDevice.devices_enabled ? 'success' : 'danger'">
+                {{ selectedDevice.devices_enabled ? '在线' : '离线' }}
+              </el-tag>
+            </el-descriptions-item>
+<!--            <el-descriptions-item label="通信状态">
+              <el-tag :type="selectedDevice.on_line ? 'success' : 'warning'">
+                {{ selectedDevice.on_line ? '通信正常' : '通信异常' }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="网关状态">
+              <el-tag :type="selectedDevice.gateway_status === 1 ? 'success' : 'danger'">
+                {{ selectedDevice.gateway_status === 1 ? '网关在线' : '网关离线' }}
+              </el-tag>
+            </el-descriptions-item>-->
+          </el-descriptions>
+        </div>
+
+        <!-- 温控数据 -->
+        <div v-if="selectedDevice.devices_json_object" class="detail-section">
+          <h3>温控数据</h3>
+          <el-descriptions :column="3" border>
+            <el-descriptions-item label="当前温度">
+              <span :class="getTempClass(getDeviceData(selectedDevice).dis_temp)">
+                {{ getDeviceData(selectedDevice).dis_temp }}°C
+              </span>
+            </el-descriptions-item>
+            <el-descriptions-item label="设定温度">{{ getDeviceData(selectedDevice).temp_setting }}°C</el-descriptions-item>
+            <el-descriptions-item label="运行模式">
+              <el-tag :type="getRunModeType(getDeviceData(selectedDevice).run_mode)">
+                {{ getRunModeText(getDeviceData(selectedDevice).run_mode) }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="开关状态">
+              <el-tag :type="getDeviceData(selectedDevice).switch_status ? 'success' : 'info'">
+                {{ getDeviceData(selectedDevice).switch_status ? '开启' : '关闭' }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="风扇状态">
+              <el-tag :type="getDeviceData(selectedDevice).fan_status ? 'success' : 'info'">
+                {{ getDeviceData(selectedDevice).fan_status ? '运行' : '停止' }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="阀门状态">
+              <el-tag :type="getDeviceData(selectedDevice).valve_status ? 'success' : 'info'">
+                {{ getDeviceData(selectedDevice).valve_status ? '开启' : '关闭' }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="风扇设定">{{ getDeviceData(selectedDevice).fan_setting }}</el-descriptions-item>
+            <el-descriptions-item label="温度范围">{{ getDeviceData(selectedDevice).temp_min }}°C - {{ getDeviceData(selectedDevice).temp_max }}°C</el-descriptions-item>
+            <el-descriptions-item label="回差设定">{{ getDeviceData(selectedDevice).backlash_setting }}°C</el-descriptions-item>
+          </el-descriptions>
+        </div>
+
+        <!-- 高级设置 -->
+        <div v-if="selectedDevice.devices_json_object" class="detail-section">
+          <h3>高级设置</h3>
+          <el-descriptions :column="3" border>
+            <el-descriptions-item label="传感器选择">{{ getDeviceData(selectedDevice).sensor_selection }}</el-descriptions-item>
+            <el-descriptions-item label="按键锁定">
+              <el-tag :type="getDeviceData(selectedDevice).key_lock ? 'warning' : 'success'">
+                {{ getDeviceData(selectedDevice).key_lock ? '已锁定' : '未锁定' }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="防冻保护">
+              <el-tag :type="getDeviceData(selectedDevice).frozen_enable ? 'success' : 'info'">
+                {{ getDeviceData(selectedDevice).frozen_enable ? '启用' : '禁用' }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="防冻温度">{{ getDeviceData(selectedDevice).frozen_temp }}°C</el-descriptions-item>
+            <el-descriptions-item label="外部温度">{{ getDeviceData(selectedDevice).out_temp }}°C</el-descriptions-item>
+            <el-descriptions-item label="温度校准">{{ getDeviceData(selectedDevice).out_temp_calibration }}°C</el-descriptions-item>
+          </el-descriptions>
+        </div>
+
+        <!-- 时间信息 -->
+        <div class="detail-section">
+          <h3>时间信息</h3>
+          <el-descriptions :column="2" border>
+            <el-descriptions-item label="创建时间">{{ parseTime(selectedDevice.devices_created) }}</el-descriptions-item>
+            <el-descriptions-item label="最后请求时间">{{ parseTime(selectedDevice.devices_last_request_time) }}</el-descriptions-item>
+            <el-descriptions-item label="网关最后请求">{{ parseTime(selectedDevice.gateway_last_request_time) }}</el-descriptions-item>
+          </el-descriptions>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="Wkq">
+import { ref, reactive, onMounted, computed } from 'vue'
+import axios from 'axios'
+import { ElMessage } from 'element-plus'
+import {
+  Search,
+  Refresh,
+  Connection,
+  Close,
+  VideoPlay,
+  Monitor,
+  OfficeBuilding,
+  View
+} from '@element-plus/icons-vue'
+
+// 响应式数据
+const loading = ref(false)
+const deviceList = ref([])
+const gatewayList = ref([])
+const total = ref(0)
+const dialogVisible = ref(false)
+const selectedDevice = ref(null)
+
+// 搜索表单
+const searchForm = reactive({
+  currentPage: 1,
+  pageSize: 10,
+  device_type_id: '',
+  devices_enabled: -1,
+  query: '',
+  gateway_id: '',
+  devices_udid: ''
+})
+
+// 计算统计数据
+const onlineCount = computed(() => {
+  return deviceList.value.filter(device => device.devices_enabled === 1).length
+})
+
+const offlineCount = computed(() => {
+  return deviceList.value.filter(device => device.devices_enabled === 0).length
+})
+
+const runningCount = computed(() => {
+  return deviceList.value.filter(device => {
+    const data = getDeviceData(device)
+    return data.switch_status === 1
+  }).length
+})
+
+// 创建FormData的函数
+const createFormData = (params) => {
+  const formData = new FormData()
+
+  // 必传参数
+  formData.append('currentPage', params.currentPage)
+  formData.append('pageSize', params.pageSize)
+
+  // 可选参数,只有在有值时才添加
+  if (params.device_type_id && params.device_type_id.toString().trim() !== '') {
+    formData.append('device_type_id', params.device_type_id)
+  }
+
+  if (params.devices_enabled !== '' && params.devices_enabled !== null && params.devices_enabled !== undefined) {
+    formData.append('devices_enabled', params.devices_enabled)
+  }
+
+  if (params.query && params.query.trim() !== '') {
+    formData.append('query', params.query.trim())
+  }
+
+  if (params.gateway_id && params.gateway_id.toString().trim() !== '') {
+    formData.append('gateway_id', params.gateway_id)
+  }
+
+  if (params.devices_udid && params.devices_udid.trim() !== '') {
+    formData.append('devices_udid', params.devices_udid.trim())
+  }
+
+  return formData
+}
+
+// 获取设备列表
+const getDeviceList = async () => {
+  loading.value = true
+  try {
+    // 创建FormData
+    const formData = createFormData(searchForm)
+
+    const response = await axios.post(`${__LOCAL_API__}/temperature/getBaseecic`, formData, {
+      headers: {
+        'Content-Type': 'multipart/form-data'
+      }
+    })
+
+    if (response.data.code === 200) {
+      deviceList.value = response.data.data.devices || []
+      total.value = response.data.data.count || 0
+    } else {
+      ElMessage.error(response.data.message || '获取设备列表失败')
+    }
+  } catch (error) {
+    console.error('获取设备列表失败:', error)
+    ElMessage.error('获取设备列表失败')
+  } finally {
+    loading.value = false
+  }
+}
+
+// 获取网关列表
+const getGatewayList = async () => {
+  try {
+    const response = await axios.get(`${__LOCAL_API__}/temperature/gatewayFind`)
+
+    if (response.data.code === 200) {
+      gatewayList.value = response.data.data.gatewayAndBacnet || []
+    } else {
+      ElMessage.error(response.data.message || '获取网关列表失败')
+    }
+  } catch (error) {
+    console.error('获取网关列表失败:', error)
+    ElMessage.error('获取网关列表失败')
+  }
+}
+
+// 解析设备JSON数据
+const getDeviceData = (device) => {
+  try {
+    return device.devices_json_object ? JSON.parse(device.devices_json_object) : {}
+  } catch (error) {
+    console.error('解析设备数据失败:', error)
+    return {}
+  }
+}
+
+// 获取运行模式文本
+const getRunModeText = (mode) => {
+  const modeMap = {
+    1: '制冷',
+    2: '制热',
+    3: '通风'
+  }
+  return modeMap[mode] || '未知'
+}
+
+// 获取运行模式类型
+const getRunModeType = (mode) => {
+  const typeMap = {
+    1: 'primary',
+    2: 'danger',
+    3: 'waring',
+  }
+  return typeMap[mode] || 'info'
+}
+
+// 获取温度样式类
+const getTempClass = (temp) => {
+  if (!temp) return ''
+  if (temp < 18) return 'temp-cold'
+  if (temp > 26) return 'temp-hot'
+  return 'temp-normal'
+}
+
+// 表格行样式
+const tableRowClassName = ({ row }) => {
+  if (!row.devices_enabled) return 'offline-row'
+  return ''
+}
+
+const formatDate = (dateTime) => {
+  if (!dateTime) return '--'
+  const date = new Date(dateTime)
+  return date.toLocaleDateString('zh-CN')
+}
+
+const formatTime = (dateTime) => {
+  if (!dateTime) return '--'
+  const date = new Date(dateTime)
+  return date.toLocaleTimeString('zh-CN')
+}
+
+// 搜索
+const handleSearch = () => {
+  searchForm.currentPage = 1
+  getDeviceList()
+}
+
+// 重置
+const handleReset = () => {
+  Object.assign(searchForm, {
+    currentPage: 1,
+    pageSize: 10,
+    device_type_id: '',
+    devices_enabled: -1,
+    query: '',
+    gateway_id: '',
+    devices_udid: ''
+  })
+  getDeviceList()
+}
+
+// 分页大小改变
+const handleSizeChange = (size) => {
+  searchForm.pageSize = size
+  searchForm.currentPage = 1
+  getDeviceList()
+}
+
+// 当前页改变
+const handleCurrentChange = (page) => {
+  searchForm.currentPage = page
+  getDeviceList()
+}
+
+// 查看详情
+const handleView = (device) => {
+  selectedDevice.value = device
+  dialogVisible.value = true
+}
+
+// 关闭弹窗
+const handleCloseDialog = () => {
+  dialogVisible.value = false
+  selectedDevice.value = null
+}
+
+// 组件挂载时获取数据
+onMounted(() => {
+  getGatewayList()
+  getDeviceList()
+})
+</script>
+
+<style scoped>
+.temperature-device-page {
+  padding: 20px;
+  background: #f5f7fa;
+  min-height: 90vh;
+}
+
+/* 搜索表单样式 */
+.search-form {
+  background: #fff;
+  padding: 24px;
+  border-radius: 12px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+  margin-bottom: 20px;
+  border: 1px solid #e4e7ed;
+}
+
+.search-form .el-form {
+  margin-bottom: 0;
+}
+
+.search-form .el-form-item {
+  margin-bottom: 16px;
+}
+
+.search-form .el-form-item__label {
+  font-weight: 600;
+  color: #303133;
+}
+
+.search-form .el-input {
+  width: 200px;
+}
+
+.search-form .el-select {
+  width: 180px;
+}
+
+/* 网关选项样式 */
+.gateway-option {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  width: 100%;
+}
+
+.gateway-name {
+  flex: 1;
+  overflow: hidden;
+  text-overflow: ellipsis;
+  white-space: nowrap;
+}
+
+.gateway-status {
+  margin-left: 8px;
+}
+
+/* 表格容器样式 */
+.table-container {
+  background: #fff;
+  border-radius: 12px;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+  overflow: hidden;
+  margin-bottom: 20px;
+}
+
+:deep(.el-table) {
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+:deep(.el-table th) {
+  background: linear-gradient(135deg, #eff2f3 100%, #f8f9fa 100%);
+  color: #333;
+  font-weight: 600;
+  padding: 12px 12px;
+}
+
+:deep(.el-table td) {
+  padding: 12px 0;
+}
+
+:deep(.el-table__row:hover) {
+  background-color: #f5f7fa;
+}
+
+/* 表格行状态样式 */
+:deep(.offline-row) {
+  background-color: #fef0f0 !important;
+}
+
+:deep(.warning-row) {
+  background-color: #fdf6ec !important;
+}
+
+/* 设备信息样式 */
+.device-info {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.device-name {
+  font-weight: 600;
+  color: #303133;
+  font-size: 14px;
+}
+
+.device-type {
+  color: #909399;
+  font-size: 12px;
+  background: #f0f2f5;
+  padding: 2px 6px;
+  border-radius: 4px;
+  display: inline-block;
+  width: fit-content;
+}
+
+.device-udid {
+  color: #909399;
+  font-size: 11px;
+  font-family: 'Courier New', monospace;
+}
+
+/* 位置信息样式 */
+.location-info {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.building {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-weight: 600;
+  color: #303133;
+  font-size: 13px;
+}
+
+.region {
+  color: #606266;
+  font-size: 12px;
+  padding-left: 16px;
+}
+
+.room {
+  color: #909399;
+  font-size: 12px;
+  padding-left: 16px;
+}
+
+/* 网关信息样式 */
+.gateway-info {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+}
+
+.gateway-name {
+  font-weight: 500;
+  color: #303133;
+  font-size: 13px;
+}
+
+/* 设备状态样式 */
+.device-status {
+  display: flex;
+  justify-content: center;
+}
+
+/* 温度信息样式 */
+.temperature-info {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  align-items: center;
+}
+
+.current-temp,
+.set-temp {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+  font-size: 12px;
+}
+
+.temp-label {
+  color: #909399;
+  font-size: 11px;
+}
+
+.temp-value {
+  font-weight: 600;
+  font-size: 13px;
+}
+
+.temp-cold {
+  color: #409eff;
+}
+
+.temp-normal {
+  color: #67c23a;
+}
+
+.temp-hot {
+  color: #f56c6c;
+}
+
+/* 运行状态样式 */
+.run-status {
+  display: flex;
+  flex-direction: column;
+  gap: 6px;
+  align-items: center;
+}
+
+.switch-status,
+.run-mode {
+  width: 100%;
+  display: flex;
+  justify-content: center;
+}
+
+/* 设备详情样式 */
+.device-details {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  font-size: 12px;
+}
+
+.fan-info,
+.valve-info,
+.fan-setting {
+  display: flex;
+  align-items: center;
+  gap: 4px;
+}
+
+.detail-label {
+  color: #909399;
+  font-size: 11px;
+  min-width: 32px;
+}
+
+.detail-value {
+  font-weight: 500;
+  color: #303133;
+}
+
+/* 温度范围样式 */
+.temp-range {
+  display: flex;
+  flex-direction: column;
+  gap: 4px;
+  font-size: 11px;
+  text-align: center;
+}
+
+.temp-min,
+.temp-max {
+  color: #606266;
+}
+
+/* 时间信息样式 */
+.time-info {
+  display: flex;
+  flex-direction: column;
+  gap: 2px;
+  text-align: center;
+}
+
+.date {
+  font-weight: 500;
+  color: #303133;
+  font-size: 12px;
+}
+
+.time {
+  color: #909399;
+  font-size: 11px;
+}
+
+/* 分页样式 */
+.pagination-container {
+  display: flex;
+  justify-content: flex-end;
+  padding: 20px;
+  background-color: #fff;
+  border-top: 1px solid #f0f2f5;
+}
+
+:deep(.el-pagination) {
+  --el-pagination-button-color: #606266;
+  --el-pagination-hover-color: #409eff;
+}
+
+/* 弹窗样式 */
+:deep(.el-dialog) {
+  border-radius: 12px;
+  overflow: hidden;
+}
+
+:deep(.el-dialog__header) {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: #fff;
+  padding: 20px 24px;
+  margin: 0;
+}
+
+:deep(.el-dialog__title) {
+  color: #fff;
+  font-weight: 600;
+  font-size: 18px;
+}
+
+:deep(.el-dialog__headerbtn .el-dialog__close) {
+  color: #fff;
+  font-size: 20px;
+}
+
+:deep(.el-dialog__body) {
+  padding: 24px;
+  max-height: 70vh;
+  overflow-y: auto;
+}
+
+/* 设备详情弹窗内容样式 */
+.device-detail {
+  display: flex;
+  flex-direction: column;
+  gap: 24px;
+}
+
+.detail-section {
+  background: #f8f9fa;
+  padding: 20px;
+  border-radius: 8px;
+  border-left: 4px solid #409eff;
+}
+
+.detail-section h3 {
+  margin: 0 0 16px 0;
+  color: #303133;
+  font-size: 16px;
+  font-weight: 600;
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.detail-section h3::before {
+  content: '';
+  width: 4px;
+  height: 16px;
+  background: #409eff;
+  border-radius: 2px;
+}
+
+/* 描述列表样式优化 */
+:deep(.el-descriptions) {
+  background: #fff;
+  border-radius: 8px;
+  overflow: hidden;
+}
+
+:deep(.el-descriptions__header) {
+  background: #f5f7fa;
+  padding: 12px 16px;
+  border-bottom: 1px solid #e4e7ed;
+}
+
+:deep(.el-descriptions__body .el-descriptions__table) {
+  border-collapse: separate;
+  border-spacing: 0;
+}
+
+:deep(.el-descriptions__label) {
+  background: #fafbfc !important;
+  font-weight: 600;
+  color: #303133;
+  border-right: 1px solid #e4e7ed;
+}
+
+:deep(.el-descriptions__content) {
+  background: #fff !important;
+  color: #606266;
+}
+
+/* 按钮样式优化 */
+.el-button {
+  border-radius: 6px;
+  font-weight: 500;
+  transition: all 0.3s ease;
+}
+
+.el-button--primary {
+  background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%);
+  border: none;
+  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
+}
+
+.el-button--primary:hover {
+  background: linear-gradient(135deg, #3a8ee6 0%, #337ecc 100%);
+  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
+  transform: translateY(-1px);
+}
+
+/* 标签样式优化 */
+.el-tag {
+  border-radius: 4px;
+  font-weight: 500;
+  border: none;
+  font-size: 11px;
+  padding: 2px 6px;
+}
+
+.el-tag--success {
+  background: linear-gradient(135deg, #67c23a 0%, #5daf34 100%);
+  color: #fff;
+}
+
+.el-tag--danger {
+  background: linear-gradient(135deg, #f56c6c 0%, #f25c5c 100%);
+  color: #fff;
+}
+
+.el-tag--warning {
+  background: linear-gradient(135deg, #e6a23c 0%, #d19e3c 100%);
+  color: #fff;
+}
+
+.el-tag--info {
+  background: linear-gradient(135deg, #909399 0%, #82848a 100%);
+  color: #fff;
+}
+
+.el-tag--primary {
+  background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%);
+  color: #fff;
+}
+
+/* 输入框和选择器样式优化 */
+:deep(.el-input__wrapper) {
+  border-radius: 6px;
+  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+  transition: all 0.3s ease;
+}
+
+:deep(.el-input__wrapper:hover) {
+  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.15);
+}
+
+:deep(.el-input__wrapper.is-focus) {
+  box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+}
+
+:deep(.el-select .el-input__wrapper) {
+  border-radius: 6px;
+}
+
+/* 加载动画优化 */
+:deep(.el-loading-mask) {
+  background-color: rgba(255, 255, 255, 0.9);
+  backdrop-filter: blur(2px);
+}
+
+/* 响应式设计 */
+@media (max-width: 1200px) {
+  .temperature-device-page {
+    padding: 16px;
+  }
+
+  .search-form .el-input,
+  .search-form .el-select {
+    width: 160px;
+  }
+}
+
+@media (max-width: 768px) {
+  .temperature-device-page {
+    padding: 12px;
+  }
+
+  .search-form {
+    padding: 16px;
+  }
+
+  .search-form .el-form-item {
+    margin-bottom: 12px;
+  }
+
+  .search-form .el-input,
+  .search-form .el-select {
+    width: 140px;
+  }
+
+  :deep(.el-table td) {
+    padding: 8px 4px;
+    font-size: 12px;
+  }
+
+  .device-info,
+  .location-info,
+  .temperature-info,
+  .device-details {
+    font-size: 11px;
+  }
+}
+
+/* 滚动条样式 */
+:deep(.el-dialog__body)::-webkit-scrollbar {
+  width: 6px;
+}
+
+:deep(.el-dialog__body)::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 3px;
+}
+
+:deep(.el-dialog__body)::-webkit-scrollbar-thumb {
+  background: #c1c1c1;
+  border-radius: 3px;
+}
+
+:deep(.el-dialog__body)::-webkit-scrollbar-thumb:hover {
+  background: #a8a8a8;
+}
+
+/* 动画效果 */
+.temperature-device-page {
+  animation: fadeIn 0.5s ease-in-out;
+}
+
+@keyframes fadeIn {
+  from {
+    opacity: 0;
+    transform: translateY(20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+.search-form,
+.table-container,
+.pagination-container {
+  animation: slideUp 0.6s ease-out;
+}
+
+@keyframes slideUp {
+  from {
+    opacity: 0;
+    transform: translateY(30px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* 表格行悬停效果增强 */
+:deep(.el-table tbody tr) {
+  transition: all 0.3s ease;
+}
+
+:deep(.el-table tbody tr:hover) {
+  transform: translateY(-1px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
+}
+
+/* 卡片阴影效果 */
+.search-form,
+.table-container,
+.pagination-container {
+  transition: box-shadow 0.3s ease;
+}
+
+.search-form:hover,
+.table-container:hover,
+.pagination-container:hover {
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
+}
+
+/* 状态指示器样式 */
+.device-status .el-tag {
+  position: relative;
+  overflow: hidden;
+}
+
+.device-status .el-tag::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: -100%;
+  width: 100%;
+  height: 100%;
+  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.3), transparent);
+  transition: left 0.5s ease;
+}
+
+.device-status .el-tag:hover::before {
+  left: 100%;
+}
+
+/* 温度显示增强效果 */
+.temp-value {
+  position: relative;
+  transition: all 0.3s ease;
+}
+
+.temp-value:hover {
+  transform: scale(1.1);
+}
+
+.temp-cold {
+  text-shadow: 0 0 8px rgba(64, 158, 255, 0.5);
+}
+
+.temp-hot {
+  text-shadow: 0 0 8px rgba(245, 108, 108, 0.5);
+}
+
+.temp-normal {
+  text-shadow: 0 0 8px rgba(103, 194, 58, 0.5);
+}
+
+/* 网关状态指示 */
+.gateway-status {
+  position: relative;
+  animation: pulse 2s infinite;
+}
+
+@keyframes pulse {
+  0% {
+    box-shadow: 0 0 0 0 rgba(64, 158, 255, 0.7);
+  }
+  70% {
+    box-shadow: 0 0 0 10px rgba(64, 158, 255, 0);
+  }
+  100% {
+    box-shadow: 0 0 0 0 rgba(64, 158, 255, 0);
+  }
+}
+
+/* 详情按钮特效 */
+.el-button--primary.is-plain {
+  background: transparent;
+  border: 2px solid #409eff;
+  color: #409eff;
+  position: relative;
+  overflow: hidden;
+  z-index: 1;
+}
+
+.el-button--primary.is-plain::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: -100%;
+  width: 100%;
+  height: 100%;
+  background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%);
+  transition: left 0.3s ease;
+  z-index: -1;
+}
+
+.el-button--primary.is-plain:hover::before {
+  left: 0;
+}
+
+.el-button--primary.is-plain:hover {
+  color: #fff;
+  border-color: #409eff;
+}
+
+/* 表格头部渐变增强 */
+:deep(.el-table th) {
+  position: relative;
+  overflow: hidden;
+}
+
+:deep(.el-table th::before) {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: -100%;
+  width: 100%;
+  height: 100%;
+  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
+  transition: left 0.8s ease;
+}
+
+:deep(.el-table th:hover::before) {
+  left: 100%;
+}
+
+/* 设备信息卡片效果 */
+.device-info,
+.location-info,
+.gateway-info,
+.temperature-info,
+.device-details {
+  padding: 8px;
+  border-radius: 6px;
+  transition: all 0.3s ease;
+}
+
+.device-info:hover,
+.location-info:hover,
+.gateway-info:hover,
+.temperature-info:hover,
+.device-details:hover {
+  background: rgba(64, 158, 255, 0.05);
+  transform: translateY(-2px);
+}
+
+/* 分页按钮增强 */
+:deep(.el-pagination .el-pager li) {
+  border-radius: 6px;
+  margin: 0 2px;
+  transition: all 0.3s ease;
+}
+
+:deep(.el-pagination .el-pager li:hover) {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15);
+}
+
+:deep(.el-pagination .el-pager li.is-active) {
+  background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%);
+  color: #fff;
+  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
+}
+
+/* 弹窗动画 */
+:deep(.el-dialog) {
+  animation: dialogSlideIn 0.4s ease-out;
+}
+
+@keyframes dialogSlideIn {
+  from {
+    opacity: 0;
+    transform: scale(0.8) translateY(-50px);
+  }
+  to {
+    opacity: 1;
+    transform: scale(1) translateY(0);
+  }
+}
+
+/* 详情区块动画 */
+.detail-section {
+  animation: sectionFadeIn 0.6s ease-out;
+  animation-fill-mode: both;
+}
+
+.detail-section:nth-child(1) { animation-delay: 0.1s; }
+.detail-section:nth-child(2) { animation-delay: 0.2s; }
+.detail-section:nth-child(3) { animation-delay: 0.3s; }
+.detail-section:nth-child(4) { animation-delay: 0.4s; }
+.detail-section:nth-child(5) { animation-delay: 0.5s; }
+
+@keyframes sectionFadeIn {
+  from {
+    opacity: 0;
+    transform: translateX(-30px);
+  }
+  to {
+    opacity: 1;
+    transform: translateX(0);
+  }
+}
+
+/* 搜索表单项动画 */
+.search-form .el-form-item {
+  animation: formItemSlide 0.5s ease-out;
+  animation-fill-mode: both;
+}
+
+.search-form .el-form-item:nth-child(1) { animation-delay: 0.1s; }
+.search-form .el-form-item:nth-child(2) { animation-delay: 0.2s; }
+.search-form .el-form-item:nth-child(3) { animation-delay: 0.3s; }
+.search-form .el-form-item:nth-child(4) { animation-delay: 0.4s; }
+
+@keyframes formItemSlide {
+  from {
+    opacity: 0;
+    transform: translateY(20px);
+  }
+  to {
+    opacity: 1;
+    transform: translateY(0);
+  }
+}
+
+/* 加载状态优化 */
+:deep(.el-loading-spinner) {
+  animation: loadingRotate 1.5s linear infinite;
+}
+
+@keyframes loadingRotate {
+  from {
+    transform: rotate(0deg);
+  }
+  to {
+    transform: rotate(360deg);
+  }
+}
+
+/* 空状态样式 */
+.empty-state {
+  text-align: center;
+  padding: 60px 20px;
+  color: #909399;
+}
+
+.empty-state .empty-icon {
+  font-size: 64px;
+  color: #c0c4cc;
+  margin-bottom: 16px;
+}
+
+.empty-state .empty-text {
+  font-size: 16px;
+  margin-bottom: 8px;
+}
+
+.empty-state .empty-description {
+  font-size: 14px;
+  color: #c0c4cc;
+}
+
+/* 工具提示样式 */
+:deep(.el-tooltip__popper) {
+  background: rgba(0, 0, 0, 0.8);
+  border-radius: 6px;
+  backdrop-filter: blur(10px);
+}
+
+/* 表格固定列阴影 */
+:deep(.el-table__fixed-right) {
+  box-shadow: -2px 0 8px rgba(0, 0, 0, 0.1);
+}
+
+/* 状态徽章样式 */
+.status-badge {
+  position: relative;
+  display: inline-block;
+}
+
+.status-badge::after {
+  content: '';
+  position: absolute;
+  top: -2px;
+  right: -2px;
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  background: #67c23a;
+  border: 2px solid #fff;
+  animation: statusPulse 2s infinite;
+}
+
+.status-badge.offline::after {
+  background: #f56c6c;
+}
+
+.status-badge.warning::after {
+  background: #e6a23c;
+}
+
+@keyframes statusPulse {
+  0% {
+    transform: scale(1);
+    opacity: 1;
+  }
+  50% {
+    transform: scale(1.2);
+    opacity: 0.7;
+  }
+  100% {
+    transform: scale(1);
+    opacity: 1;
+  }
+}
+
+/* 深色模式支持 */
+@media (prefers-color-scheme: dark) {
+  .temperature-device-page {
+    background: #1a1a1a;
+    color: #e4e7ed;
+  }
+
+  .search-form,
+  .table-container,
+  .pagination-container {
+    background: #2d2d2d;
+    border-color: #4c4d4f;
+  }
+
+  .detail-section {
+    background: #363636;
+  }
+
+  :deep(.el-table th) {
+    background: linear-gradient(135deg, #4c4d4f 0%, #5a5b5d 100%);
+  }
+
+  :deep(.el-table td) {
+    background: #2d2d2d;
+    border-color: #4c4d4f;
+  }
+}
+
+/* 打印样式 */
+@media print {
+  .search-form,
+  .pagination-container,
+  .el-table-column--selection,
+  .el-button {
+    display: none !important;
+  }
+
+  .temperature-device-page {
+    background: #fff;
+    padding: 0;
+  }
+
+  .table-container {
+    box-shadow: none;
+    border: 1px solid #000;
+  }
+
+  :deep(.el-table th) {
+    background: #f5f7fa !important;
+    color: #000 !important;
+  }
+}
+</style>

تفاوت فایلی نمایش داده نمی شود زیرا این فایل بسیار بزرگ است
+ 1236 - 961
pm_ui/src/views/nygl/index.vue


+ 1613 - 767
pm_ui/src/views/zmxt/index.vue

@@ -1,816 +1,1662 @@
-<template>
-  <div class="app-container">
-    <el-tabs v-model="activeTab">
-      <!-- 照明监控标签页 -->
-      <el-tab-pane label="照明监控" name="monitor">
+  <template>
+    <div class="app-container">
+      <!-- 查询表单 -->
+      <el-card class="search-card" shadow="never">
         <el-form :model="lightingQuery" ref="lightingQueryRef" :inline="true" label-width="80px">
-          <el-form-item label="楼栋" prop="buildingId">
-            <el-select v-model="lightingQuery.buildingId" placeholder="请选择楼栋" clearable @change="handleBuildingChange" style="width: 180px;">
+          <el-form-item label="所属网关" prop="gateway_id">
+            <el-select
+                v-model="lightingQuery.gateway_id"
+                placeholder="请选择网关"
+                clearable
+                style="width: 280px;"
+                @change="handleGatewayChange"
+            >
               <el-option
-                  v-for="item in buildingList"
-                  :key="item.id"
-                  :label="item.name"
-                  :value="item.id"
-              />
+                  v-for="item in gatewayList"
+                  :key="item.gateway_id"
+                  :label="item.gateway_name"
+                  :value="item.gateway_id"
+              >
+                <div class="gateway-option">
+                  <span class="gateway-name">{{ item.gateway_name }}</span>
+                  <el-tag
+                      size="small"
+                      :type="item.gateway_status === 1 ? 'success' : 'danger'"
+                      class="gateway-status"
+                  >
+                    {{ item.gateway_status === 1 ? '在线' : '离线' }}
+                  </el-tag>
+                </div>
+              </el-option>
             </el-select>
           </el-form-item>
-          <el-form-item label="楼层" prop="floorId">
-            <el-select v-model="lightingQuery.floorId" placeholder="请选择楼层" clearable :disabled="!lightingQuery.buildingId" style="width: 180px;">
-              <el-option
-                  v-for="item in floorList"
-                  :key="item.id"
-                  :label="item.name"
-                  :value="item.id"
-              />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="区域" prop="areaName">
-            <el-input v-model="lightingQuery.areaName" placeholder="请输入区域名称" clearable />
-          </el-form-item>
-          <el-form-item label="状态" prop="status">
-            <el-select v-model="lightingQuery.status" placeholder="请选择状态" clearable style="width: 180px;">
-              <el-option label="关闭" :value="0" />
-              <el-option label="开启" :value="1" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="控制模式" prop="controlMode">
-            <el-select v-model="lightingQuery.controlMode" placeholder="请选择控制模式" clearable style="width: 180px;">
-              <el-option label="手动" :value="0" />
-              <el-option label="自动" :value="1" />
-              <el-option label="定时" :value="2" />
-            </el-select>
+          <el-form-item label="设备名称" prop="query">
+            <el-input
+                v-model="lightingQuery.query"
+                placeholder="请输入设备名称"
+                clearable
+                style="width: 200px;"
+                @keyup.enter="getLightingList"
+                @input="handleQueryInput"
+                @clear="handleQueryClear"
+            />
           </el-form-item>
           <el-form-item>
             <el-button type="primary" icon="Search" @click="getLightingList">搜索</el-button>
             <el-button icon="Refresh" @click="resetLightingQuery">重置</el-button>
           </el-form-item>
         </el-form>
+      </el-card>
 
-        <el-row :gutter="10" class="mb8">
-          <el-col :span="1.5">
-            <el-button
-                type="success"
-                plain
-                icon="Operation"
-                @click="handleBatchControl"
-                :disabled="lightingSelection.length === 0"
-            >批量控制</el-button>
-          </el-col>
-          <el-col :span="1.5">
-            <el-button
-                type="warning"
-                plain
-                icon="Clock"
-                @click="handleSchedule"
-            >定时计划</el-button>
-          </el-col>
-        </el-row>
-
-        <el-table v-loading="lightingLoading" :data="lightingList" stripe border @selection-change="handleLightingSelectionChange">
-          <el-table-column type="selection" width="55" align="center" />
-          <el-table-column label="设备编码" prop="deviceCode"  />
-          <el-table-column label="设备名称" prop="deviceName"  />
-          <el-table-column label="楼栋" prop="buildingName"  />
-          <el-table-column label="楼层" prop="floorName"  />
-          <el-table-column label="区域" prop="areaName" />
-          <el-table-column label="回路" prop="circuitNo"  />
-          <el-table-column label="状态" prop="status" width="80" align="center">
+      <!-- 数据表格 -->
+      <el-card class="table-card" shadow="never">
+        <el-table
+            v-loading="lightingLoading"
+            :data="lightingList"
+            stripe
+            border
+            :row-class-name="tableRowClassName"
+            height="745"
+        >
+          <el-table-column label="设备信息" min-width="250" fixed="left">
             <template #default="scope">
-              <el-switch
-                  v-model="scope.row.status"
-                  :active-value="1"
-                  :inactive-value="0"
-                  @change="handleStatusChange(scope.row)"
-                  :disabled="scope.row.control_mode !== 0"
-              />
+              <div class="device-info">
+                <div class="device-name">
+                  <el-icon class="device-icon"><Monitor /></el-icon>
+                  {{ scope.row.devices_name }}
+                </div>
+                <div class="device-code">{{ scope.row.devices_udid }}</div>
+                <div class="device-type">
+                  <el-tag size="small" type="info">{{ scope.row.devices_type_name }}</el-tag>
+                </div>
+              </div>
             </template>
           </el-table-column>
-          <el-table-column label="亮度" prop="brightness"  align="center">
+
+          <el-table-column label="位置信息" min-width="200">
             <template #default="scope">
-              <el-slider
-                  v-model="scope.row.brightness"
-                  :disabled="scope.row.status === 0 || scope.row.control_mode !== 0"
-                  @change="handleBrightnessChange(scope.row)"
-                  size="small"
-              />
+              <div class="location-info">
+                <div class="building">
+                  <el-icon><OfficeBuilding /></el-icon>
+                  {{ scope.row.building_name }}
+                </div>
+                <div class="region">
+                  <el-icon><Location /></el-icon>
+                  {{ scope.row.full_region_name }}
+                </div>
+                <div class="room" v-if="scope.row.room_name">
+                  <el-icon><House /></el-icon>
+                  {{ scope.row.room_name }}
+                </div>
+              </div>
             </template>
           </el-table-column>
-          <el-table-column label="控制模式" prop="control_mode"  align="center">
+
+          <el-table-column label="网关信息" width="150">
             <template #default="scope">
-              <el-tag :type="controlModeType(scope.row.control_mode)">
-                {{ controlModeText(scope.row.control_mode) }}
-              </el-tag>
+              <div class="gateway-info">
+                <div class="gateway-name">{{ scope.row.gateway_name }}</div>
+              </div>
             </template>
           </el-table-column>
-          <el-table-column label="在线状态" prop="is_online"  align="center">
+
+          <el-table-column label="设备状态" width="120" align="center">
             <template #default="scope">
-              <el-tag :type="scope.row.is_online === 1 ? 'success' : 'info'">
-                {{ scope.row.is_online === 1 ? '在线' : '离线' }}
-              </el-tag>
+              <div class="device-status" style="padding: 10px 2px;">
+                <el-tag :type="getDeviceOnlineStatus(scope.row) ? 'success' : 'danger'" class="status-tag">
+                  <el-icon><CircleCheck v-if="getDeviceOnlineStatus(scope.row)" /><CircleClose v-else /></el-icon>
+                  {{ getDeviceOnlineStatus(scope.row) ? '在线' : '离线' }}
+                </el-tag>
+              </div>
             </template>
           </el-table-column>
-          <el-table-column label="故障状态" prop="fault_status"  align="center">
+
+          <el-table-column label="回路状态" min-width="300" align="center">
             <template #default="scope">
-              <el-tag :type="scope.row.fault_status === 0 ? 'success' : 'danger'">
-                {{ scope.row.fault_status === 0 ? '正常' : '故障' }}
-              </el-tag>
+              <div class="circuit-status" :style="getCircuitGridStyle(scope.row)">
+                <div
+                    v-for="i in getCircuitCount(scope.row)"
+                    :key="i"
+                    class="circuit-item"
+                    :class="{ 'clickable': getDeviceOnlineStatus(scope.row) && scope.row.devices_enabled === 1 }"
+                >
+                  <div class="circuit-name">{{ getCircuitName(scope.row.devices_json_object, i) }}</div>
+                  <el-tag
+                      size="small"
+                      :type="getCircuitStatus(scope.row.devices_json_object, i) ? 'success' : 'info'"
+                      class="circuit-tag"
+                  >
+                    <el-icon><Lightning v-if="getCircuitStatus(scope.row.devices_json_object, i)" /><Close v-else /></el-icon>
+                    {{ i }}
+                  </el-tag>
+                </div>
+              </div>
             </template>
           </el-table-column>
-          <el-table-column label="更新时间" prop="last_update_time" width="160">
+
+          <el-table-column label="最后通信" width="160" align="center">
             <template #default="scope">
-              <span>{{ parseTime(scope.row.last_update_time) }}</span>
+              <div class="time-info">
+                <el-icon><Clock /></el-icon>
+                <span>{{ formatTime(scope.row.devices_last_request_time) }}</span>
+              </div>
             </template>
           </el-table-column>
-          <el-table-column label="操作" fixed="right" width="120">
+
+          <el-table-column label="操作" fixed="right" width="180" align="center">
             <template #default="scope">
-              <el-button link type="primary" icon="Setting" @click="handleDeviceDetail(scope.row)">详情</el-button>
+              <div class="action-buttons">
+                <el-button
+                    type="primary"
+                    size="small"
+                    icon="Setting"
+                    @click="handleDeviceControl(scope.row)"
+                    :disabled="!getDeviceOnlineStatus(scope.row)"
+                    class="action-btn control-btn"
+                >
+                  控制
+                </el-button>
+                <el-button
+                    type="info"
+                    size="small"
+                    icon="View"
+                    @click="handleDeviceDetail(scope.row)"
+                    class="action-btn detail-btn"
+                >
+                  详情
+                </el-button>
+              </div>
             </template>
           </el-table-column>
         </el-table>
 
-        <pagination
-            v-show="lightingTotal > 0"
-            :total="lightingTotal"
-            v-model:page="lightingQuery.pageNum"
-            v-model:limit="lightingQuery.pageSize"
-            @pagination="getLightingList"
-        />
-      </el-tab-pane>
-
-      <!-- 报警记录标签页 -->
-      <el-tab-pane label="报警记录" name="alarm">
-        <el-form :model="alarmQuery" ref="alarmQueryRef" :inline="true" label-width="80px">
-          <el-form-item label="设备名称" prop="deviceName">
-            <el-input v-model="alarmQuery.deviceName" placeholder="请输入设备名称" clearable />
-          </el-form-item>
-          <el-form-item label="报警类型" prop="alarmType">
-            <el-select v-model="alarmQuery.alarmType" placeholder="请选择报警类型" clearable style="width: 180px;">
-              <el-option label="通信故障" value="communication" />
-              <el-option label="灯具故障" value="lamp" />
-              <el-option label="线路故障" value="circuit" />
-              <el-option label="控制器故障" value="controller" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="报警级别" prop="alarmLevel">
-            <el-select v-model="alarmQuery.alarmLevel" placeholder="请选择报警级别" clearable style="width: 180px;">
-              <el-option label="提示" :value="1" />
-              <el-option label="一般" :value="2" />
-              <el-option label="严重" :value="3" />
-            </el-select>
-          </el-form-item>
-          <el-form-item label="处理状态" prop="handleStatus">
-            <el-select v-model="alarmQuery.handleStatus" placeholder="请选择处理状态" clearable style="width: 180px;">
-              <el-option label="未处理" :value="0" />
-              <el-option label="已处理" :value="1" />
-            </el-select>
+        <!-- 分页 -->
+        <div class="pagination-container">
+          <el-pagination
+              v-show="lightingTotal > 0"
+              :total="lightingTotal"
+              v-model:current-page="lightingQuery.currentPage"
+              v-model:page-size="lightingQuery.pageSize"
+              :page-sizes="[10, 20, 50, 100]"
+              layout="total, sizes, prev, pager, next, jumper"
+              @size-change="handlePageSizeChange"
+              @current-change="handleCurrentPageChange"
+          />
+        </div>
+      </el-card>
+
+      <!-- 设备控制对话框 -->
+      <el-dialog v-model="controlVisible" title="设备控制" width="800px" append-to-body>
+        <el-form ref="controlFormRef" :model="controlForm" label-width="100px">
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="设备名称">
+                <el-input v-model="controlForm.deviceName" disabled />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="设备类型">
+                <el-input v-model="controlForm.deviceType" disabled />
+              </el-form-item>
+            </el-col>
+          </el-row>
+          <el-form-item label="所属位置">
+            <el-input v-model="controlForm.location" disabled />
           </el-form-item>
-          <el-form-item label="时间范围" prop="timeRange">
-            <el-date-picker
-                v-model="alarmQuery.timeRange"
-                type="datetimerange"
-                range-separator="至"
-                start-placeholder="开始时间"
-                end-placeholder="结束时间"
-                value-format="YYYY-MM-DD HH:mm:ss"
-            />
+
+          <el-divider content-position="left">回路控制</el-divider>
+
+          <el-form-item label="快捷操作">
+            <el-button-group>
+              <el-button type="success" size="small" @click="setAllCircuits(1)">
+                <el-icon><Lightning /></el-icon>
+                全部开启
+              </el-button>
+              <el-button type="warning" size="small" @click="setAllCircuits(0)">
+                <el-icon><Close /></el-icon>
+                全部关闭
+              </el-button>
+              <el-button type="info" size="small" @click="resetToOriginal">
+                <el-icon><Refresh /></el-icon>
+                重置
+              </el-button>
+            </el-button-group>
+
+            <!-- 变更提示 -->
+            <div v-if="hasChanges()" class="change-indicator">
+              <el-tag type="warning" size="small">
+                <el-icon><Warning /></el-icon>
+                检测到 {{ Object.keys(getChangedCircuits()).length }} 个回路有变更
+              </el-tag>
+            </div>
           </el-form-item>
-          <el-form-item>
-            <el-button type="primary" icon="Search" @click="getAlarmList">搜索</el-button>
-            <el-button icon="Refresh" @click="resetAlarmQuery">重置</el-button>
+
+          <el-form-item label="回路控制">
+            <div class="circuit-control-grid" :style="getControlGridStyle()">
+              <div v-for="i in controlForm.circuitCount" :key="i" class="circuit-control-item"
+                   :class="{ 'has-changes': controlForm.circuits[`tag${i}`] !== controlForm.originalCircuits[`tag${i}`] }">
+                <div class="circuit-label">
+                  <div class="circuit-number">
+                    回路 {{ i }}
+                    <el-icon v-if="controlForm.circuits[`tag${i}`] !== controlForm.originalCircuits[`tag${i}`]"
+                             class="change-icon" color="#e6a23c">
+                      <Warning />
+                    </el-icon>
+                  </div>
+                  <div class="circuit-desc">{{ getCircuitName(controlForm.deviceJsonObject, i) }}</div>
+                </div>
+                <el-switch
+                    v-model="controlForm.circuits[`tag${i}`]"
+                    :active-value="1"
+                    :inactive-value="0"
+                    size="large"
+                    active-text="开"
+                    inactive-text="关"
+                />
+              </div>
+            </div>
+
           </el-form-item>
         </el-form>
+        <template #footer>
+          <div class="dialog-footer">
+            <el-button @click="controlVisible = false">取消</el-button>
+            <el-button type="primary" @click="submitControl" :loading="controlLoading">
+              <el-icon><Check /></el-icon>
+              确定控制
+            </el-button>
+          </div>
+        </template>
+      </el-dialog>
 
-        <el-table v-loading="alarmLoading" :data="alarmList" stripe border>
-          <el-table-column label="设备编码" prop="device_code" width="120" />
-          <el-table-column label="设备名称" prop="device_name" width="150" />
-          <el-table-column label="报警类型" prop="alarm_type" width="120" />
-          <el-table-column label="报警级别" prop="alarm_level" width="100" align="center">
-            <template #default="scope">
-              <el-tag :type="alarmLevelType(scope.row.alarm_level)">
-                {{ alarmLevelText(scope.row.alarm_level) }}
-              </el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column label="报警描述" prop="alarm_desc" show-overflow-tooltip />
-          <el-table-column label="报警时间" prop="alarm_time" width="160">
-            <template #default="scope">
-              <span>{{ parseTime(scope.row.alarm_time) }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="处理状态" prop="handle_status" width="100" align="center">
-            <template #default="scope">
-              <el-tag :type="scope.row.handle_status === 1 ? 'success' : 'warning'">
-                {{ scope.row.handle_status === 1 ? '已处理' : '未处理' }}
-              </el-tag>
-            </template>
-          </el-table-column>
-          <el-table-column label="处理人" prop="handle_user" width="100" />
-          <el-table-column label="处理时间" prop="handle_time" width="160">
-            <template #default="scope">
-              <span>{{ parseTime(scope.row.handle_time) }}</span>
-            </template>
-          </el-table-column>
-          <el-table-column label="操作" fixed="right" width="120">
-            <template #default="scope">
-              <el-button
-                  link
-                  type="primary"
-                  icon="Edit"
-                  @click="handleAlarm(scope.row)"
-                  v-if="scope.row.handle_status === 0"
-              >处理</el-button>
-              <el-button
-                  link
-                  type="primary"
-                  icon="View"
-                  @click="handleAlarmDetail(scope.row)"
-                  v-else
-              >详情</el-button>
-            </template>
-          </el-table-column>
-        </el-table>
+      <!-- 设备详情对话框 -->
+      <el-dialog v-model="detailVisible" title="设备详情" width="900px" append-to-body>
+        <el-descriptions :column="2" border size="large">
+          <el-descriptions-item label="设备名称">
+            <el-tag type="primary">{{ deviceDetail.devices_name }}</el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="设备编码">
+            <el-text class="device-code-text">{{ deviceDetail.devices_udid }}</el-text>
+          </el-descriptions-item>
+          <el-descriptions-item label="设备类型">{{ deviceDetail.devices_type_name }}</el-descriptions-item>
+          <el-descriptions-item label="设备型号">{{ deviceDetail.devices_type_code }}</el-descriptions-item>
+          <el-descriptions-item label="所属建筑">{{ deviceDetail.building_name }}</el-descriptions-item>
+          <el-descriptions-item label="所属区域">{{ deviceDetail.full_region_name }}</el-descriptions-item>
+          <el-descriptions-item label="所属房间">{{ deviceDetail.room_name || '未分配' }}</el-descriptions-item>
+          <el-descriptions-item label="所属网关">
+            <span>{{ deviceDetail.gateway_name }}</span>
+          </el-descriptions-item>
+          <el-descriptions-item label="在线状态">
+            <el-tag :type="getDeviceOnlineStatus(deviceDetail) ? 'success' : 'danger'">
+              <el-icon><CircleCheck v-if="getDeviceOnlineStatus(deviceDetail)" /><CircleClose v-else /></el-icon>
+              {{ getDeviceOnlineStatus(deviceDetail) ? '在线' : '离线' }}
+            </el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="设备状态">
+            <el-tag :type="deviceDetail.devices_enabled === 1 ? 'success' : 'danger'">
+              {{ deviceDetail.devices_enabled === 1 ? '启用' : '禁用' }}
+            </el-tag>
+          </el-descriptions-item>
+          <el-descriptions-item label="创建时间">{{ formatTime(deviceDetail.devices_created) }}</el-descriptions-item>
+          <el-descriptions-item label="最后通信">{{ formatTime(deviceDetail.devices_last_request_time) }}</el-descriptions-item>
+          <el-descriptions-item label="设备描述" :span="2">
+            {{ deviceDetail.devices_description || '暂无描述' }}
+          </el-descriptions-item>
+          <el-descriptions-item label="当前回路状态" :span="2">
+            <div class="circuit-detail-status" :style="getDetailGridStyle()">
+              <div v-for="i in getCircuitCount(deviceDetail)" :key="i" class="circuit-detail-item">
+                <div class="circuit-detail-info">
+                  <div class="circuit-detail-name">{{ getCircuitName(deviceDetail.devices_json_object, i) }}</div>
+                  <el-tag
+                      size="large"
+                      :type="getCircuitStatus(deviceDetail.devices_json_object, i) ? 'success' : 'info'"
+                      class="circuit-detail-tag"
+                  >
+                    <el-icon><Lightning v-if="getCircuitStatus(deviceDetail.devices_json_object, i)" /><Close v-else /></el-icon>
+                    回路{{ i }}: {{ getCircuitStatus(deviceDetail.devices_json_object, i) ? '开启' : '关闭' }}
+                  </el-tag>
+                </div>
+              </div>
+            </div>
+          </el-descriptions-item>
+        </el-descriptions>
+      </el-dialog>
+    </div>
+  </template>
 
-        <pagination
-            v-show="alarmTotal > 0"
-            :total="alarmTotal"
-            v-model:page="alarmQuery.pageNum"
-            v-model:limit="alarmQuery.pageSize"
-            @pagination="getAlarmList"
-        />
-      </el-tab-pane>
-    </el-tabs>
-
-    <!-- 批量控制对话框 -->
-    <el-dialog v-model="batchControlVisible" title="批量控制" width="500px" append-to-body>
-      <el-form ref="batchControlRef" :model="batchControlForm" label-width="80px">
-        <el-form-item label="控制动作">
-          <el-radio-group v-model="batchControlForm.action">
-            <el-radio :label="0">关闭</el-radio>
-            <el-radio :label="1">开启</el-radio>
-            <el-radio :label="2">调光</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item label="亮度调节" v-if="batchControlForm.action === 2">
-          <el-slider v-model="batchControlForm.brightness" :min="0" :max="100" show-input />
-        </el-form-item>
-        <el-form-item label="选中设备">
-          <el-tag v-for="item in lightingSelection" :key="item.id" class="mr8 mb8">
-            {{ item.deviceName }}
-          </el-tag>
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="batchControlVisible = false">取消</el-button>
-          <el-button type="primary" @click="submitBatchControl">确定</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 定时计划对话框 -->
-    <el-dialog v-model="scheduleVisible" title="定时计划管理" width="900px" append-to-body>
-      <el-button type="primary" icon="Plus" @click="handleAddSchedule" class="mb8">新增计划</el-button>
-      <el-table :data="scheduleList" stripe border>
-        <el-table-column label="计划名称" prop="schedule_name" />
-        <el-table-column label="计划类型" prop="schedule_type" width="100" align="center">
-          <template #default="scope">
-            {{ scheduleTypeText(scope.row.schedule_type) }}
-          </template>
-        </el-table-column>
-        <el-table-column label="执行时间" width="160">
-          <template #default="scope">
-            {{ scope.row.start_time }} - {{ scope.row.end_time }}
-          </template>
-        </el-table-column>
-        <el-table-column label="动作" prop="action_type" width="100" align="center">
-          <template #default="scope">
-            {{ actionTypeText(scope.row.action_type) }}
-          </template>
-        </el-table-column>
-        <el-table-column label="状态" prop="is_enabled" width="80" align="center">
-          <template #default="scope">
-            <el-switch
-                v-model="scope.row.is_enabled"
-                :active-value="1"
-                :inactive-value="0"
-                @change="handleScheduleStatusChange(scope.row)"
-            />
-          </template>
-        </el-table-column>
-        <el-table-column label="操作" width="150" align="center">
-          <template #default="scope">
-            <el-button link type="primary" icon="Edit" @click="handleEditSchedule(scope.row)">编辑</el-button>
-            <el-button link type="danger" icon="Delete" @click="handleDeleteSchedule(scope.row)">删除</el-button>
-          </template>
-        </el-table-column>
-      </el-table>
-    </el-dialog>
-
-    <!-- 新增/编辑计划对话框 -->
-    <el-dialog v-model="scheduleFormVisible" :title="scheduleFormTitle" width="600px" append-to-body>
-      <el-form ref="scheduleFormRef" :model="scheduleForm" :rules="scheduleRules" label-width="100px">
-        <el-form-item label="计划名称" prop="scheduleName">
-          <el-input v-model="scheduleForm.scheduleName" placeholder="请输入计划名称" />
-        </el-form-item>
-        <el-form-item label="选择设备" prop="deviceIds">
-          <el-select v-model="scheduleForm.deviceIds" multiple placeholder="请选择设备" style="width: 100%">
-            <el-option
-                v-for="item in allLightingList"
-                :key="item.id"
-                :label="item.device_name"
-                :value="item.id"
-            />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="计划类型" prop="scheduleType">
-          <el-radio-group v-model="scheduleForm.scheduleType">
-            <el-radio :label="0">单次执行</el-radio>
-            <el-radio :label="1">每日执行</el-radio>
-            <el-radio :label="2">每周执行</el-radio>
-            <el-radio :label="3">每月执行</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item label="执行时间" prop="timeRange">
-          <el-time-picker
-              v-model="scheduleForm.startTime"
-              placeholder="开始时间"
-              format="HH:mm"
-              value-format="HH:mm"
-          />
-          <span class="mx8">至</span>
-          <el-time-picker
-              v-model="scheduleForm.endTime"
-              placeholder="结束时间"
-              format="HH:mm"
-              value-format="HH:mm"
-          />
-        </el-form-item>
-        <el-form-item label="周执行日" v-if="scheduleForm.scheduleType === 2" prop="weekDays">
-          <el-checkbox-group v-model="scheduleForm.weekDays">
-            <el-checkbox :label="1">周一</el-checkbox>
-            <el-checkbox :label="2">周二</el-checkbox>
-            <el-checkbox :label="3">周三</el-checkbox>
-            <el-checkbox :label="4">周四</el-checkbox>
-            <el-checkbox :label="5">周五</el-checkbox>
-            <el-checkbox :label="6">周六</el-checkbox>
-            <el-checkbox :label="7">周日</el-checkbox>
-          </el-checkbox-group>
-        </el-form-item>
-        <el-form-item label="月执行日" v-if="scheduleForm.scheduleType === 3" prop="monthDays">
-          <el-select v-model="scheduleForm.monthDays" multiple placeholder="请选择日期" style="width: 100%">
-            <el-option v-for="i in 31" :key="i" :label="i + '日'" :value="i" />
-          </el-select>
-        </el-form-item>
-        <el-form-item label="排除节假日" prop="excludeHolidays">
-          <el-switch v-model="scheduleForm.excludeHolidays" />
-        </el-form-item>
-        <el-form-item label="执行动作" prop="actionType">
-          <el-radio-group v-model="scheduleForm.actionType">
-            <el-radio :label="0">关闭</el-radio>
-            <el-radio :label="1">开启</el-radio>
-            <el-radio :label="2">调光</el-radio>
-          </el-radio-group>
-        </el-form-item>
-        <el-form-item label="亮度设置" v-if="scheduleForm.actionType === 2" prop="brightness">
-          <el-slider v-model="scheduleForm.brightness" :min="0" :max="100" show-input />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="scheduleFormVisible = false">取消</el-button>
-          <el-button type="primary" @click="submitScheduleForm">确定</el-button>
-        </span>
-      </template>
-    </el-dialog>
-
-    <!-- 处理报警对话框 -->
-    <el-dialog v-model="handleAlarmVisible" title="处理报警" width="500px" append-to-body>
-      <el-form ref="handleAlarmRef" :model="handleAlarmForm" label-width="80px">
-        <el-form-item label="设备名称">
-          <el-input v-model="handleAlarmForm.deviceName" disabled />
-        </el-form-item>
-        <el-form-item label="报警描述">
-          <el-input v-model="handleAlarmForm.alarmDesc" type="textarea" :rows="3" disabled />
-        </el-form-item>
-        <el-form-item label="处理备注" prop="handleRemark">
-          <el-input v-model="handleAlarmForm.handleRemark" type="textarea" :rows="3" placeholder="请输入处理备注" />
-        </el-form-item>
-      </el-form>
-      <template #footer>
-        <span class="dialog-footer">
-          <el-button @click="handleAlarmVisible = false">取消</el-button>
-          <el-button type="primary" @click="submitHandleAlarm">确定</el-button>
-        </span>
-      </template>
-    </el-dialog>
-  </div>
-</template>
-
-<script setup>
-import { ref, reactive, onMounted } from 'vue'
-import {
-  listLightingDevice,
-  controlLightingDevice,
-  batchControlLighting,
-  listLightingAlarm,
-  handleLightingAlarm,
-  listLightingSchedule,
-  addLightingSchedule,
-  updateLightingSchedule,
-  deleteLightingSchedule,
-  getBuildingList,
-  getFloorList
-} from '@/api/subsystem/lighting'
-
-const { proxy } = getCurrentInstance()
-const activeTab = ref('monitor')
-
-// 照明监控查询相关
-const lightingQuery = reactive({
-  pageNum: 1,
-  pageSize: 10,
-  buildingId: null,
-  floorId: null,
-  areaName: null,
-  status: null,
-  controlMode: null
-})
-const lightingList = ref([])
-const lightingTotal = ref(0)
-const lightingLoading = ref(false)
-const lightingSelection = ref([])
-const allLightingList = ref([])
-
-// 报警记录查询相关
-const alarmQuery = reactive({
-  pageNum: 1,
-  pageSize: 10,
-  deviceName: null,
-  alarmType: null,
-  alarmLevel: null,
-  handleStatus: null,
-  timeRange: []
-})
-const alarmList = ref([])
-const alarmTotal = ref(0)
-const alarmLoading = ref(false)
-
-// 楼栋楼层数据
-const buildingList = ref([])
-const floorList = ref([])
-
-// 批量控制相关
-const batchControlVisible = ref(false)
-const batchControlForm = reactive({
-  action: 1,
-  brightness: 50
-})
-
-// 定时计划相关
-const scheduleVisible = ref(false)
-const scheduleList = ref([])
-const scheduleFormVisible = ref(false)
-const scheduleFormTitle = ref('')
-const scheduleForm = reactive({
-  id: null,
-  scheduleName: '',
-  deviceIds: [],
-  scheduleType: 1,
-  startTime: '',
-  endTime: '',
-  weekDays: [],
-  monthDays: [],
-  excludeHolidays: false,
-  actionType: 1,
-  brightness: 50
-})
-const scheduleRules = {
-  scheduleName: [{ required: true, message: '请输入计划名称', trigger: 'blur' }],
-  deviceIds: [{ required: true, message: '请选择设备', trigger: 'change' }],
-  scheduleType: [{ required: true, message: '请选择计划类型', trigger: 'change' }],
-  actionType: [{ required: true, message: '请选择执行动作', trigger: 'change' }]
-}
-
-// 处理报警相关
-const handleAlarmVisible = ref(false)
-const handleAlarmForm = reactive({
-  id: null,
-  deviceName: '',
-  alarmDesc: '',
-  handleRemark: ''
-})
-
-// 控制模式类型
-const controlModeType = (mode) => {
-  const types = ['', 'success', 'warning']
-  return types[mode] || ''
-}
-
-// 控制模式文本
-const controlModeText = (mode) => {
-  const texts = ['手动', '自动', '定时']
-  return texts[mode] || '未知'
-}
-
-// 报警级别类型
-const alarmLevelType = (level) => {
-  const types = ['', 'info', 'warning', 'danger']
-  return types[level] || 'info'
-}
-
-// 报警级别文本
-const alarmLevelText = (level) => {
-  const texts = ['', '提示', '一般', '严重']
-  return texts[level] || '未知'
-}
-
-// 计划类型文本
-const scheduleTypeText = (type) => {
-  const texts = ['单次', '每日', '每周', '每月']
-  return texts[type] || '未知'
-}
-
-// 动作类型文本
-const actionTypeText = (type) => {
-  const texts = ['关闭', '开启', '调光']
-  return texts[type] || '未知'
-}
-
-// 获取楼栋列表
-async function loadBuildingList() {
-  const res = await getBuildingList()
-  buildingList.value = res.data
-}
-
-// 楼栋变化处理
-async function handleBuildingChange(val) {
-  lightingQuery.floorId = null
-  floorList.value = []
-  if (val) {
-    const res = await getFloorList({ buildingId: val })
-    floorList.value = res.data
-  }
-}
-
-// 查询照明设备列表
-function getLightingList() {
-  lightingLoading.value = true
-  const params = {
-    ...lightingQuery,
-    pageNum: lightingQuery.pageNum,
-    pageSize: lightingQuery.pageSize
-  }
-  listLightingDevice(params).then(response => {
-    lightingList.value = response.rows
-    lightingTotal.value = response.total
-    lightingLoading.value = false
+  <script setup name="Zm">
+  import { ref, reactive, onMounted, getCurrentInstance } from 'vue'
+  import {
+    Monitor, OfficeBuilding, Location, House,
+    CircleCheck, CircleClose, Lightning, Close, Clock,
+    Check, Search, Refresh
+  } from '@element-plus/icons-vue'
+
+  const { proxy } = getCurrentInstance()
+
+  // 照明监控查询相关
+  const lightingQuery = reactive({
+    currentPage: 1,
+    pageSize: 20,
+    gateway_id: null,
+    query: '',
+    devices_enabled: -1
+  })
+
+  const lightingList = ref([])
+  const lightingTotal = ref(0)
+  const lightingLoading = ref(false)
+  const gatewayList = ref([])
+
+  // 防抖定时器
+  let searchTimer = null
+
+  // 设备控制相关
+  const controlVisible = ref(false)
+  const controlLoading = ref(false)
+  const controlForm = reactive({
+    deviceId: null,
+    deviceName: '',
+    deviceType: '',
+    location: '',
+    deviceJsonObject: '',
+    deviceUdid: '', // 添加设备UDID
+    circuitCount: 0,
+    circuits: {},
+    originalCircuits: {} // 添加原始状态跟踪(修改回路 提交修改的回路其余的不提交)
   })
-}
-
-// 获取所有照明设备(用于选择)
-async function getAllLightingList() {
-  const res = await listLightingDevice({ pageSize: 1000 })
-  allLightingList.value = res.rows
-}
-
-// 重置照明查询
-function resetLightingQuery() {
-  proxy.resetForm('lightingQueryRef')
-  lightingQuery.pageNum = 1
-  getLightingList()
-}
-
-// 选择变化
-function handleLightingSelectionChange(selection) {
-  lightingSelection.value = selection
-}
-
-// 状态切换
-function handleStatusChange(row) {
-  const text = row.status === 1 ? '开启' : '关闭'
-  proxy.$modal.confirm(`确认${text}照明设备"${row.device_name}"吗?`).then(() => {
-    return controlLightingDevice({
-      deviceCode: row.device_code,
-      action: row.status,
-      brightness: row.brightness
+
+  // 设备详情相关
+  const detailVisible = ref(false)
+  const deviceDetail = ref({})
+
+  // 生成UUID
+  function generateUUID() {
+    return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
+      const r = Math.random() * 16 | 0
+      const v = c === 'x' ? r : (r & 0x3 | 0x8)
+      return v.toString(16)
     })
-  }).then(() => {
-    proxy.$modal.msgSuccess(`${text}成功`)
+  }
+
+  // 判断设备在线状态
+  function getDeviceOnlineStatus(device) {
+    if (!device.devices_last_request_time) return false
+    const lastRequestTime = new Date(device.devices_last_request_time)
+    const now = new Date()
+    const diffMinutes = (now - lastRequestTime) / (1000 * 60)
+    return diffMinutes <= 10
+  }
+
+  // 获取设备回路数量
+  function getCircuitCount(device) {
+    try {
+      if (!device.devices_json_object) return 0
+      const obj = JSON.parse(device.devices_json_object)
+      let count = 0
+      // 查找最大的tag数字
+      for (let key in obj) {
+        if (key.startsWith('tag')) {
+          const num = parseInt(key.replace('tag', ''))
+          if (!isNaN(num) && num > count) {
+            count = num
+          }
+        }
+      }
+      return count
+    } catch (error) {
+      return 0
+    }
+  }
+
+  // 获取回路网格样式
+  function getCircuitGridStyle(device) {
+    const count = getCircuitCount(device)
+    if (count <= 4) {
+      return { gridTemplateColumns: `repeat(${count}, 1fr)` }
+    } else if (count <= 8) {
+      return { gridTemplateColumns: 'repeat(4, 1fr)' }
+    } else if (count <= 12) {
+      return { gridTemplateColumns: 'repeat(4, 1fr)' }
+    } else {
+      return { gridTemplateColumns: 'repeat(5, 1fr)' }
+    }
+  }
+
+  // 获取控制对话框网格样式
+  function getControlGridStyle() {
+    const count = controlForm.circuitCount
+    if (count <= 4) {
+      return { gridTemplateColumns: `repeat(${count}, 1fr)` }
+    } else if (count <= 8) {
+      return { gridTemplateColumns: 'repeat(4, 1fr)' }
+    } else if (count <= 12) {
+      return { gridTemplateColumns: 'repeat(4, 1fr)' }
+    } else if (count <= 16) {
+      return { gridTemplateColumns: 'repeat(4, 1fr)' }
+    } else {
+      return { gridTemplateColumns: 'repeat(5, 1fr)' }
+    }
+  }
+
+  // 获取详情对话框网格样式
+  function getDetailGridStyle() {
+    const count = getCircuitCount(deviceDetail.value)
+    if (count <= 2) {
+      return { gridTemplateColumns: `repeat(${count}, 1fr)` }
+    } else if (count <= 4) {
+      return { gridTemplateColumns: 'repeat(2, 1fr)' }
+    } else if (count <= 8) {
+      return { gridTemplateColumns: 'repeat(2, 1fr)' }
+    } else {
+      return { gridTemplateColumns: 'repeat(3, 1fr)' }
+    }
+  }
+
+  // 获取网关列表
+  async function getGatewayList() {
+    try {
+      const res = await request({
+        url: `${__LOCAL_API__}/illuminating/gatewayFind`,
+        method: 'get'
+      })
+      if (res.code === 200) {
+        gatewayList.value = res.data.gatewayAndBacnet || []
+      } else {
+        proxy.$modal.msgError('获取网关列表失败')
+      }
+    } catch (error) {
+      proxy.$modal.msgError('获取网关列表失败')
+    }
+  }
+
+  // 查询照明设备列表
+  async function getLightingList() {
+    lightingLoading.value = true
+    try {
+      const formData = new FormData()
+      formData.append('currentPage', lightingQuery.currentPage)
+      formData.append('pageSize', lightingQuery.pageSize)
+      formData.append('devices_enabled', lightingQuery.devices_enabled)
+
+      if (lightingQuery.gateway_id !== null && lightingQuery.gateway_id !== '') {
+        formData.append('gateway_id', lightingQuery.gateway_id)
+      }
+
+      if (lightingQuery.query && lightingQuery.query.trim() !== '') {
+        formData.append('query', lightingQuery.query.trim())
+      }
+
+      const res = await request({
+        url: `${__LOCAL_API__}/illuminating/getBaseecic`,
+        method: 'post',
+        data: formData,
+        headers: {
+          'Content-Type': 'multipart/form-data'
+        }
+      })
+
+      if (res.code === 200) {
+        lightingList.value = res.data.devices || []
+        lightingTotal.value = res.data.count || 0
+      } else {
+        proxy.$modal.msgError(res.message || '获取设备列表失败')
+      }
+    } catch (error) {
+      proxy.$modal.msgError('获取设备列表失败')
+    } finally {
+      lightingLoading.value = false
+    }
+  }
+
+  // 网关变化处理
+  function handleGatewayChange() {
+    lightingQuery.currentPage = 1
     getLightingList()
-  }).catch(() => {
-    row.status = row.status === 1 ? 0 : 1
-  })
-}
-
-// 亮度调节
-function handleBrightnessChange(row) {
-  controlLightingDevice({
-    deviceCode: row.device_code,
-    action: 2,
-    brightness: row.brightness
-  }).then(() => {
-    proxy.$modal.msgSuccess('亮度调节成功')
-  })
-}
-
-// 批量控制
-function handleBatchControl() {
-  batchControlVisible.value = true
-}
-
-// 提交批量控制
-function submitBatchControl() {
-  const deviceCodes = lightingSelection.value.map(item => item.device_code)
-  batchControlLighting({
-    deviceCodes: deviceCodes,
-    action: batchControlForm.action,
-    brightness: batchControlForm.brightness
-  }).then(() => {
-    proxy.$modal.msgSuccess('批量控制成功')
-    batchControlVisible.value = false
+  }
+
+  // 搜索输入处理(防抖)
+  function handleQueryInput() {
+    if (searchTimer) {
+      clearTimeout(searchTimer)
+    }
+    searchTimer = setTimeout(() => {
+      lightingQuery.currentPage = 1
+      getLightingList()
+    }, 500)
+  }
+
+  // 清空搜索处理
+  function handleQueryClear() {
+    lightingQuery.currentPage = 1
     getLightingList()
-  })
-}
-
-// 定时计划管理
-function handleSchedule() {
-  scheduleVisible.value = true
-  getScheduleList()
-}
-
-// 获取计划列表
-function getScheduleList() {
-  listLightingSchedule().then(response => {
-    scheduleList.value = response.rows
-  })
-}
-
-// 新增计划
-function handleAddSchedule() {
-  resetScheduleForm()
-  scheduleFormTitle.value = '新增定时计划'
-  scheduleFormVisible.value = true
-}
-
-// 编辑计划
-function handleEditSchedule(row) {
-  resetScheduleForm()
-  scheduleFormTitle.value = '编辑定时计划'
-  Object.assign(scheduleForm, {
-    id: row.id,
-    scheduleName: row.schedule_name,
-    deviceIds: row.device_ids ? row.device_ids.split(',').map(Number) : [],
-    scheduleType: row.schedule_type,
-    startTime: row.start_time,
-    endTime: row.end_time,
-    weekDays: row.week_days ? row.week_days.split(',').map(Number) : [],
-    monthDays: row.month_days ? row.month_days.split(',').map(Number) : [],
-    excludeHolidays: row.exclude_holidays === 1,
-    actionType: row.action_type,
-    brightness: row.brightness || 50
-  })
-  scheduleFormVisible.value = true
-}
-
-// 删除计划
-function handleDeleteSchedule(row) {
-  proxy.$modal.confirm(`确认删除计划"${row.schedule_name}"吗?`).then(() => {
-    return deleteLightingSchedule(row.id)
-  }).then(() => {
-    proxy.$modal.msgSuccess('删除成功')
-    getScheduleList()
-  })
-}
-
-// 计划状态切换
-function handleScheduleStatusChange(row) {
-  updateLightingSchedule({
-    id: row.id,
-    isEnabled: row.is_enabled
-  }).then(() => {
-    proxy.$modal.msgSuccess('状态更新成功')
-  })
-}
-
-// 重置计划表单
-function resetScheduleForm() {
-  scheduleForm.id = null
-  scheduleForm.scheduleName = ''
-  scheduleForm.deviceIds = []
-  scheduleForm.scheduleType = 1
-  scheduleForm.startTime = ''
-  scheduleForm.endTime = ''
-  scheduleForm.weekDays = []
-  scheduleForm.monthDays = []
-  scheduleForm.excludeHolidays = false
-  scheduleForm.actionType = 1
-  scheduleForm.brightness = 50
-  proxy.resetForm('scheduleFormRef')
-}
-
-// 提交计划表单
-function submitScheduleForm() {
-  proxy.$refs['scheduleFormRef'].validate(valid => {
-    if (valid) {
-      const data = {
-        ...scheduleForm,
-        deviceIds: scheduleForm.deviceIds.join(','),
-        weekDays: scheduleForm.weekDays.join(','),
-        monthDays: scheduleForm.monthDays.join(','),
-        excludeHolidays: scheduleForm.excludeHolidays ? 1 : 0
+  }
+
+  // 页码变化处理
+  function handleCurrentPageChange() {
+    getLightingList()
+  }
+
+  // 页大小变化处理
+  function handlePageSizeChange() {
+    lightingQuery.currentPage = 1
+    getLightingList()
+  }
+
+  // 重置查询条件
+  function resetLightingQuery() {
+    Object.assign(lightingQuery, {
+      currentPage: 1,
+      pageSize: 20,
+      gateway_id: null,
+      query: '',
+      devices_enabled: -1
+    })
+    getLightingList()
+  }
+
+  // 解析JSON对象获取回路状态
+  function getCircuitStatus(jsonStr, index) {
+    try {
+      if (!jsonStr) return 0
+      const obj = JSON.parse(jsonStr)
+      return obj[`tag${index}`] === 1 ? 1 : 0
+    } catch (error) {
+      return 0
+    }
+  }
+
+  // 获取回路名称
+  function getCircuitName(jsonStr, index) {
+    try {
+      if (!jsonStr) return `回路${index}`
+      const obj = JSON.parse(jsonStr)
+      return obj[`name${index}`] || `回路${index}`
+    } catch (error) {
+      return `回路${index}`
+    }
+  }
+
+  // 设备控制
+  function handleDeviceControl(row) {
+    controlForm.deviceId = row.devices_id
+    controlForm.deviceName = row.devices_name
+    controlForm.deviceType = row.devices_type_name
+    controlForm.deviceUdid = row.devices_udid
+    controlForm.location = `${row.building_name} / ${row.full_region_name}${row.room_name ? ' / ' + row.room_name : ''}`
+    controlForm.deviceJsonObject = row.devices_json_object
+    controlForm.circuitCount = getCircuitCount(row)
+
+    // 重置circuits对象
+    controlForm.circuits = {}
+    controlForm.originalCircuits = {} // 重置原始状态
+
+    try {
+      const deviceStatus = JSON.parse(row.devices_json_object || '{}')
+      for (let i = 1; i <= controlForm.circuitCount; i++) {
+        const status = deviceStatus[`tag${i}`] || 0
+        controlForm.circuits[`tag${i}`] = status
+        controlForm.originalCircuits[`tag${i}`] = status // 保存原始状态
+      }
+    } catch (error) {
+      for (let i = 1; i <= controlForm.circuitCount; i++) {
+        controlForm.circuits[`tag${i}`] = 0
+        controlForm.originalCircuits[`tag${i}`] = 0 // 保存原始状态
+      }
+    }
+
+    controlVisible.value = true
+  }
+
+  // 检测变更的回路
+  function getChangedCircuits() {
+    const changedCircuits = {}
+    for (let i = 1; i <= controlForm.circuitCount; i++) {
+      const tagKey = `tag${i}`
+      if (controlForm.circuits[tagKey] !== controlForm.originalCircuits[tagKey]) {
+        changedCircuits[tagKey] = controlForm.circuits[tagKey]
+      }
+    }
+    return changedCircuits
+  }
+
+  // 检查是否有变更
+  function hasChanges() {
+    for (let i = 1; i <= controlForm.circuitCount; i++) {
+      const tagKey = `tag${i}`
+      if (controlForm.circuits[tagKey] !== controlForm.originalCircuits[tagKey]) {
+        return true
+      }
+    }
+    return false
+  }
+
+  // 设置所有回路
+  function setAllCircuits(value) {
+    for (let i = 1; i <= controlForm.circuitCount; i++) {
+      controlForm.circuits[`tag${i}`] = value
+    }
+  }
+
+  // 添加重置到原始状态的函数
+  function resetToOriginal() {
+    for (let i = 1; i <= controlForm.circuitCount; i++) {
+      const tagKey = `tag${i}`
+      controlForm.circuits[tagKey] = controlForm.originalCircuits[tagKey]
+    }
+  }
+
+  // 提交控制
+  async function submitControl() {
+    // 检查是否有变更
+    if (!hasChanges()) {
+      proxy.$modal.msgWarning('没有检测到任何变更')
+      return
+    }
+
+    // 获取变更的回路
+    const changedCircuits = getChangedCircuits()
+    const changedCount = Object.keys(changedCircuits).length
+
+    // 确认提交
+    const confirmResult = await proxy.$modal.confirm(
+        `检测到 ${changedCount} 个回路状态发生变更,是否确认提交?`,
+        '确认控制',
+        {
+          confirmButtonText: '确认',
+          cancelButtonText: '取消',
+          type: 'warning'
+        }
+    ).catch(() => false)
+
+    if (!confirmResult) return
+
+    controlLoading.value = true
+    try {
+      const requestData = {
+        udid: controlForm.deviceUdid,
+        source: 1,
+        agreement_json: JSON.stringify(changedCircuits) // 只提交变更的回路
       }
-      if (scheduleForm.id) {
-        updateLightingSchedule(data).then(() => {
-          proxy.$modal.msgSuccess('修改成功')
-          scheduleFormVisible.value = false
-          getScheduleList()
-        })
+
+      console.log('提交的变更回路:', changedCircuits)
+
+      const res = await request({
+        url: `${__LOCAL_API__}/illuminating/devicesControl`,
+        method: 'post',
+        data: requestData,
+        headers: {
+          'Content-Type': 'multipart/form-data'
+        }
+      })
+
+      if (res.code === 200) {
+        proxy.$modal.msgSuccess(`控制指令已发送,变更了 ${changedCount} 个回路`)
+        controlVisible.value = false
+        // 延迟刷新列表,给设备一些响应时间
+        setTimeout(() => {
+          getLightingList()
+        }, 1000)
       } else {
-        addLightingSchedule(data).then(() => {
-          proxy.$modal.msgSuccess('新增成功')
-          scheduleFormVisible.value = false
-          getScheduleList()
-        })
+        proxy.$modal.msgError(res.message || '控制失败')
       }
+    } catch (error) {
+      console.error('设备控制失败:', error)
+      proxy.$modal.msgError('控制失败')
+    } finally {
+      controlLoading.value = false
     }
+  }
+
+  // 查看设备详情
+  function handleDeviceDetail(row) {
+    deviceDetail.value = { ...row }
+    detailVisible.value = true
+  }
+
+  // 表格行样式
+  function tableRowClassName({ row }) {
+    if (!getDeviceOnlineStatus(row)) {
+      return 'offline-row'
+    }
+    if (row.devices_enabled === 0) {
+      return 'disabled-row'
+    }
+    return ''
+  }
+
+  // 时间格式化
+  function formatTime(time) {
+    if (!time) return '-'
+    try {
+      const date = new Date(time)
+      const Y = date.getFullYear()
+      const M = (date.getMonth() + 1).toString().padStart(2, '0')
+      const D = date.getDate().toString().padStart(2, '0')
+      const h = date.getHours().toString().padStart(2, '0')
+      const m = date.getMinutes().toString().padStart(2, '0')
+      const s = date.getSeconds().toString().padStart(2, '0')
+      return `${Y}-${M}-${D} ${h}:${m}:${s}`
+    } catch (error) {
+      return '-'
+    }
+  }
+
+  // 导入request工具
+  import request from '@/utils/request'
+
+  // 初始化
+  onMounted(async () => {
+    await getGatewayList()
+    await getLightingList()
+
+    // 设置定时刷新
+    /*setInterval(() => {
+      getLightingList()
+    }, 60000)*/
   })
-}
-
-// 查询报警列表
-function getAlarmList() {
-  alarmLoading.value = true
-  const params = {
-    ...alarmQuery,
-    pageNum: alarmQuery.pageNum,
-    pageSize: alarmQuery.pageSize
-  }
-  if (alarmQuery.timeRange && alarmQuery.timeRange.length === 2) {
-    params.beginTime = alarmQuery.timeRange[0]
-    params.endTime = alarmQuery.timeRange[1]
-  }
-  listLightingAlarm(params).then(response => {
-    alarmList.value = response.rows
-    alarmTotal.value = response.total
-    alarmLoading.value = false
-  })
-}
-
-// 重置报警查询
-function resetAlarmQuery() {
-  proxy.resetForm('alarmQueryRef')
-  alarmQuery.pageNum = 1
-  getAlarmList()
-}
-
-// 处理报警
-function handleAlarm(row) {
-  handleAlarmForm.id = row.id
-  handleAlarmForm.deviceName = row.device_name
-  handleAlarmForm.alarmDesc = row.alarm_desc
-  handleAlarmForm.handleRemark = ''
-  handleAlarmVisible.value = true
-}
-
-// 提交处理报警
-function submitHandleAlarm() {
-  handleLightingAlarm({
-    id: handleAlarmForm.id,
-    handleRemark: handleAlarmForm.handleRemark
-  }).then(() => {
-    proxy.$modal.msgSuccess('处理成功')
-    handleAlarmVisible.value = false
-    getAlarmList()
-  })
-}
-
-// 查看报警详情
-function handleAlarmDetail(row) {
-  // 可以打开详情对话框显示更多信息
-  proxy.$modal.msgWarning(`待开发`)
-}
-
-// 查看设备详情
-function handleDeviceDetail(row) {
-  // 可以跳转到设备详情页面或打开详情对话框
-  proxy.$modal.msgWarning(`设备编码:${row.deviceCode}`+'详情对话框')
-}
-
-// 初始化
-onMounted(() => {
-  loadBuildingList()
-  getLightingList()
-  getAllLightingList()
-})
-</script>
-
-<style scoped>
-.mr8 {
-  margin-right: 8px;
-}
-.mb8 {
-  margin-bottom: 8px;
-}
-.mx8 {
-  margin: 0 8px;
-}
-</style>
+  </script>
+
+  <style scoped>
+
+  .change-indicator {
+    margin-top: 8px;
+    display: flex;
+    align-items: center;
+    gap: 4px;
+  }
+
+  .change-indicator .el-tag {
+    display: flex;
+    align-items: center;
+    gap: 4px;
+  }
+  .app-container {
+    padding: 20px;
+    background-color: #f5f7fa;
+    min-height: 90vh;
+  }
+
+  /* 卡片样式 */
+  .search-card, .table-card {
+    margin-bottom: 20px;
+    border-radius: 12px;
+    border: none;
+    box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+  }
+
+  .search-card {
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    color: white;
+  }
+
+  .search-card :deep(.el-form-item__label) {
+    color: white;
+    font-weight: 500;
+  }
+
+  .search-card :deep(.el-input__wrapper) {
+    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.3) inset;
+  }
+
+  .search-card :deep(.el-select .el-input__wrapper) {
+    box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.3) inset;
+  }
+
+  .table-card {
+    padding: 0;
+    background: white;
+  }
+
+  /* 网关选择器样式 */
+  .gateway-option {
+    display: flex;
+    justify-content: space-between;
+    align-items: center;
+    width: 100%;
+  }
+
+  .gateway-name {
+    flex: 1;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  .gateway-status {
+    margin-left: 8px;
+  }
+
+  /* 设备信息样式 */
+  .device-info {
+    padding: 12px 0;
+  }
+
+  .device-name {
+    display: flex;
+    align-items: center;
+    font-weight: 600;
+    color: #303133;
+    margin-bottom: 6px;
+    font-size: 15px;
+  }
+
+  .device-icon {
+    margin-right: 8px;
+    color: #409eff;
+    font-size: 16px;
+  }
+
+  .device-code {
+    font-size: 12px;
+    color: #909399;
+    font-family: 'Courier New', monospace;
+    margin-bottom: 6px;
+    background: #f5f7fa;
+    padding: 2px 6px;
+    border-radius: 4px;
+    display: inline-block;
+  }
+
+  /* 位置信息样式 */
+  .location-info {
+    padding: 12px 0;
+  }
+
+  .location-info > div {
+    display: flex;
+    align-items: center;
+    margin-bottom: 6px;
+    font-size: 13px;
+    color: #606266;
+  }
+
+  .location-info .el-icon {
+    margin-right: 8px;
+    color: #909399;
+    font-size: 14px;
+  }
+
+  .building {
+    font-weight: 600;
+    color: #303133;
+  }
+
+  /* 网关信息样式 */
+  .gateway-info {
+    padding: 12px 0;
+    text-align: center;
+  }
+
+  .gateway-name {
+    font-weight: 500;
+    color: #303133;
+    font-size: 14px;
+  }
+
+  /* 设备状态样式 */
+  .device-status {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    gap: 10px;
+  }
+
+  .status-tag {
+    display: flex;
+    align-items: center;
+    font-weight: 500;
+  }
+
+  .status-tag .el-icon {
+    margin-right: 4px;
+  }
+
+  .device-switch {
+    margin-top: 6px;
+  }
+
+  /* 回路状态样式 */
+  .circuit-status {
+    display: grid;
+    gap: 8px;
+    padding: 12px;
+    min-height: 80px;
+    max-height: 200px;
+    overflow-y: auto;
+  }
+
+  .circuit-item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 6px;
+    background: #f8f9fa;
+    border-radius: 6px;
+    transition: all 0.3s;
+    cursor: default;
+    min-width: 50px;
+    min-height: 60px;
+  }
+
+  .circuit-item.clickable {
+    cursor: pointer;
+    border: 2px solid transparent;
+  }
+
+  .circuit-item.clickable:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+    border-color: #409eff;
+    background: white;
+  }
+
+  .circuit-name {
+    font-size: 10px;
+    color: #666;
+    margin-bottom: 3px;
+    text-align: center;
+    line-height: 1.1;
+    max-width: 70px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  .circuit-tag {
+    display: flex;
+    align-items: center;
+    min-width: 35px;
+    justify-content: center;
+    font-weight: 500;
+    font-size: 11px;
+    padding: 2px 4px;
+  }
+
+  .circuit-tag .el-icon {
+    margin-right: 2px;
+    font-size: 10px;
+  }
+
+  /* 时间信息样式 */
+  .time-info {
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    font-size: 12px;
+    color: #606266;
+  }
+
+  .time-info .el-icon {
+    margin-right: 6px;
+    color: #909399;
+  }
+
+  /* 操作按钮样式 */
+  .action-buttons {
+    display: flex;
+    gap: 8px;
+    justify-content: center;
+    align-items: center;
+  }
+
+  .action-btn {
+    min-width: 60px;
+    height: 32px;
+    border-radius: 6px;
+    font-weight: 500;
+    font-size: 12px;
+    transition: all 0.3s ease;
+    border: 1px solid transparent;
+  }
+
+  .control-btn {
+    background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
+    color: white;
+    border: none;
+  }
+
+  .control-btn:hover:not(:disabled) {
+    transform: translateY(-1px);
+    box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
+  }
+
+  .control-btn:disabled {
+    background: #c0c4cc;
+    color: #ffffff;
+    cursor: not-allowed;
+    transform: none;
+    box-shadow: none;
+  }
+
+  .detail-btn {
+    background: linear-gradient(135deg, #909399 0%, #a6a9ad 100%);
+    color: white;
+    border: none;
+  }
+
+  .detail-btn:hover {
+    transform: translateY(-1px);
+    box-shadow: 0 4px 12px rgba(144, 147, 153, 0.4);
+  }
+
+  /* 表格样式 */
+  :deep(.el-table) {
+    border-radius: 12px;
+    overflow: hidden;
+    border: none;
+  }
+
+  :deep(.el-table th) {
+    background: linear-gradient(135deg, #f8f9fa 100%, #e9ecef 100%);
+    color: #303133;
+    font-weight: 600;
+    border: none;
+    padding: 12px 12px;
+  }
+
+  :deep(.el-table td) {
+    border-bottom: 1px solid #f0f2f5;
+  }
+
+  :deep(.el-table tr:hover > td) {
+    background-color: #f8f9fa;
+  }
+
+  :deep(.el-table .offline-row) {
+    background-color: #fef0f0;
+  }
+
+  :deep(.el-table .offline-row:hover > td) {
+    background-color: #fde2e2;
+  }
+
+  :deep(.el-table .disabled-row) {
+    background-color: #f5f5f5;
+  }
+
+  :deep(.el-table .disabled-row:hover > td) {
+    background-color: #eeeeee;
+  }
+
+  /* 控制对话框样式 */
+  .circuit-control-grid {
+    display: grid;
+    gap: 16px;
+    padding: 16px;
+    background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
+    border-radius: 12px;
+    border: 1px solid #e4e7ed;
+    max-height: 500px;
+    overflow-y: auto;
+  }
+
+  .circuit-control-item {
+    display: flex;
+    flex-direction: column;
+    align-items: center;
+    padding: 16px;
+    background: white;
+    border-radius: 10px;
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+    transition: all 0.3s ease;
+    border: 2px solid transparent;
+    min-height: 120px;
+  }
+
+  .circuit-control-item:hover {
+    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.15);
+    transform: translateY(-2px);
+    border-color: #409eff;
+  }
+
+  .circuit-label {
+    text-align: center;
+    margin-bottom: 10px;
+  }
+
+  .circuit-number {
+    font-size: 14px;
+    font-weight: 600;
+    color: #303133;
+    margin-bottom: 3px;
+  }
+
+  .circuit-desc {
+    font-size: 11px;
+    color: #666;
+    line-height: 1.2;
+    max-width: 80px;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  /* 设备详情样式 */
+  .device-code-text {
+    font-family: 'Courier New', monospace;
+    font-size: 12px;
+    color: #606266;
+    background-color: #f5f7fa;
+    padding: 6px 10px;
+    border-radius: 6px;
+    border: 1px solid #e4e7ed;
+  }
+
+  .circuit-detail-status {
+    display: grid;
+    gap: 10px;
+    padding: 12px;
+    background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
+    border-radius: 8px;
+    max-height: 500px;
+    overflow-y: auto;
+  }
+
+  .circuit-detail-item {
+    background: white;
+    padding: 10px;
+    border-radius: 6px;
+    box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
+  }
+
+  .circuit-detail-info {
+    display: flex;
+    flex-direction: column;
+    gap: 6px;
+  }
+
+  .circuit-detail-name {
+    font-size: 12px;
+    color: #666;
+    font-weight: 500;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+
+  .circuit-detail-tag {
+    display: flex;
+    align-items: center;
+    padding: 6px 10px;
+    border-radius: 5px;
+    font-weight: 500;
+    font-size: 12px;
+  }
+
+  .circuit-detail-tag .el-icon {
+    margin-right: 4px;
+    font-size: 12px;
+  }
+
+  /* 分页样式 */
+  .pagination-container {
+    display: flex;
+    justify-content: flex-end;
+    padding: 20px;
+    background-color: #fff;
+    border-top: 1px solid #f0f2f5;
+  }
+
+  /* 对话框样式 */
+  :deep(.el-dialog) {
+    border-radius: 12px;
+    overflow: hidden;
+  }
+
+  :deep(.el-dialog__header) {
+    background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+    color: white;
+    padding: 20px;
+  }
+
+  :deep(.el-dialog__title) {
+    color: white;
+    font-weight: 600;
+  }
+
+  :deep(.el-dialog__headerbtn .el-dialog__close) {
+    color: white;
+  }
+
+  .dialog-footer {
+    display: flex;
+    justify-content: flex-end;
+    gap: 12px;
+    padding: 20px;
+    background: #f8f9fa;
+    margin: 0 -20px -20px -20px;
+  }
+
+  /* 按钮样式优化 */
+  .el-button {
+    border-radius: 8px;
+    font-weight: 500;
+    transition: all 0.3s ease;
+  }
+
+  .el-button:hover {
+    transform: translateY(-2px);
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+  }
+
+  .el-button--primary {
+    background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
+    border: none;
+  }
+
+  .el-button--success {
+    background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
+    border: none;
+  }
+
+  .el-button--warning {
+    background: linear-gradient(135deg, #e6a23c 0%, #ebb563 100%);
+    border: none;
+  }
+
+  .el-button--info {
+    background: linear-gradient(135deg, #909399 0%, #a6a9ad 100%);
+    border: none;
+  }
+
+  /* 标签样式优化 */
+  .el-tag {
+    border: none;
+    font-weight: 500;
+    border-radius: 6px;
+    padding: 4px 8px;
+  }
+
+  .el-tag--success {
+    background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
+    color: #059669;
+    border: 1px solid #a7f3d0;
+  }
+
+  .el-tag--danger {
+    background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
+    color: #dc2626;
+    border: 1px solid #fca5a5;
+  }
+
+  .el-tag--info {
+    background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
+    color: #64748b;
+    border: 1px solid #cbd5e1;
+  }
+
+  .el-tag--primary {
+    background: linear-gradient(135deg, #eff6ff 0%, #dbeafe 100%);
+    color: #2563eb;
+    border: 1px solid #93c5fd;
+  }
+
+  /* 开关样式优化 */
+  :deep(.el-switch) {
+    --el-switch-on-color: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
+    --el-switch-off-color: #dcdfe6;
+  }
+
+  :deep(.el-switch.is-checked .el-switch__core) {
+    background: linear-gradient(135deg, #67c23a 0%, #85ce61 100%);
+    border-color: #67c23a;
+  }
+
+  /* 描述列表样式优化 */
+  :deep(.el-descriptions) {
+    border-radius: 8px;
+    overflow: hidden;
+  }
+
+  :deep(.el-descriptions__label) {
+    font-weight: 600;
+    color: #374151;
+    background: linear-gradient(135deg, #f9fafb 0%, #f3f4f6 100%);
+  }
+
+  :deep(.el-descriptions__content) {
+    color: #111827;
+    background: white;
+  }
+
+  /* 表单样式优化 */
+  :deep(.el-form-item__label) {
+    font-weight: 500;
+    color: #374151;
+  }
+
+  :deep(.el-input__wrapper) {
+    border-radius: 8px;
+    box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+    transition: all 0.3s;
+  }
+
+  :deep(.el-input__wrapper:hover) {
+    box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
+  }
+
+  :deep(.el-input__wrapper.is-focus) {
+    box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
+  }
+
+  :deep(.el-select .el-input__wrapper) {
+    border-radius: 8px;
+  }
+
+  /* 响应式设计 */
+  @media (max-width: 1200px) {
+    .circuit-control-grid {
+      grid-template-columns: repeat(3, 1fr) !important;
+      gap: 12px;
+    }
+
+    .circuit-status {
+      grid-template-columns: repeat(3, 1fr) !important;
+      gap: 6px;
+    }
+  }
+
+  @media (max-width: 768px) {
+    .app-container {
+      padding: 10px;
+    }
+
+    .circuit-control-grid {
+      grid-template-columns: repeat(2, 1fr) !important;
+      gap: 10px;
+      padding: 12px;
+    }
+
+    .circuit-status {
+      grid-template-columns: repeat(2, 1fr) !important;
+      gap: 4px;
+      padding: 8px;
+    }
+
+    .circuit-detail-status {
+      grid-template-columns: 1fr !important;
+    }
+
+    .action-buttons {
+      flex-direction: column;
+      gap: 4px;
+    }
+
+    .action-btn {
+      width: 100%;
+      min-width: auto;
+    }
+
+    .circuit-control-item {
+      padding: 12px;
+      min-height: 100px;
+    }
+
+    .circuit-item {
+      padding: 4px;
+      min-width: 40px;
+      min-height: 50px;
+    }
+
+    .circuit-name {
+      font-size: 9px;
+      max-width: 60px;
+    }
+
+    .circuit-tag {
+      font-size: 10px;
+      min-width: 30px;
+    }
+  }
+
+  @media (max-width: 480px) {
+    .circuit-control-grid {
+      grid-template-columns: 1fr !important;
+    }
+
+    .circuit-status {
+      grid-template-columns: repeat(2, 1fr) !important;
+    }
+  }
+
+  /* 为12路及以上设备添加特殊样式 */
+  .circuit-status[style*="repeat(4, 1fr)"] .circuit-item {
+    min-width: 45px;
+    padding: 5px;
+  }
+
+  .circuit-status[style*="repeat(4, 1fr)"] .circuit-name {
+    font-size: 9px;
+    max-width: 65px;
+  }
+
+  .circuit-status[style*="repeat(4, 1fr)"] .circuit-tag {
+    font-size: 10px;
+    min-width: 32px;
+  }
+
+  /* 控制对话框滚动条样式 */
+  .circuit-control-grid::-webkit-scrollbar,
+  .circuit-detail-status::-webkit-scrollbar,
+  .circuit-status::-webkit-scrollbar {
+    width: 6px;
+  }
+
+  .circuit-control-grid::-webkit-scrollbar-track,
+  .circuit-detail-status::-webkit-scrollbar-track,
+  .circuit-status::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 3px;
+  }
+
+  .circuit-control-grid::-webkit-scrollbar-thumb,
+  .circuit-detail-status::-webkit-scrollbar-thumb,
+  .circuit-status::-webkit-scrollbar-thumb {
+    background: #c1c1c1;
+    border-radius: 3px;
+  }
+
+  .circuit-control-grid::-webkit-scrollbar-thumb:hover,
+  .circuit-detail-status::-webkit-scrollbar-thumb:hover,
+  .circuit-status::-webkit-scrollbar-thumb:hover {
+    background: #a8a8a8;
+  }
+
+  /* 滚动条样式 */
+  ::-webkit-scrollbar {
+    width: 8px;
+    height: 8px;
+  }
+
+  ::-webkit-scrollbar-track {
+    background: #f1f1f1;
+    border-radius: 4px;
+  }
+
+  ::-webkit-scrollbar-thumb {
+    background: linear-gradient(135deg, #c1c1c1 0%, #a8a8a8 100%);
+    border-radius: 4px;
+  }
+
+  ::-webkit-scrollbar-thumb:hover {
+    background: linear-gradient(135deg, #a8a8a8 0%, #909090 100%);
+  }
+
+  /* 加载动画优化 */
+  :deep(.el-loading-mask) {
+    border-radius: 12px;
+    background: rgba(255, 255, 255, 0.9);
+    backdrop-filter: blur(4px);
+  }
+
+  /* 动画效果 */
+  .el-card {
+    transition: all 0.3s ease;
+  }
+
+  .el-card:hover {
+    box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+  }
+
+  /* 分页器样式优化 */
+  :deep(.el-pagination) {
+    display: flex;
+    align-items: center;
+    gap: 8px;
+  }
+
+  :deep(.el-pagination .btn-prev),
+  :deep(.el-pagination .btn-next) {
+    border-radius: 8px;
+    border: 1px solid #e4e7ed;
+    transition: all 0.3s;
+  }
+
+  :deep(.el-pagination .btn-prev:hover),
+  :deep(.el-pagination .btn-next:hover) {
+    border-color: #409eff;
+    color: #409eff;
+    box-shadow: 0 2px 4px rgba(64, 158, 255, 0.2);
+  }
+
+  :deep(.el-pager li) {
+    border-radius: 8px;
+    margin: 0 2px;
+    transition: all 0.3s;
+  }
+
+  :deep(.el-pager li:hover) {
+    background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
+    color: #409eff;
+  }
+
+  :deep(.el-pager li.is-active) {
+    background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
+    color: white;
+    box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
+  }
+
+  /* 表格选择框样式 */
+  :deep(.el-table .el-checkbox) {
+    transform: scale(1.1);
+  }
+
+  :deep(.el-table .el-checkbox__input.is-checked .el-checkbox__inner) {
+    background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
+    border-color: #409eff;
+  }
+
+  /* 下拉菜单样式 */
+  :deep(.el-select-dropdown) {
+    border-radius: 8px;
+    box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+    border: none;
+  }
+
+  :deep(.el-select-dropdown__item) {
+    border-radius: 4px;
+    margin: 2px 8px;
+    transition: all 0.3s;
+  }
+
+  :deep(.el-select-dropdown__item:hover) {
+    background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
+    color: #409eff;
+  }
+
+  /* 分割线样式 */
+  :deep(.el-divider) {
+    border-color: #e4e7ed;
+    margin: 24px 0;
+  }
+
+  :deep(.el-divider__text) {
+    background: white;
+    color: #303133;
+    font-weight: 500;
+    padding: 0 20px;
+  }
+
+  /* 脉冲动画(用于在线状态指示) */
+  @keyframes pulse {
+    0% {
+      box-shadow: 0 0 0 0 rgba(103, 194, 58, 0.7);
+    }
+    70% {
+      box-shadow: 0 0 0 10px rgba(103, 194, 58, 0);
+    }
+    100% {
+      box-shadow: 0 0 0 0 rgba(103, 194, 58, 0);
+    }
+  }
+
+  .status-tag.el-tag--success {
+    animation: pulse 2s infinite;
+  }
+
+  /* 控制成功提示样式 */
+  .control-success {
+    background: linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%);
+    border: 1px solid #93c5fd;
+    color: #1e40af;
+  }
+
+  /* 控制失败提示样式 */
+  .control-error {
+    background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
+    border: 1px solid #fca5a5;
+    color: #dc2626;
+  }
+
+  /* 加载状态样式 */
+  .loading-overlay {
+    position: absolute;
+    top: 0;
+    left: 0;
+    right: 0;
+    bottom: 0;
+    background: rgba(255, 255, 255, 0.8);
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    border-radius: 8px;
+    z-index: 10;
+  }
+
+  /* 控制按钮加载状态 */
+  .control-btn.is-loading {
+    pointer-events: none;
+    opacity: 0.6;
+  }
+
+  /* 回路项点击反馈 */
+  .circuit-item.clickable:active {
+    transform: translateY(0);
+    box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
+  }
+
+  /* 设备离线时的样式 */
+  .device-offline .circuit-item {
+    opacity: 0.5;
+    cursor: not-allowed;
+  }
+
+  .device-offline .circuit-item:hover {
+    transform: none;
+    box-shadow: none;
+    border-color: transparent;
+  }
+
+  /* 设备禁用时的样式 */
+  .device-disabled .circuit-item {
+    opacity: 0.3;
+    cursor: not-allowed;
+  }
+
+  .device-disabled .circuit-item:hover {
+    transform: none;
+    box-shadow: none;
+    border-color: transparent;
+  }
+  </style>

+ 4483 - 0
pm_ui/src/views/zmxt/index2.vue

@@ -0,0 +1,4483 @@
+<template>
+  <div class="timing-strategy-page">
+    <!-- 页面标题 -->
+    <div class="page-header">
+      <h2 class="page-title">
+        <el-icon><Clock /></el-icon>
+        定时策略管理
+      </h2>
+      <p class="page-description">管理设备的定时控制策略,支持按周期和时间自动执行设备操作</p>
+    </div>
+
+    <!-- 查询条件卡片 -->
+    <el-card class="search-card" shadow="hover">
+      <el-form :model="searchForm" inline class="search-form">
+        <el-form-item label="策略名称">
+          <el-input
+              v-model="searchForm.searchText"
+              placeholder="请输入策略名称搜索"
+              clearable
+              prefix-icon="Search"
+              @keyup.enter="handleSearch"
+              class="search-input"
+          />
+        </el-form-item>
+        <el-form-item>
+          <el-button type="primary" @click="handleSearch" :icon="Search">
+            搜索
+          </el-button>
+          <el-button @click="handleReset" :icon="Refresh">
+            重置
+          </el-button>
+          <el-button type="success" @click="handleAdd" :icon="Plus">
+            新增策略
+          </el-button>
+        </el-form-item>
+      </el-form>
+    </el-card>
+
+    <!-- 数据表格卡片 -->
+    <el-card class="table-card" shadow="hover">
+      <template #header>
+        <div class="card-header">
+          <div class="header-left">
+            <el-icon><List /></el-icon>
+            <span>策略列表</span>
+            <el-tag type="info" class="count-tag">共 {{ pagination.total }} 条</el-tag>
+          </div>
+        </div>
+      </template>
+
+      <el-table
+          :data="tableData"
+          stripe
+          class="strategy-table"
+      >
+        <el-table-column prop="timing_name" label="策略名称" min-width="150">
+          <template #default="{ row }">
+            <div class="strategy-name">
+              <el-icon class="name-icon"><Timer /></el-icon>
+              {{ row.timing_name }}
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="weeks" label="重复日期" min-width="200">
+          <template #default="{ row }">
+            <div class="weeks-container">
+              <el-tag
+                  v-for="week in getWeekTags(row.weeks)"
+                  :key="week.value"
+                  :type="week.type"
+                  size="small"
+                  class="week-tag"
+              >
+                {{ week.label }}
+              </el-tag>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="timing_start_time" label="执行时间" width="120">
+          <template #default="{ row }">
+            <div class="time-display">
+              <el-icon><Clock /></el-icon>
+              {{ row.timing_start_time }}
+            </div>
+          </template>
+        </el-table-column>
+
+        <!-- 保持原来的设备指令列显示方式,但优化内容 -->
+        <el-table-column prop="timing_agreement" label="设备指令" min-width="180">
+          <template #default="{ row }">
+            <div class="agreement-display">
+              <el-tag
+                  v-for="(item, index) in getAgreementTags(row.timing_agreement)"
+                  :key="index"
+                  :type="item.type"
+                  size="small"
+                  class="agreement-tag"
+                  :class="item.configType"
+              >
+        <span class="tag-icon" v-if="item.icon">
+          <el-icon>
+            <component :is="item.icon" />
+          </el-icon>
+        </span>
+                {{ item.label }}
+              </el-tag>
+
+              <!-- 如果没有任何配置显示提示 -->
+              <el-tag v-if="getAgreementTags(row.timing_agreement).length === 0" type="info" size="small" class="no-config-tag">
+                <el-icon><InfoFilled /></el-icon>
+                未配置路数
+              </el-tag>
+            </div>
+          </template>
+        </el-table-column>
+
+        <el-table-column prop="timing_state" label="状态" width="100">
+          <template #default="{ row }">
+            <el-switch
+                v-model="row.timing_state"
+                :active-value="1"
+                :inactive-value="0"
+                active-color="#13ce66"
+                inactive-color="#ff4949"
+                @change="handleStatusChange(row)"
+            />
+          </template>
+        </el-table-column>
+
+        <el-table-column label="操作" width="180" fixed="right">
+          <template #default="{ row }">
+            <div class="action-buttons">
+              <el-button
+                  type="primary"
+                  size="small"
+                  :icon="Edit"
+                  @click="handleEdit(row)"
+                  link
+                  class="action-btn control-btn"
+              >
+                编辑
+              </el-button>
+              <el-button
+                  type="info"
+                  size="small"
+                  :icon="View"
+                  @click="handleDetail(row)"
+                  link
+              >
+                详情
+              </el-button>
+            </div>
+          </template>
+        </el-table-column>
+      </el-table>
+
+      <!-- 分页 -->
+      <div class="pagination-container">
+        <el-pagination
+            v-model:current-page="pagination.currentPage"
+            v-model:page-size="pagination.pageSize"
+            :total="pagination.total"
+            :page-sizes="[10, 20, 50, 100]"
+            layout="total, sizes, prev, pager, next, jumper"
+            background
+            @size-change="handleSizeChange"
+            @current-change="handleCurrentChange"
+        />
+      </div>
+    </el-card>
+
+    <!-- 新增/编辑弹窗 -->
+    <el-dialog
+        v-model="dialogVisible"
+        :title="dialogTitle"
+        width="900px"
+        :close-on-click-modal="false"
+        class="strategy-dialog"
+        @close="handleDialogClose"
+    >
+      <el-form
+          :model="formData"
+          :rules="formRules"
+          ref="formRef"
+          label-width="120px"
+          class="strategy-form"
+      >
+        <!-- 基本信息 -->
+        <div class="form-section">
+          <h3 class="section-title">
+            <el-icon><InfoFilled /></el-icon>
+            基本信息
+          </h3>
+
+          <el-row :gutter="20">
+            <el-col :span="12">
+              <el-form-item label="策略名称" prop="timing_name">
+                <el-input
+                    v-model="formData.timing_name"
+                    placeholder="请输入策略名称"
+                    prefix-icon="Edit"
+                    maxlength="50"
+                    show-word-limit
+                />
+              </el-form-item>
+            </el-col>
+            <el-col :span="12">
+              <el-form-item label="执行时间" prop="timing_start_time">
+                <el-time-picker
+                    v-model="formData.timing_start_time"
+                    format="HH:mm"
+                    value-format="HH:mm"
+                    placeholder="选择时间"
+                    style="width: 100%"
+                />
+              </el-form-item>
+            </el-col>
+          </el-row>
+
+          <!-- 重复日期选择 -->
+          <el-form-item label="重复日期" prop="weeks">
+            <div class="weeks-selection">
+              <!-- 快捷选择按钮 -->
+              <div class="quick-select-buttons">
+                <el-button
+                    size="small"
+                    @click="selectAllWeekdays"
+                    :type="isAllWeekdaysSelected ? 'primary' : ''"
+                >
+                  工作日
+                </el-button>
+                <el-button
+                    size="small"
+                    @click="selectWeekend"
+                    :type="isWeekendSelected ? 'primary' : ''"
+                >
+                  周末
+                </el-button>
+                <el-button
+                    size="small"
+                    @click="selectAllWeek"
+                    :type="isAllWeekSelected ? 'primary' : ''"
+                >
+                  全选
+                </el-button>
+                <el-button
+                    size="small"
+                    @click="clearAllWeeks"
+                >
+                  清空
+                </el-button>
+              </div>
+
+              <!-- 星期选择 -->
+              <div class="week-selection-container">
+                <div class="week-days-grid">
+                  <div
+                      v-for="day in weekDays"
+                      :key="day.value"
+                      :class="[
+                        'week-day-item',
+                        { 'selected': formData.weeks.includes(day.value) },
+                        { 'weekend': day.isWeekend }
+                      ]"
+                      @click="toggleWeekDay(day.value)"
+                  >
+                    <div class="day-content">
+                      <div class="day-name">{{ day.name }}</div>
+                      <div class="day-en">{{ day.en }}</div>
+                      <div class="day-icon">
+                        <el-icon v-if="formData.weeks.includes(day.value)">
+                          <Check />
+                        </el-icon>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+
+                <!-- 特殊选项 -->
+                <div class="special-options">
+                  <div class="special-option-group">
+                    <h4 class="option-group-title">特殊设置</h4>
+                    <div class="special-checkboxes">
+                      <div
+                          :class="[
+                            'special-checkbox',
+                            { 'checked': formData.weeks.includes(256) }
+                          ]"
+                          @click="toggleSpecialOption(256)"
+                      >
+                        <el-icon class="checkbox-icon">
+                          <Check v-if="formData.weeks.includes(256)" />
+                        </el-icon>
+                        <span class="checkbox-label">节假日执行</span>
+                      </div>
+                      <div
+                          :class="[
+                            'special-checkbox',
+                            { 'checked': formData.weeks.includes(512) }
+                          ]"
+                          @click="toggleSpecialOption(512)"
+                      >
+                        <el-icon class="checkbox-icon">
+                          <Check v-if="formData.weeks.includes(512)" />
+                        </el-icon>
+                        <span class="checkbox-label">节假日不执行</span>
+                      </div>
+                    </div>
+                  </div>
+                </div>
+              </div>
+
+              <!-- 选择结果预览 -->
+              <div class="selection-preview" v-if="formData.weeks.length > 0">
+                <div class="preview-title">已选择:</div>
+                <div class="preview-tags">
+                  <el-tag
+                      v-for="week in getSelectedWeekTags"
+                      :key="week.value"
+                      :type="week.type"
+                      size="small"
+                      closable
+                      @close="removeWeekDay(week.value)"
+                      class="preview-tag"
+                  >
+                    {{ week.label }}
+                  </el-tag>
+                </div>
+              </div>
+            </div>
+          </el-form-item>
+        </div>
+
+        <!-- 设备选择 -->
+        <div class="form-section">
+          <h3 class="section-title">
+            <el-icon><Monitor /></el-icon>
+            设备选择
+          </h3>
+
+          <el-form-item label="选择方式">
+            <el-radio-group v-model="deviceSelectType" class="select-type-radio" @change="handleSelectTypeChange">
+              <el-radio label="region">
+                <el-icon><Location /></el-icon>
+                按区域选择
+              </el-radio>
+              <el-radio label="group">
+                <el-icon><Collection /></el-icon>
+                按分组选择
+              </el-radio>
+            </el-radio-group>
+          </el-form-item>
+
+          <el-row :gutter="20">
+            <el-col :span="8">
+              <el-form-item label="所属位置" prop="region_ids">
+                <!-- 区域选择 - 使用树形选择器 -->
+                <el-tree-select
+                    ref="regionTreeRef"
+                    v-model="formData.region_ids"
+                    :data="regionTreeData"
+                    :props="regionTreeProps"
+                    placeholder="请选择所属位置"
+                    clearable
+                    filterable
+                    check-strictly
+                    :load="loadRegionNode"
+                    lazy
+                    style="width: 100%"
+                    @change="handleRegionSelectChange"
+                    :default-expanded-keys="[]"
+                    :show-checkbox="false"
+                    node-key="id"
+                    :render-after-expand="false"
+                    :cache-data="false"
+                    :key="`region-tree-${treeKey}`"
+                />
+              </el-form-item>
+            </el-col>
+
+            <el-col :span="8">
+              <el-form-item label="设备分类" prop="category_id">
+                <el-select
+                    v-model="formData.category_id"
+                    placeholder="请选择设备分类"
+                    clearable
+                    style="width: 100%"
+                    @change="handleCategoryChange"
+                >
+                  <el-option
+                      v-for="item in categoryOptions"
+                      :key="item.value"
+                      :label="item.label"
+                      :value="item.value"
+                  />
+                </el-select>
+              </el-form-item>
+            </el-col>
+
+            <el-col :span="8">
+              <el-form-item label="设备型号" prop="device_type_id">
+                <el-select
+                    v-model="formData.device_type_id"
+                    placeholder="请选择设备型号"
+                    clearable
+                    style="width: 100%"
+                    @change="handleDeviceTypeChange"
+                >
+                  <el-option
+                      v-for="item in deviceTypeOptions"
+                      :key="item.value"
+                      :label="item.label"
+                      :value="item.value"
+                  />
+                </el-select>
+              </el-form-item>
+            </el-col>
+          </el-row>
+
+          <el-form-item label="设备名称" prop="device_ids">
+            <div class="device-select-container">
+              <el-select
+                  v-model="formData.device_ids"
+                  placeholder="请选择设备名称"
+                  multiple
+                  clearable
+                  collapse-tags
+                  collapse-tags-tooltip
+                  :max-collapse-tags="30"
+                  style="width: 100%"
+                  @change="handleDeviceChange"
+              >
+                <template #header>
+                  <div class="device-select-header">
+                    <el-checkbox
+                        v-model="isAllDevicesSelected"
+                        :indeterminate="isDeviceIndeterminate"
+                        @change="handleSelectAllDevices"
+                    >
+                      全选设备 ({{ deviceOptions.length }})
+                    </el-checkbox>
+                  </div>
+                </template>
+                <el-option
+                    v-for="item in deviceOptions"
+                    :key="item.value"
+                    :label="item.label"
+                    :value="item.value"
+                />
+              </el-select>
+              <div class="device-count-info" v-if="formData.device_ids.length > 0">
+                已选择 {{ formData.device_ids.length }} 个设备
+              </div>
+            </div>
+          </el-form-item>
+        </div>
+
+        <!-- 修改路数配置部分的模板 -->
+        <div v-if="channelCount > 0" class="form-section">
+          <h3 class="section-title">
+            <el-icon><Connection /></el-icon>
+            路数配置
+            <el-tag type="info" size="small">{{ channelCount }}路设备</el-tag>
+            <el-tag type="warning" size="small" style="margin-left: 8px;">可选配置</el-tag>
+          </h3>
+
+          <!-- 路数操作按钮 -->
+          <!-- 修改路数操作按钮部分 -->
+          <div class="channel-operations">
+            <div class="operation-buttons">
+              <el-button-group>
+                <el-button size="small" @click="selectAllChannels" :icon="Check">
+                  全部开启
+                </el-button>
+                <el-button size="small" @click="unselectAllChannels" :icon="Close">
+                  全部关闭
+                </el-button>
+                <el-button size="small" @click="reverseAllChannels" :icon="Refresh">
+                  反选
+                </el-button>
+              </el-button-group>
+              <el-button size="small" @click="restoreChannelConfig" :icon="RefreshLeft" v-if="isEdit">
+                恢复原配置
+              </el-button>
+              <el-button size="small" @click="clearAllChannels" :icon="Delete" type="danger">
+                清空配置
+              </el-button>
+            </div>
+            <div class="channel-status-info">
+              <div class="status-line">
+                开启: {{ getChannelStatusCount().on }} |
+                关闭: {{ getChannelStatusCount().off }}
+              </div>
+            </div>
+          </div>
+
+          <div class="channel-grid">
+            <div
+                v-for="i in channelCount"
+                :key="i"
+                class="channel-item"
+                :class="{
+          'has-name': hasChannelName(i),
+          'has-status': hasChannelStatus(i),
+          'configured': hasChannelName(i) || hasChannelStatus(i)
+        }"
+            >
+              <div class="channel-header">
+                <div class="channel-info">
+                  <span class="channel-number">{{ getChineseNumber(i) }}路</span>
+                </div>
+                <el-form-item
+                    :prop="`channel_names.Tag${i}`"
+                    :rules="channelNameRules"
+                    class="channel-name-form-item"
+                >
+                  <el-input
+                      v-model="formData.channel_names[`Tag${i}`]"
+                      size="small"
+                      placeholder="可选:输入路数名称"
+                      class="channel-name-input"
+                      maxlength="20"
+                      clearable
+                      @input="handleChannelNameChange(i)"
+                      @clear="handleChannelNameClear(i)"
+                  />
+                </el-form-item>
+              </div>
+
+              <div class="channel-control">
+                <div class="channel-buttons">
+                  <el-button
+                      size="small"
+                      :type="formData.timing_agreement[`Tag${i}`] === 0 ? 'danger' : ''"
+                      :plain="formData.timing_agreement[`Tag${i}`] !== 0"
+                      @click="toggleChannelStatus(i, 0)"
+                      class="status-button"
+                  >
+                    <el-icon><SwitchButton /></el-icon>
+                    关闭
+                  </el-button>
+                  <el-button
+                      size="small"
+                      :type="formData.timing_agreement[`Tag${i}`] === 1 ? 'success' : ''"
+                      :plain="formData.timing_agreement[`Tag${i}`] !== 1"
+                      @click="toggleChannelStatus(i, 1)"
+                      class="status-button"
+                  >
+                    <el-icon><SwitchButton /></el-icon>
+                    开启
+                  </el-button>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+      </el-form>
+
+      <template #footer>
+        <div class="dialog-footer">
+          <el-button @click="dialogVisible = false" :icon="Close">
+            取消
+          </el-button>
+          <el-button type="primary" @click="handleSubmit" :icon="Check" :loading="submitLoading">
+            {{ submitLoading ? '保存中...' : '确定' }}
+          </el-button>
+        </div>
+      </template>
+    </el-dialog>
+
+    <!-- 详情弹窗 -->
+    <el-dialog
+        v-model="detailVisible"
+        title="策略详情"
+        width="900px"
+        class="detail-dialog"
+    >
+      <div class="detail-content" v-if="detailData">
+        <div class="detail-section">
+          <h4>基本信息</h4>
+          <el-descriptions :column="2" border>
+            <el-descriptions-item label="策略名称">
+              {{ detailData.timing_name }}
+            </el-descriptions-item>
+            <el-descriptions-item label="执行时间">
+              {{ detailData.timing_start_time }}
+            </el-descriptions-item>
+            <el-descriptions-item label="重复日期" :span="2">
+              <el-tag
+                  v-for="week in getWeekTags(detailData.weeks)"
+                  :key="week.value"
+                  :type="week.type"
+                  size="small"
+                  class="week-tag"
+              >
+                {{ week.label }}
+              </el-tag>
+            </el-descriptions-item>
+            <el-descriptions-item label="状态">
+              <el-tag :type="detailData.timing_state === 1 ? 'success' : 'danger'">
+                {{ detailData.timing_state === 1 ? '启用' : '禁用' }}
+              </el-tag>
+            </el-descriptions-item>
+          </el-descriptions>
+        </div>
+
+        <div class="detail-section">
+          <h4>设备指令</h4>
+          <div class="agreement-detail">
+            <div
+                v-for="item in getAgreementTags(detailData.timing_agreement)"
+                :key="item.label"
+                class="agreement-item"
+            >
+              <span class="channel-name">{{ item.label }}</span>
+              <el-tag :type="item.type" size="small">{{ item.status }}</el-tag>
+            </div>
+          </div>
+        </div>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script setup name="zmTime">
+import {
+  Clock, Search, Refresh, Plus, List, Timer, Edit, View, Delete,
+  InfoFilled, Monitor, Location, Collection, Connection, SwitchButton,
+  Close, Check, RefreshLeft
+} from '@element-plus/icons-vue'
+import { ref, reactive, onMounted, computed, watch, nextTick } from 'vue'
+import { ElMessage, ElMessageBox } from 'element-plus'
+import axios from 'axios'
+
+// 响应式数据
+const searchForm = reactive({
+  searchText: ''
+})
+
+const pagination = reactive({
+  currentPage: 1,
+  pageSize: 10,
+  total: 0
+})
+
+const tableData = ref([])
+const dialogVisible = ref(false)
+const detailVisible = ref(false)
+const detailData = ref(null)
+const dialogTitle = ref('新增策略')
+const isEdit = ref(false)
+const deviceSelectType = ref('region')
+const channelCount = ref(0)
+const submitLoading = ref(false)
+const regionTreeRef = ref(null)
+
+const requestCache = new Map() // 请求缓存
+const requestQueue = new Map() // 请求队列,防止重复请求
+const treeKey = ref(0) // 用于强制刷新树组件
+let debounceTimer = null
+
+// 请求缓存工具函数
+const getCacheKey = (url, params) => {
+  return `${url}?${new URLSearchParams(params).toString()}`
+}
+
+const getCachedRequest = (url, params) => {
+  const key = getCacheKey(url, params)
+  return requestCache.get(key)
+}
+
+const setCachedRequest = (url, params, data) => {
+  const key = getCacheKey(url, params)
+  requestCache.set(key, {
+    data,
+    timestamp: Date.now(),
+    expiry: 5 * 60 * 1000 // 5分钟过期
+  })
+}
+
+const isCacheValid = (cachedItem) => {
+  return cachedItem && (Date.now() - cachedItem.timestamp < cachedItem.expiry)
+}
+
+// 区域树形数据
+const regionTreeData = ref([])
+const regionTreeProps = {
+  value: 'id',
+  label: 'name',
+  children: 'children',
+  isLeaf: (data) => data.children_count === 0
+}
+
+// 存储原始配置用于恢复
+const originalChannelConfig = ref({
+  timing_agreement: {},
+  channel_names: {}
+})
+
+const formData = reactive({
+  timing_id: null,
+  timing_name: '',
+  weeks: [],
+  timing_start_time: '',
+  region_ids: '',
+  group_id: '',
+  category_id: '',
+  device_type_id: '',
+  device_ids: [],
+  timing_agreement: {},
+  channel_names: {},
+  region_type: null,
+  selectedRegionNode: null  // 新增:保存完整的区域节点信息
+})
+
+const categoryOptions = ref([])
+const deviceTypeOptions = ref([])
+const deviceOptions = ref([])
+const formRef = ref()
+
+// 星期数据配置
+const weekDays = [
+  { value: 2, name: '周一', en: 'MON', isWeekend: false },
+  { value: 4, name: '周二', en: 'TUE', isWeekend: false },
+  { value: 8, name: '周三', en: 'WED', isWeekend: false },
+  { value: 16, name: '周四', en: 'THU', isWeekend: false },
+  { value: 32, name: '周五', en: 'FRI', isWeekend: false },
+  { value: 64, name: '周六', en: 'SAT', isWeekend: true },
+  { value: 128, name: '周日', en: 'SUN', isWeekend: true }
+]
+
+// 表单验证规则
+const formRules = {
+  timing_name: [
+    { required: true, message: '请输入策略名称', trigger: 'blur' },
+    { min: 2, max: 50, message: '策略名称长度在 2 到 50 个字符', trigger: 'blur' }
+  ],
+  weeks: [
+    {
+      required: true,
+      validator: (rule, value, callback) => {
+        if (!value || value.length === 0) {
+          callback(new Error('请选择重复日期'))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'change'
+    }
+  ],
+  timing_start_time: [
+    { required: true, message: '请选择执行时间', trigger: 'change' }
+  ],
+  region_ids: [
+    {
+      validator: (rule, value, callback) => {
+        if (deviceSelectType.value === 'region' && !value) {
+          callback(new Error('请选择所属位置'))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'change'
+    }
+  ],
+  group_id: [
+    {
+      validator: (rule, value, callback) => {
+        if (deviceSelectType.value === 'group' && !value) {
+          callback(new Error('请选择设备分组'))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'change'
+    }
+  ],
+  category_id: [
+    { required: true, message: '请选择设备分类', trigger: 'change' }
+  ],
+  device_type_id: [
+    { required: true, message: '请选择设备型号', trigger: 'change' }
+  ],
+  device_ids: [
+    {
+      required: true,
+      validator: (rule, value, callback) => {
+        if (!value || value.length === 0) {
+          callback(new Error('请选择至少一个设备'))
+        } else {
+          callback()
+        }
+      },
+      trigger: 'change'
+    }
+  ]
+}
+
+// 路数名称验证规则
+const channelNameRules = [
+  { min: 1, max: 20, message: '路数名称长度在 1 到 20 个字符', trigger: 'blur' }
+]
+
+// 计算属性
+const weekMap = {
+  2: { label: '周一', type: 'primary' },
+  4: { label: '周二', type: 'success' },
+  8: { label: '周三', type: 'info' },
+  16: { label: '周四', type: 'warning' },
+  32: { label: '周五', type: 'danger' },
+  64: { label: '周六', type: '' },
+  128: { label: '周日', type: '' },
+  256: { label: '节假日执行', type: 'success' },
+  512: { label: '节假日不执行', type: 'danger' }
+}
+
+const hasChannelName = (channelNum) => {
+  return formData.channel_names[`Tag${channelNum}`] &&
+      formData.channel_names[`Tag${channelNum}`].trim() !== ''
+}
+
+const hasChannelStatus = (channelNum) => {
+  return formData.timing_agreement[`Tag${channelNum}`] !== undefined &&
+      formData.timing_agreement[`Tag${channelNum}`] !== null
+}
+
+const getNamedChannelCount = () => {
+  let count = 0
+  for (let i = 1; i <= channelCount.value; i++) {
+    if (hasChannelName(i)) {
+      count++
+    }
+  }
+  return count
+}
+
+const getChannelStatusType = (channelNum) => {
+  const status = formData.timing_agreement[`Tag${channelNum}`]
+  if (status === 1) return 'success'
+  if (status === 0) return 'danger'
+  return 'info'
+}
+
+const getChannelStatusText = (channelNum) => {
+  const status = formData.timing_agreement[`Tag${channelNum}`]
+  if (status === 1) return '开启'
+  if (status === 0) return '关闭'
+  return '未设置'
+}
+
+const toggleChannelStatus = (channelNum, targetStatus) => {
+  const currentStatus = formData.timing_agreement[`Tag${channelNum}`]
+
+  if (currentStatus === targetStatus) {
+    // 如果当前状态与目标状态相同,则取消选择
+    formData.timing_agreement[`Tag${channelNum}`] = undefined
+  } else {
+    // 否则设置为目标状态
+    formData.timing_agreement[`Tag${channelNum}`] = targetStatus
+
+    // 只有在没有名称时才设置默认名称
+    if (!formData.channel_names[`Tag${channelNum}`] || formData.channel_names[`Tag${channelNum}`].trim() === '') {
+      formData.channel_names[`Tag${channelNum}`] = `${channelNum}路`
+    }
+  }
+}
+
+const handleChannelNameChange = (channelNum) => {
+  // 当用户输入路数名称时,不自动设置状态
+  // 用户可以选择只设置名称而不设置开关状态
+}
+
+const handleChannelNameClear = (channelNum) => {
+  // 当用户清空路数名称时,只清空名称,不影响状态
+  formData.channel_names[`Tag${channelNum}`] = ''
+}
+
+// 修改批量操作方法,避免强制设置名称
+const selectAllChannels = () => {
+  for (let i = 1; i <= channelCount.value; i++) {
+    formData.timing_agreement[`Tag${i}`] = 1
+    // 只有在没有名称时才设置默认名称
+    if (!formData.channel_names[`Tag${i}`] || formData.channel_names[`Tag${i}`].trim() === '') {
+      formData.channel_names[`Tag${i}`] = `${i}路`
+    }
+  }
+}
+
+const unselectAllChannels = () => {
+  for (let i = 1; i <= channelCount.value; i++) {
+    formData.timing_agreement[`Tag${i}`] = 0
+    // 只有在没有名称时才设置默认名称
+    if (!formData.channel_names[`Tag${i}`] || formData.channel_names[`Tag${i}`].trim() === '') {
+      formData.channel_names[`Tag${i}`] = `${i}路`
+    }
+  }
+}
+
+const reverseAllChannels = () => {
+  for (let i = 1; i <= channelCount.value; i++) {
+    const currentStatus = formData.timing_agreement[`Tag${i}`]
+    if (currentStatus === 1) {
+      formData.timing_agreement[`Tag${i}`] = 0
+    } else if (currentStatus === 0) {
+      formData.timing_agreement[`Tag${i}`] = 1
+    } else {
+      // 如果是未配置状态,设置为开启
+      formData.timing_agreement[`Tag${i}`] = 1
+      // 只有在没有名称时才设置默认名称
+      if (!formData.channel_names[`Tag${i}`] || formData.channel_names[`Tag${i}`].trim() === '') {
+        formData.channel_names[`Tag${i}`] = `${i}路`
+      }
+    }
+  }
+}
+
+// 修改判断是否有配置的方法
+const hasChannelConfig = (channelNum) => {
+  return hasChannelName(channelNum) || hasChannelStatus(channelNum)
+}
+
+const clearChannelStatus = (channelNum) => {
+  formData.timing_agreement[`Tag${channelNum}`] = undefined
+}
+
+
+const isChannelConfigured = (channelNum) => {
+  return formData.timing_agreement[`Tag${channelNum}`] !== undefined
+}
+
+const getChannelStatusCount = () => {
+  let on = 0, off = 0
+  for (let i = 1; i <= channelCount.value; i++) {
+    if (formData.timing_agreement[`Tag${i}`] === 1) {
+      on++
+    } else if (formData.timing_agreement[`Tag${i}`] === 0) {
+      off++
+    }
+  }
+  return { on, off }
+}
+
+const clearAllChannels = () => {
+  for (let i = 1; i <= channelCount.value; i++) {
+    formData.timing_agreement[`Tag${i}`] = undefined
+    formData.channel_names[`Tag${i}`] = ''
+  }
+}
+
+// 快捷选择相关计算属性
+const isAllWeekdaysSelected = computed(() => {
+  const weekdays = [2, 4, 8, 16, 32]
+  return weekdays.every(day => formData.weeks.includes(day))
+})
+
+const isWeekendSelected = computed(() => {
+  const weekend = [64, 128]
+  return weekend.every(day => formData.weeks.includes(day))
+})
+
+const isAllWeekSelected = computed(() => {
+  const allWeek = [2, 4, 8, 16, 32, 64, 128]
+  return allWeek.every(day => formData.weeks.includes(day))
+})
+
+const getSelectedWeekTags = computed(() => {
+  return formData.weeks.map(week => ({
+    value: week,
+    label: weekMap[week]?.label || week,
+    type: weekMap[week]?.type || ''
+  }))
+})
+
+// 设备选择相关计算属性
+const isAllDevicesSelected = computed({
+  get() {
+    return deviceOptions.value.length > 0 && formData.device_ids.length === deviceOptions.value.length
+  },
+  set(value) {
+    if (value) {
+      formData.device_ids = deviceOptions.value.map(item => item.value)
+    } else {
+      formData.device_ids = []
+    }
+  }
+})
+
+const isDeviceIndeterminate = computed(() => {
+  return formData.device_ids.length > 0 && formData.device_ids.length < deviceOptions.value.length
+})
+
+// 监听设备选择变化
+watch(() => formData.device_ids, (newVal) => {
+  if (newVal.length > 0 && channelCount.value > 0) {
+    // 确保路数配置存在,但不强制设置值
+    for (let i = 1; i <= channelCount.value; i++) {
+      if (formData.timing_agreement[`Tag${i}`] === undefined) {
+        // 保持未配置状态
+        formData.timing_agreement[`Tag${i}`] = undefined
+      }
+      if (formData.channel_names[`Tag${i}`] === undefined) {
+        formData.channel_names[`Tag${i}`] = ''
+      }
+    }
+  }
+}, { deep: true })
+
+// 工具方法
+const getWeekTags = (weeks) => {
+  if (!weeks || !Array.isArray(weeks)) return []
+  return weeks.map(week => ({
+    value: week,
+    label: weekMap[week]?.label || week,
+    type: weekMap[week]?.type || ''
+  }))
+}
+
+const getAgreementTags = (agreement, channelNames = null) => {
+  if (!agreement) return []
+  try {
+    const parsed = typeof agreement === 'string' ? JSON.parse(agreement) : agreement
+
+    const result = []
+    const processedTags = new Set()
+
+    // 收集所有的Tag和name信息
+    const tagStatus = {}
+    const nameInfo = {}
+
+    Object.keys(parsed).forEach(key => {
+      if (key.startsWith('Tag') || key.startsWith('tag')) {
+        const tagKey = key.startsWith('Tag') ? key : `Tag${key.replace('tag', '')}`
+        const num = tagKey.replace('Tag', '')
+        tagStatus[num] = parseInt(parsed[key])
+      } else if (key.startsWith('name')) {
+        const num = key.replace('name', '')
+        nameInfo[num] = parsed[key]
+      }
+    })
+
+    // 处理所有有配置的路数
+    const allNums = new Set([...Object.keys(tagStatus), ...Object.keys(nameInfo)])
+
+    allNums.forEach(num => {
+      if (processedTags.has(num)) return
+      processedTags.add(num)
+
+      const hasStatus = tagStatus.hasOwnProperty(num)
+      const hasName = nameInfo.hasOwnProperty(num)
+
+      if (hasStatus || hasName) {
+        let channelName = nameInfo[num] || `${num}路`
+        let label = ''
+        let type = 'info'
+        let configType = ''
+        let icon = null
+
+        if (hasStatus && hasName) {
+          // 完整配置:显示名称和状态
+          const status = tagStatus[num] === 1 ? '开启' : '关闭'
+          label = `${channelName}: ${status}`
+          type = tagStatus[num] === 1 ? 'success' : 'danger'
+          configType = 'full-config'
+          icon = tagStatus[num] === 1 ? 'Check' : 'Close'
+        } else if (hasStatus && !hasName) {
+          // 仅状态配置:显示默认名称和状态
+          const status = tagStatus[num] === 1 ? '开启' : '关闭'
+          label = `${num}路: ${status}`
+          type = tagStatus[num] === 1 ? 'success' : 'danger'
+          configType = 'status-only'
+          icon = tagStatus[num] === 1 ? 'Check' : 'Close'
+        } else if (hasName && !hasStatus) {
+          // 仅名称配置:只显示名称,标记为标识用
+          label = `${channelName} `
+          type = 'warning'
+          configType = 'name-only'
+          icon = 'PriceTag'
+        }
+
+        result.push({
+          label: label,
+          status: hasStatus ? (tagStatus[num] === 1 ? '开启' : '关闭') : '标识',
+          type: type,
+          configType: configType,
+          channelName: channelName,
+          tagNum: num,
+          hasStatus: hasStatus,
+          hasName: hasName,
+          icon: icon
+        })
+      }
+    })
+
+    // 按路数排序
+    result.sort((a, b) => parseInt(a.tagNum) - parseInt(b.tagNum))
+
+    return result
+  } catch (e) {
+    console.error('解析设备指令失败:', e)
+    return []
+  }
+}
+
+const getChineseNumber = (num) => {
+  const numbers = ['', '一', '二', '三', '四', '五', '六', '七', '八', '九', '十', '十一', '十二']
+  return numbers[num] || num
+}
+
+const restoreChannelConfig = () => {
+  if (isEdit.value && originalChannelConfig.value.timing_agreement) {
+    formData.timing_agreement = { ...originalChannelConfig.value.timing_agreement }
+    formData.channel_names = { ...originalChannelConfig.value.channel_names }
+    ElMessage.success('已恢复原配置')
+  }
+}
+
+// 星期选择相关方法
+const toggleWeekDay = (value) => {
+  const index = formData.weeks.indexOf(value)
+  if (index > -1) {
+    formData.weeks.splice(index, 1)
+  } else {
+    formData.weeks.push(value)
+  }
+}
+
+const toggleSpecialOption = (value) => {
+  const index = formData.weeks.indexOf(value)
+  if (index > -1) {
+    formData.weeks.splice(index, 1)
+  } else {
+    // 如果是节假日相关选项,需要互斥
+    if (value === 256) {
+      const notRunIndex = formData.weeks.indexOf(512)
+      if (notRunIndex > -1) {
+        formData.weeks.splice(notRunIndex, 1)
+      }
+    } else if (value === 512) {
+      const runIndex = formData.weeks.indexOf(256)
+      if (runIndex > -1) {
+        formData.weeks.splice(runIndex, 1)
+      }
+    }
+    formData.weeks.push(value)
+  }
+}
+
+const removeWeekDay = (value) => {
+  const index = formData.weeks.indexOf(value)
+  if (index > -1) {
+    formData.weeks.splice(index, 1)
+  }
+}
+
+const selectAllWeekdays = () => {
+  const weekdays = [2, 4, 8, 16, 32]
+  if (isAllWeekdaysSelected.value) {
+    weekdays.forEach(day => {
+      const index = formData.weeks.indexOf(day)
+      if (index > -1) {
+        formData.weeks.splice(index, 1)
+      }
+    })
+  } else {
+    weekdays.forEach(day => {
+      if (!formData.weeks.includes(day)) {
+        formData.weeks.push(day)
+      }
+    })
+  }
+}
+
+const selectWeekend = () => {
+  const weekend = [64, 128]
+  if (isWeekendSelected.value) {
+    weekend.forEach(day => {
+      const index = formData.weeks.indexOf(day)
+      if (index > -1) {
+        formData.weeks.splice(index, 1)
+      }
+    })
+  } else {
+    weekend.forEach(day => {
+      if (!formData.weeks.includes(day)) {
+        formData.weeks.push(day)
+      }
+    })
+  }
+}
+
+const selectAllWeek = () => {
+  const allWeek = [2, 4, 8, 16, 32, 64, 128]
+  if (isAllWeekSelected.value) {
+    allWeek.forEach(day => {
+      const index = formData.weeks.indexOf(day)
+      if (index > -1) {
+        formData.weeks.splice(index, 1)
+      }
+    })
+  } else {
+    allWeek.forEach(day => {
+      if (!formData.weeks.includes(day)) {
+        formData.weeks.push(day)
+      }
+    })
+  }
+}
+
+const clearAllWeeks = () => {
+  formData.weeks = []
+}
+
+// 设备选择相关方法
+const handleSelectAllDevices = (value) => {
+  if (value) {
+    formData.device_ids = deviceOptions.value.map(item => item.value)
+  } else {
+    formData.device_ids = []
+  }
+}
+
+const handleSelectTypeChange = () => {
+  // 切换选择方式时清空相关数据
+  formData.region_ids = ''
+  formData.group_id = ''
+  formData.category_id = ''
+  formData.device_type_id = ''
+  formData.device_ids = []
+  categoryOptions.value = []
+  deviceTypeOptions.value = []
+  deviceOptions.value = []
+  channelCount.value = 0
+  formData.timing_agreement = {}
+  formData.channel_names = {}
+}
+
+const handleLocationOrGroupChange = async (value, node) => {
+  // 清空相关数据
+  formData.category_id = ''
+  formData.device_type_id = ''
+  formData.device_ids = []
+  deviceTypeOptions.value = []
+  deviceOptions.value = []
+  channelCount.value = 0
+  formData.timing_agreement = {}
+  formData.channel_names = {}
+
+  if (value && node) {
+    // 从选中的节点获取完整的数据
+    formData.region_ids = node.id || node.value  // 获取选中节点的id
+    formData.region_type = node.type             // 获取选中节点的type
+
+    console.log('选中的区域数据:', {
+      id: formData.region_ids,
+      type: formData.region_type,
+      node: node
+    })
+
+    await fetchDeviceCategories()
+  } else {
+    // 如果没有选中值,清空相关字段
+    formData.region_ids = ''
+    formData.region_type = null
+  }
+}
+
+// 处理区域选择变化
+const handleRegionSelectChange = async (value) => {
+  // 清除之前的防抖定时器
+  if (debounceTimer) {
+    clearTimeout(debounceTimer)
+  }
+
+  debounceTimer = setTimeout(async () => {
+    console.log('区域选择变化:', value)
+
+    if (value) {
+      // 获取选中节点的完整数据
+      const selectedNode = regionTreeRef.value?.getCurrentNode?.() ||
+          regionTreeRef.value?.getNode?.(value)?.data
+
+      if (selectedNode) {
+        formData.region_ids = selectedNode.id
+        formData.region_type = selectedNode.type
+        formData.selectedRegionNode = selectedNode
+
+        console.log('区域选择变化:', {
+          value: value,
+          selectedNode: selectedNode
+        })
+
+        // 清空相关数据
+        resetDeviceSelections()
+
+        // 获取设备分类
+        await fetchDeviceCategories()
+      }
+    } else {
+      // 清空选择
+      formData.region_ids = ''
+      formData.region_type = null
+      formData.selectedRegionNode = null
+      resetDeviceSelections()
+    }
+  }, 300) // 300ms 防抖
+}
+
+// 重置设备相关选择
+const resetDeviceSelections = () => {
+  formData.category_id = ''
+  formData.device_type_id = ''
+  formData.device_ids = []
+  deviceTypeOptions.value = []
+  deviceOptions.value = []
+  channelCount.value = 0
+  formData.timing_agreement = {}
+  formData.channel_names = {}
+}
+
+// 丰富节点信息,获取父级信息
+const enrichNodeWithParentInfo = async (node) => {
+  try {
+    const enrichedNode = { ...node }
+
+    // 如果是房间类型且没有parentName,尝试获取
+    if (node.type === 3 && !node.parentName && node.parentId) {
+      const parentNode = await getNodeById(node.parentId)
+      if (parentNode) {
+        enrichedNode.parentName = parentNode.name
+      }
+    }
+
+    // 确保有建筑信息
+    if (!enrichedNode.buildingName && enrichedNode.BuildingId) {
+      const buildingNode = await getNodeById(enrichedNode.BuildingId)
+      if (buildingNode) {
+        enrichedNode.buildingName = buildingNode.name
+      }
+    }
+
+    return enrichedNode
+  } catch (error) {
+    console.error('丰富节点信息失败:', error)
+    return node
+  }
+}
+
+// 根据ID获取节点信息(可能需要调用API)
+const getNodeById = async (nodeId) => {
+  try {
+    // 首先在已加载的树数据中查找
+    const foundNode = findNodeInTree(regionTreeData.value, nodeId)
+    if (foundNode) {
+      return foundNode
+    }
+
+    // 如果在树中找不到,可能需要调用API获取
+    // 这里可以根据实际情况调用相应的API
+    return null
+  } catch (error) {
+    console.error('获取节点信息失败:', error)
+    return null
+  }
+}
+
+// 强制刷新树形选择器
+const refreshTreeSelect = async () => {
+  if (regionTreeRef.value) {
+    try {
+      // 清空树数据
+      regionTreeData.value = []
+      await nextTick()
+
+      // 重新加载根节点
+      await loadRootRegions()
+      await nextTick()
+
+      // 重新设置选中值
+      if (formData.region_ids) {
+        regionTreeRef.value.setCurrentKey(formData.region_ids)
+      }
+    } catch (error) {
+      console.error('刷新树形选择器失败:', error)
+    }
+  }
+}
+
+// 区域树形加载方法
+const loadRegionNode = async (node, resolve) => {
+  try {
+    let params = {}
+    let url = `${__LOCAL_API__}/illuminating/getBuildingRegionRoomTree`
+
+    if (node.level === 0) {
+      params = {}
+    } else if (node.level === 1) {
+      params = { building_id: node.data.id }
+    } else {
+      params = {
+        building_id: node.data.BuildingId || getBuildingIdFromPath(node),
+        region_parent_id: node.data.id
+      }
+    }
+
+    // 检查缓存
+    const cacheKey = getCacheKey(url, params)
+    const cachedData = getCachedRequest(url, params)
+
+    if (isCacheValid(cachedData)) {
+      console.log('使用缓存数据:', cacheKey)
+      resolve(cachedData.data)
+      return
+    }
+
+    // 检查是否正在请求中
+    if (requestQueue.has(cacheKey)) {
+      console.log('等待现有请求:', cacheKey)
+      const existingRequest = requestQueue.get(cacheKey)
+      const result = await existingRequest
+      resolve(result)
+      return
+    }
+
+    // 创建新请求
+    const requestPromise = axios.get(url, { params }).then(response => {
+      if (response.data.code === 200) {
+        const data = response.data.data || []
+        const treeData = data.map(item => ({
+          ...item,
+          id: item.id,
+          type: item.type,
+          BuildingId: item.BuildingId || params.building_id || item.id,
+          children: item.children_count > 0 ? [] : undefined,
+          RegionId: item.RegionId,
+          IsLastRegion: item.IsLastRegion,
+          RegionType: item.RegionType,
+          parentName: item.parentName,
+          parentId: item.parentId,
+          label: item.name,
+          value: item.id
+        }))
+
+        // 缓存结果
+        setCachedRequest(url, params, treeData)
+        return treeData
+      } else {
+        throw new Error(response.data.message || '获取区域数据失败')
+      }
+    }).finally(() => {
+      // 请求完成后从队列中移除
+      requestQueue.delete(cacheKey)
+    })
+
+    // 将请求加入队列
+    requestQueue.set(cacheKey, requestPromise)
+
+    const result = await requestPromise
+    resolve(result)
+
+  } catch (error) {
+    console.error('获取区域数据失败:', error)
+    ElMessage.error('获取区域数据失败,请检查网络连接')
+    resolve([])
+  }
+}
+
+// 辅助函数:从节点路径中获取建筑ID
+const getBuildingIdFromPath = (node) => {
+  let currentNode = node
+  while (currentNode && currentNode.level > 1) {
+    currentNode = currentNode.parent
+  }
+  return currentNode?.data?.BuildingId || currentNode?.data?.id
+}
+
+// 为编辑模式加载完整的区域路径
+const loadRegionPathForEdit = async (regionId, regionType, buildingId, parentId, regionName, fullPath) => {
+  try {
+    console.log('加载编辑区域路径:', { regionId, regionType, buildingId, parentId, regionName, fullPath })
+
+    // 增加树组件的key,强制重新渲染
+    treeKey.value++
+
+    // 等待组件重新渲染
+    await nextTick()
+
+    // 1. 首先加载根节点(建筑列表)- 只在没有数据时加载
+    if (regionTreeData.value.length === 0) {
+      await loadRootRegions()
+      await nextTick()
+    }
+
+    // 2. 根据类型逐级展开节点
+    if (regionType >= 2 && buildingId) {
+      await expandBuildingNode(buildingId)
+      await nextTick()
+    }
+
+    if (regionType === 3 && parentId) {
+      await expandRegionNode(parentId, buildingId)
+      await nextTick()
+    }
+
+    // 3. 设置选中值
+    formData.region_ids = regionId
+
+    // 4. 手动设置树的当前节点
+    if (regionTreeRef.value) {
+      try {
+        regionTreeRef.value.setCurrentKey(regionId)
+      } catch (e) {
+        console.warn('setCurrentKey失败:', e)
+      }
+    }
+
+    console.log('区域路径加载完成')
+
+  } catch (error) {
+    console.error('加载编辑区域路径失败:', error)
+  }
+}
+
+// 加载根节点区域
+const loadRootRegions = async () => {
+  try {
+    // 如果已经有数据,直接返回,避免重复加载
+    if (regionTreeData.value.length > 0) {
+      return
+    }
+
+    const response = await axios.get(`${__LOCAL_API__}/illuminating/getBuildingRegionRoomTree`)
+
+    if (response.data.code === 200) {
+      const data = response.data.data || []
+
+      // 去重处理
+      const uniqueData = data.filter((item, index, self) =>
+          index === self.findIndex(t => t.id === item.id)
+      )
+
+      regionTreeData.value = uniqueData.map(item => ({
+        ...item,
+        id: item.id,
+        type: item.type,
+        BuildingId: item.id, // 建筑的BuildingId就是自己的id
+        children: item.children_count > 0 ? [] : undefined,
+        RegionId: item.RegionId,
+        IsLastRegion: item.IsLastRegion,
+        RegionType: item.RegionType,
+        parentName: item.parentName,
+        parentId: item.parentId || 0,
+        label: item.name,
+        value: item.id
+      }))
+    }
+  } catch (error) {
+    console.error('加载根节点失败:', error)
+  }
+}
+
+// 展开建筑节点
+const expandBuildingNode = async (buildingId) => {
+  try {
+    const response = await axios.get(`${__LOCAL_API__}/illuminating/getBuildingRegionRoomTree`, {
+      params: { building_id: buildingId }
+    })
+
+    if (response.data.code === 200) {
+      const data = response.data.data || []
+
+      // 找到对应的建筑节点并更新其children
+      const updateBuildingChildren = (nodes) => {
+        for (let node of nodes) {
+          if (node.id === buildingId && node.type === 1) {
+            node.children = data.map(item => ({
+              ...item,
+              id: item.id,
+              type: item.type,
+              BuildingId: buildingId,
+              children: item.children_count > 0 ? [] : undefined,
+              RegionId: item.RegionId,
+              IsLastRegion: item.IsLastRegion,
+              RegionType: item.RegionType,
+              parentName: item.parentName,
+              parentId: item.parentId,
+              label: item.name,
+              value: item.id
+            }))
+            return true
+          }
+          if (node.children && node.children.length > 0) {
+            if (updateBuildingChildren(node.children)) {
+              return true
+            }
+          }
+        }
+        return false
+      }
+
+      updateBuildingChildren(regionTreeData.value)
+    }
+  } catch (error) {
+    console.error('展开建筑节点失败:', error)
+  }
+}
+
+// 展开区域节点
+const expandRegionNode = async (regionId, buildingId) => {
+  try {
+    const response = await axios.get(`${__LOCAL_API__}/illuminating/getBuildingRegionRoomTree`, {
+      params: {
+        building_id: buildingId,
+        region_parent_id: regionId
+      }
+    })
+
+    if (response.data.code === 200) {
+      const data = response.data.data || []
+
+      // 找到对应的区域节点并更新其children
+      const updateRegionChildren = (nodes) => {
+        for (let node of nodes) {
+          if (node.id === regionId && node.type === 2) {
+            node.children = data.map(item => ({
+              ...item,
+              id: item.id,
+              type: item.type,
+              BuildingId: buildingId,
+              children: item.children_count > 0 ? [] : undefined,
+              RegionId: item.RegionId,
+              IsLastRegion: item.IsLastRegion,
+              RegionType: item.RegionType,
+              parentName: item.parentName,
+              parentId: item.parentId,
+              label: item.name,
+              value: item.id
+            }))
+            return true
+          }
+          if (node.children && node.children.length > 0) {
+            if (updateRegionChildren(node.children)) {
+              return true
+            }
+          }
+        }
+        return false
+      }
+
+      updateRegionChildren(regionTreeData.value)
+    }
+  } catch (error) {
+    console.error('展开区域节点失败:', error)
+  }
+}
+
+// 事件处理方法
+const handleSearch = () => {
+  pagination.currentPage = 1
+  fetchData()
+}
+
+const handleReset = () => {
+  searchForm.searchText = ''
+  handleSearch()
+}
+
+const handleAdd = () => {
+  dialogTitle.value = '新增策略'
+  isEdit.value = false
+  resetForm()
+  dialogVisible.value = true
+}
+
+const handleEdit = async (row) => {
+  try {
+    dialogTitle.value = '编辑策略'
+    isEdit.value = true
+
+    // 先重置表单和清空树数据
+    resetForm()
+
+    // 先调用详情接口获取完整数据
+    const response = await axios.get(`${__LOCAL_API__}/illuminating/getTimingId`, {
+      params: { timing_id: row.timing_id }
+    })
+
+    if (response.data.code === 200) {
+      const data = response.data.data
+
+      // 解析region_ids,格式为 "2-5-1=09A/09A"
+      let regionId = ''
+      let regionType = 1
+      let buildingId = null
+      let parentId = 0
+      let selectedRegionNode = null
+      let regionName = ''
+      let fullPath = ''
+
+      if (data.region_ids) {
+        const parts = data.region_ids.split('=')
+        fullPath = parts[1] || ''
+        const idParts = parts[0].split('-')
+
+        if (idParts.length >= 2) {
+          regionType = parseInt(idParts[0])
+          regionId = parseInt(idParts[1])
+
+          if (idParts.length > 2) {
+            buildingId = parseInt(idParts[2])
+          }
+
+          // 从完整路径中解析当前节点名称
+          const pathParts = fullPath.split('/')
+          if (regionType === 1) {
+            regionName = pathParts[0] || ''
+            buildingId = regionId
+          } else if (regionType === 2) {
+            regionName = pathParts[pathParts.length - 1] || ''
+          } else if (regionType === 3) {
+            regionName = pathParts[pathParts.length - 1] || ''
+          }
+
+          selectedRegionNode = {
+            id: regionId,
+            name: regionName,
+            type: regionType,
+            parentId: parentId,
+            RegionId: parentId || regionId,
+            BuildingId: buildingId,
+            buildingName: pathParts[0] || '',
+            parentName: pathParts.length > 2 ? pathParts[pathParts.length - 2] : '',
+            fullPath: fullPath
+          }
+        }
+      }
+
+      // 设置选择方式
+      deviceSelectType.value = data.group_id ? 'group' : 'region'
+
+      // 填充表单数据
+      Object.assign(formData, {
+        timing_id: data.timing_id,
+        timing_name: data.timing_name,
+        weeks: data.weeks || [],
+        timing_start_time: data.timing_start_time,
+        region_ids: regionId,
+        region_type: regionType,
+        selectedRegionNode: selectedRegionNode,
+        group_id: data.group_id || '',
+        category_id: data.category_id || '',
+        device_type_id: data.device_type_id || '',
+        device_ids: data.device_ids || []
+      })
+
+      // 处理路数配置
+      await handleTimingAgreementData(data)
+
+      // 先打开对话框
+      dialogVisible.value = true
+
+      // 等待DOM更新后再加载数据
+      await nextTick()
+
+      // 然后异步加载区域树和其他数据
+      await Promise.all([
+        loadRegionPathForEdit(regionId, regionType, buildingId, parentId, regionName, fullPath),
+        loadEditData()
+      ])
+
+    } else {
+      ElMessage.error(response.data.message || '获取策略详情失败')
+    }
+  } catch (error) {
+    console.error('加载编辑数据失败:', error)
+    ElMessage.error('加载数据失败')
+  }
+}
+
+// 处理设备指令数据
+const handleTimingAgreementData = async (data) => {
+  if (data.timing_agreement) {
+    try {
+      const agreement = typeof data.timing_agreement === 'string'
+          ? JSON.parse(data.timing_agreement)
+          : data.timing_agreement
+
+      console.log('原始设备配置:', agreement)
+
+      // 分离Tag配置和name配置
+      const tagAgreement = {}
+      const channelNames = {}
+      let maxTagNum = 0
+
+      // 先初始化所有路数为未配置状态
+      for (let i = 1; i <= channelCount.value; i++) {
+        tagAgreement[`Tag${i}`] = undefined
+        channelNames[`Tag${i}`] = ''
+      }
+
+      Object.keys(agreement).forEach(key => {
+        if (key.startsWith('Tag')) {
+          // 直接使用Tag1格式存储
+          tagAgreement[key] = parseInt(agreement[key])
+          const tagNum = parseInt(key.replace('Tag', ''))
+          maxTagNum = Math.max(maxTagNum, tagNum)
+        } else if (key.startsWith('name')) {
+          const tagNum = key.replace('name', '')
+          channelNames[`Tag${tagNum}`] = agreement[key]
+        }
+      })
+
+      // 如果没有从agreement中获取到name配置,尝试从设备数据中获取
+      if (Object.keys(channelNames).filter(key => channelNames[key]).length === 0 && data.devices && data.devices.length > 0) {
+        const firstDevice = data.devices[0]
+        if (firstDevice.devices_json_object) {
+          try {
+            const deviceData = JSON.parse(firstDevice.devices_json_object)
+            for (let i = 1; i <= maxTagNum; i++) {
+              if (deviceData[`name${i}`] && !channelNames[`Tag${i}`]) {
+                channelNames[`Tag${i}`] = deviceData[`name${i}`]
+              }
+            }
+          } catch (e) {
+            console.error('解析设备数据失败:', e)
+          }
+        }
+      }
+
+      formData.timing_agreement = tagAgreement
+      formData.channel_names = channelNames
+      channelCount.value = Math.max(maxTagNum, channelCount.value)
+
+      // 保存原始配置用于恢复
+      originalChannelConfig.value = {
+        timing_agreement: { ...tagAgreement },
+        channel_names: { ...channelNames }
+      }
+
+      console.log('解析后的配置:', {
+        tagAgreement,
+        channelNames,
+        maxTagNum
+      })
+
+    } catch (e) {
+      console.error('解析设备指令失败:', e)
+    }
+  }
+}
+
+// 加载编辑数据的辅助方法
+const loadEditData = async () => {
+  try {
+    // 1. 先加载设备分类
+    await fetchDeviceCategories()
+
+    // 2. 如果有分类ID,加载设备型号
+    if (formData.category_id) {
+      await fetchDeviceTypes()
+    }
+
+    // 3. 如果有设备型号ID,加载设备列表和路数信息
+    if (formData.device_type_id) {
+      await Promise.all([
+        fetchDevices(),
+        fetchChannelCount(formData.device_type_id)
+      ])
+    }
+  } catch (error) {
+    console.error('加载编辑数据失败:', error)
+    throw error
+  }
+}
+
+const handleDetail = (row) => {
+  detailData.value = row
+  detailVisible.value = true
+}
+
+const handleStatusChange = async (row) => {
+  try {
+    await updateStrategyStatus(row.timing_id, row.timing_state)
+    ElMessage.success(`策略已${row.timing_state === 1 ? '启用' : '禁用'}`)
+  } catch (error) {
+    // 恢复原状态
+    row.timing_state = row.timing_state === 1 ? 0 : 1
+    ElMessage.error('状态更新失败')
+  }
+}
+
+const handleSizeChange = (size) => {
+  pagination.pageSize = size
+  fetchData()
+}
+
+const handleCurrentChange = (page) => {
+  pagination.currentPage = page
+  fetchData()
+}
+
+const handleDialogClose = () => {
+  resetForm()
+}
+
+const handleCategoryChange = (categoryId) => {
+  formData.device_type_id = ''
+  formData.device_ids = []
+  deviceTypeOptions.value = []
+  deviceOptions.value = []
+  channelCount.value = 0
+  formData.timing_agreement = {}
+  formData.channel_names = {}
+
+  if (categoryId) {
+    fetchDeviceTypes()
+  }
+}
+
+const handleDeviceTypeChange = (deviceTypeId) => {
+  formData.device_ids = []
+  deviceOptions.value = []
+  channelCount.value = 0
+  formData.timing_agreement = {}
+  formData.channel_names = {}
+
+  if (deviceTypeId) {
+    fetchDevices()
+    fetchChannelCount(deviceTypeId)
+  }
+}
+
+const handleDeviceChange = (deviceIds) => {
+  // 设备选择变化时的处理
+  if (deviceIds.length === 0) {
+    // 如果没有选择设备,清空路数配置
+    formData.timing_agreement = {}
+    formData.channel_names = {}
+  }
+}
+
+const handleSubmit = async () => {
+  try {
+    await formRef.value.validate()
+
+    submitLoading.value = true
+
+    // 构建region_ids格式
+    let regionIdsStr = ''
+    if (deviceSelectType.value === 'region' && formData.selectedRegionNode) {
+      regionIdsStr = buildRegionIdsString(formData.selectedRegionNode)
+    }
+
+    // 构建完整的设备控制配置
+    const fullAgreement = {}
+    const fullCommitAgreement = {}
+
+    // 处理所有有配置的路数(包括只有名称的和只有状态的)
+    for (let i = 1; i <= channelCount.value; i++) {
+      const tagKey = `Tag${i}`
+      const hasName = hasChannelName(i)
+      const hasStatus = hasChannelStatus(i)
+
+      if (hasName || hasStatus) {
+        // 如果有状态配置,添加状态
+        if (hasStatus) {
+          const lowerKey = tagKey.toLowerCase() // Tag1 -> tag1
+          const value = formData.timing_agreement[tagKey]
+          fullAgreement[lowerKey] = value.toString()
+          fullCommitAgreement[lowerKey] = value.toString()
+        }
+
+        // 如果有名称配置,添加名称
+        if (hasName) {
+          const nameKey = tagKey.replace('Tag', 'name') // Tag1 -> name1
+          const nameValue = formData.channel_names[tagKey]
+          fullAgreement[nameKey] = nameValue
+          fullCommitAgreement[nameKey] = nameValue
+        } else if (hasStatus) {
+          // 如果只有状态没有名称,使用默认名称
+          /*const nameKey = tagKey.replace('Tag', 'name')
+          const defaultName = `${i}路`
+          fullAgreement[nameKey] = defaultName
+          fullCommitAgreement[nameKey] = defaultName*/
+        }
+      }
+    }
+
+    console.log('完整的设备配置:', fullAgreement)
+
+    // 构建提交数据
+    const submitData = {
+      timing_name: formData.timing_name,
+      weeks: formData.weeks,
+      timing_start_time: formData.timing_start_time,
+      device_ids: formData.device_ids,
+      timing_agreement: JSON.stringify(fullAgreement),
+      commit_agreement: JSON.stringify(fullCommitAgreement),
+      timing_state: 1,
+      radio: 0
+    }
+
+    // 根据选择方式添加不同的参数
+    if (deviceSelectType.value === 'region') {
+      submitData.region_ids = regionIdsStr
+    } else {
+      submitData.group_id = formData.group_id
+    }
+
+    // 如果是编辑模式,添加timing_id
+    if (isEdit.value) {
+      submitData.timing_id = formData.timing_id
+    }
+
+    console.log('提交的数据:', submitData)
+
+    if (isEdit.value) {
+      await updateStrategy(submitData)
+      ElMessage.success('编辑成功')
+    } else {
+      await createStrategy(submitData)
+      ElMessage.success('新增成功')
+    }
+
+    dialogVisible.value = false
+    fetchData()
+  } catch (error) {
+    if (error.message) {
+      ElMessage.error(error.message)
+    } else {
+      console.error('表单验证失败:', error)
+    }
+  } finally {
+    submitLoading.value = false
+  }
+}
+
+// 查找选中的区域节点
+const findSelectedRegionNode = (regionId) => {
+  // 从区域树中查找选中的节点
+  return findNodeInTree(regionTreeData.value, regionId)
+}
+
+// 构建region_ids字符串
+const buildRegionIdsString = (regionNode) => {
+  try {
+    console.log('构建region_ids,节点数据:', regionNode)
+
+    // 根据节点数据构建ID字符串
+    let idParts = []
+
+    // 添加type
+    if (regionNode.type !== undefined) {
+      idParts.push(regionNode.type)
+    }
+
+    // 添加id
+    if (regionNode.id !== undefined) {
+      idParts.push(regionNode.id)
+    }
+
+    // 根据类型添加第三个参数
+    if (regionNode.type === 2 && regionNode.BuildingId !== undefined) {
+      // 区域类型:type-id-buildingId
+      idParts.push(regionNode.BuildingId)
+    } else if (regionNode.type === 3 && regionNode.RegionId !== undefined) {
+      // 房间类型:type-id-regionId
+      idParts.push(regionNode.RegionId)
+    }
+
+    // 构建完整的层级路径名称
+    const fullPath = buildFullPathName(regionNode)
+
+    // 组装最终的region_ids字符串
+    const idString = idParts.join('-')
+    const result = `${idString}=${fullPath}`
+
+    console.log('构建的region_ids:', result)
+
+    return result
+  } catch (error) {
+    console.error('构建region_ids失败:', error)
+    return `${regionNode.id}=${regionNode.name || ''}`
+  }
+}
+
+// 构建完整的层级路径名称(优化版)
+const buildFullPathName = (regionNode) => {
+  try {
+    const pathParts = []
+
+    // 根据节点类型构建路径
+    if (regionNode.type === 1) {
+      // 建筑类型:只显示建筑名称
+      pathParts.push(regionNode.name)
+    } else if (regionNode.type === 2) {
+      // 区域类型:建筑名称/区域名称
+      if (regionNode.buildingName) {
+        pathParts.push(regionNode.buildingName)
+      } else {
+        // 如果没有buildingName,尝试从树中获取
+        const buildingName = getBuildingNameById(regionNode.BuildingId)
+        if (buildingName) {
+          pathParts.push(buildingName)
+        }
+      }
+      pathParts.push(regionNode.name)
+    } else if (regionNode.type === 3) {
+      // 房间类型:建筑名称/父区域名称/房间名称
+      if (regionNode.buildingName) {
+        pathParts.push(regionNode.buildingName)
+      } else {
+        const buildingName = getBuildingNameById(regionNode.BuildingId)
+        if (buildingName) {
+          pathParts.push(buildingName)
+        }
+      }
+
+      // 添加父区域名称
+      if (regionNode.parentName) {
+        pathParts.push(regionNode.parentName)
+      }
+
+      // 添加当前房间名称
+      pathParts.push(regionNode.name)
+    }
+
+    return pathParts.join('/')
+  } catch (error) {
+    console.error('构建路径名称失败:', error)
+    return regionNode.name || ''
+  }
+}
+
+// 根据建筑ID获取建筑名称
+const getBuildingNameById = (buildingId) => {
+  try {
+    // 从区域树数据中查找建筑名称
+    const building = findBuildingInTree(regionTreeData.value, buildingId)
+    return building ? building.name : null
+  } catch (error) {
+    console.error('获取建筑名称失败:', error)
+    return null
+  }
+}
+
+// 在树中查找建筑
+const findBuildingInTree = (treeData, buildingId) => {
+  for (const node of treeData) {
+    if (node.type === 1 && node.id === buildingId) {
+      return node
+    }
+    if (node.children && node.children.length > 0) {
+      const found = findBuildingInTree(node.children, buildingId)
+      if (found) return found
+    }
+  }
+  return null
+}
+
+// 递归查找树节点
+const findNodeInTree = (treeData, targetId) => {
+  for (const node of treeData) {
+    if (node.id === targetId) {
+      return node
+    }
+    if (node.children && node.children.length > 0) {
+      const found = findNodeInTree(node.children, targetId)
+      if (found) return found
+    }
+  }
+  return null
+}
+
+const resetForm = () => {
+  // 清除防抖定时器
+  if (debounceTimer) {
+    clearTimeout(debounceTimer)
+    debounceTimer = null
+  }
+
+  Object.assign(formData, {
+    timing_id: null,
+    timing_name: '',
+    weeks: [],
+    timing_start_time: '',
+    region_ids: '',
+    group_id: '',
+    category_id: '',
+    device_type_id: '',
+    device_ids: [],
+    timing_agreement: {},
+    channel_names: {},
+    region_type: null,
+    selectedRegionNode: null
+  })
+
+  channelCount.value = 0
+  deviceSelectType.value = 'region'
+  categoryOptions.value = []
+  deviceTypeOptions.value = []
+  deviceOptions.value = []
+
+  // 清空树形数据缓存
+  regionTreeData.value = []
+
+  // 增加树组件的key,强制重新渲染
+  treeKey.value++
+
+  originalChannelConfig.value = {
+    timing_agreement: {},
+    channel_names: {}
+  }
+
+  // 清除表单验证
+  if (formRef.value) {
+    formRef.value.clearValidate()
+  }
+}
+
+// API 调用方法 (获取定时策略列表)
+const fetchData = async () => {
+  try {
+    const params = {
+      currentPage: pagination.currentPage,
+      pageSize: pagination.pageSize,
+      searchText: searchForm.searchText
+    }
+
+    const response = await axios.get(`${__LOCAL_API__}/illuminating/getDeviceOrgid`, {
+      params
+    })
+
+    if (response.data.code === 200) {
+      tableData.value = response.data.data.Timing || []
+      pagination.total = response.data.data.count || 0
+    } else {
+      ElMessage.error(response.data.message || '获取数据失败')
+    }
+  } catch (error) {
+    console.error('获取列表数据失败:', error)
+    ElMessage.error('获取数据失败,请检查网络连接')
+  }
+}
+
+// 获取设备分类
+const fetchDeviceCategories = async () => {
+  try {
+    // 必须有id和type才能查询
+    if (!formData.region_ids || !formData.region_type) {
+      console.log('缺少必要参数:', {
+        region_ids: formData.region_ids,
+        region_type: formData.region_type
+      })
+      return
+    }
+
+    // 构建请求参数
+    const params = {
+      id: formData.region_ids,
+      type: formData.region_type
+    }
+
+    console.log('fetchDeviceCategories 请求参数:', params)
+
+    const response = await axios.get(`${__LOCAL_API__}/illuminating/categoryPart`, {
+      params
+    })
+
+    if (response.data.code === 200) {
+      const categories = response.data.data.BaseDevicesCategorys || []
+      categoryOptions.value = categories.map(item => ({
+        label: item.category_name,
+        value: item.category_id
+      }))
+      console.log('获取到的设备分类:', categoryOptions.value)
+    } else {
+      ElMessage.error(response.data.message || '获取设备分类失败')
+    }
+  } catch (error) {
+    console.error('获取设备分类失败:', error)
+    ElMessage.error('获取设备分类失败,请检查网络连接')
+  }
+}
+
+// 获取设备型号
+const fetchDeviceTypes = async () => {
+  try {
+    const params = {
+      category_id: formData.category_id,
+      devices_enabled: -1
+    }
+
+    // 根据选择方式添加不同的参数
+    if (deviceSelectType.value === 'region' && formData.region_ids) {
+      params.id = formData.region_ids
+      params.type = formData.region_type  // 使用从区域选择中获取的真实type值
+    } else if (deviceSelectType.value === 'group' && formData.group_id) {
+      params.id = formData.group_id
+      params.type = 'group'  // 或者根据实际情况设置group的type值
+    }
+
+    console.log('fetchDeviceTypes 请求参数:', params)
+
+    const response = await axios.get(`${__LOCAL_API__}/illuminating/getTypeFind`, {
+      params
+    })
+
+    if (response.data.code === 200) {
+      const deviceTypes = response.data.data.baseDeviceType || []
+      deviceTypeOptions.value = deviceTypes.map(item => ({
+        label: item.devices_type_name,
+        value: item.devices_type_id,
+        code: item.devices_type_code,
+        // 从dialect_agreements中获取路数信息
+        channelCount: getChannelCountFromDialects(item.dialect_agreements || [])
+      }))
+    } else {
+      ElMessage.error(response.data.message || '获取设备型号失败')
+      deviceTypeOptions.value = []
+    }
+  } catch (error) {
+    console.error('获取设备型号失败:', error)
+    ElMessage.error('获取设备型号失败,请检查网络连接')
+    deviceTypeOptions.value = []
+  }
+}
+
+// 从dialect_agreements中获取路数信息
+const getChannelCountFromDialects = (dialects) => {
+  const tagDialects = dialects.filter(dialect =>
+      dialect.dialect_key && dialect.dialect_key.startsWith('Tag')
+  )
+
+  if (tagDialects.length > 0) {
+    // 获取最大的Tag数字
+    const maxTag = Math.max(...tagDialects.map(dialect => {
+      const match = dialect.dialect_key.match(/Tag(\d+)/)
+      return match ? parseInt(match[1]) : 0
+    }))
+    return maxTag
+  }
+
+  // 默认返回8路
+  return 8
+}
+
+// 获取设备列表
+const fetchDevices = async () => {
+  try {
+    const params = {
+      device_type_id: formData.device_type_id,
+      devices_enabled: -1
+    }
+
+    // 根据选择方式添加不同的参数
+    if (deviceSelectType.value === 'region' && formData.region_ids) {
+      params.position_id = formData.region_ids
+      params.position_type = formData.region_type  // 使用真实的type值,而不是固定的'region'
+    } else if (deviceSelectType.value === 'group' && formData.group_id) {
+      params.position_id = formData.group_id
+      params.position_type = 'group'  // 或者根据实际情况设置
+    }
+
+    console.log('fetchDevices 请求参数:', params)
+
+    const response = await axios.get(`${__LOCAL_API__}/illuminating/deviceAllMini`, {
+      params
+    })
+
+    if (response.data.code === 200) {
+      const devices = response.data.data.devices || []
+      deviceOptions.value = devices.map(item => ({
+        label: `${item.devices_name} (${item.full_region_name}/${item.room_name})`,
+        value: item.devices_id,
+        deviceInfo: item
+      }))
+    } else {
+      ElMessage.error(response.data.message || '获取设备列表失败')
+      deviceOptions.value = []
+    }
+  } catch (error) {
+    console.error('获取设备列表失败:', error)
+    ElMessage.error('获取设备列表失败,请检查网络连接')
+    deviceOptions.value = []
+  }
+}
+
+// 获取设备路数信息
+const fetchChannelCount = async (deviceTypeId) => {
+  try {
+    const deviceType = deviceTypeOptions.value.find(item => item.value === deviceTypeId)
+    if (deviceType && deviceType.channelCount) {
+      channelCount.value = deviceType.channelCount
+
+      // 初始化路数配置(设置为未配置状态)
+      if (!isEdit.value || Object.keys(formData.timing_agreement).length === 0) {
+        const agreement = {}
+        const names = {}
+        for (let i = 1; i <= deviceType.channelCount; i++) {
+          agreement[`Tag${i}`] = undefined // 设置为未配置状态
+          names[`Tag${i}`] = '' // 名称为空
+        }
+        formData.timing_agreement = agreement
+        formData.channel_names = names
+      }
+    } else {
+      // 如果没有找到路数信息,使用默认值
+      channelCount.value = 8
+      if (!isEdit.value || Object.keys(formData.timing_agreement).length === 0) {
+        const agreement = {}
+        const names = {}
+        for (let i = 1; i <= 8; i++) {
+          agreement[`Tag${i}`] = undefined // 设置为未配置状态
+          names[`Tag${i}`] = '' // 名称为空
+        }
+        formData.timing_agreement = agreement
+        formData.channel_names = names
+      }
+    }
+  } catch (error) {
+    console.error('获取路数信息失败:', error)
+    // 使用默认路数
+    channelCount.value = 8
+    if (!isEdit.value || Object.keys(formData.timing_agreement).length === 0) {
+      const agreement = {}
+      const names = {}
+      for (let i = 1; i <= 8; i++) {
+        agreement[`Tag${i}`] = undefined // 设置为未配置状态
+        names[`Tag${i}`] = '' // 名称为空
+      }
+      formData.timing_agreement = agreement
+      formData.channel_names = names
+    }
+  }
+}
+
+// 创建定时策略
+const createStrategy = async (data) => {
+  try {
+    const response = await axios.post(`${__LOCAL_API__}/illuminating/timingSave`, data, {
+      headers: {
+        'Content-Type': 'application/json'
+      }
+    })
+
+    if (response.data.code === 200) {
+      return response.data
+    } else {
+      throw new Error(response.data.message || '创建策略失败')
+    }
+  } catch (error) {
+    console.error('创建策略失败:', error)
+    throw new Error(error.response?.data?.message || error.message || '创建策略失败')
+  }
+}
+
+// 更新定时策略
+const updateStrategy = async (data) => {
+  try {
+    const response = await axios.post(`${__LOCAL_API__}/illuminating/timingSave`, data, {
+      headers: {
+        'Content-Type': 'application/json'
+      }
+    })
+
+    if (response.data.code === 200) {
+      return response.data
+    } else {
+      throw new Error(response.data.message || '更新策略失败')
+    }
+  } catch (error) {
+    console.error('更新策略失败:', error)
+    throw new Error(error.response?.data?.message || error.message || '更新策略失败')
+  }
+}
+
+// 更新策略状态
+const updateStrategyStatus = async (timingId, status) => {
+  try {
+    const params = {
+      timing_id: timingId,
+      timing_state: status
+    }
+
+    const response = await axios.get(`${__LOCAL_API__}/illuminating/changeState`, {
+      params
+    })
+
+    if (response.data.code === 200) {
+      return response.data
+    } else {
+      throw new Error(response.data.message || '状态更新失败')
+    }
+  } catch (error) {
+    console.error('更新状态失败:', error)
+    throw new Error(error.response?.data?.message || error.message || '状态更新失败')
+  }
+}
+
+// 删除定时策略
+const deleteStrategy = async (timingId) => {
+  try {
+    const response = await axios.delete(`${__LOCAL_API__}/illuminating/deleteTimingStrategy/${timingId}`)
+
+    if (response.data.code === 200) {
+      return response.data
+    } else {
+      throw new Error(response.data.message || '删除策略失败')
+    }
+  } catch (error) {
+    console.error('删除策略失败:', error)
+    throw new Error(error.response?.data?.message || error.message || '删除策略失败')
+  }
+}
+
+// 批量操作方法
+const handleBatchDelete = async () => {
+  const selectedRows = tableData.value.filter(row => row.selected)
+
+  if (selectedRows.length === 0) {
+    ElMessage.warning('请选择要删除的策略')
+    return
+  }
+
+  try {
+    await ElMessageBox.confirm(
+        `确定要删除选中的 ${selectedRows.length} 个策略吗?`,
+        '批量删除',
+        {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning',
+        }
+    )
+
+    const deletePromises = selectedRows.map(row => deleteStrategy(row.timing_id))
+    await Promise.all(deletePromises)
+
+    ElMessage.success('批量删除成功')
+    fetchData()
+  } catch (error) {
+    if (error !== 'cancel') {
+      ElMessage.error('批量删除失败')
+    }
+  }
+}
+
+const handleBatchEnable = async (enable = true) => {
+  const selectedRows = tableData.value.filter(row => row.selected)
+
+  if (selectedRows.length === 0) {
+    ElMessage.warning('请选择要操作的策略')
+    return
+  }
+
+  try {
+    const action = enable ? '启用' : '禁用'
+    await ElMessageBox.confirm(
+        `确定要${action}选中的 ${selectedRows.length} 个策略吗?`,
+        `批量${action}`,
+        {
+          confirmButtonText: '确定',
+          cancelButtonText: '取消',
+          type: 'warning',
+        }
+    )
+
+    const updatePromises = selectedRows.map(row =>
+        updateStrategyStatus(row.timing_id, enable ? 1 : 0)
+    )
+    await Promise.all(updatePromises)
+
+    ElMessage.success(`批量${action}成功`)
+    fetchData()
+  } catch (error) {
+    if (error !== 'cancel') {
+      ElMessage.error(`批量${enable ? '启用' : '禁用'}失败`)
+    }
+  }
+}
+
+// 导出功能
+const handleExport = async () => {
+  try {
+    const response = await axios.get(`${__LOCAL_API__}/illuminating/exportTimingStrategies`, {
+      params: {
+        searchText: searchForm.searchText
+      },
+      responseType: 'blob'
+    })
+
+    // 创建下载链接
+    const blob = new Blob([response.data], {
+      type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet'
+    })
+    const url = window.URL.createObjectURL(blob)
+    const link = document.createElement('a')
+    link.href = url
+    link.download = `定时策略_${new Date().toISOString().slice(0, 10)}.xlsx`
+    document.body.appendChild(link)
+    link.click()
+    document.body.removeChild(link)
+    window.URL.revokeObjectURL(url)
+
+    ElMessage.success('导出成功')
+  } catch (error) {
+    console.error('导出失败:', error)
+    ElMessage.error('导出失败')
+  }
+}
+
+// 复制策略
+const handleCopyStrategy = async (row) => {
+  try {
+    dialogTitle.value = '复制策略'
+    isEdit.value = false
+
+    // 先调用详情接口获取完整数据
+    const response = await axios.get(`${__LOCAL_API__}/illuminating/getTimingId`, {
+      params: { timing_id: row.timing_id }
+    })
+
+    if (response.data.code === 200) {
+      const data = response.data.data
+
+      // 解析region_ids
+      let regionId = ''
+      if (data.region_ids) {
+        const parts = data.region_ids.split('=')[0]
+        const idParts = parts.split('-')
+        regionId = idParts.length === 2 ? idParts[1] : idParts[0]
+      }
+
+      // 设置选择方式
+      deviceSelectType.value = data.group_id ? 'group' : 'region'
+
+      // 复制数据但不包含ID
+      Object.assign(formData, {
+        timing_id: null,
+        timing_name: `${data.timing_name}_副本`,
+        weeks: [...(data.weeks || [])],
+        timing_start_time: data.timing_start_time,
+        region_ids: regionId,
+        group_id: data.group_id || '',
+        category_id: data.category_id || '',
+        device_type_id: data.device_type_id || '',
+        device_ids: [...(data.device_ids || [])]
+      })
+
+      // 复制路数配置
+      if (data.timing_agreement) {
+        try {
+          const agreement = typeof data.timing_agreement === 'string'
+              ? JSON.parse(data.timing_agreement)
+              : data.timing_agreement
+
+          // 从设备数据中提取路数名称
+          const channelNames = {}
+          if (data.devices && data.devices.length > 0) {
+            const firstDevice = data.devices[0]
+            if (firstDevice.devices_json_object) {
+              try {
+                const deviceData = JSON.parse(firstDevice.devices_json_object)
+                for (let i = 1; i <= 12; i++) {
+                  if (deviceData[`name${i}`]) {
+                    channelNames[`Tag${i}`] = deviceData[`name${i}`]
+                  }
+                }
+              } catch (e) {
+                console.error('解析设备数据失败:', e)
+              }
+            }
+          }
+
+          formData.timing_agreement = { ...agreement }
+          formData.channel_names = { ...channelNames }
+
+          const tagCount = Object.keys(agreement).length
+          channelCount.value = tagCount
+        } catch (e) {
+          console.error('解析设备指令失败:', e)
+        }
+      }
+
+      // 按顺序加载相关选项数据
+      await loadEditData()
+
+      dialogVisible.value = true
+    } else {
+      ElMessage.error(response.data.message || '获取策略详情失败')
+    }
+  } catch (error) {
+    console.error('加载复制数据失败:', error)
+    ElMessage.error('复制策略失败')
+  }
+}
+
+// 预览策略执行时间
+const previewExecutionTimes = (weeks, startTime) => {
+  if (!weeks || weeks.length === 0 || !startTime) {
+    return []
+  }
+
+  const now = new Date()
+  const times = []
+
+  // 获取未来7天的执行时间
+  for (let i = 0; i < 7; i++) {
+    const date = new Date(now)
+    date.setDate(date.getDate() + i)
+
+    const dayOfWeek = Math.pow(2, date.getDay() === 0 ? 7 : date.getDay()) // 转换为位值
+
+    if (weeks.includes(dayOfWeek)) {
+      const [hours, minutes] = startTime.split(':')
+      date.setHours(parseInt(hours), parseInt(minutes), 0, 0)
+
+      times.push({
+        date: date.toLocaleDateString(),
+        time: startTime,
+        dayName: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'][date.getDay()]
+      })
+    }
+  }
+
+  return times
+}
+
+// 验证策略冲突
+const validateStrategyConflict = async (formData) => {
+  try {
+    const response = await axios.post(`${__LOCAL_API__}/illuminating/validateTimingConflict`, {
+      weeks: formData.weeks,
+      timing_start_time: formData.timing_start_time,
+      device_ids: formData.device_ids,
+      timing_id: formData.timing_id // 编辑时传入,用于排除自身
+    })
+
+    if (response.data.code === 200) {
+      const conflicts = response.data.data.conflicts || []
+      if (conflicts.length > 0) {
+        const conflictMessages = conflicts.map(conflict =>
+            `策略"${conflict.timing_name}"在${conflict.conflict_time}存在冲突`
+        ).join('\n')
+
+        await ElMessageBox.confirm(
+            `检测到以下时间冲突:\n${conflictMessages}\n\n是否继续保存?`,
+            '策略冲突警告',
+            {
+              confirmButtonText: '继续保存',
+              cancelButtonText: '取消',
+              type: 'warning',
+            }
+        )
+      }
+      return true
+    }
+  } catch (error) {
+    if (error === 'cancel') {
+      return false
+    }
+    console.error('验证策略冲突失败:', error)
+    // 验证失败时允许继续,但给出提示
+    ElMessage.warning('无法验证策略冲突,请手动检查')
+    return true
+  }
+}
+
+// 处理周数据转换
+const convertWeeksToArray = (weekValue) => {
+  if (Array.isArray(weekValue)) {
+    return weekValue
+  }
+
+  if (typeof weekValue === 'number') {
+    const weeks = []
+    const weekValues = [2, 4, 8, 16, 32, 64, 128, 256, 512]
+
+    weekValues.forEach(value => {
+      if (weekValue & value) {
+        weeks.push(value)
+      }
+    })
+
+    return weeks
+  }
+
+  return []
+}
+
+// 处理周数据转换为数字
+const convertWeeksToNumber = (weeksArray) => {
+  if (!Array.isArray(weeksArray)) {
+    return 0
+  }
+
+  return weeksArray.reduce((sum, week) => sum + week, 0)
+}
+
+// 格式化区域显示
+const formatRegionDisplay = (regionIds) => {
+  if (!regionIds) return ''
+
+  // 如果是 "1-1=南京芯片之城一期" 格式
+  if (regionIds.includes('=')) {
+    return regionIds.split('=')[1]
+  }
+
+  return regionIds
+}
+
+// 解析设备JSON对象获取路数名称
+const parseDeviceChannelNames = (devices) => {
+  const channelNames = {}
+
+  if (!devices || devices.length === 0) {
+    return channelNames
+  }
+
+  // 从第一个设备获取路数名称配置
+  const firstDevice = devices[0]
+  if (firstDevice && firstDevice.devices_json_object) {
+    try {
+      const deviceData = JSON.parse(firstDevice.devices_json_object)
+
+      // 提取name1-name12的配置
+      for (let i = 1; i <= 12; i++) {
+        const nameKey = `name${i}`
+        if (deviceData[nameKey] && deviceData[nameKey].trim() !== '' && deviceData[nameKey] !== '备用') {
+          channelNames[`Tag${i}`] = deviceData[nameKey]
+        }
+      }
+    } catch (error) {
+      console.error('解析设备JSON失败:', error)
+    }
+  }
+
+  return channelNames
+}
+
+// 生成默认路数名称
+const generateDefaultChannelNames = (count) => {
+  const names = {}
+  for (let i = 1; i <= count; i++) {
+    names[`Tag${i}`] = `${getChineseNumber(i)}路`
+  }
+  return names
+}
+
+// 验证表单数据完整性
+const validateFormData = () => {
+  // 验证基本信息
+  if (!formData.timing_name.trim()) {
+    throw new Error('请输入策略名称')
+  }
+
+  if (!formData.timing_start_time) {
+    throw new Error('请选择执行时间')
+  }
+
+  if (!formData.weeks || formData.weeks.length === 0) {
+    throw new Error('请选择重复日期')
+  }
+
+  // 验证设备选择
+  if (deviceSelectType.value === 'region' && !formData.region_ids) {
+    throw new Error('请选择所属位置')
+  }
+
+  if (deviceSelectType.value === 'group' && !formData.group_id) {
+    throw new Error('请选择设备分组')
+  }
+
+  if (!formData.category_id) {
+    throw new Error('请选择设备分类')
+  }
+
+  if (!formData.device_type_id) {
+    throw new Error('请选择设备型号')
+  }
+
+  if (!formData.device_ids || formData.device_ids.length === 0) {
+    throw new Error('请选择至少一个设备')
+  }
+
+  // 路数配置验证(可选)
+  if (channelCount.value > 0) {
+    // 验证已配置的路数名称长度
+    for (let i = 1; i <= channelCount.value; i++) {
+      const tagKey = `Tag${i}`
+      if (formData.timing_agreement[tagKey] !== undefined) {
+        const channelName = formData.channel_names[tagKey]
+        if (channelName && channelName.length > 20) {
+          throw new Error(`第${i}路的名称长度不能超过20个字符`)
+        }
+      }
+    }
+  }
+
+  return true
+}
+
+// 处理表格数据显示
+const processTableData = (data) => {
+  return data.map(item => ({
+    ...item,
+    // 处理周数据
+    weeks: convertWeeksToArray(item.timing_week || item.weeks),
+    // 格式化区域显示
+    region_display: formatRegionDisplay(item.region_ids),
+    // 处理设备指令显示
+    agreement_display: item.timing_agreement ?
+        (typeof item.timing_agreement === 'string' ? item.timing_agreement : JSON.stringify(item.timing_agreement)) : '',
+  }))
+}
+
+// 初始化页面数据
+const initPageData = async () => {
+  try {
+    await fetchData()
+  } catch (error) {
+    console.error('初始化页面数据失败:', error)
+    ElMessage.error('初始化页面失败')
+  }
+}
+
+// 重置所有选择器
+const resetAllSelectors = () => {
+  formData.category_id = ''
+  formData.device_type_id = ''
+  formData.device_ids = []
+  categoryOptions.value = []
+  deviceTypeOptions.value = []
+  deviceOptions.value = []
+  channelCount.value = 0
+  formData.timing_agreement = {}
+  formData.channel_names = {}
+}
+
+// 处理区域选择变化
+const handleRegionChange = (value) => {
+  formData.region_ids = value
+  resetAllSelectors()
+
+  if (value) {
+    fetchDeviceCategories()
+  }
+}
+
+// 处理分组选择变化
+const handleGroupChange = (value) => {
+  formData.group_id = value
+  resetAllSelectors()
+
+  if (value) {
+    fetchDeviceCategories()
+  }
+}
+
+// 获取设备型号的路数配置
+const getDeviceTypeChannelConfig = async (deviceTypeId) => {
+  try {
+    const deviceType = deviceTypeOptions.value.find(item => item.value === deviceTypeId)
+    if (!deviceType) {
+      return { count: 8, names: generateDefaultChannelNames(8) }
+    }
+
+    const count = deviceType.channelCount || 8
+    const names = generateDefaultChannelNames(count)
+
+    return { count, names }
+  } catch (error) {
+    console.error('获取设备型号路数配置失败:', error)
+    return { count: 8, names: generateDefaultChannelNames(8) }
+  }
+}
+
+// 更新路数配置
+const updateChannelConfig = async (deviceTypeId) => {
+  try {
+    const { count, names } = await getDeviceTypeChannelConfig(deviceTypeId)
+
+    channelCount.value = count
+
+    // 如果是新增或者没有现有配置,初始化配置
+    if (!isEdit.value || Object.keys(formData.timing_agreement).length === 0) {
+      const agreement = {}
+      for (let i = 1; i <= count; i++) {
+        agreement[`Tag${i}`] = 0
+      }
+      formData.timing_agreement = agreement
+      formData.channel_names = { ...names }
+    } else {
+      // 编辑模式下,确保路数名称存在
+      for (let i = 1; i <= count; i++) {
+        if (!formData.channel_names[`Tag${i}`]) {
+          formData.channel_names[`Tag${i}`] = names[`Tag${i}`] || `${i}路`
+        }
+      }
+    }
+  } catch (error) {
+    console.error('更新路数配置失败:', error)
+  }
+}
+
+// 处理编辑数据的特殊逻辑
+const handleEditDataSpecial = (data) => {
+  // 处理周数据
+  if (data.timing_week !== undefined) {
+    formData.weeks = convertWeeksToArray(data.timing_week)
+  } else if (data.weeks) {
+    formData.weeks = Array.isArray(data.weeks) ? data.weeks : convertWeeksToArray(data.weeks)
+  }
+
+  // 处理区域ID
+  if (data.region_ids) {
+    const parts = data.region_ids.split('=')[0]
+    const idParts = parts.split('-')
+    formData.region_ids = idParts.length === 2 ? idParts[1] : idParts[0]
+  }
+
+  // 处理设备指令和路数名称
+  if (data.timing_agreement) {
+    try {
+      const agreement = typeof data.timing_agreement === 'string'
+          ? JSON.parse(data.timing_agreement)
+          : data.timing_agreement
+
+      formData.timing_agreement = { ...agreement }
+
+      // 从设备数据中提取路数名称
+      const deviceChannelNames = parseDeviceChannelNames(data.devices)
+
+      // 合并路数名称(优先使用设备中的名称)
+      const channelNames = { ...generateDefaultChannelNames(Object.keys(agreement).length) }
+      Object.assign(channelNames, deviceChannelNames)
+
+      formData.channel_names = channelNames
+
+      // 设置路数
+      channelCount.value = Object.keys(agreement).length
+
+      // 保存原始配置
+      originalChannelConfig.value = {
+        timing_agreement: { ...agreement },
+        channel_names: { ...channelNames }
+      }
+    } catch (error) {
+      console.error('处理设备指令失败:', error)
+    }
+  }
+}
+
+// 生命周期
+onMounted(() => {
+  initPageData()
+})
+
+
+// 导出组件需要的方法和数据(如果需要的话)
+defineExpose({
+  fetchData,
+  resetForm,
+  handleAdd,
+  handleEdit
+})
+
+// 添加清理过期缓存的方法
+const clearExpiredCache = () => {
+  const now = Date.now()
+  for (const [key, value] of requestCache.entries()) {
+    if (now - value.timestamp > value.expiry) {
+      requestCache.delete(key)
+    }
+  }
+}
+
+// 在组件卸载时清理
+onUnmounted(() => {
+  if (debounceTimer) {
+    clearTimeout(debounceTimer)
+  }
+  requestCache.clear()
+  requestQueue.clear()
+})
+
+// 定期清理过期缓存
+setInterval(clearExpiredCache, 1800000) // 每分钟清理一次
+</script>
+
+<style scoped>
+.timing-strategy-page {
+  padding: 24px;
+  background: #f5f7fa;
+  min-height: 90vh;
+}
+
+/* 页面标题 */
+.page-header {
+  margin-bottom: 24px;
+}
+
+.page-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-size: 24px;
+  font-weight: 600;
+  color: #303133;
+  margin: 0 0 8px 0;
+}
+
+.page-description {
+  color: #909399;
+  font-size: 14px;
+  margin: 0;
+}
+
+/* 卡片样式 */
+.search-card,
+.table-card {
+  margin-bottom: 24px;
+  border-radius: 12px;
+  border: none;
+  box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
+}
+
+.card-header {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  font-weight: 600;
+  color: #303133;
+}
+
+.card-header .header-left {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+}
+
+.count-tag {
+  margin-left: 12px;
+}
+
+/* 搜索表单 */
+.search-form {
+  margin: 0;
+}
+
+.el-form--inline .el-form-item {
+  display: inline-flex;
+  margin-right: 32px;
+  vertical-align: baseline;
+}
+
+.search-input {
+  width: 280px;
+}
+
+/* 表格样式 */
+.strategy-table {
+  margin-bottom: 24px;
+  padding: 12px 12px;
+}
+
+.strategy-name {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  font-weight: 500;
+}
+
+.name-icon {
+  color: #409eff;
+}
+
+.weeks-container {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 4px;
+}
+
+.week-tag {
+  margin: 2px 0;
+}
+
+.time-display {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  font-weight: 500;
+  color: #606266;
+}
+
+.agreement-display {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 4px;
+}
+
+.agreement-tag {
+  margin: 2px 0;
+}
+
+.action-buttons {
+  display: flex;
+  gap: 8px;
+}
+
+/* 分页 */
+.pagination-container {
+  display: flex;
+  justify-content: flex-end;
+  margin-top: 24px;
+}
+
+/* 弹窗样式 */
+.strategy-dialog :deep(.el-dialog__header) {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+  border-radius: 12px 12px 0 0;
+  padding: 20px 24px;
+}
+
+.strategy-dialog :deep(.el-dialog__title) {
+  color: white;
+  font-weight: 600;
+}
+
+.strategy-dialog :deep(.el-dialog__headerbtn .el-dialog__close) {
+  color: white;
+}
+
+.strategy-dialog :deep(.el-dialog__body) {
+  padding: 24px;
+}
+
+/* 表单样式 */
+.strategy-form {
+  max-height: 60vh;
+  overflow-y: auto;
+  padding-right: 8px;
+}
+
+.form-section {
+  margin-bottom: 32px;
+  padding: 20px;
+  background: #fafbfc;
+  border-radius: 8px;
+  border-left: 4px solid #409eff;
+}
+
+.section-title {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  margin: 0 0 20px 0;
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+
+/* 设备选择样式 */
+.device-select-container {
+  width: 100%;
+}
+
+.device-select-header {
+  padding: 8px 12px;
+  border-bottom: 1px solid #e4e7ed;
+  background: #f8f9fa;
+}
+
+.device-count-info {
+  margin-top: 8px;
+  font-size: 12px;
+  color: #909399;
+  text-align: right;
+}
+
+/* 路数配置操作按钮 */
+.channel-operations {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 20px;
+  padding: 16px;
+  background: #f0f9ff;
+  border-radius: 8px;
+  border: 1px solid #b3d8ff;
+}
+
+.operation-buttons {
+  display: flex;
+  gap: 12px;
+  align-items: center;
+}
+
+.channel-status-info {
+  font-size: 14px;
+  color: #606266;
+  font-weight: 500;
+}
+
+/* 重复日期选择样式 */
+.weeks-selection {
+  width: 100%;
+  background: white;
+  border-radius: 12px;
+  padding: 20px;
+  border: 1px solid #e4e7ed;
+}
+
+.quick-select-buttons {
+  display: flex;
+  gap: 12px;
+  margin-bottom: 20px;
+  padding-bottom: 16px;
+  border-bottom: 1px solid #f0f0f0;
+}
+
+.quick-select-buttons .el-button {
+  border-radius: 20px;
+  padding: 8px 16px;
+  font-size: 13px;
+  transition: all 0.3s ease;
+}
+
+.quick-select-buttons .el-button:hover {
+  transform: translateY(-1px);
+  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.3);
+}
+
+.week-selection-container {
+  display: flex;
+  flex-direction: column;
+  gap: 24px;
+}
+
+.week-days-grid {
+  display: grid;
+  grid-template-columns: repeat(7, 1fr);
+  gap: 12px;
+}
+
+.week-day-item {
+  position: relative;
+  background: #f8f9fa;
+  border: 2px solid #e9ecef;
+  border-radius: 12px;
+  padding: 16px 8px;
+  cursor: pointer;
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+  user-select: none;
+  overflow: hidden;
+}
+
+.week-day-item::before {
+  content: '';
+  position: absolute;
+  top: 0;
+  left: 0;
+  right: 0;
+  bottom: 0;
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  opacity: 0;
+  transition: opacity 0.3s ease;
+  z-index: 1;
+}
+
+.week-day-item:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.15);
+  border-color: #409eff;
+}
+
+.week-day-item:hover::before {
+  opacity: 0.1;
+}
+
+.week-day-item.selected {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  border-color: #667eea;
+  color: white;
+  transform: translateY(-2px);
+  box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
+}
+
+.week-day-item.weekend {
+  background: #fff5f5;
+  border-color: #fed7d7;
+}
+
+.week-day-item.weekend.selected {
+  background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%);
+  border-color: #f093fb;
+}
+
+.day-content {
+  position: relative;
+  z-index: 2;
+  display: flex;
+  flex-direction: column;
+  align-items: center;
+  gap: 4px;
+}
+
+.day-name {
+  font-size: 14px;
+  font-weight: 600;
+  line-height: 1;
+}
+
+.day-en {
+  font-size: 11px;
+  opacity: 0.7;
+  line-height: 1;
+}
+
+.day-icon {
+  position: absolute;
+  top: -8px;
+  right: -8px;
+  width: 20px;
+  height: 20px;
+  background: #67c23a;
+  border-radius: 50%;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  color: white;
+  font-size: 12px;
+  opacity: 0;
+  transform: scale(0);
+  transition: all 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
+}
+
+.week-day-item.selected .day-icon {
+  opacity: 1;
+  transform: scale(1);
+}
+
+/* 特殊选项 */
+.special-options {
+  background: #f8f9fa;
+  border-radius: 8px;
+  padding: 16px;
+  border: 1px solid #e9ecef;
+}
+
+.special-option-group {
+  width: 100%;
+}
+
+.option-group-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #303133;
+  margin: 0 0 12px 0;
+  padding-bottom: 8px;
+  border-bottom: 1px solid #e9ecef;
+}
+
+.special-checkboxes {
+  display: flex;
+  gap: 16px;
+}
+
+.special-checkbox {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 10px 16px;
+  background: white;
+  border: 2px solid #e9ecef;
+  border-radius: 8px;
+  cursor: pointer;
+  transition: all 0.3s ease;
+  user-select: none;
+  flex: 1;
+}
+
+.special-checkbox:hover {
+  border-color: #409eff;
+  background: #f0f9ff;
+}
+
+.special-checkbox.checked {
+  border-color: #409eff;
+  background: #ecf5ff;
+  color: #409eff;
+}
+
+.checkbox-icon {
+  width: 16px;
+  height: 16px;
+  border: 1px solid #dcdfe6;
+  border-radius: 3px;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  background: white;
+  transition: all 0.3s ease;
+}
+
+.special-checkbox.checked .checkbox-icon {
+  background: #409eff;
+  border-color: #409eff;
+  color: white;
+}
+
+.checkbox-label {
+  font-size: 14px;
+  font-weight: 500;
+}
+
+/* 选择结果预览 */
+.selection-preview {
+  margin-top: 20px;
+  padding: 16px;
+  background: #f0f9ff;
+  border-radius: 8px;
+  border: 1px solid #b3d8ff;
+}
+
+.preview-title {
+  font-size: 14px;
+  font-weight: 600;
+  color: #409eff;
+  margin-bottom: 12px;
+}
+
+.preview-tags {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+}
+
+.preview-tag {
+  transition: all 0.3s ease;
+}
+
+.preview-tag:hover {
+  transform: scale(1.05);
+}
+
+/* 设备选择样式 */
+.select-type-radio {
+  display: flex;
+  gap: 24px;
+}
+
+.select-type-radio .el-radio {
+  margin-right: 0;
+  padding: 12px 20px;
+  border: 1px solid #e4e7ed;
+  border-radius: 8px;
+  background: white;
+  transition: all 0.3s ease;
+}
+
+.select-type-radio .el-radio:hover {
+  border-color: #409eff;
+  background: #f0f9ff;
+}
+
+.select-type-radio .el-radio.is-checked {
+  border-color: #409eff;
+  background: #ecf5ff;
+  color: #409eff;
+}
+
+.select-type-radio .el-radio :deep(.el-radio__input) {
+  display: none;
+}
+
+.select-type-radio .el-radio :deep(.el-radio__label) {
+  display: flex;
+  align-items: center;
+  gap: 8px;
+  padding: 0;
+}
+
+/* 路数配置样式 */
+.channel-grid {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
+  gap: 20px;
+}
+
+.channel-item {
+  background: white;
+  border: 1px solid #e4e7ed;
+  border-radius: 8px;
+  padding: 16px;
+  transition: all 0.3s ease;
+}
+
+.channel-item:hover {
+  border-color: #409eff;
+  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.1);
+}
+
+.channel-header {
+  display: flex;
+  align-items: flex-start;
+  gap: 12px;
+  margin-bottom: 16px;
+}
+
+.channel-number {
+  font-weight: 600;
+  color: #303133;
+  min-width: 40px;
+  line-height: 32px;
+}
+
+/* 路数名称表单项样式 */
+.channel-name-form-item {
+  flex: 1;
+  margin-bottom: 0;
+}
+
+.channel-name-form-item :deep(.el-form-item__content) {
+  margin-left: 0 !important;
+}
+
+.channel-name-form-item :deep(.el-form-item__error) {
+  position: static;
+  margin-top: 4px;
+  font-size: 12px;
+}
+
+.channel-name-input {
+  flex: 1;
+}
+
+.channel-control {
+  display: flex;
+  justify-content: center;
+}
+
+.channel-radio {
+  display: flex;
+  gap: 16px;
+}
+
+.channel-radio .el-radio {
+  margin: 0;
+  padding: 8px 16px;
+  border: 1px solid #e4e7ed;
+  border-radius: 6px;
+  background: white;
+  transition: all 0.3s ease;
+}
+
+.radio-off.is-checked {
+  border-color: #f56c6c;
+  background: #fef0f0;
+  color: #f56c6c;
+}
+
+.radio-on.is-checked {
+  border-color: #67c23a;
+  background: #f0f9ff;
+  color: #67c23a;
+}
+
+.channel-radio .el-radio :deep(.el-radio__input) {
+  display: none;
+}
+
+.channel-radio .el-radio :deep(.el-radio__label) {
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  padding: 0;
+  font-size: 14px;
+}
+
+/* 弹窗底部 */
+.dialog-footer {
+  display: flex;
+  justify-content: flex-end;
+  gap: 12px;
+  padding: 20px 24px;
+  background: #fafbfc;
+  border-radius: 0 0 12px 12px;
+  margin: 0 -24px -24px -24px;
+}
+
+/* 详情弹窗 */
+.detail-dialog :deep(.el-dialog__header) {
+  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
+  color: white;
+  border-radius: 12px 12px 0 0;
+}
+
+.detail-content {
+  padding: 8px 0;
+}
+
+.detail-section {
+  margin-bottom: 24px;
+}
+
+.detail-section h4 {
+  margin: 0 0 16px 0;
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+  padding-bottom: 8px;
+  border-bottom: 2px solid #e4e7ed;
+}
+
+.agreement-detail {
+  display: grid;
+  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
+  gap: 12px;
+}
+
+.agreement-item {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+  padding: 12px 16px;
+  background: #f8f9fa;
+  border-radius: 6px;
+  border-left: 3px solid #409eff;
+}
+
+.channel-name {
+  font-weight: 500;
+  color: #303133;
+}
+
+/* 树形选择器样式优化 */
+.el-tree-select :deep(.el-tree-node__content) {
+  padding: 8px 12px;
+  border-radius: 4px;
+  transition: all 0.3s ease;
+}
+
+.el-tree-select :deep(.el-tree-node__content):hover {
+  background-color: #f0f9ff;
+}
+
+.el-tree-select :deep(.el-tree-node.is-current > .el-tree-node__content) {
+  background-color: #ecf5ff;
+  color: #409eff;
+  font-weight: 500;
+}
+
+.el-tree-select :deep(.el-tree-node__expand-icon) {
+  color: #409eff;
+}
+
+.el-tree-select :deep(.el-tree-node__loading-icon) {
+  color: #409eff;
+}
+
+/* 响应式设计 */
+@media (max-width: 1200px) {
+  .channel-grid {
+    grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
+  }
+
+  .week-days-grid {
+    grid-template-columns: repeat(4, 1fr);
+    gap: 10px;
+  }
+}
+
+@media (max-width: 768px) {
+  .timing-strategy-page {
+    padding: 16px;
+  }
+
+  .page-title {
+    font-size: 20px;
+  }
+
+  .search-input {
+    width: 100%;
+  }
+
+  .search-form .el-form-item {
+    margin-bottom: 16px;
+  }
+
+  .week-days-grid {
+    grid-template-columns: repeat(2, 1fr);
+    gap: 8px;
+  }
+
+  .week-day-item {
+    padding: 12px 6px;
+  }
+
+  .day-name {
+    font-size: 13px;
+  }
+
+  .day-en {
+    font-size: 10px;
+  }
+
+  .quick-select-buttons {
+    flex-wrap: wrap;
+    gap: 8px;
+  }
+
+  .special-checkboxes {
+    flex-direction: column;
+    gap: 12px;
+  }
+
+  .channel-grid {
+    grid-template-columns: 1fr;
+  }
+
+  .agreement-detail {
+    grid-template-columns: 1fr;
+  }
+
+  .action-buttons {
+    flex-direction: column;
+    gap: 4px;
+  }
+
+  .strategy-dialog {
+    width: 95% !important;
+    margin: 0 auto;
+  }
+
+  .detail-dialog {
+    width: 95% !important;
+    margin: 0 auto;
+  }
+
+  .weeks-selection {
+    padding: 16px;
+  }
+
+  .preview-tags {
+    gap: 6px;
+  }
+
+  .channel-operations {
+    flex-direction: column;
+    gap: 12px;
+    align-items: stretch;
+  }
+
+  .operation-buttons {
+    justify-content: center;
+  }
+
+  .select-type-radio {
+    flex-direction: column;
+    gap: 12px;
+  }
+}
+
+@media (max-width: 480px) {
+  .week-days-grid {
+    grid-template-columns: 1fr;
+    gap: 6px;
+  }
+
+  .week-day-item {
+    padding: 10px;
+  }
+
+  .quick-select-buttons {
+    grid-template-columns: repeat(2, 1fr);
+    display: grid;
+  }
+
+  .channel-radio {
+    flex-direction: column;
+    gap: 8px;
+  }
+
+  .special-checkboxes {
+    gap: 8px;
+  }
+
+  .special-checkbox {
+    padding: 8px 12px;
+  }
+}
+
+/* 滚动条样式 */
+.strategy-form::-webkit-scrollbar {
+  width: 6px;
+}
+
+.strategy-form::-webkit-scrollbar-track {
+  background: #f1f1f1;
+  border-radius: 3px;
+}
+
+.strategy-form::-webkit-scrollbar-thumb {
+  background: #c1c1c1;
+  border-radius: 3px;
+}
+
+.strategy-form::-webkit-scrollbar-thumb:hover {
+  background: #a8a8a8;
+}
+
+/* 动画效果 */
+.el-card {
+  transition: all 0.3s ease;
+}
+
+.el-card:hover {
+  transform: translateY(-2px);
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
+}
+
+.el-button {
+  transition: all 0.3s ease;
+}
+
+.el-table__row {
+  transition: all 0.3s ease;
+}
+
+.el-table__row:hover {
+  background-color: #f5f7fa !important;
+}
+
+/* 设备选择下拉框头部样式 */
+.device-select-header :deep(.el-checkbox) {
+  width: 100%;
+}
+
+.device-select-header :deep(.el-checkbox__label) {
+  font-weight: 500;
+  color: #606266;
+}
+
+/* 表单验证错误样式优化 */
+.el-form-item.is-error :deep(.el-input__wrapper) {
+  box-shadow: 0 0 0 1px #f56c6c inset;
+}
+
+.el-form-item.is-error :deep(.el-select .el-input__wrapper) {
+  box-shadow: 0 0 0 1px #f56c6c inset;
+}
+
+.el-form-item.is-error :deep(.el-tree-select .el-input__wrapper) {
+  box-shadow: 0 0 0 1px #f56c6c inset;
+}
+
+/* 提交按钮加载状态 */
+.el-button.is-loading {
+  pointer-events: none;
+}
+
+/* 路数配置状态指示 */
+.channel-status-info {
+  display: flex;
+  gap: 16px;
+  align-items: center;
+}
+
+.channel-status-info::before {
+  content: '';
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  background: #67c23a;
+  display: inline-block;
+}
+
+/* 优化选择器样式 */
+.el-select-dropdown__item.selected {
+  background-color: #f0f9ff;
+  color: #409eff;
+  font-weight: 500;
+}
+
+/* 优化时间选择器样式 */
+.el-time-picker {
+  width: 100%;
+}
+
+.el-time-picker :deep(.el-input__wrapper) {
+  transition: all 0.3s ease;
+}
+
+.el-time-picker :deep(.el-input__wrapper):hover {
+  box-shadow: 0 0 0 1px #409eff inset;
+}
+
+/* 优化多选标签样式 */
+.el-select :deep(.el-tag) {
+  margin: 2px 4px 2px 0;
+  background-color: #f0f9ff;
+  border-color: #b3d8ff;
+  color: #409eff;
+}
+
+.el-select :deep(.el-tag .el-tag__close) {
+  color: #409eff;
+}
+
+.el-select :deep(.el-tag .el-tag__close):hover {
+  background-color: #409eff;
+  color: white;
+}
+
+/* 优化开关样式 */
+.el-switch.is-checked :deep(.el-switch__core) {
+  background-color: #13ce66;
+}
+
+.el-switch :deep(.el-switch__core) {
+  background-color: #ff4949;
+}
+
+/* 优化描述列表样式 */
+.el-descriptions :deep(.el-descriptions__header) {
+  margin-bottom: 16px;
+}
+
+.el-descriptions :deep(.el-descriptions__title) {
+  font-size: 16px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.el-descriptions :deep(.el-descriptions__body) {
+  background-color: #fafbfc;
+}
+
+.el-descriptions :deep(.el-descriptions-item__label) {
+  font-weight: 500;
+  color: #606266;
+  background-color: #f8f9fa;
+}
+
+.el-descriptions :deep(.el-descriptions-item__content) {
+  color: #303133;
+}
+
+/* 工具提示样式 */
+.el-tooltip__popper {
+  max-width: 300px;
+  word-break: break-all;
+}
+
+/* 过渡动画 */
+.week-day-item,
+.special-checkbox,
+.preview-tag {
+  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+}
+
+/* 焦点状态 */
+.week-day-item:focus,
+.special-checkbox:focus {
+  outline: 2px solid #409eff;
+  outline-offset: 2px;
+}
+
+/* 禁用状态 */
+.week-day-item.disabled {
+  opacity: 0.5;
+  cursor: not-allowed;
+  pointer-events: none;
+}
+
+/* 减少动画模式支持 */
+@media (prefers-reduced-motion: reduce) {
+  .week-day-item,
+  .special-checkbox,
+  .preview-tag,
+  .day-icon {
+    transition: none;
+  }
+}
+
+/* 树形选择器下拉面板样式 */
+.el-tree-select-dropdown {
+  max-height: 300px;
+}
+
+.el-tree-select-dropdown :deep(.el-tree) {
+  padding: 8px;
+}
+
+.el-tree-select-dropdown :deep(.el-tree-node) {
+  margin-bottom: 4px;
+}
+
+.el-tree-select-dropdown :deep(.el-tree-node__content) {
+  border-radius: 6px;
+  padding: 8px 12px;
+  transition: all 0.2s ease;
+}
+
+.el-tree-select-dropdown :deep(.el-tree-node__content):hover {
+  background-color: #f0f9ff;
+}
+
+.el-tree-select-dropdown :deep(.el-tree-node.is-current > .el-tree-node__content) {
+  background-color: #ecf5ff;
+  color: #409eff;
+  font-weight: 500;
+}
+
+.el-tree-select-dropdown :deep(.el-tree-node__label) {
+  font-size: 14px;
+  color: #606266;
+}
+
+.el-tree-select-dropdown :deep(.el-tree-node.is-current .el-tree-node__label) {
+  color: #409eff;
+}
+
+/* 加载状态样式 */
+.el-tree-select-dropdown :deep(.el-tree-node__loading-icon) {
+  color: #409eff;
+  animation: rotating 2s linear infinite;
+}
+
+@keyframes rotating {
+  0% {
+    transform: rotate(0deg);
+  }
+  100% {
+    transform: rotate(360deg);
+  }
+}
+
+/* 空状态样式 */
+.el-tree-select-dropdown :deep(.el-tree__empty-block) {
+  padding: 20px;
+  text-align: center;
+  color: #909399;
+  font-size: 14px;
+}
+
+/* 优化表格行高 */
+.strategy-table :deep(.el-table__row) {
+  height: 60px;
+}
+
+.strategy-table :deep(.el-table__cell) {
+  padding: 12px 0;
+}
+
+/* 优化表格头部样式 */
+.strategy-table :deep(.el-table__header-wrapper) {
+  background: #f8f9fa;
+}
+
+.strategy-table :deep(.el-table__header th) {
+  background: #f8f9fa;
+  color: #495057;
+  font-weight: 600;
+  border-bottom: 2px solid #e9ecef;
+}
+
+/* 优化分页样式 */
+.el-pagination {
+  padding: 20px 0;
+}
+
+.el-pagination :deep(.el-pagination__total) {
+  color: #606266;
+  font-weight: 500;
+}
+
+.el-pagination :deep(.btn-prev),
+.el-pagination :deep(.btn-next) {
+  border-radius: 6px;
+  transition: all 0.3s ease;
+}
+
+.el-pagination :deep(.btn-prev):hover,
+.el-pagination :deep(.btn-next):hover {
+  color: #409eff;
+  transform: translateY(-1px);
+}
+
+.el-pagination :deep(.el-pager li) {
+  border-radius: 6px;
+  margin: 0 2px;
+  transition: all 0.3s ease;
+}
+
+.el-pagination :deep(.el-pager li:hover) {
+  color: #409eff;
+  transform: translateY(-1px);
+}
+
+.el-pagination :deep(.el-pager li.is-active) {
+  background-color: #409eff;
+  color: white;
+  transform: translateY(-1px);
+  box-shadow: 0 2px 8px rgba(64, 158, 255, 0.3);
+}
+
+/* 优化消息提示样式 */
+.el-message {
+  border-radius: 8px;
+  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
+}
+
+.el-message.el-message--success {
+  background-color: #f0f9ff;
+  border-color: #b3d8ff;
+  color: #409eff;
+}
+
+.el-message.el-message--error {
+  background-color: #fef0f0;
+  border-color: #fbc4c4;
+  color: #f56c6c;
+}
+
+/* 优化确认框样式 */
+.el-message-box {
+  border-radius: 12px;
+  box-shadow: 0 8px 40px rgba(0, 0, 0, 0.2);
+}
+
+.el-message-box__header {
+  padding: 20px 24px 16px;
+  border-bottom: 1px solid #e9ecef;
+}
+
+.el-message-box__title {
+  font-size: 18px;
+  font-weight: 600;
+  color: #303133;
+}
+
+.el-message-box__content {
+  padding: 20px 24px;
+}
+
+.el-message-box__btns {
+  padding: 16px 24px 20px;
+  border-top: 1px solid #e9ecef;
+}
+
+/* 优化加载动画 */
+.el-loading-mask {
+  background-color: rgba(255, 255, 255, 0.9);
+  backdrop-filter: blur(4px);
+}
+
+.el-loading-spinner {
+  color: #409eff;
+}
+
+/* 优化表单项间距 */
+.el-form-item {
+  margin-bottom: 20px;
+}
+
+.el-form-item:last-child {
+  margin-bottom: 0;
+}
+
+/* 优化输入框样式 */
+.el-input__wrapper {
+  border-radius: 6px;
+  transition: all 0.3s ease;
+}
+
+.el-input__wrapper:hover {
+  box-shadow: 0 0 0 1px #c0c4cc inset;
+}
+
+.el-input__wrapper.is-focus {
+  box-shadow: 0 0 0 1px #409eff inset;
+}
+
+/* 优化选择器样式 */
+.el-select .el-input__wrapper {
+  cursor: pointer;
+}
+
+.el-select .el-input__wrapper:hover {
+  box-shadow: 0 0 0 1px #c0c4cc inset;
+}
+
+.el-select .el-input__wrapper.is-focus {
+  box-shadow: 0 0 0 1px #409eff inset;
+}
+
+/* 优化按钮样式 */
+.el-button {
+  border-radius: 6px;
+  font-weight: 500;
+  transition: all 0.3s ease;
+}
+
+.el-button:hover {
+  transform: translateY(-1px);
+  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
+}
+
+.el-button--primary {
+  background: linear-gradient(135deg, #409eff 0%, #3a8ee6 100%);
+  border-color: #409eff;
+}
+
+.el-button--primary:hover {
+  background: linear-gradient(135deg, #66b1ff 0%, #409eff 100%);
+  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
+}
+
+.el-button--success {
+  background: linear-gradient(135deg, #67c23a 0%, #5daf34 100%);
+  border-color: #67c23a;
+}
+
+.el-button--success:hover {
+  background: linear-gradient(135deg, #85ce61 0%, #67c23a 100%);
+  box-shadow: 0 4px 12px rgba(103, 194, 58, 0.4);
+}
+
+/* 优化标签样式 */
+.el-tag {
+  border-radius: 4px;
+  font-weight: 500;
+  transition: all 0.3s ease;
+}
+
+.el-tag:hover {
+  transform: scale(1.05);
+}
+
+.el-tag--primary {
+  background-color: #ecf5ff;
+  border-color: #b3d8ff;
+  color: #409eff;
+}
+
+.el-tag--success {
+  background-color: #f0f9ff;
+  border-color: #b3e19d;
+  color: #67c23a;
+}
+
+.el-tag--info {
+  background-color: #f4f4f5;
+  border-color: #d3d4d6;
+  color: #909399;
+}
+
+.el-tag--warning {
+  background-color: #fdf6ec;
+  border-color: #f5dab1;
+  color: #e6a23c;
+}
+
+.el-tag--danger {
+  background-color: #fef0f0;
+  border-color: #fbc4c4;
+  color: #f56c6c;
+}
+
+/* 打印样式 */
+@media print {
+  .timing-strategy-page {
+    background: white;
+    padding: 0;
+  }
+
+  .search-card,
+  .pagination-container,
+  .action-buttons {
+    display: none;
+  }
+
+  .table-card {
+    box-shadow: none;
+    border: 1px solid #ddd;
+  }
+
+  .strategy-table {
+    font-size: 12px;
+  }
+}
+
+/* 高对比度模式支持 */
+@media (prefers-contrast: high) {
+  .week-day-item {
+    border-width: 3px;
+  }
+
+  .week-day-item.selected {
+    border-width: 4px;
+  }
+
+  .special-checkbox {
+    border-width: 2px;
+  }
+
+  .el-button {
+    border-width: 2px;
+  }
+}
+
+/* 深色模式支持 */
+@media (prefers-color-scheme: dark) {
+  .timing-strategy-page {
+    background: #1a1a1a;
+    color: #e5e5e5;
+  }
+
+  .search-card,
+  .table-card {
+    background: #2d2d2d;
+    border-color: #404040;
+  }
+
+  .form-section {
+    background: #2a2a2a;
+    border-left-color: #409eff;
+  }
+
+  .week-day-item {
+    background: #3a3a3a;
+    border-color: #505050;
+    color: #e5e5e5;
+  }
+
+  .week-day-item:hover {
+    background: #404040;
+  }
+
+  .channel-item {
+    background: #3a3a3a;
+    border-color: #505050;
+  }
+}
+.action-btn {
+  min-width: 60px;
+  height: 32px;
+  border-radius: 6px;
+  font-weight: 500;
+  font-size: 12px;
+  transition: all 0.3s ease;
+  border: 1px solid transparent;
+}
+
+.action-btn {
+  width: 40%;
+  min-width: auto;
+}
+
+.control-btn {
+  background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%);
+  color: white;
+  border: none;
+}
+
+.control-btn:hover:not(:disabled) {
+  transform: translateY(-1px);
+  box-shadow: 0 4px 12px rgba(64, 158, 255, 0.4);
+}
+
+.control-btn:disabled {
+  background: #c0c4cc;
+  color: #ffffff;
+  cursor: not-allowed;
+  transform: none;
+  box-shadow: none;
+}
+</style>

برخی فایل ها در این مقایسه diff نمایش داده نمی شوند زیرا تعداد فایل ها بسیار زیاد است