Vibe Coding 從零到部署: 30 個小時寫出一套學習儀表板(上)|專家論點【鄭緯筌Vista】

作者:鄭緯筌(專欄作家,「臺灣電子商務暨創業聯誼會」共同創辦人,前「APP01」網站總監、《風傳媒》產品總監和《數位時代》雜誌主編)

當理想遇上現實:從研究生的數位焦慮談起

身為一名大學講師,我知道很多研究生每天都在與時間賽跑。諸如論文進度、文獻閱讀、實驗數據以及與指導教授開會的會議記錄⋯⋯這些資訊,散落在不同的應用程式中:待辦事項用 Notion,筆記用 Heptabase,文獻管理用 Zotero,時間追蹤用番茄鐘 App。我看到很多人,每天光是在這些工具之間切換,就耗費了大量的認知負荷。

「為什麼沒有一個專為研究生設計的整合平臺?」這個念頭在我腦海中盤旋許久。直到某個深夜,當我再次因為找不到某篇文獻筆記而焦慮時,我決定親手打造一個解決方案。這就是 GradPilot 2.0 (https://vista.im/gradpilot)的誕生契機——一個專為研究生設計的全方位學習儀表板。

螢幕擷取畫面 2026 01 20 163458

技術選型:在理想與務實之間取得平衡

作為一個不諳程式設計的新手,之前只有少數使用 Lovable 和 Gemini 寫程式的經驗。這回,我改用 Claude Code 來開發。原本很擔心看不懂它的文字介面,但實際操作之後發現自己多慮了。遇到問題是很正常的事情,多跟 AI 討教就是了!不過,當然 tokens 也燒得很快。

在 Claude Code 的建議之下,我選擇了 Flask 作為後端框架。倒不是因為它最新潮,而是因為它夠簡單、文件完整,讓我能夠快速上手。前端則選擇 React 搭配 Vite,這個組合在 2024 年時已經非常成熟,社群資源豐富,遇到問題時能快速找到解決方案。

資料庫方面,開發環境使用 SQLite 讓我能快速迭代,生產環境則切換到 PostgreSQL。這種彈性得益於 SQLAlchemy 的抽象層,讓我不需要為了資料庫切換而重寫大量代碼。最重要的是,所有這些技術都能在 Render 的免費方案上運行,這對預算有限的開發者來說非常重要。

架構設計:從混亂到清晰的演進

老實說,因為自己不是資訊背景出身,所以最初的架構設計其實相當簡陋。我只是想著先讓它跑起來再說,結果就是一個巨大的 app.py 檔案,所有路由、邏輯和資料庫操作全部擠在一起。當檔案超過 500 行時,我開始感受到維護的痛苦。

重構的契機,來自於新增文獻管理功能。我意識到如果繼續這樣下去,專案很快就會變成無法維護的龐大代碼。於是我花了半天光景,請 Claude Code 將專案重構成清晰的模組化架構:

後端採用藍圖(Blueprint)模式,將認證、待辦事項、筆記、番茄鐘與文獻管理等功能分離成獨立的模組。每個模組都有自己的路由檔案和業務邏輯,資料庫模型則統一放在 models 目錄下。這樣的架構讓我能夠專注於單一功能的開發,而不會被其他部分干擾。

前端的組件化,也遵循類似的思路。每個功能都有自己的組件目錄,包含相關的子組件。例如待辦事項功能包含 TodoList、TodoItem、TodoForm 等組件,每個組件職責單一,易於測試和維護。

功能實現:細節中的魔鬼

番茄鐘的時間魔法

番茄鐘功能看似簡單,實際上充滿了細節考量。最初的實現中,我直接在組件內硬寫入 25 分鐘的專注時間和 5 分鐘的休息時間。這在開發階段沒有問題,但當用戶回饋希望能自訂時長時,問題就來了。

第一個挑戰,是如何在組件中讀取用戶設定。我使用 React Context 來管理全域的用戶狀態,但番茄鐘組件卻忘記從 Context 中讀取 pomodoro_duration 和 break_duration。這導致了一個有趣的 bug:用戶在設定頁面修改時長後,回到 Dashboard 卻發現番茄鐘仍然顯示 25:00。

若想修復這個問題,就需要在多個地方調整,好比:初始化狀態時使用用戶設定、重置時使用用戶設定、模式切換時使用用戶設定以及進度計算時使用用戶設定。每一個地方,都需要將硬編碼的數字替換成從 Context 讀取的動態值。這個看似簡單的改動,實際上觸及了組件的每個角落。

文獻管理的格式化陷阱

文獻管理功能,則是整個專案中最複雜的部分。學術引用有多種格式標準(APA、MLA、Chicago、Harvard等),每種格式都有嚴格的規範。Claude Code 幫我實作了一個 formatter.py 模組來處理不同的引用格式,每種格式都有對應的類別和格式化方法。

然而,一個看似無害的設計決策差點毀了整個功能:在格式化期刊名稱時,我使用了 Markdown 的斜體語法 *Journal Name*,希望在顯示時能呈現斜體效果。問題是,當用戶複製引用格式到 Word 或其他編輯器時,這些星號也被一併複製了,導致引用格式不符合標準。

這個 bug 的修復看似簡單——移除星號即可。但實際上我需要在六個不同的格式化方法中找出所有使用星號的地方,確保期刊名稱、書名與網站名稱等都使用純文字而非 Markdown 格式。這提醒了我一個重要的原則:資料的儲存格式應該與顯示格式分離。Markdown 應該只用於顯示層,而不應該滲透到資料層。

螢幕擷取畫面 2026 01 20 163716

標籤系統的陣列與字串之爭

在待辦事項和筆記中,都有使用標籤功能。後端資料庫將標籤儲存為逗號分隔的字串(例如 “研究,論文,實驗”),但為了方便前端處理,我在 to_dict() 方法中將其轉換為陣列(例如 [“研究”, “論文”, “實驗”])。

這個設計在大多數情況下運作良好,直到用戶嘗試編輯筆記。編輯表單期望 tags 是一個字串(因為 input 欄位只能接受字串),但從 API 獲取的筆記物件中 tags 是一個陣列。當我直接將陣列賦值給表單的 tags 欄位時,JavaScript 會隱式地將陣列轉換成字串,結果變成了 “研究,論文,實驗”——看起來沒問題,但當有空格時就會出現意外的行為。

最終的解決方案,是在 handleEdit 函數中明確地進行轉換:tags: Array.isArray(note.tags) ? note.tags.join(‘,’) : (note.tags || ”)。這行代碼確保無論後端返回的是陣列還是字串,都能正確地轉換成表單需要的格式。這個經驗告訴我,資料類型的一致性非常重要,在資料流轉的每個環節都需要明確定義期望的類型。

Loading

在 Google News 上追蹤我們

發佈留言

Back to top button