SecondPage.vue 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829
  1. <template>
  2. <div class="room-management-container"> <!-- 修改为 room-management-container -->
  3. <h1>房间管理</h1>
  4. <!-- 提示信息 -->
  5. <div v-if="message" :class="['message', messageType]">
  6. {{ message }}
  7. </div>
  8. <button class="add-room-button" @click="showModal = true">新增房间</button>
  9. <!-- 新增房间的模态框 -->
  10. <div v-if="showModal" class="modal">
  11. <div class="modal-content">
  12. <span class="close" @click="showModal = false">&times;</span>
  13. <h2>新增房间</h2>
  14. <input v-model="newRoomName" placeholder="请输入房间名称" />
  15. <input v-model="newRoomDescription" placeholder="请输入房间描述" />
  16. <input v-model="newRoomFloor" placeholder="请输入楼层" type="number" />
  17. <select v-model="newRoomOrientation">
  18. <option value="" disabled>请选择朝向</option>
  19. <option value="东">东</option>
  20. <option value="南">南</option>
  21. <option value="西">西</option>
  22. <option value="北">北</option>
  23. </select>
  24. <button class="modal-button" @click="addNewRoom">确定</button>
  25. </div>
  26. </div>
  27. <!-- 显示所有房间的列表 -->
  28. <h2>所有房间</h2>
  29. <input v-model="roomFilter" placeholder="搜索房间名称" class="filter-input" />
  30. <table class="room-table">
  31. <thead>
  32. <tr>
  33. <th>房间名称</th>
  34. <th>描述</th>
  35. <th>楼层</th>
  36. <th>朝向</th>
  37. <th>操作</th>
  38. </tr>
  39. </thead>
  40. <tbody>
  41. <tr v-for="room in paginatedRooms" :key="room.id">
  42. <td>{{ room.room_name }}</td>
  43. <td>{{ room.description }}</td>
  44. <td>{{ room.floor }}</td>
  45. <td>{{ room.orientation }}</td>
  46. <td>
  47. <!-- 只有绑定设备的房间才显示"查看设备"按钮 -->
  48. <button
  49. v-if="room.device_count > 0"
  50. class="view-devices-button"
  51. @click="toggleDevices(room.id)"
  52. >
  53. 查看设备
  54. </button>
  55. <button class="edit-button" @click="openEditModal(room)">编辑</button>
  56. </td>
  57. </tr>
  58. </tbody>
  59. </table>
  60. <div class="pagination">
  61. <button @click="prevRoomPage" :disabled="currentRoomPage === 1">上一页</button>
  62. <span>第 {{ currentRoomPage }} 页</span>
  63. <button @click="nextRoomPage" :disabled="currentRoomPage === totalRoomPages">下一页</button>
  64. </div>
  65. <!-- 编辑房间的模态框 -->
  66. <div v-if="showEditModal" class="modal">
  67. <div class="modal-content">
  68. <span class="close" @click="showEditModal = false">&times;</span>
  69. <h2>编辑房间</h2>
  70. <input v-model="editRoom.room_name" placeholder="请输入房间名称" />
  71. <input v-model="editRoom.description" placeholder="请输入房间描述" />
  72. <input v-model="editRoom.floor" placeholder="请输入楼层" type="number" />
  73. <select v-model="editRoom.orientation">
  74. <option value="" disabled>请选择朝向</option>
  75. <option value="东">东</option>
  76. <option value="南">南</option>
  77. <option value="西">西</option>
  78. <option value="北">北</option>
  79. </select>
  80. <button class="modal-button" @click="saveEditedRoom">保存</button>
  81. <button
  82. v-if="editRoom.device_count === 0"
  83. class="delete-button"
  84. @click="deleteRoom(editRoom.id)"
  85. >
  86. 删除房间
  87. </button>
  88. </div>
  89. </div>
  90. <!-- 查看设备弹框 -->
  91. <div v-if="showDevicesModal" class="device-modal">
  92. <div class="device-modal-content">
  93. <span class="close" @click="showDevicesModal = false">&times;</span>
  94. <h2>已绑定设备</h2>
  95. <table class="device-table">
  96. <thead>
  97. <tr>
  98. <th>设备ID</th>
  99. <th>设备名称</th>
  100. <th>在线状态</th>
  101. <th>温度</th>
  102. <th>继电器状态</th>
  103. <th>人体传感器状态</th>
  104. <th>操作</th>
  105. </tr>
  106. </thead>
  107. <tbody>
  108. <tr v-for="device in boundDevices" :key="device.device_id">
  109. <td>{{ device.device_id }}</td>
  110. <td>{{ device.name }}</td>
  111. <td>
  112. <span :style="{ color: device.status === 'online' ? 'green' : 'red' }">
  113. {{ device.status === 'online' ? '在线' : '离线' }}
  114. </span>
  115. </td>
  116. <td>{{ device.temperature }}°C</td>
  117. <td>
  118. <span :style="{ color: device.switch_status === 'on' ? 'green' : 'red' }">
  119. {{ device.switch_status === 'on' ? '开启' : '关闭' }}
  120. </span>
  121. </td>
  122. <td>
  123. <span :style="{ color: device.level_status === 'high' ? 'green' : 'red' }">
  124. {{ device.level_status === 'high' ? '有人' : '无人' }}
  125. </span>
  126. </td>
  127. <td>
  128. <!-- 绑定继电器按钮 -->
  129. <button
  130. v-if="device.device_id.includes('24G-') && hasRelayInRoom"
  131. class="bind-button"
  132. @click="bindRelayToSensor(device.device_id)"
  133. >
  134. 绑定继电器
  135. </button>
  136. </td>
  137. </tr>
  138. </tbody>
  139. </table>
  140. </div>
  141. </div>
  142. </div>
  143. </template>
  144. <script>
  145. export default {
  146. name: 'RoomManagement',
  147. data() {
  148. return {
  149. message: '',
  150. messageType: '',
  151. rooms: [],
  152. roomFilter: '',
  153. showModal: false,
  154. newRoomName: '',
  155. newRoomDescription: '',
  156. newRoomFloor: null,
  157. newRoomOrientation: '',
  158. currentRoomPage: 1,
  159. itemsPerPage: 10,
  160. showDevicesModal: false,
  161. boundDevices: [],
  162. showEditModal: false,
  163. editRoom: {
  164. id: null,
  165. room_name: '',
  166. description: '',
  167. floor: null,
  168. orientation: '',
  169. device_count: 0,
  170. },
  171. hasHumanSensor: false, // 是否有人体传感器
  172. hasRelayInRoom: false, // 是否有继电器
  173. roomId: null, // 当前查看的房间ID
  174. };
  175. },
  176. computed: {
  177. filteredRooms() {
  178. return this.rooms.filter(room =>
  179. room.room_name.includes(this.roomFilter)
  180. );
  181. },
  182. paginatedRooms() {
  183. const start = (this.currentRoomPage - 1) * this.itemsPerPage;
  184. const end = start + this.itemsPerPage;
  185. return this.filteredRooms.slice(start, end);
  186. },
  187. totalRoomPages() {
  188. return Math.ceil(this.filteredRooms.length / this.itemsPerPage);
  189. },
  190. },
  191. async created() {
  192. await this.fetchRooms();
  193. },
  194. methods: {
  195. // 打开编辑房间的模态框
  196. openEditModal(room) {
  197. this.editRoom = { ...room }; // 将当前房间信息赋值给 editRoom
  198. this.showEditModal = true; // 显示编辑模态框
  199. },
  200. prevRoomPage() {
  201. if (this.currentRoomPage > 1) this.currentRoomPage--;
  202. },
  203. nextRoomPage() {
  204. if (this.currentRoomPage < this.totalRoomPages) this.currentRoomPage++;
  205. },
  206. async fetchRooms() {
  207. try {
  208. // 获取房间列表
  209. const response = await fetch('/api/rooms');
  210. if (!response.ok) {
  211. console.error('Failed to fetch rooms:', response.statusText);
  212. return;
  213. }
  214. const rooms = await response.json();
  215. // 为每个房间获取绑定的设备数量
  216. const roomsWithDeviceCount = await Promise.all(
  217. rooms.map(async (room) => {
  218. const devicesResponse = await fetch(`/api/devices-by-room?roomId=${room.id}`);
  219. if (!devicesResponse.ok) {
  220. console.error('Failed to fetch devices for room:', room.id);
  221. return { ...room, device_count: 0 };
  222. }
  223. const devices = await devicesResponse.json();
  224. return { ...room, device_count: devices.length };
  225. })
  226. );
  227. this.rooms = roomsWithDeviceCount;
  228. } catch (error) {
  229. console.error('Error fetching rooms:', error);
  230. }
  231. },
  232. // 绑定继电器到人体传感器
  233. async bindRelayToSensor(sensorId) {
  234. try {
  235. const response = await fetch('/api/devices/bind-relay-to-sensor', {
  236. method: 'POST',
  237. headers: {
  238. 'Content-Type': 'application/json',
  239. },
  240. body: JSON.stringify({
  241. sensorId: sensorId,
  242. roomId: this.roomId,
  243. }),
  244. });
  245. if (response.ok) {
  246. const result = await response.json();
  247. this.message = result.message || '继电器绑定成功';
  248. this.messageType = 'success';
  249. console.log('bindRelayToSensor 返回结果:', result);
  250. // 重新获取设备列表以更新界面
  251. await this.toggleDevices(this.roomId);
  252. } else {
  253. const result = await response.json();
  254. this.message = result.message || '绑定继电器时出错';
  255. this.messageType = 'error';
  256. console.error('绑定继电器时出错');
  257. }
  258. this.autoHideMessage();
  259. } catch (error) {
  260. this.message = '绑定设备时出错';
  261. this.messageType = 'error';
  262. console.error('绑定设备时出错:', error);
  263. this.autoHideMessage();
  264. }
  265. },
  266. async toggleDevices(roomId) {
  267. this.roomId = roomId; // 保存当前房间ID
  268. try {
  269. const response = await fetch('/api/devices-by-room?roomId=' + roomId);
  270. if (response.ok) {
  271. this.boundDevices = await response.json();
  272. // 检查房间中是否同时存在人体传感器和继电器
  273. this.hasHumanSensor = this.boundDevices.some(
  274. (device) => device.device_id.includes('24G-')
  275. );
  276. this.hasRelayInRoom = this.boundDevices.some(
  277. (device) => device.device_id.includes('ESP32-')
  278. );
  279. this.showDevicesModal = true;
  280. } else {
  281. console.error('Failed to fetch devices:', response.statusText);
  282. }
  283. } catch (error) {
  284. console.error('Error fetching devices:', error);
  285. }
  286. },
  287. async addNewRoom() {
  288. try {
  289. const response = await fetch('/api/rooms', {
  290. method: 'POST',
  291. headers: {
  292. 'Content-Type': 'application/json',
  293. },
  294. body: JSON.stringify({
  295. room_name: this.newRoomName,
  296. description: this.newRoomDescription,
  297. floor: this.newRoomFloor,
  298. orientation: this.newRoomOrientation,
  299. }),
  300. });
  301. if (response.ok) {
  302. const result = await response.json();
  303. this.message = result.message || '房间新增成功';
  304. this.messageType = 'success';
  305. await this.fetchRooms();
  306. this.showModal = false;
  307. this.newRoomName = '';
  308. this.newRoomDescription = '';
  309. this.newRoomFloor = null;
  310. this.newRoomOrientation = '';
  311. } else {
  312. const result = await response.json();
  313. this.message = result.message || '新增房间时出错';
  314. this.messageType = 'error';
  315. console.error('新增房间时出错');
  316. }
  317. this.autoHideMessage();
  318. } catch (error) {
  319. this.message = '新增房间时出错';
  320. this.messageType = 'error';
  321. console.error('新增房间时出错:', error);
  322. this.autoHideMessage();
  323. }
  324. },
  325. async saveEditedRoom() {
  326. try {
  327. const response = await fetch(`/api/rooms/${this.editRoom.id}`, {
  328. method: 'PUT',
  329. headers: {
  330. 'Content-Type': 'application/json',
  331. },
  332. body: JSON.stringify(this.editRoom),
  333. });
  334. if (response.ok) {
  335. const result = await response.json();
  336. this.message = result.message || '房间信息更新成功';
  337. this.messageType = 'success';
  338. await this.fetchRooms();
  339. this.showEditModal = false;
  340. } else {
  341. const result = await response.json();
  342. this.message = result.message || '更新房间信息时出错';
  343. this.messageType = 'error';
  344. console.error('更新房间信息时出错');
  345. }
  346. this.autoHideMessage();
  347. } catch (error) {
  348. this.message = '更新房间信息时出错';
  349. this.messageType = 'error';
  350. console.error('更新房间信息时出错:', error);
  351. this.autoHideMessage();
  352. }
  353. },
  354. async deleteRoom(roomId) {
  355. try {
  356. const response = await fetch(`/api/rooms/${roomId}`, {
  357. method: 'DELETE',
  358. });
  359. if (response.ok) {
  360. this.message = '房间删除成功';
  361. this.messageType = 'success';
  362. await this.fetchRooms();
  363. this.showEditModal = false;
  364. } else {
  365. const result = await response.json();
  366. this.message = result.message || '删除房间时出错';
  367. this.messageType = 'error';
  368. console.error('删除房间时出错');
  369. }
  370. this.autoHideMessage();
  371. } catch (error) {
  372. this.message = '删除房间时出错';
  373. this.messageType = 'error';
  374. console.error('删除房间时出错:', error);
  375. this.autoHideMessage();
  376. }
  377. },
  378. autoHideMessage() {
  379. setTimeout(() => {
  380. this.message = '';
  381. }, 2000);
  382. },
  383. },
  384. };
  385. </script>
  386. <style>/* 房间管理界面的整体布局 */
  387. .room-management-container {
  388. max-height: 90vh; /* 设置最大高度为视口的 90% */
  389. overflow-y: auto; /* 启用垂直滚动条 */
  390. padding: 20px; /* 内边距 */
  391. background-color: #f5f7fa; /* 背景颜色 */
  392. }
  393. /* 主标题样式 */
  394. .page-title {
  395. font-size: 24px; /* 字体大小 */
  396. margin-bottom: 20px; /* 底部外边距 */
  397. color: #333; /* 字体颜色 */
  398. text-align: center; /* 文字居中 */
  399. }
  400. /* 楼层标题样式 */
  401. .floor-title {
  402. font-size: 20px; /* 字体大小 */
  403. margin-bottom: 15px; /* 底部外边距 */
  404. color: #555; /* 字体颜色 */
  405. }
  406. /* 楼层内容区域样式 */
  407. .floor-content {
  408. display: flex; /* 弹性布局 */
  409. gap: 20px; /* 子元素间距 */
  410. margin-bottom: 30px; /* 底部外边距 */
  411. }
  412. /* 房间列表样式 */
  413. .room-list {
  414. flex: 1; /* 占据剩余空间 */
  415. display: flex; /* 弹性布局 */
  416. flex-wrap: wrap; /* 允许换行 */
  417. gap: 10px; /* 子元素间距 */
  418. }
  419. /* 房间卡片样式 */
  420. .room-card {
  421. flex: 1 1 calc(25% - 10px); /* 每行显示 4 个房间卡片 */
  422. border: 1px solid #e0e0e0; /* 边框 */
  423. border-radius: 8px; /* 圆角 */
  424. padding: 15px; /* 内边距 */
  425. background-color: #fff; /* 背景颜色 */
  426. box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1); /* 阴影效果 */
  427. transition: transform 0.2s, box-shadow 0.2s; /* 过渡效果 */
  428. }
  429. /* 房间卡片悬停时的样式 */
  430. .room-card:hover {
  431. transform: translateY(-3px); /* 上移效果 */
  432. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); /* 阴影效果 */
  433. }
  434. /* 房间卡片标题样式 */
  435. .room-card h4 {
  436. font-size: 18px; /* 字体大小 */
  437. margin: 0 0 10px; /* 底部外边距 */
  438. color: #333; /* 字体颜色 */
  439. }
  440. /* 房间卡片内容样式 */
  441. .room-card p {
  442. font-size: 14px; /* 字体大小 */
  443. margin: 6px 0; /* 上下外边距 */
  444. color: #666; /* 字体颜色 */
  445. }
  446. /* 房间状态样式 */
  447. .room-card p span {
  448. font-weight: 500; /* 字体粗细 */
  449. }
  450. /* 房间状态为"有人"时的样式 */
  451. .room-card.occupied {
  452. background-color: #fff3cd; /* 背景颜色 */
  453. }
  454. /* 房间状态为"无人"时的样式 */
  455. .room-card.unoccupied {
  456. background-color: #d4edda; /* 背景颜色 */
  457. }
  458. /* 房间状态为"人体存在掉线"时的样式 */
  459. .room-card.sensor-offline {
  460. background-color: #f8d7da; /* 背景颜色 */
  461. }
  462. /* 房间状态为"离线"时的样式 */
  463. .room-card.offline {
  464. background-color: #f8f8f8; /* 背景颜色 */
  465. color: #999; /* 字体颜色 */
  466. }
  467. /* 新增房间按钮样式 */
  468. .add-room-button {
  469. background: linear-gradient(135deg, #4caf50, #81c784); /* 渐变背景 */
  470. color: white; /* 字体颜色 */
  471. border: none; /* 去除边框 */
  472. padding: 10px 20px; /* 内边距 */
  473. border-radius: 6px; /* 圆角 */
  474. font-size: 16px; /* 字体大小 */
  475. font-weight: 500; /* 字体粗细 */
  476. cursor: pointer; /* 鼠标指针 */
  477. transition: all 0.3s ease; /* 过渡效果 */
  478. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 阴影效果 */
  479. margin-bottom: 20px; /* 底部外边距 */
  480. }
  481. /* 新增房间按钮悬停时的样式 */
  482. .add-room-button:hover {
  483. background: linear-gradient(135deg, #45a049, #6bbf70); /* 渐变背景 */
  484. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* 阴影效果 */
  485. }
  486. /* 模态框按钮样式 */
  487. .modal-button {
  488. background: linear-gradient(135deg, #4caf50, #81c784); /* 渐变背景 */
  489. color: white; /* 字体颜色 */
  490. border: none; /* 去除边框 */
  491. padding: 10px 20px; /* 内边距 */
  492. border-radius: 6px; /* 圆角 */
  493. font-size: 16px; /* 字体大小 */
  494. font-weight: 500; /* 字体粗细 */
  495. cursor: pointer; /* 鼠标指针 */
  496. transition: all 0.3s ease; /* 过渡效果 */
  497. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 阴影效果 */
  498. margin-top: 10px; /* 顶部外边距 */
  499. }
  500. /* 模态框按钮悬停时的样式 */
  501. .modal-button:hover {
  502. background: linear-gradient(135deg, #45a049, #6bbf70); /* 渐变背景 */
  503. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* 阴影效果 */
  504. }
  505. /* 输入框和选择框的样式 */
  506. .modal-content input,
  507. .modal-content select {
  508. width: 100%; /* 宽度 */
  509. padding: 8px; /* 内边距 */
  510. border: 1px solid #e0e0e0; /* 边框 */
  511. border-radius: 6px; /* 圆角 */
  512. font-size: 14px; /* 字体大小 */
  513. transition: all 0.3s ease; /* 过渡效果 */
  514. margin-bottom: 15px; /* 底部外边距 */
  515. }
  516. /* 输入框和选择框聚焦时的样式 */
  517. .modal-content input:focus,
  518. .modal-content select:focus {
  519. border-color: #4caf50; /* 边框颜色 */
  520. box-shadow: 0 0 8px rgba(76, 175, 80, 0.3); /* 阴影效果 */
  521. outline: none; /* 去除默认的聚焦轮廓 */
  522. }
  523. /* 输入框和选择框悬停时的样式 */
  524. .modal-content input:hover,
  525. .modal-content select:hover {
  526. border-color: #4caf50; /* 边框颜色 */
  527. }
  528. /* 模态框样式 */
  529. .modal {
  530. position: fixed; /* 固定定位 */
  531. top: 0; /* 顶部距离 */
  532. left: 0; /* 左侧距离 */
  533. width: 100%; /* 宽度 */
  534. height: 100%; /* 高度 */
  535. background-color: rgba(0, 0, 0, 0.5); /* 背景颜色 */
  536. display: flex; /* 弹性布局 */
  537. justify-content: center; /* 水平居中 */
  538. align-items: center; /* 垂直居中 */
  539. }
  540. /* 模态框内容样式 */
  541. .modal-content {
  542. background-color: white; /* 背景颜色 */
  543. padding: 20px; /* 内边距 */
  544. border-radius: 8px; /* 圆角 */
  545. width: 400px; /* 宽度 */
  546. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* 阴影效果 */
  547. }
  548. /* 模态框标题样式 */
  549. .modal-content h2 {
  550. margin-top: 0; /* 顶部外边距 */
  551. color: #333; /* 字体颜色 */
  552. }
  553. /* 关闭按钮样式 */
  554. .close {
  555. float: right; /* 右浮动 */
  556. font-size: 24px; /* 字体大小 */
  557. cursor: pointer; /* 鼠标指针 */
  558. color: #666; /* 字体颜色 */
  559. }
  560. /* 关闭按钮悬停时的样式 */
  561. .close:hover {
  562. color: #333; /* 字体颜色 */
  563. }
  564. /* 分页按钮的样式 */
  565. .pagination {
  566. margin-top: 20px; /* 顶部外边距 */
  567. text-align: center; /* 文字居中 */
  568. }
  569. /* 分页按钮的样式 */
  570. .pagination button {
  571. padding: 8px 16px; /* 内边距 */
  572. border: none; /* 去除边框 */
  573. border-radius: 6px; /* 圆角 */
  574. font-size: 14px; /* 字体大小 */
  575. font-weight: 500; /* 字体粗细 */
  576. cursor: pointer; /* 鼠标指针 */
  577. transition: all 0.3s ease; /* 过渡效果 */
  578. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 阴影效果 */
  579. background-color: #4caf50; /* 背景颜色 */
  580. color: white; /* 字体颜色 */
  581. margin: 0 5px; /* 左右外边距 */
  582. }
  583. /* 分页按钮悬停时的样式 */
  584. .pagination button:hover {
  585. background-color: #45a049; /* 背景颜色 */
  586. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* 阴影效果 */
  587. }
  588. /* 分页按钮禁用时的样式 */
  589. .pagination button:disabled {
  590. background-color: #e0e0e0; /* 背景颜色 */
  591. color: #999; /* 字体颜色 */
  592. cursor: not-allowed; /* 禁用鼠标指针 */
  593. box-shadow: none; /* 去除阴影 */
  594. }
  595. /* 表格样式 */
  596. .room-table {
  597. width: 100%; /* 表格宽度 */
  598. border-collapse: separate; /* 使用 separate 而不是 collapse */
  599. border-spacing: 0; /* 单元格间距 */
  600. margin-bottom: 20px; /* 底部外边距 */
  601. background-color: #fff; /* 背景颜色 */
  602. border-radius: 8px; /* 圆角 */
  603. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); /* 阴影效果 */
  604. }
  605. /* 表头样式 */
  606. .room-table th {
  607. background-color: #f8f9fa; /* 表头背景颜色 */
  608. font-weight: 600; /* 字体粗细 */
  609. color: #333; /* 字体颜色 */
  610. padding: 12px; /* 内边距 */
  611. text-align: center; /* 文字居中 */
  612. border-bottom: 2px solid #e0e0e0; /* 底部边框 */
  613. }
  614. /* 表格数据样式 */
  615. .room-table td {
  616. padding: 12px; /* 内边距 */
  617. text-align: center; /* 文字居中 */
  618. border-bottom: 1px solid #e0e0e0; /* 底部边框 */
  619. }
  620. /* 表格行悬停效果 */
  621. .room-table tr:hover {
  622. background-color: #f1f1f1; /* 悬停背景颜色 */
  623. transition: background-color 0.3s ease; /* 过渡效果 */
  624. }
  625. /* 表格最后一行去除底部边框 */
  626. .room-table tr:last-child td {
  627. border-bottom: none; /* 去除底部边框 */
  628. }
  629. /* 查看设备弹框样式 */
  630. .device-modal {
  631. position: fixed;
  632. top: 0;
  633. left: 0;
  634. width: 100%;
  635. height: 100%;
  636. background-color: rgba(0, 0, 0, 0.5);
  637. display: flex;
  638. justify-content: center;
  639. align-items: center;
  640. }
  641. .device-modal-content {
  642. background-color: white;
  643. padding: 20px;
  644. border-radius: 8px;
  645. width: 80%;
  646. max-width: 800px;
  647. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  648. }
  649. .device-modal-content h2 {
  650. margin-top: 0;
  651. color: #333;
  652. }
  653. .device-modal-content .close {
  654. float: right;
  655. font-size: 24px;
  656. cursor: pointer;
  657. color: #666;
  658. }
  659. .device-modal-content .close:hover {
  660. color: #333;
  661. }
  662. /* 设备表格样式 */
  663. .device-table {
  664. width: 100%;
  665. border-collapse: separate;
  666. border-spacing: 0;
  667. margin-bottom: 20px;
  668. background-color: #fff;
  669. border-radius: 8px;
  670. box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  671. }
  672. .device-table th {
  673. background-color: #f8f9fa;
  674. font-weight: 600;
  675. color: #333;
  676. padding: 12px;
  677. text-align: center;
  678. border-bottom: 2px solid #e0e0e0;
  679. }
  680. .device-table td {
  681. padding: 12px;
  682. text-align: center;
  683. border-bottom: 1px solid #e0e0e0;
  684. }
  685. .device-table tr:hover {
  686. background-color: #f1f1f1;
  687. transition: background-color 0.3s ease;
  688. }
  689. .device-table tr:last-child td {
  690. border-bottom: none;
  691. }
  692. /* 绑定按钮样式 */
  693. .bind-button {
  694. background: linear-gradient(135deg, #4caf50, #81c784);
  695. color: white;
  696. border: none;
  697. padding: 8px 16px;
  698. border-radius: 6px;
  699. font-size: 14px;
  700. font-weight: 500;
  701. cursor: pointer;
  702. transition: all 0.3s ease;
  703. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  704. }
  705. .bind-button:hover {
  706. background: linear-gradient(135deg, #45a049, #6bbf70);
  707. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  708. }
  709. /* 查看设备按钮样式 */
  710. .view-devices-button {
  711. background: linear-gradient(135deg, #4caf50, #81c784); /* 渐变背景 */
  712. color: white; /* 字体颜色 */
  713. border: none; /* 去除边框 */
  714. padding: 8px 16px; /* 内边距 */
  715. border-radius: 6px; /* 圆角 */
  716. font-size: 14px; /* 字体大小 */
  717. font-weight: 500; /* 字体粗细 */
  718. cursor: pointer; /* 鼠标指针 */
  719. transition: all 0.3s ease; /* 过渡效果 */
  720. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 阴影效果 */
  721. margin-right: 8px; /* 右侧外边距 */
  722. }
  723. .view-devices-button:hover {
  724. background: linear-gradient(135deg, #45a049, #6bbf70); /* 渐变背景 */
  725. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* 阴影效果 */
  726. }
  727. /* 编辑按钮样式 */
  728. .edit-button {
  729. background: linear-gradient(135deg, #2196f3, #64b5f6); /* 渐变背景 */
  730. color: white; /* 字体颜色 */
  731. border: none; /* 去除边框 */
  732. padding: 8px 16px; /* 内边距 */
  733. border-radius: 6px; /* 圆角 */
  734. font-size: 14px; /* 字体大小 */
  735. font-weight: 500; /* 字体粗细 */
  736. cursor: pointer; /* 鼠标指针 */
  737. transition: all 0.3s ease; /* 过渡效果 */
  738. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); /* 阴影效果 */
  739. }
  740. .edit-button:hover {
  741. background: linear-gradient(135deg, #1e88e5, #42a5f5); /* 渐变背景 */
  742. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2); /* 阴影效果 */
  743. }
  744. /* 删除按钮样式 */
  745. .delete-button {
  746. background: linear-gradient(135deg, #f44336, #e57373);
  747. color: white;
  748. border: none;
  749. padding: 10px 20px;
  750. border-radius: 6px;
  751. font-size: 16px;
  752. font-weight: 500;
  753. cursor: pointer;
  754. transition: all 0.3s ease;
  755. box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  756. margin-top: 10px;
  757. }
  758. .delete-button:hover {
  759. background: linear-gradient(135deg, #e53935, #d32f2f);
  760. box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  761. }
  762. </style>