D11 学习笔记:大屏数据状态条——30 秒自动刷新的实时数据,v1.0 收官

系列:海口三港 AI 全栈实战 · 从参赛大屏到 AI 平台
仓库:https://github.com/2003Tim/haikou-ai-port


前言:v1.0 的最后一块拼图

D9 让 index.html 动态化,D10 让 3 个详情页共用 JS。D11 我要完成 v1.0 的最后一步:改造 platform.html 大屏

但 platform.html 太复杂(百度地图、多个 ECharts 图表、滚动表格),完全重写不现实。D11 我用了最小可行改动:

  • 大屏顶部加一个"数据状态条"
  • 显示"数据源 + 港口数 + 总容量 + API 在线状态"
  • 30 秒自动刷新
  • 完成 v1.0

这一步完成后,v1.0 全链路打通:浏览器 → uvicorn → FastAPI → SQLAlchemy → SQLite → 数据回传 → 渲染。


一、为什么加"数据状态条"?

大屏(platform.html)是项目的"门面",给领导/客户演示用的。一个显示实时数据的状态条能立刻传达:

显示 含义
📡 数据源:FastAPI + SQLite 这是"真"的全栈项目(不是假数据)
🚢 港口数:3 实时从数据库读
📊 总容量:19,600 累加计算
🟢(绿色指示灯) API 在线

这些数字从 API 实时计算,不是硬编码——这让大屏"活"起来了。


二、CSS 设计:让状态条好看

视觉设计目标

  • ✅ 在大屏顶部居中,不抢主要图表的视觉
  • ✅ 半透明背景(科技感,和大屏风格统一)
  • ✅ 数字高亮(青色,显眼)
  • ✅ 指示灯带脉冲动画(显示"实时"感)

CSS 关键样式

.data-status {
    position: absolute;
    left: 50%;
    top: 50%;
    transform: translate(-50%, -50%);
    display: flex;
    align-items: center;
    gap: 15px;
    z-index: 100;
    font-family: "微软雅黑";
}

.status-item {
    background: rgba(0, 24, 86, 0.5);
    color: white;
    padding: 8px 18px;
    border-radius: 4px;
    border: 1px solid rgba(0, 195, 255, 0.3);
    font-size: 14px;
}

.status-item span {
    color: #00d8ff;  /* 数字用青色,更显眼 */
    font-weight: bold;
}

.status-dot {
    width: 12px;
    height: 12px;
    border-radius: 50%;
    background: #4caf50;  /* 在线:绿 */
    box-shadow: 0 0 12px #4caf50;
    animation: pulse 2s infinite;  /* 脉冲动画 */
}

.status-dot.offline {
    background: #f44336;  /* 离线:红 */
    box-shadow: 0 0 12px #f44336;
    animation: none;
}

@keyframes pulse {
    0%, 100% { opacity: 1; }
    50% { opacity: 0.5; }  /* 透明度变化,像心跳 */
}

关键技术点

技巧 说明
position: absolute; left: 50%; transform: translateX(-50%) 水平居中(标准 CSS 居中技巧)
rgba(0, 24, 86, 0.5) 半透明背景(科技感)
border: 1px solid rgba(0, 195, 255, 0.3) 边框带蓝绿色(科幻风)
@keyframes pulse CSS 动画,关键帧定义
box-shadow: 0 0 12px #4caf50 发光效果

三、JS 实现:30 秒自动刷新

完整代码

新建 js/platform-status.js:

/**
 * js/platform-status.js
 * 大屏顶部数据状态条
 */

const API_BASE = 'http://127.0.0.1:8000'

async function loadSystemStatus() {
    const dot = document.getElementById('status-dot')
    const portCountEl = document.getElementById('port-count')
    const totalCapacityEl = document.getElementById('total-capacity')

    if (!portCountEl || !totalCapacityEl) return

    try {
        const response = await fetch(`${API_BASE}/ports`)
        if (!response.ok) throw new Error(`HTTP ${response.status}`)

        const ports = await response.json()

        // 更新数据
        portCountEl.textContent = ports.length

        const totalCapacity = ports.reduce((sum, p) => sum + (p.capacity || 0), 0)
        totalCapacityEl.textContent = totalCapacity.toLocaleString()

        // 在线指示灯变绿
        if (dot) {
            dot.classList.remove('offline')
            dot.title = 'API 在线 ✅'
        }
    } catch (error) {
        console.error('加载系统状态失败:', error)
        portCountEl.textContent = '离线'
        totalCapacityEl.textContent = '离线'

        if (dot) {
            dot.classList.add('offline')
            dot.title = 'API 离线 ❌'
        }
    }
}

// 立即加载
loadSystemStatus()

// 每 30 秒自动刷新
setInterval(loadSystemStatus, 30000)

关键技术点

技术 说明
ports.reduce((sum, p) => sum + p.capacity, 0) 累加每个港口的容量
.toLocaleString() 数字加千分位(1960019,600)
element.classList.add/remove 切换 CSS 类(切换离线/在线样式)
setInterval(fn, 30000) 每 30 秒调用一次(自动刷新)
element.title = '...' 鼠标悬停 tooltip

为什么是 30 秒?

间隔 优缺点
1 秒 太频繁,服务器压力大
10 秒 较频繁,演示效果好
30 秒 平衡:看起来"实时",服务器压力小
60 秒 慢,看起来"卡"
5 分钟 几乎看不出在刷新

四、platform.html 改造

加 HTML 状态条

<header> 的 logo 之后、showTime 之前加:

<header>
    <div class="logo">
        <a href="index.html">
            <img src="images/index-logo.png">
        </a>
    </div>

    <!-- D11 新增:数据状态条 -->
    <div class="data-status">
        <span class="status-item">📡 数据源:<span id="data-source">FastAPI + SQLite</span></span>
        <span class="status-item">🚢 港口数:<span id="port-count">--</span></span>
        <span class="status-item">📊 总容量:<span id="total-capacity">--</span></span>
        <span class="status-dot" id="status-dot" title="API 在线状态"></span>
    </div>

    <div class="showTime">...</div>
</header>

加 JS 引用

在末尾加一行:

<script src="js/sailing.js"></script>
<script src="js/platform-status.js"></script>  <!-- D11 新增 -->

五、见证 v1.0 完整闭环 ⭐

D11 是 v1.0 的最后一块。这一步让我们能从浏览器演示完整的数据流:

测试 1:动态数据

POST http://127.0.0.1:8000/ports
{"name":"":"测试港","location":"":"测试","capacity":1000}

等 30 秒(自动刷新)或刷新页面 → 港口数:4,总容量:20,600

测试 2:离线检测

在 uvicorn 窗口按 Ctrl+C 停服务
刷新 platform.html → 状态灯变红,显示"离线" ⭐

测试 3:恢复

uvicorn app.main:app --reload

刷新 → 状态灯恢复绿色

🎯 这证明了:浏览器能实时感知后端服务状态,这是生产级应用的标志。


六、为什么需要"实时状态指示"?

真实生产环境里,API 可能挂掉(服务器宕机、网络故障、代码 bug)。没有状态指示:

页面正常显示,但数据是旧的 ❌
用户看到假数据,做出错误决策

有状态指示:

离线 → 立刻知道 API 挂了

这是 DevOps / SRE 的基本功


七、v1.0 完整数据流

D11 完成后,v1.0 的完整数据流:

┌─────────────┐
│ 浏览器       │ (打开 index.html)
│   ↓         │
│ fetch /ports│ ← D9 / D10 / D11 写
└──────┬──────┘
       ↓ HTTP
┌─────────────┐
│ uvicorn     │ ← D6 学
│   ↓         │
│ FastAPI     │ ← D6 / D7 学
│   ↓         │
│ Depends     │ ← D8 学(get_db 注入 Session)
│   ↓         │
│ crud.py     │ ← D8 学(CRUD 函数)
│   ↓         │
│ SQLAlchemy  │ ← D8 学(ORM)
│   ↓         │
│ SQLite      │ ← D8 学(数据库)
│ ports.db    │ ← D8 学(持久化)
└─────────────┘
       ↓ 数据回传
┌─────────────┐
│ JSON 响应   │
│   ↓         │
│ JS 渲染     │ ← D9 / D10 / D11 学
│   ↓         │
│ 用户看到港口 │
└─────────────┘

每一层你都亲手写过过,这就是 v1.0。


八、主流的"实时数据"方案对比

方案 延迟 服务器压力 复杂度
轮询(我们 D11 用) 30 秒 简单
短轮询(5 秒) 5 秒 简单
长轮询 秒级
WebSocket 实时
SSE(Server-Sent Events) 实时

为什么我们用轮询?

理由 说明
简单 setInterval + fetch,5 行代码
足够 大屏数据 30 秒更新足够
兼容 所有浏览器、所有服务器都支持
易调试 Network 标签能看到每次请求

未来如果要"真正实时"(LLM 流式输出、聊天) → 用 WebSocket


九、我踩过的坑(D11)

坑 1:CSS 状态条居中错位

症状:状态条不在中间,偏左/偏右。

原因:用了 margin: auto,但父元素没设宽度。

修复:用 position: absolute; left: 50%; transform: translateX(-50%)

坑 2:setInterval 不停止

症状:即使离开页面,定时器还在跑(浪费资源)。

原因:setInterval 不会自动清除。

修复(可选):

const intervalId = setInterval(...)
// 离开页面时
clearInterval(intervalId)

我们 D11 简单实现,没做清除。如果页面长开,可能累积。

坑 3:ports.reduce 在空数组上报错

症状:数据库是空的,reduce 初始值 0 返回 0,但 reduce 第二个参数 0 是 fallback,OK 不报错**。

教训:始终给 reduce / 类似函数提供初始值

症状:状态条覆盖了左上角的 logo。

原因:z-index 没设置。

修复:状态条 .data-statusz-index: 100


十、D11 关键收获

  1. CSS 居中三件套:position: absolute + left: 50% + transform
  2. CSS 动画:@keyframes + animation 让指示灯"心跳"
  3. JS 自动刷新:setInterval + fetch
  4. 离线检测:try/catch + classList 切换样式
  5. 状态可见性:生产应用必须有"系统状态"展示

十一、v1.0 完整时间线

Day 主题 状态
D1 Git + GitHub 初始化
D2 README 初稿
D3 Python 基础
D4 Python 进阶(装饰器/生成器/async)
D5 HTTP + requests
D6 FastAPI Hello World + uvicorn
D7 Pydantic 数据校验
D8 SQLAlchemy + 数据库
D9 index.html 动态化 + CORS
D10 3 个详情页共用 JS
D11 大屏状态条 + v1.0 收官

十二、v1.0 完成清单

  • 后端:FastAPI + SQLAlchemy + Pydantic + SQLite
  • 前端:jQuery(原项目)+ fetch(动态部分)+ CSS 动画
  • API:6 个 RESTful 端点(GET/POST/PUT/DELETE)
  • 数据:3 个真实港口(可增删改查)
  • 持久化:重启服务数据还在
  • 响应式:动态数据 + 自动刷新 + 离线检测

作品集评分维度:

维度 v1.0 状态
功能完整性 ⭐⭐⭐⭐ 端到端跑通
代码规范 ⭐⭐⭐ 模块化、有注释
技术广度 ⭐⭐⭐⭐ 涵盖 FastAPI / Pydantic / SQLAlchemy / 前端
可演示性 ⭐⭐⭐⭐⭐ 浏览器一键演示
简历加分 ⭐⭐⭐⭐ 可写"全栈开发"

下一步:v2.0

v1.0 完成!v2.0 我会:

  • scikit-learn 做车流量时序预测
  • PyTorch 基础
  • 模型服务化:从 Jupyter 到 FastAPI

但在那之前,可以先:

  • 写"v1.0 完成"总博客(总结 11 天)
  • 修复百度地图 403(换 Leaflet)
  • 修复响应式问题(viewport + rem)

参考资料

posted @ 2026-08-28 17:07  吴逸清  阅读(21)  评论(0)    收藏  举报