昨天,我們終於把廣播的規則差不多補完了。
誰可以發廣播、誰可以確認、什麼時候算逾期、回覆屬於哪個班級,甚至連「你們學校幾點放學關資料庫屁事」這種問題都處理掉了。
照理來說,今天應該可以很開心地開始寫 API 了。
CRUD?
不要。
蛤?
不是所有東西都值得拿 CRUD 出來講好嗎。
建立廣播、讀取廣播、確認廣播、回覆廣播,這些東西當然還是得做,但如果今天整篇都在寫:
POST /broadcasts
GET /broadcasts
POST /broadcasts/{id}/confirm
POST /broadcasts/{id}/replies
好像也沒什麼意思。
反正規則昨天都定完了,剩下那些東西交給 Agent 慢慢補就好。
今天先來處理另一個更直接的問題。
我們花了這麼多天定義廣播怎麼運作,那它到底要怎麼讓人看懂?
如果只是把資料庫裡面的東西原封不動搬到畫面上,其實非常簡單。
大概就是:
id
author_id
content
created_at
ack_deadline_at
...
好醜。
確實。
而且使用者根本不在乎 author_id 是多少。
他想知道的是:
誰發的、什麼時候發的、發了什麼、確認了沒、有沒有人回覆。
所以先把資料庫丟到旁邊。
如果站在學生的角度,一則廣播真正需要看到的資訊其實只有五個:
發送者
發送時間
廣播內容
確認狀態
回覆
就這樣。
以前可能會很自然地再塞一個「編號」、一個「呼叫對象」、一個「確認者」。
但新版其實都不太需要。
學生登入之後,本來就只能看到發給自己班級的廣播,所以每一列再寫一次「399班」沒有什麼意義。
至於確認者,也不用為了它浪費一整欄。
直接寫:
✓ 已確認
資訊股長
就好了。
於是第一版的結構很快就出來了:
| 發送者 | 發送時間 | 廣播內容 | 確認狀態 | 回覆 |
|---|---|---|---|---|
| 張老師 | 14:32 | 明天第一節請攜帶平板…… | 已確認 · 班代表 | 0 |
| 李老師 | 10:15 | 本週五第六節將進行…… | 已確認 · 資訊股長 | 4 |
嗯。
看起來像個 Table。
這題其實讓我猶豫了一下。
因為這次重寫有一個很重要的目標:
手機要能用。
而講到手機,Table 好像就是一個很容易被抓出來公審的東西。
螢幕就這麼窄,你硬塞五欄進去,最後大概就會變成:
發 時 廣 確 回
送 間 播 認 覆
者 內 狀
容 態
好。
不用了,謝謝。
那就 Card 啊。
一開始我也是這麼想的。
但想了一下又覺得不對。
因為桌面上,Table 其實非常適合廣播。
每一則廣播都有固定的資訊結構,使用者又常常需要一次掃過很多則訊息。
誰發的、幾點發的、確認了沒、有沒有回覆,全部都能在同一條水平線上快速比較。
為了手機直接把桌面的 Table 砍掉,好像有點因噎廢食。
然後我突然想到一件事。
誰說 Table 在手機上還一定要長得像 Table?
HTML 的語意是一回事。
CSS 怎麼把它畫出來,又是另一回事。
桌面上:
| 發送者 | 發送時間 | 廣播內容 | 確認狀態 | 回覆 |
|---|---|---|---|---|
| 張老師 | 14:32 | 明天第一節請攜帶平板…… | 已確認 · 班代表 | 0 |
到了手機,只要在 breakpoint 之後把 Row 重新排列:
┌──────────────────────────┐
│ 張老師 14:32 │
│ │
│ 明天第一節請攜帶平板…… │
│ │
│ ✓ 已確認 回覆 0 ↓│
└──────────────────────────┘
不就好了?
所以桌面是 Table,手機是 Card?
對。
但重點不是做兩套 Component。
資料是一樣的、DOM 是同一份、互動也是同一套。
改變的只有 Layout。
這樣就不用為了 Responsive 再養一套 Mobile Broadcast Component,也不會發生桌面加了功能,手機忘記一起改的問題。
好新奇的解法。
昨天我們已經決定,廣播的主要顯示週期是一週。
所以畫面真正的結構應該是:
本週
├── 今天
│ ├── Broadcast
│ └── Broadcast
├── 昨天
│ └── Broadcast
└── 星期一
├── Broadcast
└── Broadcast
最簡單的方法當然是在每一列塞完整日期:
張老師|10/07 14:32|……
李老師|10/07 10:15|……
王老師|10/06 15:40|……
但這樣「時間流」的感覺其實不太明顯。
所以最後決定直接拿日期當 Separator:
今天 · 10/07
──────────────────────────
張老師 │ 14:32 │ ...
李老師 │ 10:15 │ ...
昨天 · 10/06
──────────────────────────
王老師 │ 15:40 │ ...
到了手機也不用重新發明:
今天 · 10/07
[ Broadcast Card ]
[ Broadcast Card ]
昨天 · 10/06
[ Broadcast Card ]
同一個資訊結構,繼續用。
接下來還有一個問題。
如果老師真的很能寫怎麼辦?
例如:
本週五第六節將進行校園防災演練,請各班先閱讀以下注意事項。
聽到廣播後,請依導師指示攜帶防災頭套……
班代表請於集合後清點人數……
若有同學因公差或身體不適無法參加……
這種東西塞進 Table,Row 直接長到天上去。
一般 Feed 很常見的處理方式是:
本週五第六節將進行校園防災演練,請各班……
顯示更多
但我最後還是決定——
不要。
你不是才說它會長到天上去?
會啊。
那又怎樣?
這是廣播系統。
使用者進來就是要看廣播的。
如果為了讓每一列長得整整齊齊,結果每一則廣播都只能看到三行,還得再點一下「顯示更多」,好像有點本末倒置。
所以這次直接讓內容完整顯示。
短的就短。
長的就長。
Table 的每一列本來就沒有一定要一樣高。
同樣的問題也發生在確認。
資訊股長看到一則廣播之後,如果還得:
點進廣播
↓
找到確認按鈕
↓
按確認
↓
「您確定要確認嗎?」
↓
確定
↓
回列表
……
儀式感拉滿。
只是確認一則廣播而已。
所以如果目前登入的是資訊股長或班代表,確認按鈕直接放在列表上。
[ 確認廣播 ]
逾期了:
[ 補確認 ]
按下去就確認。
沒有 Dialog,也沒有「您確定嗎?」
因為這又不是刪除資料。
確認完成之後,按鈕直接變成:
✓ 已確認
資訊股長
如果已經逾期:
! 逾期確認
班代表
至於一般學生,他沒有確認權限,所以只會看到結果,不會看到按鈕。
昨天 Service Layer 定下來的權限,到了今天終於開始真的反映在 UI 上了。
這裡本來有一個很直覺的方案。
點「回覆 4」:
右邊拉一個 Drawer 出來。
很漂亮。
很現代。
也很合理。
直到我想到手機。
桌面:
Broadcast │ Drawer
手機:
Broadcast
↓
Bottom Sheet?
Full Screen?
另一頁?
突然之間,為了同一個「看回覆」的動作,我開始需要維護兩套 Interaction Model。
不對。
既然前面 Table 都能做到桌面跟手機共用同一個資訊結構,Reply 為什麼不行?
所以最後決定:
直接 Inline 展開。
桌面:
張老師 │ 14:32 │ …… │ 已確認 │ 回覆 4 ↑
────────────────────────────────────────
資訊股長:已將注意事項轉達給全班。
張老師:謝謝。
班代表:收到,已補充集合位置。
資訊股長:收到通知,謝謝老師。
[ 輸入回覆…… ]
手機:
┌──────────────────────────┐
│ 張老師 14:32 │
│ │
│ 廣播內容…… │
│ │
│ ✓ 已確認 回覆 4 ↑ │
├──────────────────────────┤
│ 資訊股長 │
│ 已將注意事項轉達給全班。 │
│ │
│ └ 張老師 │
│ 謝謝。 │
│ │
│ [ 輸入回覆…… ] │
└──────────────────────────┘
還是一樣。
Desktop 跟 Mobile 的差異只有排版,沒有行為。
這件事情做到這裡,整個 UI 的方向基本上也就定下來了。
規則定完之後,實作反而沒什麼好糾結的。
但這次我沒有直接叫 Agent 去串昨天設計的 Backend。
因為現在最重要的問題是:
這套 UI 實際長出來到底能不能用?
如果現在 API、Service、Schema、Frontend 一起動,最後發現 UI 還是得大改,那只是讓自己一次改四個地方。
所以先塞 Mock Data。
我給 Agent 準備了幾種情境:
先把那些之後真的可能遇到的狀況全部丟進畫面裡。
然後讓它開始刻。
最後得到這個:

目前畫面上可以直接切換 Mock 身分。
用資訊股長登入時,可以確認、可以回覆。
換成一般學生之後,同一份 Broadcast Feed 還是在,但操作按鈕會消失。
確認一則廣播之後,Navigation 上的未確認提示也會跟著更新。
Reply 點下去則直接在原本那一列下面展開。
而且最重要的是——
它真的還是一張 Table。
只是這張 Table 到手機上之後會變成 Card。
Agent 最後也順便幫我跑了一輪不同寬度:
320px
390px
768px
769px
1280px
沒有水平 Overflow。
測試則是:
14 / 14 passed
至少目前看起來,沒有炸。
所以 API 呢?
……
明天的我會處理。
你最近是不是很喜歡把事情丟給明天的自己?
這叫合理安排工作。
那昨天的你是不是也是這樣想的?
……
總之。
今天至少把一件很重要的事情定下來了。
Responsive Design 不一定代表:
「手機版要重新設計一套。」
有時候真正該保持一致的是資訊結構與操作方式。
桌面適合 Table,那就讓它是 Table。
手機適合 Card,那就讓它長得像 Card。
但它們背後仍然可以是同一份資料、同一套 DOM、同一個 Component,甚至同一種操作邏輯。
對這套廣播系統來說,我覺得這比單純把桌面版硬塞進 390px 的螢幕裡合理多了。
至於 Mock Data——
明天差不多也該讓它下班了。
吧。