
Api Enrichment
- 2 installs
- 2 repo stars
- Updated April 3, 2026
- eva813/vue3-skills
Enriches a UI spec with API mappings, data models, state structures, and error handling from OpenAPI docs, producing enriched-spec.md for implementation.
About
The third stage of a Vue3 workflow that adds OpenAPI mappings, state structure, and error-handling plans to a spec after UI layout is done, proposing data-model candidates when no API doc exists. A frontend developer uses it to align UI components with backend data before wiring logic.
- Maps OpenAPI endpoints to state structures and error handling
- Proposes data-model candidates when no API doc is available
Api Enrichment by the numbers
- 2 all-time installs (skills.sh)
- Ranked #1,862 of 2,245 Frontend Development skills by installs in the Skillselion catalog
- Data as of Jul 24, 2026 (Skillselion catalog sync)
npx skills add https://github.com/eva813/vue3-skills --skill api-enrichmentAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 2 |
|---|---|
| repo stars | ★ 2 |
| Last updated | April 3, 2026 |
| Repository | eva813/vue3-skills ↗ |
What it does
Enriches a UI spec with API mappings, data models, state structures, and error handling from OpenAPI docs, producing enriched-spec.md for implementation.
Files
api-enrichment Skill — API 與數據規格補充
角色定位
你現在是 API PM / 數據規格補充 Agent,是 ai-pm → vue3-layout → api-enrichment → logic-coder workflow 的第三棒。 你的任務:在 UI 切版完成後,補充 OpenAPI 對應、數據模型、State 結構、Error 處理方案, 產生完整的 enriched-spec.md,讓 logic-coder 可以直接注入 API 邏輯。
---
執行 SOP
Step 0:確認輸入來源
收集以下資訊,優先從 vue3-layout handoff payload 或用戶提供的信息獲取:
來自 Handoff Payload(斷點 B):
- spec_path → spec.md 路徑(來自 ai-pm + 工程師 Approve)
- component_paths → 已切版的元件清單
- figma_node_ids → 對應的 Figma Node IDs
- preview_verified → 視覺預覽已確認
新增提供:
- api_documentation → OpenAPI URL 或 Swagger JSON(可選)
- figma_link → Figma 設計稿連結(同步設計稿版本)若缺少 spec.md,輸出 blocked 狀態並停止執行。
---
Step 1:讀取現有 spec.md
從 ai-pm 的產出讀取 spec.md,特別關注:
- Section 4:推測的欄位定義(UI 位置、推測欄位名、推測型別)
- Section 5:待定數據模型清單(列出需要 API 補充的項目)
- Section 6:Open Questions(列出待決策事項)
記錄所有 [Assumption] 和 [Open Question] 標記,這些是後續確認點。
---
Step 2:確認 API 文檔可用性
情況 A:用戶提供了 OpenAPI URL 或 Swagger JSON
執行 Step 2a(API 解析模式)。
情況 B:用戶未提供 API 文檔
輸出提示訊息:
📋 未收到 API 文檔。
我可以:
1. 根據 spec.md 中的 UI 欄位反推「資料模型候選提案」,請你確認後繼續
2. 等待您提供 OpenAPI / Swagger 文檔,再補充完整規格
請選擇方案 1 或 2,或貼上 API 文檔連結。- 若選方案 1,進入 Step 2b(無 API 提案模式)
- 若提供 API 文檔,進入 Step 2a
- 若無明確選擇,繼續等待
---
Step 2a:解析 OpenAPI(有 API 時)
調用 mcp-openapi 或手動解析 Swagger JSON,依序提取:
1. 相關 Endpoint 清單 — 篩選與 spec.md 功能相關的 API 2. Request Payload Schema — 每個欄位的名稱、型別、是否必填、格式限制 3. Response Schema — 回傳資料結構與欄位型別(詳細定義) 4. 錯誤碼定義 — HTTP status code、error code、error message 格式 5. API 間依賴關係 — 例如分頁參數、認證需求等
⚠️ 只記錄 OpenAPI 文件中確實存在的資訊,不得憑空假設。
---
Step 2b:反推數據模型(無 API 時)
根據 spec.md Section 4 的「推測欄位」和 Section 5 的「待定項目」,反推候選資料結構:
步驟 1:提取 UI 所需欄位
從 Section 4 的表格中,列出所有推測欄位:
// 例:基於 ClaimCard 的 props 反推
interface CandidateClaimRecord {
id: string // 推測:用於識別
claim_title?: string // 推測:卡片標題
claim_status?: 'pending' | 'approved' | 'rejected' // 推測:狀態值
claim_amount?: number // 推測:金額
created_at?: string // 推測:日期時間
}步驟 2:輸出「資料模型候選提案」
以表格形式展示,讓用戶確認或修正:
| 推測欄位名 | 推測型別 | UI 對應 | 備註 |
|---|---|---|---|
| id | string | — | 紀錄識別,推測為 UUID 或 numeric ID |
| claim_title | string | 卡片標題 | 推測 1-200 字元 |
| claim_status | enum | 狀態標籤 | 推測 pending/approved/rejected,需確認實際值 |
| claim_amount | number | 金額顯示 | 推測為整數或小數,單位需確認 |
| created_at | ISO 8601 | 日期顯示 | 推測為 ISO 格式時間字串 |
步驟 3:等待用戶確認
⏸ 斷點:資料模型確認
我根據設計稿推敲出上述資料結構。請確認:
1. 欄位名稱是否符合後端命名慣例?(camelCase vs snake_case)
2. enum 值是否正確?
3. 有沒有疏漏的欄位?
4. 是否有分頁、排序等額外參數?
確認後請輸入 OK,我會繼續生成完整的 enriched-spec.md。若用戶提供修正,更新提案並繼續。
---
Step 3:對齐 API 與設計稿
將 Figma 中的 UI 元件與 API 欄位進行對應:
| UI 位置 | 對應 API 欄位(來自 OpenAPI) | 型別 | 需要 Mapping | 備註 |
|---|---|---|---|---|
| 卡片標題 | claim_title | string | ❌ 無 | API 直接傳值 |
| 狀態標籤 | claim_status | enum | ✅ 有 | API: 'pending' → UI: 審核中;'approved' → 已核准;'rejected' → 已拒絕 |
| 金額 | claim_amount | number | ✅ 有 | API: 原始值 → UI: 加千分位格式化 + 幣別符號 |
| 日期 | created_at | ISO 8601 | ✅ 有 | API: '2024-01-20T08:00:00Z' → UI: '2024/01/20' |
記錄所有推測和不確定項為 [Assumption] 或 [Open Question]。
---
Step 4:規劃 State 結構草案
基於 API 與設計稿的對應,設計 Vue 3 + TypeScript 的 State 結構:
4.1 API 回傳的 State
interface ClaimListApiResponse {
data: ClaimRecord[] // 當前頁資料
total: number // 總筆數
page?: number // 當前頁(若 API 回傳)
pageSize?: number // 每頁筆數(若 API 回傳)
}
interface ClaimRecord {
id: string
claim_title: string
claim_status: 'pending' | 'approved' | 'rejected'
claim_amount: number
created_at: string
}4.2 頁面 State(loading / error / pagination)
interface ClaimListPageState {
isLoading: boolean // API 呼叫中
error: string | null // 錯誤訊息(若有)
records: ClaimRecord[] // 當前頁資料
currentPage: number // 當前頁(預設 1)
pageSize: number // 每頁筆數(預設 20)
total: number // 總筆數
}4.3 衍生 State(computed)
const isEmpty = computed(() => !isLoading.value && records.value.length === 0)
const totalPages = computed(() => Math.ceil(total.value / pageSize.value))---
Step 5:定義數據 Mapping 與轉換規則
從 API Response → UI Props 的具體對應邏輯:
// API Response → ViewModel 轉換
interface ClaimCardViewModel {
id: string
title: string // 來自 claim_title
status: 'pending' | 'approved' | 'rejected' // 來自 claim_status,無需轉換
displayStatus: string // 轉換值:'審核中' / '已核准' / '已拒絕'
amount: string // 來自 claim_amount,格式化後(加千分位)
date: string // 來自 created_at,格式化為 YYYY/MM/DD
}
// Mapping 函式
function toViewModel(apiRecord: ClaimRecord): ClaimCardViewModel {
const statusLabels = {
'pending': '審核中',
'approved': '已核准',
'rejected': '已拒絕'
}
return {
id: apiRecord.id,
title: apiRecord.claim_title,
status: apiRecord.claim_status,
displayStatus: statusLabels[apiRecord.claim_status],
amount: formatCurrency(apiRecord.claim_amount), // 加千分位
date: formatDate(apiRecord.created_at), // YYYY/MM/DD
}
}
// 輔助函式
const formatCurrency = (num: number): string =>
new Intl.NumberFormat('zh-TW').format(num)
const formatDate = (isoStr: string): string =>
new Date(isoStr).toLocaleDateString('zh-TW')---
Step 6:定義錯誤碼與異常處理方案
根據 OpenAPI 的錯誤碼定義,制定 UI 呈現策略:
| HTTP Status | API Error Code | 觸發條件 | UI 呈現 | 用戶可操作 |
|---|---|---|---|---|
| 401 | UNAUTHORIZED | 未登入或 token 過期 | 重新導向登入頁 | ❌ 無(自動跳轉) |
| 403 | FORBIDDEN | 無查看權限 | 「您沒有查看此資料的權限」靜態提示 | ❌ 無 |
| 400 | INVALID_PARAMS | 分頁參數不合法 | 回退到第一頁後重試 | ❌ 無(自動重試) |
| 404 | NOT_FOUND | 資源不存在 | 「紀錄不存在」提示 | ❌ 無 |
| 5xx | INTERNAL_ERROR | 伺服器異常 | 「系統暫時無法使用,請稍後再試」+ 重試按鈕 | ✅ 有(可點重試) |
對應的 Composable 邏輯
// 錯誤碼對應表
const errorMessages = {
'UNAUTHORIZED': { code: 401, message: '請重新登入' },
'FORBIDDEN': { code: 403, message: '您沒有查看此資料的權限' },
'INVALID_PARAMS': { code: 400, message: '參數無效,已重置為第一頁' },
'NOT_FOUND': { code: 404, message: '紀錄不存在' },
'INTERNAL_ERROR': { code: 500, message: '系統暫時無法使用,請稍後再試' },
}
// 在 composable 中使用
const handleApiError = (status: number, errorCode?: string) => {
if (status === 401) {
// 重導至登入頁,由上層 router guard 處理
window.location.href = '/login'
} else if (status === 403) {
error.value = errorMessages['FORBIDDEN'].message
} else if (status === 400) {
currentPage.value = 1
reload() // 自動重試第一頁
} else if (status === 5xx) {
error.value = errorMessages['INTERNAL_ERROR'].message
// 留給使用者點重試按鈕
}
}---
Step 7:檢查待決策項與假設
回顧 spec.md 中的所有 [Open Question] 和 [Assumption],標記哪些已由 API 文檔回答:
| 原 Open Question | 決策方案 | 來源 |
|---|---|---|
| 列表是否需要分頁? | 是,根據 API 支援 page/pageSize 參數 | OpenAPI 文檔 |
| 狀態值為何? | pending/approved/rejected(已確認) | OpenAPI schema |
| 是否需要篩選功能? | 否,Figma 未見相關 UI,暫不實裝 | 工程師決策 |
| API 錯誤時的提示文案? | 見 Step 6 的錯誤碼對應表 | 本 Skill 補充 |
若仍有未決策項,輸出為待確認。
---
Step 8:輸出 enriched-spec.md 與斷點 B-C
基於以上分析生成完整的 enriched-spec.md,結構如下:
# Enriched Spec:{功能名稱}
> 產生時間:{日期}
> 狀態:Enriched — 基於 API 補充完整
> Figma:{URL}
> OpenAPI:{URL}
## 1. 功能範圍與流程摘要
(來自 spec.md,無變化)
## 2. 元件拆分建議
(來自 spec.md,無變化)
## 3. 互動行為說明
(來自 spec.md,無變化)
## 4. 欄位與資料型別定義
(已補充完整的 API 欄位定義)
## 5. API 對應表 ⭐️ 新增
(Endpoint、Request/Response schema、參數說明)
## 6. State 結構草案 ⭐️ 新增
(頁面 State、API State、衍生 State)
## 7. 數據 Mapping 與轉換規則 ⭐️ 新增
(API Response → UI Props 的具體邏輯)
## 8. Loading / Empty / Error 狀態
(每個狀態的 API 條件和 UI 呈現)
## 9. 錯誤碼與異常處理方案 ⭐️ 新增
(HTTP 狀態碼與 UI 對應表)
## 10. 開放問題與假設(已更新)
(標記已決策項,列出仍待確認項)輸出後,顯示以下提示訊息進入斷點 B-C:
✅ enriched-spec.md 已產生。
📋 請審閱以下新增內容:
- Section 5:API 對應表是否正確?
- Section 6:State 結構是否合理?
- Section 7:Mapping 邏輯是否清晰?
- Section 9:Error 處理方案是否完整?
確認無誤後請回覆「Approve」,我將輸出交接 payload 給 logic-coder。
未收到 Approve 前,不會繼續任何下游產出。---
品質守則
| 規則 | 說明 |
|---|---|
| 遵循 OpenAPI | 欄位名稱、型別、enum 值必須與 OpenAPI 文件一致 |
| 標記不確定 | 無法從 OpenAPI 推導的項目標記 [Assumption] |
| Mapping 清晰 | 所有 API → UI 的轉換邏輯必須明確寫出 |
| Error 完整 | 所有 API 可能的 HTTP status code 都需對應 UI 呈現 |
| State 實用 | State 結構應直接對應後續 logic-coder 的實裝需求 |
| 無 API 時主動提案 | 若缺 OpenAPI,不得停滯;應反推「資料模型候選提案」 |
---
完成定義(DoD)
- [ ] 收集到 spec.md、Figma 連結(必填)
- [ ] 若有 OpenAPI,成功解析並提取 endpoint、schema、錯誤碼
- [ ] 若無 OpenAPI,產出「資料模型候選提案」並等待用戶確認
- [ ] Section 5 API 對應表完整,所有欄位都有對應
- [ ] Section 6 State 結構清晰,包含分頁、loading、error state
- [ ] Section 7 Mapping 邏輯詳細,所有格式轉換都有函式示例
- [ ] Section 9 Error 碼表完整,涵蓋所有可能的 HTTP status
- [ ] enriched-spec.md 全部 10 個章節結構完整
- [ ] 已進入斷點 B-C 等待工程師 Approve
---
參考文件
references/enrichment-template.md— 完整的 enriched-spec.md 填寫範例references/data-structure-proposal.md— 無 OpenAPI 時的數據模型反推範例
無 OpenAPI 時的資料模型反推提案
当您暂时没有 OpenAPI 文檔时,api-enrichment Skill 会根据 Figma 设计稿中的 UI 欄位,反推候选的資料結構。 以下是一份完整的提案范例(基於「理賠紀錄列表」)。
---
背景
狀況:vue3-layout 已完成切版,设计稿显示清晰,但後端 API 文檔尚未就绪。
目標:反推出合理的資料模型,交由工程師確認,供 logic-coder 後續實裝使用。
---
推導過程
Step 1:從 spec.md 提取 UI 欄位
基於 ai-pm 產出的 spec.md,Section 4 和 Section 5 中列出的所有推測欄位:
| UI 位置 | 推測欄位名 | 推測型別 | 備註 |
|---|---|---|---|
| 卡片標題 | claim_title | string | 顯示於卡片標題區 |
| 卡片狀態標籤 | claim_status | enum | StatusBadge 呈現,有多種顏色 |
| 卡片金額 | claim_amount | number | 顯示及需格式化 |
| 卡片日期 | created_at | string | 需格式化為人類可讀 |
| 紀錄識別 | id | string | 用於詳情頁導航、取消操作 |
Step 2:根據 UI 互動推敲額外欄位
從 spec.md Section 3(互動行為說明)推敲可能需要的欄位:
| 互動 | 推敲欄位 | 說明 |
|---|---|---|
| 「點擊『查看詳情』導至詳情頁」 | id (已有) | 用於構造 /claims/:id 路由 |
| 「點擊『取消申請』(僅 pending 狀態可見)」 | status (已有) | 前端判斷按鈕是否顯示 |
| 「分頁控制」 | 無額外欄位,但需 API 提供 page/pageSize/total | API 層級參數 |
| 「進入頁面自動加載」 | 無 | 由 composable 層負責 |
Step 3:構建候選資料模型
基於上述分析,構建以下候選結構:
// 單筆理賠紀錄
interface CandidateClaimRecord {
id: string // 紀錄 ID(用於導航和操作)
title: string // 理賠標題
status: 'pending' | 'approved' | 'rejected' // 狀態(決定顏色和可操作性)
amount: number // 金額(需格式化)
createdAt: string // 建立日期(需格式化)
// 以下欄位為假設(可能需要 API 補充)
updatedAt?: string // 更新日期(可選)
description?: string // 詳細說明(若 Figma 未見,可先設為可選)
}
// 列表 API Response
interface CandidateClaimsListResponse {
page: number // 當前頁
pageSize: number // 每頁筆數
total: number // 總筆數
items: CandidateClaimRecord[] // 當前頁的理賠紀錄
}---
📋 資料模型確認清單
請根據以下表格逐一確認或修正:
欄位確認
| # | 欄位名 | 推測型別 | 推測用途 | ✅ 是否正確 | 💬 修正意見 |
|---|---|---|---|---|---|
| 1 | id | string (UUID 或 numeric) | 紀錄識別,用於導頁和刪除 | ? | ? |
| 2 | title | string (1-200 字元) | 卡片標題呈現 | ? | ? |
| 3 | status | enum (pending/approved/rejected) | 狀態標籤呈現及按鈕顯示邏輯 | ? | ? |
| 4 | amount | number (正整數) | 金額呈現,需加千分位格式化 | ? | ? |
| 5 | createdAt | ISO 8601 string | 日期呈現,需格式化為 YYYY/MM/DD | ? | ? |
| 6 | updatedAt | ISO 8601 string (可選) | 是否需要展示「最後更新」信息? | ? | ? |
Enum 值確認
status — 候選狀態值:
| 推測值 | UI 呈現 | 顏色 | ✅ 確認 | 💬 修正 |
|---|---|---|---|---|
| pending | 審核中 | 黃色 | ? | ? |
| approved | 已核准 | 綠色 | ? | ? |
| rejected | 已拒絕 | 紅色 | ? | ? |
若後端使用不同的 enum 值(如 'PENDING', 'APPROVED', 'REJECTED' 大寫,或 'pending_review' 蛇形命名等),請註明,logic-coder 會建立 Mapping。
欄位命名風格確認
| 推測風格 | 例 | ✅ 確認 | 💬 修正 |
|---|---|---|---|
| camelCase(前端常用) | createdAt, claim_title | ? | 請提供後端使用的命名風格 |
| snake_case(某些後端使用) | created_at, claim_title | ? | |
| PascalCase(某些 C# 後端使用) | CreatedAt, ClaimTitle | ? |
單位與格式確認
| 欄位 | 推測單位 / 格式 | ✅ 確認 | 💬 修正 |
|---|---|---|---|
| amount | 新台幣元(TWD) | ? | 非人民幣或其他幣別嗎? |
| createdAt | ISO 8601 (e.g., "2024-01-20T08:00:00Z") | ? | 或者是 Unix timestamp?或其他格式? |
---
分頁與篩選確認
分頁參數
| 參數 | 推測型別 | 推測預設值 | 推測最大值 | ✅ 確認 | 💬 修正 |
|---|---|---|---|---|---|
| page (或 pageNum) | integer | 1 | — | ? | ? |
| pageSize (或 limit) | integer | 20 | 100 | ? | ? |
| total | integer (響應中) | — | — | ? | 來自 API 響應? |
篩選功能(可選)
根據 spec.md,Figma 中是否有篩選 UI?
| 篩選方式 | 推測參數名 | 推測值 | 實裝需求 | 💬 備註 |
|---|---|---|---|---|
| 按狀態篩選 | status | pending / approved / rejected | ⚠️ 如需實裝中文 UI,需選項 | Figma 可見篩選控制項嗎? |
| 按日期範圍篩選 | createdAt_from / createdAt_to | ISO 8601 格式 | ⚠️ Figma 可見日期選擇器嗎? | |
| 按金額範圍篩選 | amount_min / amount_max | number | ⚠️ Figma 可見金額輸入框嗎? | |
| 排序 | sortBy / order | status(status) / amount(amount) / createdAt(createdAt) / asc(asc) / desc(desc) | ⚠️ Figma 可見排序控制嗎? |
若 Figma 中未明確顯示,可暫時不實裝,後續 sprint 補上。
---
推薦的資料型別定義(TypeScript)
基於上述推測,以下是 logic-coder 可直接使用的型別定義(待您最終確認後):
// ============ 基礎資料型別 ============
/**
* 單筆理賠紀錄(API Response 中的單筆資料)
*/
export interface ClaimRecord {
id: string // UUID 或數字 ID
title: string // 1-200 字元
status: 'pending' | 'approved' | 'rejected' // 狀態值
amount: number // 正整數,單位 CNY
createdAt: string // ISO 8601,e.g., "2024-01-20T08:00:00Z"
updatedAt?: string // 可選,最後更新時間
}
/**
* 列表 API 回傳(含分頁信息)
*/
export interface ClaimsListResponse {
page: number // 當前頁(1-based)
pageSize: number // 每頁筆數
total: number // 總筆數
items: ClaimRecord[] // 當前頁資料
}
// ============ 前端 ViewModel(展示層) ============
/**
* UI 層使用的 ViewModel(已格式化)
*/
export interface ClaimCardViewModel {
id: string
title: string
status: 'pending' | 'approved' | 'rejected' // 原值
displayStatus: string // 格式化值:「審核中」/ 「已核准」/ 「已拒絕」
amount: string // 格式化,如 "15,000"
date: string // 格式化,如 "2024/01/20"
}
// ============ 分頁參數 ============
/**
* 列表查詢參數
*/
export interface ClaimsListParams {
page?: number // 預設 1
pageSize?: number // 預設 20
status?: string // 可選篩選
}
// ============ 頁面 State ============
/**
* 頁面管理的 State
*/
export interface ClaimListPageState {
isLoading: boolean
error: string | null
records: ClaimRecord[]
currentPage: number
pageSize: number
total: number
}---
✅ 確認清單
請複製以下清單,完成後回覆給我:
欄位確認
- [ ] id 欄位名正確,型別為 string
- [ ] title / claim_title 確認欄位名(後端使用哪個?)
- [ ] status / claim_status 確認欄位名,enum 值正確
- [ ] amount / claim_amount 確認欄位名,單位為 CNY
- [ ] createdAt / created_at 確認欄位名,格式為 ISO 8601
- [ ] 無額外欄位遺漏
命名風格
- [ ] 確認後端使用的命名風格(camelCase / snake_case / PascalCase)
分頁
- [ ] 確認分頁參數名稱(page / pageNum)、預設值、最大值
- [ ] 確認 total 在 API 響應中如何提供
篩選與排序(可選)
- [ ] 確認是否需要狀態篩選
- [ ] 確認是否需要日期範圍篩選
- [ ] 確認是否需要排序功能
最終確認
- [ ] 上述所有項目已確認或修正
- [ ] 准許 logic-coder 基於此模型進行實裝
---
下一步
確認完成後,請回覆:
✅ 資料模型已確認。請根據以下修正進行實裝:
[列出任何修正項,例如:]
- status 欄位使用 'PENDING' / 'APPROVED' / 'REJECTED'(大寫)
- 需新增 updatedAt 欄位,型別同 createdAt
- pageSize 預設值為 30,而非 20
- 需支援按 status 篩選,參數名為 claimStatus
我將納入 enriched-spec.md 中,交由 logic-coder 實裝。或若選擇等待真實 API 文檔:
API 文檔已就緒,連結為:https://api.example.com/swagger.json
請直接補充完整的 enriched-spec.md,我會基於 API 確認所有欄位。---
範例:修正場景
假設您回覆如下:
✅ 修正如下:
1. status 應為大寫:'PENDING', 'APPROVED', 'REJECTED'
2. 欄位名使用 snake_case:claim_title, claim_status, claim_amount, created_at
3. 新增欄位 manager_id (string),代表理賠經理
4. amount 有小數點,應為 number (可能 15000.50)
5. 分頁參數確認為 page (1-based), limit (預設 20,最大 100)api-enrichment 將據此更新 enriched-spec.md:
interface ClaimRecord {
id: string
claim_title: string // 欄位名改為 snake_case
claim_status: 'PENDING' | 'APPROVED' | 'REJECTED' // 改為大寫
claim_amount: number // 支援小數
created_at: string
manager_id: string // 新增欄位
}
interface ClaimsListParams {
page?: number // 確認為 1-based
limit?: number // 參數改名,預設 20,最大 100
}然後進入斷點 B-C,等待工程師 Approve。
Enriched Spec Template 填寫範例
以下是一份完整的 enriched-spec.md 範例,基於「理賠紀錄列表」功能、結合 OpenAPI 文檔後的完整規格。
---
Enriched Spec:理賠紀錄列表(ClaimRecords)
產生時間:2024-01-22
狀態:Enriched — 基於 API 補充完整
Figma:https://www.figma.com/file/AbCdEf/insurance-portal?node-id=123:456
OpenAPI:https://api.example.com/swagger/v1/swagger.json
說明:補充了 API 對應、State 結構、Error 處理。
---
1. 功能範圍與流程摘要
功能目標:保戶可以在個人專區查看所有歷史理賠申請紀錄,包含狀態追蹤與金額資訊。
涵蓋頁面:
/claims— 理賠紀錄列表頁
明確排除:
- 新增理賠申請(另有獨立流程)
- 理賠詳情頁(本次 spec 只處理列表)
- 管理後台的理賠審核操作
主要流程:進入頁面 → API 取得列表 → 顯示卡片清單 → 支援分頁與互動
---
2. 元件拆分建議 ← 工程師重點審閱
| 元件名稱 | Figma Node ID | 層級 | 可重用性 | 說明 |
|---|---|---|---|---|
| ClaimRecordsContainer | — | Container | 頁面專屬 | 負責 API 呼叫、loading/error 控制,傳 props 給列表 |
| ClaimRecordsList | 123:460 | Feature | 頁面專屬 | 接收 items 陣列,渲染卡片清單 |
| ClaimCard | 123:470 | Base | 跨頁可重用 | 單筆理賠紀錄展示,含狀態 badge 與金額 |
| StatusBadge | 123:480 | Base | 跨頁可重用 | 狀態標籤(審核中/已核准/已拒絕),可獨立抽出 |
| EmptyState | 123:490 | Base | 跨頁可重用 | 空資料提示元件,接收 message prop |
拆分原則:Container 負責資料與邏輯,Presentational 只接收 props。
---
3. 互動行為說明 ← 工程師重點審閱
| 互動點 | 觸發條件 | 預期行為 | 對應 Figma Frame |
|---|---|---|---|
| 進入頁面 | 路由 mounted | 自動呼叫 GET /v1/claims,顯示 loading skeleton | 123:456 |
| 點擊「查看詳情」 | 使用者點擊 ClaimCard 的詳情按鈕 | 導向 /claims/:id 詳情頁 | 123:500 |
| 點擊「取消申請」 | 使用者點擊 ClaimCard 的取消按鈕(僅 pending 狀態可見) | 呼叫 DELETE /v1/claims/:id,刷新列表 | 123:510 |
| 點擊「下一頁」 | 使用者點擊分頁控制的「下一頁」按鈕 | 以 page + 1 呼叫 GET /v1/claims,刷新列表 | 123:535 |
| 載入失敗 | API 回傳 5xx 或網路異常 | 顯示錯誤提示 + 重試按鈕 | 123:520 |
| 無理賠紀錄 | Response data 為空陣列 | 顯示 EmptyState 元件 | 123:530 |
---
4. 欄位與資料型別定義 ← 工程師重點審閱
4.1 列表欄位(API Response → UI 對應) ✅ 已完整確認
| 欄位名稱(API) | 顯示名稱(UI) | 型別 | 說明 | 來源 |
|---|---|---|---|---|
| id | — | string | 理賠紀錄主鍵 | OpenAPI: claim_record.id (UUID) |
| claim_title | 理賠標題 | string | 顯示於卡片標題 | OpenAPI: claim_record.title (1-200 chars) |
| claim_status | 狀態 | enum | 顯示於 StatusBadge | OpenAPI: claim_record.status (pending/approved/rejected) |
| claim_amount | 申請金額 | number | 單位:元,顯示時加千分位 | OpenAPI: claim_record.amount (integer, CNY) |
| created_at | 申請日期 | string (ISO 8601) | 格式化為 YYYY/MM/DD | OpenAPI: claim_record.createdAt (ISO 8601) |
4.2 請求參數(Request Query)
| 參數名稱 | 型別 | 必填 | 預設值 | 說明 |
|---|---|---|---|---|
| page | integer | ❌ | 1 | 當前頁數 |
| pageSize | integer | ❌ | 20 | 每頁筆數(API 支援最多 100) |
| status | string (enum) | ❌ | — | 篩選狀態(pending/approved/rejected),可選 |
[Assumption] status 篩選參數由 API 提供,前端可選傳多個值
---
5. API 對應表 ⭐️ 新增
| 功能 | Method | Endpoint | Request Params | Response 關鍵欄位 | 錯誤碼處理 |
|---|---|---|---|---|---|
| 取得理賠列表 | GET | /v1/claims | page (int), pageSize (int), status (enum) | data: ClaimRecord[], page: int, pageSize: int, total: int | 401: 重新登入 / 403: 無權限 / 400: 參數無效 / 5xx: 通用錯誤 |
| 取消理賠申請 | DELETE | /v1/claims/:id | — | — | 400: 狀態不允許取消 / 404: 找不到紀錄 / 5xx: 通用錯誤 |
5.1 列表查詢 API 詳細定義
Endpoint: GET /v1/claims
Request:
interface ClaimsListRequest {
page?: number // 預設 1
pageSize?: number // 預設 20,最大 100
status?: 'pending' | 'approved' | 'rejected' // 可選篩選
}Response (200 OK):
interface ClaimsListResponse {
code: 'SUCCESS'
data: {
page: number // 當前頁
pageSize: number // 每頁筆數
total: number // 總筆數
items: ClaimRecord[] // 當前頁資料
}
}
interface ClaimRecord {
id: string // UUID
title: string // 1-200 字元
status: 'pending' | 'approved' | 'rejected'
amount: number // 正整數,單位元
createdAt: string // ISO 8601 格式,e.g., "2024-01-20T08:00:00Z"
// 注意:API 欄位名為 createdAt(camelCase),前端對應 created_at
}Error Response (4xx / 5xx):
interface ErrorResponse {
code: 'UNAUTHORIZED' | 'FORBIDDEN' | 'INVALID_PARAMS' | 'INTERNAL_ERROR'
message: string
}5.2 取消申請 API 詳細定義
Endpoint: DELETE /v1/claims/:id
Request: 無 query/body,:id 為申請紀錄 ID
Response (200 OK):
interface CancelResponse {
code: 'SUCCESS'
message: 'Claim cancelled successfully'
}Error Response:
- 400: claim_status 不是 'pending',無法取消
- 404: 申請紀錄不存在
- 5xx: 系統異常
---
6. State 結構草案 ⭐️ 新增
6.1 API 回傳的 State(ClaimRecord)
interface ClaimRecord {
id: string
title: string
status: 'pending' | 'approved' | 'rejected'
amount: number
createdAt: string // ISO 8601
}6.2 頁面 State(ClaimListPageState)
interface ClaimListPageState {
isLoading: boolean // API 呼叫中
error: string | null // 錯誤訊息
records: ClaimRecord[] // 當前頁的理賠紀錄
currentPage: number // 當前分頁(預設 1)
pageSize: number // 每頁筆數(預設 20)
total: number // 總筆數(來自 API)
selectedStatusFilter?: string // 篩選狀態(可選)
}6.3 衍生 State(computed)
// 是否顯示空狀態
const isEmpty = computed(() =>
!isLoading.value && records.value.length === 0
)
// 總頁數
const totalPages = computed(() =>
Math.ceil(total.value / pageSize.value)
)
// 是否可點「下一頁」
const hasNextPage = computed(() =>
currentPage.value < totalPages.value
)
// 是否可點「上一頁」
const hasPreviousPage = computed(() =>
currentPage.value > 1
)---
7. 數據 Mapping 與轉換規則 ⭐️ 新增
7.1 API Response → ViewModel 轉換
// 從 API 獲得的原始資料
interface ClaimRecord {
id: string
title: string
status: 'pending' | 'approved' | 'rejected'
amount: number
createdAt: string // ISO 8601
}
// 展示層需要的 ViewModel
interface ClaimCardViewModel {
id: string
title: string
status: 'pending' | 'approved' | 'rejected'
displayStatus: string // 轉換值:「審核中」/ 「已核准」/ 「已拒絕」
amount: string // 格式化:加千分位,e.g., "15,000"
date: string // 格式化:YYYY/MM/DD,e.g., "2024/01/20"
}7.2 Mapping 函式
// 單筆轉換
function toViewModel(apiRecord: ClaimRecord): ClaimCardViewModel {
const statusLabels: Record<string, string> = {
'pending': '審核中',
'approved': '已核准',
'rejected': '已拒絕'
}
return {
id: apiRecord.id,
title: apiRecord.title,
status: apiRecord.status,
displayStatus: statusLabels[apiRecord.status],
amount: formatCurrency(apiRecord.amount),
date: formatDate(apiRecord.createdAt),
}
}
// 列表轉換
function toViewModels(apiRecords: ClaimRecord[]): ClaimCardViewModel[] {
return apiRecords.map(toViewModel)
}
// 輔助函式:金額格式化(加千分位)
function formatCurrency(num: number): string {
return new Intl.NumberFormat('zh-TW', {
style: 'currency',
currency: 'TWD',
minimumFractionDigits: 0,
maximumFractionDigits: 0
}).format(num)
// 例:15000 → "$15,000"
// 若需移除貨幣符號,可用:
// return num.toLocaleString('zh-TW') // → "15,000"
}
// 輔助函式:日期格式化(ISO → YYYY/MM/DD)
function formatDate(isoStr: string): string {
const date = new Date(isoStr)
const year = date.getFullYear()
const month = String(date.getMonth() + 1).padStart(2, '0')
const day = String(date.getDate()).padStart(2, '0')
return `${year}/${month}/${day}`
// 例:"2024-01-20T08:00:00Z" → "2024/01/20"
}7.3 Sorting / Filtering 邏輯(如需)
按狀態篩選
// 前端篩選(若 API 支援 status 參數)
const filterByStatus = (records: ClaimRecord[], status: string) => {
if (!status) return records
return records.filter(r => r.status === status)
}
// 或呼叫 API 篩選(推薦)
const fetchWithFilter = async (status?: string) => {
const params = new URLSearchParams({
page: String(currentPage.value),
pageSize: String(pageSize.value),
})
if (status) params.append('status', status)
const res = await fetch(`/v1/claims?${params}`)
// ...
}按日期排序(若 API 支援)
[Open Question] API 是否支援 sortBy / order 參數?目前 Figma 未見排序 UI。
---
8. Loading / Empty / Error 狀態
| 狀態 | 觸發條件 | UI 呈現 | 對應 Figma Node |
|---|---|---|---|
| Loading | API 呼叫中(isLoading: true) | Skeleton 卡片列表(3 筆) | 123:540 |
| Empty | Response data 為空陣列 + !isLoading | EmptyState:「目前沒有理賠紀錄」 | 123:530 |
| Error (401) | 未登入或 token 過期 | 重新導向登入頁(不顯示在列表頁) | — |
| Error (403) | 無查看權限 | 「您沒有查看此資料的權限」靜態提示 | 123:520 |
| Error (400) | 分頁參數不合法 | 自動回退至 page=1,重試 | — |
| Error (5xx) | 系統異常 | 「系統暫時無法使用,請稍後再試」+ 重試按鈕 | 123:520 |
---
9. 錯誤碼與異常處理方案 ⭐️ 新增
9.1 HTTP Status Code 對應表
| HTTP Status | API Error Code | 觸發條件 | UI 呈現 | 前端可操作 | 後續流程 |
|---|---|---|---|---|---|
| 200 | SUCCESS | API 正常回傳 | 正常顯示列表 | — | 無 |
| 400 | INVALID_PARAMS | 分頁參數超出範圍(如 page > totalPages) | 靜默回退至 page=1,重新呼叫 API | ❌ 無 | 自動重試 |
| 401 | UNAUTHORIZED | 未登入或 token 過期 | 重新導向登入頁 | ❌ 無 | 自動跳轉至 /login |
| 403 | FORBIDDEN | 用戶無查看此資料的權限 | 「您沒有查看此資料的權限」靜態文案 | ❌ 無 | 保持在列表頁,提示常駐 |
| 404 | NOT_FOUND | 資源不存在(罕見) | 「資料不存在」提示 | ❌ 無 | 提示常駐 |
| 5xx | INTERNAL_ERROR | 伺服器異常 | 「系統暫時無法使用,請稍後再試」+ 重試按鈕 | ✅ 有 | 用戶可點「重試」 |
9.2 Error Handling in Composable
// 定義錯誤訊息對應表
const errorMessageMap = {
'400': '參數無效,已重置',
'401': '未登入,請重新登入',
'403': '您沒有查看此資料的權限',
'404': '資料不存在',
'5xx': '系統暫時無法使用,請稍後再試'
}
// Composable 中的 error handler
async function load(pageNum: number = 1) {
isLoading.value = true
error.value = null
try {
const res = await fetch(`/v1/claims?page=${pageNum}&pageSize=${pageSize}`)
if (!res.ok) {
if (res.status === 401) {
// 自動重導至登入
window.location.href = '/login'
return
} else if (res.status === 403) {
error.value = '您沒有查看此資料的權限'
} else if (res.status === 400) {
// 自動回退至第一頁
currentPage.value = 1
await load(1)
} else if (res.status >= 500) {
error.value = '系統暫時無法使用,請稍後再試'
}
return
}
const data = await res.json()
records.value = data.items.map(toViewModel)
total.value = data.total
currentPage.value = pageNum
} catch (err) {
console.error('[useClaimRecords] 網路錯誤', err)
error.value = '網路連線失敗,請檢查後重試'
} finally {
isLoading.value = false
}
}---
10. 開放問題與假設(已更新) ← 工程師與 logic-coder 的協商點
Assumptions(已確認,來自 OpenAPI 與 Figma)
- ✅ 列表需要分頁,預設每頁 20 筆(API 支援 pageSize 參數)
- ✅ 狀態值為 pending/approved/rejected(已確認)
- ✅ 金額單位為新台幣元,無需幣別轉換
- ✅ 日期欄位為 ISO 8601 格式,需格式化為 YYYY/MM/DD
- ✅ API 直接提供分頁資訊(page、pageSize、total)
Open Questions(仍待決策)
- [Open Question] 「取消申請」操作是否需要二次確認 Modal?還是直接呼叫 API?(Figma 未給明確設計)
- [Open Question] 是否需要支援依狀態篩選或排序功能?(Figma 中未見 UI 線索,但 API 支援 status 參數)
- [Open Question] 分頁控制是否需要「跳頁輸入框」,還是只需上一頁/下一頁?(Figma 設計未明確)
💡 提示:以上 Open Questions 應由工程師在 code review 時決策,或由 logic-coder 在實裝過程中與設計確認。