iT邦幫忙

2026 iThome 鐵人賽

DAY 23
0

昨天,我們終於把廣播的規則差不多補完了。

誰可以發廣播、誰可以確認、什麼時候算逾期、回覆屬於哪個班級,甚至連「你們學校幾點放學關資料庫屁事」這種問題都處理掉了。

照理來說,今天應該可以很開心地開始寫 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?

這題其實讓我猶豫了一下。

因為這次重寫有一個很重要的目標:

手機要能用。

而講到手機,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 了

規則定完之後,實作反而沒什麼好糾結的。

但這次我沒有直接叫 Agent 去串昨天設計的 Backend。

因為現在最重要的問題是:

這套 UI 實際長出來到底能不能用?

如果現在 API、Service、Schema、Frontend 一起動,最後發現 UI 還是得大改,那只是讓自己一次改四個地方。

所以先塞 Mock Data。

我給 Agent 準備了幾種情境:

  • 短廣播
  • 長廣播
  • 未確認
  • 已確認
  • 逾期未確認
  • 逾期確認
  • 沒有回覆
  • 單層回覆
  • 多層 Reply Tree
  • 資訊股長
  • 班代表
  • 一般學生

先把那些之後真的可能遇到的狀況全部丟進畫面裡。

然後讓它開始刻。

最後得到這個:

https://ithelp.ithome.com.tw/upload/images/20261008/201820316Q1mKQvM3r.jpg

目前畫面上可以直接切換 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——

明天差不多也該讓它下班了。

吧。


上一篇
Day 22|資料庫:你們學校幾點放學關我屁事
下一篇
Day 24|每 30 秒問一次,你有沒有大小變?
系列文
重寫一套比我還老的系統:21 歲的校園文字廣播系統 共 24 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言