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() |
数字加千分位(19600 → 19,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 / 类似函数提供初始值。
坑 4:状态条挡住了 logo
症状:状态条覆盖了左上角的 logo。
原因:z-index 没设置。
修复:状态条 .data-status 设 z-index: 100。
十、D11 关键收获
- CSS 居中三件套:
position: absolute+left: 50%+transform - CSS 动画:
@keyframes+animation让指示灯"心跳" - JS 自动刷新:
setInterval+fetch - 离线检测:
try/catch+classList切换样式 - 状态可见性:生产应用必须有"系统状态"展示
十一、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)
参考资料
- MDN - CSS Animations:https://developer.mozilla.org/zh-CN/docs/Web/CSS/CSS_Animations
- MDN - setInterval:https://developer.mozilla.org/zh-CN/docs/Web/API/setInterval
- MDN - Array.reduce:https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
- 《CSS 揭秘》:https://csssecrets.io/

浙公网安备 33010602011771号