2025-08-15

<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>智能家居控制面板</title>
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" />
    <style>
      * {
        margin: 0;
        padding: 0;
        box-sizing: border-box;
        font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
      }

      body {
        display: flex;
        justify-content: center;
        align-items: center;
        min-height: 100vh;
        background: linear-gradient(135deg, #1a2a6c, #b21f1f, #1a2a6c);
        padding: 20px;
      }

      .container {
        width: 100%;
        max-width: 500px;
        background-color: rgba(255, 255, 255, 0.1);
        backdrop-filter: blur(10px);
        border-radius: 20px;
        padding: 30px;
        box-shadow: 0 15px 35px rgba(0, 0, 0, 0.3);
        border: 1px solid rgba(255, 255, 255, 0.1);
      }

      h1 {
        text-align: center;
        color: white;
        margin-bottom: 30px;
        font-weight: 600;
        text-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
      }

      .status-container {
        display: flex;
        justify-content: space-between;
        margin-bottom: 40px;
        gap: 20px;
      }

      .status-card {
        flex: 1;
        background: rgba(255, 255, 255, 0.15);
        border-radius: 15px;
        padding: 20px;
        text-align: center;
        box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
        transition: transform 0.3s ease;
      }

      .status-card:hover {
        transform: translateY(-5px);
      }

      .status-icon {
        font-size: 50px;
        margin-bottom: 15px;
      }

      .door-icon {
        color: #ff9a8b;
      }

      .window-icon {
        color: #8bd3ff;
      }

      .status-name {
        font-size: 18px;
        color: white;
        margin-bottom: 10px;
        font-weight: 500;
      }

      .status-value {
        font-size: 22px;
        font-weight: 700;
        color: #ffd700;
        text-transform: uppercase;
      }

      .closed {
        color: #4caf50;
      }

      .open {
        color: #f44336;
      }

      .control-btn {
        display: block;
        width: 100%;
        padding: 18px;
        background: linear-gradient(to right, #ff416c, #ff4b2b);
        color: white;
        border: none;
        border-radius: 50px;
        font-size: 20px;
        font-weight: 600;
        cursor: pointer;
        transition: all 0.3s ease;
        box-shadow: 0 5px 15px rgba(255, 75, 43, 0.4);
        margin-bottom: 30px;
      }

      .control-btn:hover {
        transform: translateY(-3px);
        box-shadow: 0 8px 20px rgba(255, 75, 43, 0.6);
      }

      .control-btn:active {
        transform: translateY(1px);
      }

      .control-btn:disabled {
        background: linear-gradient(to right, #8e9eab, #eef2f3);
        cursor: not-allowed;
        transform: none;
        box-shadow: none;
      }

      .toast-container {
        position: fixed;
        bottom: 30px;
        left: 50%;
        transform: translateX(-50%);
        z-index: 1000;
      }

      .toast {
        background-color: #323232;
        color: white;
        padding: 18px 25px;
        border-radius: 10px;
        box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
        display: flex;
        align-items: center;
        max-width: 90vw;
        animation: slideIn 0.3s, fadeOut 0.5s 2.5s;
      }

      .toast i {
        margin-right: 15px;
        font-size: 24px;
      }

      .toast.success i {
        color: #4caf50;
      }

      .toast.error i {
        color: #f44336;
      }

      .toast-content {
        flex: 1;
      }

      .toast-title {
        font-weight: 600;
        margin-bottom: 5px;
      }

      @keyframes slideIn {
        from {
          opacity: 0;
          transform: translateY(20px);
        }
        to {
          opacity: 1;
          transform: translateY(0);
        }
      }

      @keyframes fadeOut {
        from {
          opacity: 1;
        }
        to {
          opacity: 0;
        }
      }
     .loading {
        display: inline-block;
        width: 20px;
        height: 20px;
        border: 3px solid rgba(255, 255, 255, 0.3);
        border-radius: 50%;
        border-top-color: white;
        animation: spin 1s linear infinite;
        margin-right: 10px;
      }

      @keyframes spin {
        to {
          transform: rotate(360deg);
        }
      }

      .log-container {
        background: rgba(0, 0, 0, 0.2);
        border-radius: 10px;
        padding: 15px;
        margin-top: 20px;
        max-height: 200px;
        overflow-y: auto;
      }

      .log-title {
        color: white;
        font-size: 16px;
        margin-bottom: 10px;
        text-align: center;
      }

      .log-entry {
        color: rgba(255, 255, 255, 0.8);
        font-size: 14px;
        padding: 5px 0;
        border-bottom: 1px solid rgba(255, 255, 255, 0.1);
      }
    </style>
  </head>
  <body>
    <div class="container">
      <h1><i class="fas fa-home"></i> 智能家居控制面板</h1>

      <div class="status-container">
        <div class="status-card">
          <div class="status-icon door-icon">
            <i class="fas fa-door-open"></i>
          </div>
          <div class="status-name">门状态</div>
          <div class="status-value open" id="door-status">开启</div>
        </div>

        <div class="status-card">
          <div class="status-icon window-icon">
            <i class="fas fa-window-maximize"></i>
          </div>
          <div class="status-name">窗状态</div>
          <div class="status-value open" id="window-status">开启</div>
        </div>
      </div>

      <button class="control-btn" id="close-all-btn"><i class="fas fa-power-off"></i> 一键关闭所有</button>

      <div class="log-container">
        <div class="log-title">操作日志</div>
        <div id="log-entries"></div>
      </div>
    </div>

    <div class="toast-container" id="toast-container"></div>
    <script>
      // DOM 元素
      const closeAllBtn = document.getElementById('close-all-btn')
      const doorStatus = document.getElementById('door-status')
      const windowStatus = document.getElementById('window-status')
      const toastContainer = document.getElementById('toast-container')
      const logEntries = document.getElementById('log-entries')

      // 初始状态
      let doorOpen = true
      let windowOpen = true

      // 添加日志条目
      function addLogEntry(message) {
        const now = new Date()
        const timeString = now.toLocaleTimeString()
        const logEntry = document.createElement('div')
        logEntry.className = 'log-entry'
        logEntry.innerHTML = `<span style="color: #FFD700;">[${timeString}]</span> ${message}`
        logEntries.prepend(logEntry)
      }

      // 显示 toast 消息
      function showToast(type, title, message) {
        const toast = document.createElement('div')
        toast.className = `toast ${type}`

        const icon = type === 'success' ? 'fa-check-circle' : 'fa-exclamation-circle'

        toast.innerHTML = `
                <i class="fas ${icon}"></i>
                <div class="toast-content">
                    <div class="toast-title">${title}</div>
                    <div>${message}</div>
                </div>
            `

        toastContainer.innerHTML = ''
        toastContainer.appendChild(toast)

        // 5秒后移除 toast
        setTimeout(() => {
          toast.style.animation = 'fadeOut 0.5s forwards'
          setTimeout(() => {
            if (toastContainer.contains(toast)) {
              toastContainer.removeChild(toast)
            }
          }, 500)
        }, 2500)
      }

      // 模拟关门接口
      function closeDoor() {
        return new Promise((resolve, reject) => {
          // 模拟网络延迟
          const delay = 800 + Math.random() * 1200

          setTimeout(() => {
            // 模拟80%成功率
            const success = Math.random() > 0.2

            if (success) {
              doorOpen = false
              doorStatus.textContent = '已关闭'
              doorStatus.className = 'status-value closed'
              resolve('门已成功关闭')
            } else {
              reject('关门失败:设备未响应')
            }
          }, delay)
        })
      }

      // 模拟关窗接口
      function closeWindow() {
        return new Promise((resolve, reject) => {
          // 模拟网络延迟
          const delay = 1000 + Math.random() * 1500

          setTimeout(() => {
            // 模拟70%成功率
            const success = Math.random() > 0.3

            if (success) {
              windowOpen = false
              windowStatus.textContent = '已关闭'
              windowStatus.className = 'status-value closed'
              resolve('窗已成功关闭')
            } else {
              reject('关窗失败:传感器故障')
            }
          }, delay)
        })
      }

      // 一键关闭所有
      async function closeAll() {
        // 禁用按钮防止重复点击
        closeAllBtn.disabled = true
        closeAllBtn.innerHTML = '<span class="loading"></span> 正在关闭中...'
        addLogEntry('开始执行一键关闭操作...')
        try {
          // 同时调用两个接口
          const results = await Promise.allSettled([closeDoor(), closeWindow()])

          const doorResult = results[0]
          const windowResult = results[1]

          // 收集失败信息
          const errors = []
          console.log("doorResult",doorResult);//{"status": "rejected", "reason": "关门失败:设备未响应"}
          console.log("windowResult",windowResult);//{"status": "fulfilled", "reason": "窗已成功关闭"}
          if (doorResult.status === 'rejected') {
            errors.push(doorResult.reason)
            doorStatus.textContent = '开启'
            doorStatus.className = 'status-value open'
          }

          if (windowResult.status === 'rejected') {
            errors.push(windowResult.reason)
            windowStatus.textContent = '开启'
            windowStatus.className = 'status-value open'
          }
          console.log("errors",errors);

          if (errors.length === 0) {
            // 两个都成功
            showToast('success', '操作成功', '门和窗都已成功关闭!')
            addLogEntry('一键关闭操作成功:门和窗都已关闭')
          } else {
            // 一个或两个失败
            const errorMessage = errors.join(';')
            showToast('error', '操作失败', errorMessage)
            addLogEntry(`一键关闭操作失败:${errorMessage}`)
          }
        } catch (error) {
          // 这里理论上不会执行,因为使用了 allSettled
          showToast('error', '系统错误', '发生意外错误')
          addLogEntry(`系统错误:${error.message}`)
        } finally {
          // 重新启用按钮
          closeAllBtn.disabled = false
          closeAllBtn.innerHTML = '<i class="fas fa-power-off"></i> 一键关闭所有'
        }
      }

      // 事件监听
      closeAllBtn.addEventListener('click', closeAll)

      // 初始日志
      addLogEntry('系统已就绪,等待操作...')
    </script>
  </body>
</html>

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 做更好的人 最近几天中午都没有回去,今天太阳又大,没有想要回去的,早上就和 我说了他没有时间中午,我还告诉他让他点...
    64fbe1876343阅读 16评论 0 0
  • 红岩读后感 “在东方的地平线上,渐渐透出一派红光,闪烁的绿草的嘉陵江上。湛蓝的...
    Lxr_db8f阅读 559评论 0 0
  • 这一章主要讲述的是可能性效应和确定性效应之间的不对称性。 决策权重 只有在确定的情况下,人们的决策和概率一致。其它...
    燕归来2021阅读 43评论 0 0
  • 1,指定,做你男朋友o,别总躲着我o 2,指定,把课本翻烦,不爱社娇 3,指定,白色Converse,如有熹光 4...
    草没味u阅读 681评论 0 0
  • 题清-八大山人 《彩笔山水图》 山石如铁木似戟, 天地笼罩萧煞气。 梦中乾坤一空亭, 遗世独立破压抑。
    19f31e7d61ed阅读 12评论 0 0