Agent Skills教學手冊

Agent Skills 教學手冊(企業級 SSDLC + GitHub Copilot + Claude Code) 版本:v1.1.0 更新日期:2026-04-1 適用對象:資深工程師、架構師、Tech Lead、DevSecOps 工程師 技術棧:Agent Skills 開放標準、GitHub Copilot Skills、Claude Code Skills、VS Code、Spring Boot、Vue 3 規範參考:Agent Skills Specification(開放標準 v1.0) 目錄 第 1 章:Agent Skills 概念與架構 1.1 什麼是 Agent Skills 1.2 與傳統 Prompt Engineering 的差異 1.3 Skills vs Prompt vs Tool vs Agent 比較 1.4 漸進式揭露(Progressive Disclosure)設計原則 1.5 Skills 組成結構 第 2 章:Skills 平台深度解析(GitHub Copilot + Claude Code) 2.1 多平台 Skills 架構 2.2 Skills 運作流程 2.3 Skills Metadata 設計(Frontmatter 完整參考) 2.3.1 開放標準欄位(agentskills.io Specification) 2.3.2 Claude Code 擴展欄位 2.3.3 字串替換(String Substitutions) 2.3.4 動態上下文注入(Dynamic Context Injection) 2.4 Claude Code 內建 Skills(Bundled Skills) 2.5 Skills 與 Agent 整合方式 2.6 Skills Repository 設計(企業級) 第 3 章:SSDLC × Skills(核心章節) 3.1 Requirements(需求階段) 3.2 Design(設計階段) 3.3 Development(開發階段) 3.4 Testing(測試階段) 3.5 Security(安全) 3.6 Deployment(部署) 3.7 Maintenance(維運) 第 4 章:Skills 設計最佳實務 4.1 高可重用性設計 4.2 低 Token 消耗策略 4.3 命名規範 4.4 模組化與版本控管 4.5 安全性與權限控管 第 5 章:Skills 實作教學(Hands-on) 5.1 範例 1:產生 API 設計文件 Skill 5.2 範例 2:程式碼審查 Skill 5.3 範例 3:Spring Boot 服務生成 Skill 第 6 章:企業級 Skills Repository 架構 6.1 建議 GitHub Repo 結構 6.2 Skills 分類策略 6.3 權限控管(RBAC) 6.4 與 CI/CD 整合 第 7 章:與開發工具整合 7.1 GitHub Copilot 整合 7.2 Claude Code 進階整合 7.3 VS Code 整合 7.4 CI/CD(GitHub Actions)整合 7.5 Issue / PR 流程整合 第 8 章:Skills 治理(Governance) 8.1 Skills 審核機制 8.2 品質控管(Quality Gate) 8.3 安全審查(Security Review) 8.4 使用追蹤與優化(Telemetry) 第 9 章:常見錯誤與反模式 9.1 過度設計 Skills 9.2 Token 爆炸問題 9.3 Skills 過於耦合 9.4 其他常見反模式 第 10 章:未來趨勢 10.1 Agentic Workflow 10.2 Multi-Agent Collaboration 10.3 Skills Marketplace 與開放生態 附錄 A:企業導入檢查清單 附錄 B:Prompt → Skill 轉換速查表 附錄 C:常用 Skills 清單 附錄 D:參考資源 第 1 章:Agent Skills 概念與架構 1.1 什麼是 Agent Skills 定義:Agent Skills 是一種基於 agentskills.io 開放標準的模組化、可重複使用「AI 能力包」(Capability Package),以資料夾形式存在,內含說明文件(SKILL.md)、腳本(Python / Bash / PowerShell)、範本(Templates)和參考資源(References)。 ...

April 1, 2026 · 42 min · 8913 words · Eric Cheng

使用 GitHub Copilot 進行逆向工程並產出需求規格書

使用 GitHub Copilot 進行逆向工程並產出需求規格書 版本:2.0 | 更新日期:2026-03-31 適用對象:資深工程師、系統分析師、架構師、PM 技術環境:VS Code + GitHub Copilot(含 Agent Mode、Coding Agent、MCP 整合) 產業適用:銀行、金融、保險等高合規性企業系統 AI 模型支援:GPT-5.4 / GPT-5.4 mini / GPT-5.3-Codex / Gemini 3.1 Pro / 自動模型選擇 目錄 第 1 章 逆向工程方法論 1.1 什麼是逆向工程 1.2 企業系統中的逆向工程應用場景 1.3 Code → Requirement 轉換模型 1.4 各層級程式碼的需求推導策略 1.5 逆向工程成熟度模型 第 2 章 GitHub Copilot 使用策略 2.1 Copilot 在逆向工程中的角色定位 2.1.1 GitHub Copilot 2026 功能架構全景 2.1.2 各功能在逆向工程中的定位 2.1.3 AI 模型選擇策略 2.2 Prompt Engineering 核心原則 2.2.1 角色設定(Role Setting) 2.2.2 結構化 Prompt 模板 2.2.3 五大 Prompt 策略 2.3 逐段分析程式碼的技巧 2.3.1 分段策略 2.3.2 VS Code 操作技巧 2.4 C# 程式碼解讀策略與 Prompt 範例 2.5 SQL / Stored Procedure 解讀策略與 Prompt 範例 2.6 Batch Job 解讀策略與 Prompt 範例 2.7 從技術描述轉換為商業需求描述 2.8 Before / After 完整範例 2.9 Agent Mode 與 Coding Agent 在逆向工程中的進階應用 2.9.1 使用 Agent Mode 進行跨檔案逆向分析 2.9.2 使用 Plan Agent 規劃逆向工程任務 2.9.3 使用 Copilot Coding Agent 進行自動化逆向分析 2.9.4 使用 MCP 擴展逆向工程能力 2.9.5 使用 Custom Agent 建立專用逆向工程分析師 2.9.6 使用 Copilot Memory 累積逆向工程知識 2.9.7 第三方 Coding Agent 與外部平台整合 2.9.8 使用背景代理進行非同步逆向分析 第 3 章 實戰流程(Step-by-Step) 3.1 Step 1 — 程式碼盤點 3.1.1 模組分類方法 3.1.2 使用 Copilot 快速摘要 3.1.3 自動化盤點腳本 3.2 Step 2 — 邏輯解析 3.2.1 API 邏輯分析 3.2.2 Batch Job 邏輯分析 3.2.3 DB 邏輯分析 3.3 Step 3 — 商業邏輯抽取 3.3.1 從技術邏輯到業務需求的轉換方法 3.3.2 商業規則分類框架 3.3.3 需求追溯矩陣 3.4 Step 4 — 文件產出 3.4.1 SRS 文件自動生成流程 3.4.2 文件品質檢查 3.5 端到端流程圖 第 4 章 SRS 文件標準格式 4.1 SRS 文件結構概覽 4.2 系統概述 4.3 Use Case 描述 4.4 功能需求(Functional Requirements) 4.5 非功能需求(Non-functional Requirements) 4.6 資料流程(DFD) 4.7 ER Model(資料結構) 4.8 Batch Flow 4.9 企業級 SRS 範本(完整) 第 5 章 完整實戰範例 5.1 範例一:C# API 控制器逆向分析 5.1.1 原始程式碼 5.1.2 Copilot 分析過程 5.1.3 推導出的需求 5.2 範例二:Stored Procedure 逆向分析 5.2.1 原始程式碼 5.2.2 Copilot 分析過程 5.2.3 推導出的需求 5.3 範例三:Batch Job 逆向分析 5.3.1 分析場景 5.3.2 Copilot 分析結果 5.4 最終 SRS 文件片段產出 第 6 章 企業最佳實務 6.1 避免誤判需求的策略 6.1.1 常見的誤判類型 6.1.2 防範策略 6.1.3 信心度評估框架 6.2 需求驗證流程 6.2.1 三層驗證機制 6.2.2 驗證會議範本 6.3 整合 SSDLC 6.4 搭配版本控制(Git) 6.4.1 SRS 文件的 Git 管理策略 6.4.2 Git Branching 策略 6.4.3 Commit 訊息規範 6.5 常見錯誤與修正策略 6.6 使用 Copilot Code Review 驗證 SRS 品質 6.6.1 設定 Copilot Code Review 規則 6.6.2 SRS Pull Request 審查流程 6.7 使用 Copilot Spaces 管理逆向工程上下文 第 7 章 架構延伸(進階) 7.1 從逆向結果到 Spring Boot API 設計 7.1.1 技術對照表 7.1.2 SRS → API 設計的轉換流程 7.1.3 範例:從 SRS 到 Spring Boot 7.2 微服務架構轉換 7.2.1 從單體到微服務的拆分策略 7.2.2 Stored Procedure 的遷移策略 7.3 Domain Modeling(DDD) 7.3.1 從逆向結果推導領域模型 7.3.2 統一語言表 7.4 Clean Architecture 對應 7.4.1 分層架構設計 7.4.2 Spring Boot 專案結構 7.5 使用 Copilot Coding Agent 自動化遷移工作 7.5.1 從 SRS 到 Spring Boot 骨架自動生成 7.5.2 SP 邏輯遷移自動化 7.5.3 Hooks 自動化品質檢查 第 8 章 Prompt 工程模板庫 8.1 程式碼摘要類 8.2 邏輯解析類 8.3 需求轉換類 8.4 文件產出類 8.5 驗證與審查類 第 9 章 自動化流程(AI + Script) 9.1 自動化盤點腳本 9.1.1 C# 專案自動盤點(PowerShell) 9.1.2 SQL Server Stored Procedure 盤點 9.2 批次分析流程 9.2.1 分析工作流程自動化 9.2.2 分析任務分配模板 9.3 SRS 自動組裝 9.3.1 SRS 組裝腳本(PowerShell) 9.3.2 SRS 品質檢查腳本 9.4 Copilot Coding Agent 端到端自動化流程 9.4.1 自動化流程架構 9.4.2 GitHub Actions 工作流配置 9.4.3 批次 Issue 建立腳本 9.4.4 追溯矩陣自動生成 附錄 A 檢查清單(Checklist) A.1 逆向工程啟動檢查清單 A.2 每日分析檢查清單 A.3 模組完成檢查清單 A.4 SRS 發佈前檢查清單 A.5 Copilot 使用安全檢查清單 A.6 Copilot Coding Agent 使用檢查清單 附錄 B 術語表 附錄 C 參考資源 C.1 工具 C.2 標準與規範 C.3 推薦閱讀 C.4 GitHub Copilot 相關資源 第 1 章 逆向工程方法論 1.1 什麼是逆向工程 逆向工程(Reverse Engineering) 是從既有系統的成品(程式碼、資料庫、設定檔等)出發,反推出系統的設計意圖、業務規則與需求規格的過程。 ...

March 31, 2026 · 49 min · 10428 words · Eric Cheng

企業級程式碼品質分析方法論教學手冊

企業級程式碼品質分析方法論教學手冊 版本:v2.0|日期:2026-03-30|適用對象:初階至資深工程師、Tech Lead、架構師 適用技術棧:Java / Spring Boot、Vue / TypeScript、微服務架構 適用產業:銀行、金融業、保險業等高穩定度 / 高安全系統 重要更新:OWASP Top 10: 2025 版、PMD 7.x、Checkstyle 13.x、SpotBugs 4.9.x、ESLint 9.x Flat Config 📋 目錄 第 1 章:程式碼品質概論 1.1 程式碼品質的定義 1.2 技術債(Technical Debt) 1.3 為什麼企業需要品質管理 1.4 品質管理全景圖 1.5 實務落地建議 第 2 章:程式碼品質模型(核心方法論) 2.1 品質模型總覽 2.2 可讀性(Readability) 2.3 可維護性(Maintainability) 2.4 可測試性(Testability) 2.5 效能(Performance) 2.6 安全性(Security) 2.7 可擴展性(Scalability) 2.8 實務落地建議 第 3 章:程式碼異味(Code Smells) 3.1 什麼是 Code Smell 3.2 常見 15+ 種 Code Smells 3.3 實務落地建議 第 4 章:靜態分析與工具 4.1 靜態分析概述 4.2 SonarQube 4.3 ESLint 4.4 PMD / Checkstyle 4.5 SpotBugs 4.6 工具功能比較 4.7 企業導入方式 4.8 實務落地建議 第 5 章:Code Review 方法論 5.1 Code Review 的價值 5.2 Review Checklist(企業版) 5.3 Pull Request 標準 5.4 Review 角色分工 5.5 常見錯誤 5.6 實務落地建議 第 6 章:CI/CD 與品質門檻(Quality Gate) 6.1 CI/CD 品質整合概述 6.2 Quality Gate 設計 6.3 Pipeline 品質檢查流程 6.4 Fail Pipeline 策略 6.5 實務落地建議 第 7 章:測試策略與品質關聯 7.1 測試金字塔 7.2 單元測試(Unit Test) 7.3 整合測試(Integration Test) 7.4 覆蓋率迷思 7.5 測試與品質的關係 7.6 實務落地建議 第 8 章:重構(Refactoring)策略 8.1 何時該重構 8.2 安全重構流程 8.3 常見重構技巧 8.4 實務落地建議 第 9 章:企業實務最佳實踐(Best Practices) 9.1 銀行 / 金融系統案例 9.2 微服務品質管理 9.3 DevSecOps 整合 9.4 實務落地建議 第 10 章:導入策略(企業落地) 10.1 推動步驟(Roadmap) 10.2 組織角色 10.3 KPI 指標 10.4 成熟度模型(Level 1~5) 10.5 實務落地建議 附錄 A:新進成員檢查清單(Checklist) 附錄 B:品質指標速查表 附錄 C:推薦學習資源 第 1 章:程式碼品質概論 1.1 程式碼品質的定義 程式碼品質不只是「能跑就好」,在企業級系統中,品質直接影響系統的穩定性、安全性與長期維護成本。 ...

March 30, 2026 · 31 min · 6462 words · Eric Cheng

軟體開發標準程序(Software Development Standard Process)教學手冊

軟體開發標準程序(Software Development Standard Process)教學手冊 版本:1.0 最後更新:2026 年 2 月 適用對象:軟體開發團隊全體成員 文件性質:內部技術規範與教育訓練教材 📋 目錄 第一章:前言與目的 1.1 為什麼需要軟體開發標準程序 1.2 對組織與工程師的價值 1.3 本手冊適用範圍 第二章:軟體開發生命週期(SDLC)總覽 2.1 SDLC 各階段說明 2.2 與實務專案的關係 2.3 敏捷與瀑布的選擇 第三章:需求管理(Requirements Engineering) 3.1 需求來源與分類 3.2 功能性與非功能性需求 3.3 需求文件標準 3.4 需求異動管理流程 第四章:系統分析與設計 4.1 系統架構設計原則 4.2 邏輯架構與實體架構 4.3 API 設計規範 4.4 資料庫設計與資料治理 4.5 非功能性設計 第五章:開發實作規範 5.1 程式碼風格與命名規範 5.2 架構分層原則 5.3 重用性與模組化 5.4 Secure Coding 基本原則 第六章:測試策略與品質保證 6.1 測試類型與層級 6.2 測試責任分工 6.3 測試資料管理 6.4 缺陷(Bug)管理流程 第七章:版本控制與組態管理 7.1 Git 分支策略 7.2 版號管理原則 7.3 設定檔與環境管理 第八章:CI/CD 與部署流程 8.1 自動化建置流程 8.2 部署策略 8.3 回滾與風險控管 第九章:資安與 SSDLC 9.1 安全需求納入時機 9.2 程式碼掃描與弱點管理 9.3 權限、稽核與日誌 第十章:上線、維運與監控 10.1 上線檢核清單 10.2 監控與告警 10.3 問題處理與 RCA 第十一章:文件化與知識交接 11.1 必備文件清單 11.2 文件維護責任 第十二章:持續改善與流程治理 12.1 專案回顧(Post-mortem) 12.2 指標與成熟度模型 12.3 流程優化建議 附錄 A:檢查清單(Checklist) A.1 開發階段檢查清單 A.2 部署階段檢查清單 A.3 Code Review 檢查清單 A.4 安全性檢查清單 附錄 B:文件範本索引 附錄 C:術語對照表 第一章:前言與目的 1.1 為什麼需要軟體開發標準程序 在企業軟體開發環境中,缺乏標準化流程將導致以下問題: ...

February 5, 2026 · 34 min · 7214 words · Eric Cheng

系統資料轉置教學指引

系統資料轉置教學指引 版本:1.0 更新日期:2026-02-02 適用對象:系統分析師、資料工程師、後端開發人員 文件性質:內部教育訓練與專案執行標準參考文件 目錄 主要章節 章節 內容概述 第 1 章 資料轉置整體概念與常見失敗原因 第 2 章 舊系統分析(As-Is Analysis) 第 3 章 新系統設計(To-Be Design) 第 4 章 資料轉置策略與架構設計 第 5 章 資料轉置流程設計(ETL Flow) 第 6 章 資料驗證與比對機制 第 7 章 工具與技術選型建議 第 8 章 測試策略與上線前檢核 第 9 章 實務經驗與最佳實踐 附錄 A 資料轉置專案檢查清單 附錄 B 常用 SQL 範本 附錄 C 名詞解釋 詳細目錄 第 1 章:資料轉置整體概念與常見失敗原因 1.1 Data Migration vs Data Transformation 差異 1.2 為何資料轉置是高風險專案 1.3 常見失敗原因與風險分析 第 2 章:舊系統分析(As-Is Analysis) 2.1 資料來源盤點 2.2 資料結構分析 2.3 Key 與邏輯關聯分析 2.4 資料品質檢測 第 3 章:新系統設計(To-Be Design) 3.1 新系統資料模型設計原則 3.2 舊欄位到新欄位 Mapping 規則 3.3 Code / Enum / Reference Data 對應策略 3.4 歷史資料保留與否的決策考量 第 4 章:資料轉置策略與架構設計 4.1 一次性轉置 vs 分批轉置 4.2 Online vs Batch 4.3 Big Bang vs Parallel Run 4.4 Rollback 與 Re-run 設計 第 5 章:資料轉置流程設計(ETL Flow) 5.1 Extract(資料抽取) 5.2 Transform(資料轉換) 5.3 Load(資料載入) 5.4 Staging Table 設計 5.5 Error Handling 與 Retry 機制 第 6 章:資料驗證與比對機制 6.1 筆數驗證(Record Count) 6.2 金額 / 數值驗證(Sum / Balance Check) 6.3 Key-based 資料比對 6.4 抽樣驗證(Sampling) 6.5 自動化驗證報表設計 第 7 章:工具與技術選型建議 7.1 SQL / Stored Procedure 7.2 ETL 工具 7.3 程式語言選擇 7.4 檔案處理工具 7.5 驗證與測試輔助工具 第 8 章:測試策略與上線前檢核 8.1 Unit Test(轉換邏輯) 8.2 Integration Test(流程驗證) 8.3 UAT 驗證模式 8.4 上線前 Checklist 第 9 章:實務經驗與最佳實踐 9.1 常見踩雷案例 9.2 與業務單位的資料驗證合作方式 9.3 文件化、稽核與可追溯性設計 9.4 金融與核心系統常見合規考量 附錄 A:資料轉置專案檢查清單(Checklist) A.1 專案啟動階段 A.2 分析設計階段 A.3 開發測試階段 A.4 UAT 階段 A.5 上線階段 附錄 B:常用 SQL 範本 B.1 資料品質檢測 B.2 資料比對 B.3 轉置進度追蹤 附錄 C:名詞解釋 第 1 章:資料轉置整體概念與常見失敗原因 1.1 Data Migration vs Data Transformation 差異 在開始資料轉置專案前,必須先釐清兩個核心概念的差異: ...

February 2, 2026 · 45 min · 9454 words · Eric Cheng

12-Factor App 說明與對應解決方案

Codebase(單一程式碼庫,多個部署環境) 原則:一個應用程式應有單一程式碼庫,透過不同的部署(deploy)對應不同環境(dev/test/prod)。 解決方案: 使用 GitLab repository 管理專案。 每個環境使用不同 branch 或 tag(如 develop, release, main)。 CI/CD pipeline 進行自動化部署,避免分散程式碼庫。 Dependencies(明確宣告與隔離依賴) 原則:應用程式必須明確管理相依性,避免依賴系統環境。 解決方案: 後端:使用 Maven pom.xml 宣告所有 dependencies,不依賴本地安裝的 jar。 前端:使用 package.json 鎖定依賴版本。 建議使用 Docker 建立一致的 build/runtime 環境。 Config(將設定與程式碼分離) 原則:設定(如 DB 密碼、API key)不應寫死在程式碼中。 解決方案: Spring Boot 使用 application.yml + 外部設定檔 或 環境變數。 GitLab CI/CD 提供 Environment Variables 管理不同環境的設定。 建議搭配 Vault / AWS Secrets Manager / Kubernetes Secrets。 Backing Services(後端服務當作附加資源) 原則:資料庫、快取、MQ、外部 API 都應視為「可替換的資源」。 解決方案: DB(MySQL/DB2/PostgreSQL)、Redis、RabbitMQ 等連線資訊放在設定檔,不耦合程式。 測試環境可用輕量替代(如 testcontainers 啟動 DB/Redis)。 Build, Release, Run(明確分離建置、發佈、執行) 原則:建置(build)、發佈(release)、執行(run)必須分開,避免環境污染。 解決方案: ...

October 31, 2025 · 2 min · 292 words · Eric Cheng

code review 指引

Code Review 指引 目錄 前言 1.1 目的 1.2 適用範圍 1.3 Code Review 的價值 Code Review 基本原則 2.1 核心原則 2.2 責任分工 Code Review 流程 3.1 提交 Pull Request (PR) 3.2 指定 Reviewers 3.3 進行程式碼檢查 詳細檢查項目 4.1 程式碼風格與規範 4.2 邏輯正確性檢查 4.3 效能考量檢查 4.4 安全性檢查 4.5 測試覆蓋率檢查 Code Review 工具與自動化 5.1 GitHub Pull Request Review 5.2 GitLab Merge Request Review 5.3 SonarQube 程式碼品質檢查 5.4 ESLint 與 Prettier(前端) 實務操作指南 6.1 Review 意見分類與標準 6.2 常見審查重點清單 6.3 溝通技巧與最佳實務 6.4 Review 會議與討論 常見問題與解決方案 7.1 常見 Review 問題 7.2 效率提升技巧 團隊協作與衝突處理 8.1 Review 意見衝突處理 8.2 跨團隊 Review 協作 8.3 新人培訓與指導 特殊情況處理 9.1 緊急修正流程 9.2 大型重構 Review 9.3 第三方程式碼整合 持續改進與測量 10.1 Review 品質指標 10.2 流程效率分析 10.3 團隊成長追蹤 參考資源與延伸閱讀 11.1 程式碼品質標準 11.2 安全性資源 11.3 工具文件 11.4 最佳實務書籍 附錄 12.1 Review 檢查清單範本 12.2 團隊 Code Review 文化建立 1. 前言 1.1 目的 本指引旨在建立標準化的程式碼審查流程,確保所有程式碼在合併至主分支前都經過充分的檢查與評審,以提升程式碼品質、降低潛在錯誤與技術負債,並促進團隊知識分享與技能提升。 ...

October 31, 2025 · 21 min · 4448 words · Eric Cheng

JdbcTemplate 安全 SQL 實作指引文件

以下是完整的 《JdbcTemplate 安全 SQL 實作指引文件》Markdown 版本,可直接放入你的 Git Repository(例如 /docs/security/jdbctemplate-sql-guideline.md),作為團隊安全規範或 Code Review checklist 使用。 # 🧭 JdbcTemplate 安全 SQL 實作指引文件 **版本:v1.0** **適用範圍:** Spring Boot 專案中使用 `JdbcTemplate` 或 `NamedParameterJdbcTemplate` 的資料存取層 **目的:** 防止 SQL Injection 攻擊與弱掃誤判 --- ## 1️⃣ 目的與原則 SQL Injection(SQL 注入)是 OWASP Top 10 的主要風險之一。 若在 API 中直接拼接 SQL 字串(尤其包含前端輸入),將導致弱掃報告出現 Injection 問題,甚至被惡意利用。 **安全實作原則:** 1. 所有 SQL 查詢必須採用 **參數化查詢(Parameterized Query)**。 2. 不可直接拼接使用者輸入字串到 SQL。 3. 動態欄位或排序需求必須使用 **白名單機制(Whitelist)**。 4. 禁止讓 client 直接傳入完整 SQL。 5. 所有 DB 帳號採用最小權限原則(Least Privilege)。 --- ## 2️⃣ 正確安全作法範例 ### ✅ 查詢範例 ```java String sql = "SELECT id, name, email FROM users WHERE email = ?"; return jdbcTemplate.query(sql, new Object[]{email}, new UserRowMapper()); ✅ 插入範例 String sql = "INSERT INTO users (name, email) VALUES (?, ?)"; jdbcTemplate.update(sql, name, email); ✅ 更新範例 String sql = "UPDATE users SET status = ? WHERE id = ?"; jdbcTemplate.update(sql, status, id); ✅ 刪除範例 String sql = "DELETE FROM users WHERE id = ?"; jdbcTemplate.update(sql, id); ✅ 動態查詢(條件可變) 重點:條件以程式判斷拼接,但參數仍使用 ? 綁定。 ...

October 31, 2025 · 3 min · 498 words · Eric Cheng

refactor 重構指引

重構指引(Refactoring Guide) 目錄 前言與目標 什麼是重構? 重構的核心目標 重構原則 基本原則 SOLID 原則在重構中的應用 重構時機 何時應該進行重構? 重構的紅綠燈系統 常見重構手法 提煉函數(Extract Function) 提煉類別(Extract Class) 簡化條件表達式(Simplify Conditional Expressions) 提煉常數(Extract Constants) 移除死程式碼(Remove Dead Code) 重構流程 標準重構流程 重構檢核清單 Java 重構最佳實務 IDE 重構工具使用 Maven 設定重構支援 重構中的測試策略 安全性考量 重構過程中的安全原則 重構中的資安檢核清單 效能考量 重構對效能的影響 效能測試與監控 重構工具與技術 靜態分析工具 自動化重構工具 持續整合中的重構 常見重構陷阱與解決方案 常見錯誤 最佳實務建議 重構案例研究 遺留系統重構 微服務重構 重構檢核清單 重構前檢核 重構中檢核 重構後檢核 團隊協作與重構 Code Review 中的重構 重構溝通策略 重構效果追蹤 短期追蹤 中期追蹤 長期追蹤 結論 前言與目標 什麼是重構? 重構(Refactoring)是指在不改變程式碼外部行為的前提下,對程式碼內部結構進行改善的過程。這是一種持續性的改進活動,旨在提升程式碼品質、可讀性和可維護性。 重構的核心目標 1. 提高可讀性 目標:讓程式碼更容易理解,降低未來維護的難度 效益: 新團隊成員能快速上手 減少程式碼理解時間 降低錯誤修改的風險 評估指標: 程式碼複雜度(Cyclomatic Complexity) 方法長度 類別職責單一性 2. 改善結構與設計 目標:優化架構,使程式更具彈性、可擴充性 效益: 更容易添加新功能 更好的模組化設計 符合 SOLID 設計原則 評估指標: 耦合度(Coupling) 內聚性(Cohesion) 設計模式使用適當性 3. 減少重複(DRY 原則) 目標:把重複的邏輯抽出來,讓程式碼更簡潔 效益: 減少程式碼維護成本 降低一致性問題 提高程式碼重用性 評估指標: 重複程式碼比例 共用元件使用率 4. 提升可測試性 目標:更清晰的結構有助於單元測試與整合測試 效益: 更容易編寫單元測試 提高測試覆蓋率 更好的依賴注入設計 評估指標: 測試覆蓋率 測試案例數量 模擬物件使用便利性 5. 降低技術債(Technical Debt) 目標:清理過時或混亂的程式碼,避免未來出現更多問題 效益: 提升開發效率 減少維護成本 降低系統風險 評估指標: SonarQube 品質評分 程式碼異味數量 安全漏洞數量 6. 促進團隊協作 目標:統一風格與結構,讓不同開發者更容易接手 效益: 提升團隊開發效率 降低知識傳承成本 統一開發標準 評估指標: 程式碼風格一致性 Code Review 效率 團隊生產力 重構原則 基本原則 1. 保持外部行為不變 重構過程中,程式的功能和對外介面不應改變 所有現有的測試案例應該繼續通過 使用者感受不到任何功能上的差異 2. 小步快跑 每次重構應該是小幅度的改動 頻繁進行測試驗證 避免大範圍的同時修改 3. 測試先行 重構前確保有足夠的測試覆蓋 重構過程中持續執行測試 新增測試案例以驗證重構結果 4. 循序漸進 按照優先順序進行重構 先解決最嚴重的程式碼異味 避免過度重構 SOLID 原則在重構中的應用 1. 單一職責原則(Single Responsibility Principle) // 重構前:一個類別負責多個職責 public class UserManager { public void saveUser(User user) { // 驗證使用者資料 if (user.getEmail() == null || !user.getEmail().contains("@")) { throw new IllegalArgumentException("Invalid email"); } // 儲存到資料庫 DatabaseConnection conn = new DatabaseConnection(); conn.save(user); // 發送通知郵件 EmailService emailService = new EmailService(); emailService.sendWelcomeEmail(user.getEmail()); } } // 重構後:職責分離 public class UserValidator { public void validate(User user) { if (user.getEmail() == null || !user.getEmail().contains("@")) { throw new IllegalArgumentException("Invalid email"); } } } public class UserRepository { public void save(User user) { DatabaseConnection conn = new DatabaseConnection(); conn.save(user); } } public class UserNotificationService { public void sendWelcomeNotification(String email) { EmailService emailService = new EmailService(); emailService.sendWelcomeEmail(email); } } public class UserService { private final UserValidator validator; private final UserRepository repository; private final UserNotificationService notificationService; public UserService(UserValidator validator, UserRepository repository, UserNotificationService notificationService) { this.validator = validator; this.repository = repository; this.notificationService = notificationService; } public void createUser(User user) { validator.validate(user); repository.save(user); notificationService.sendWelcomeNotification(user.getEmail()); } } 2. 開放封閉原則(Open/Closed Principle) // 重構前:修改現有程式碼來新增功能 public class DiscountCalculator { public double calculateDiscount(String customerType, double amount) { if ("REGULAR".equals(customerType)) { return amount * 0.05; } else if ("VIP".equals(customerType)) { return amount * 0.10; } else if ("PREMIUM".equals(customerType)) { return amount * 0.15; } return 0; } } // 重構後:使用策略模式,對擴展開放,對修改封閉 public interface DiscountStrategy { double calculateDiscount(double amount); } public class RegularCustomerDiscount implements DiscountStrategy { @Override public double calculateDiscount(double amount) { return amount * 0.05; } } public class VipCustomerDiscount implements DiscountStrategy { @Override public double calculateDiscount(double amount) { return amount * 0.10; } } public class PremiumCustomerDiscount implements DiscountStrategy { @Override public double calculateDiscount(double amount) { return amount * 0.15; } } public class DiscountCalculator { private final Map<String, DiscountStrategy> strategies; public DiscountCalculator() { strategies = Map.of( "REGULAR", new RegularCustomerDiscount(), "VIP", new VipCustomerDiscount(), "PREMIUM", new PremiumCustomerDiscount() ); } public double calculateDiscount(String customerType, double amount) { DiscountStrategy strategy = strategies.get(customerType); return strategy != null ? strategy.calculateDiscount(amount) : 0; } } 重構時機 何時應該進行重構? 1. 程式碼異味(Code Smells)出現時 長方法(Long Method):方法超過 20-30 行 大類別(Large Class):類別職責過多,超過 200-300 行 重複程式碼(Duplicated Code):相同或相似的程式碼片段重複出現 長參數列表(Long Parameter List):方法參數超過 3-4 個 2. 新增功能前 為新功能建立適當的架構基礎 清理相關的程式碼區域 確保新功能不會增加技術債 3. 修復 Bug 時 分析 Bug 產生的根本原因 改善可能導致類似問題的程式結構 增加相關的測試覆蓋 4. Code Review 過程中 發現程式碼可讀性問題 識別潛在的設計問題 統一團隊的程式碼風格 重構的紅綠燈系統 🟢 綠燈:適合重構 有充足的測試覆蓋(>80%) 沒有緊急的產品發布壓力 團隊對重構區域有充分了解 有足夠的時間進行測試驗證 🟡 黃燈:謹慎重構 測試覆蓋率中等(60-80%) 有適度的時間壓力 重構範圍較大 需要多人協作 🔴 紅燈:暫停重構 測試覆蓋率不足(<60%) 有緊急的產品發布 程式碼變動頻繁 缺乏領域知識 常見重構手法 1. 提煉函數(Extract Function) 目的 將重複的程式碼片段提煉成獨立的函數,提高重用性和可讀性。 ...

October 31, 2025 · 26 min · 5417 words · Eric Cheng

UIUX指引

UI/UX 開發指引 目錄 文件概述 設計原則 1.1 銀行系統的安全性與一致性要求 1.2 清晰的資訊層次與導航設計 1.3 金融資料顯示與重點突顯規範 1.4 無障礙設計(WCAG 2.1 AA 級) 1.5 多語系支援 UI 元件規範 2.1 樣式系統 (Design System) 2.2 響應式設計規範 2.3 常用元件設計 UX 流程規範 3.1 表單驗證與錯誤回饋 3.2 使用者引導 (Onboarding) 3.3 搜尋、篩選與排序互動設計 3.4 資料載入與狀態設計 設計資產與交付 4.1 設計 Token 系統 4.2 元件庫架構 Vue 3 + Tailwind CSS 最佳實踐 5.1 元件命名與結構規範 5.2 Tailwind CSS 自定義配置 檢測與驗證 6.1 視覺回歸測試 6.2 無障礙檢測 6.3 使用性測試清單 性能優化指引 設計協作流程 維護與版本控制 附錄 文件概述 本指引適用於大型金融共用平台的前端 UI/UX 開發,涵蓋設計原則、UI 元件規範、UX 流程、設計資產交付等完整內容。 專案技術堆疊 前端架構: 微前端 + SPA (Single-Page App— 第一部分完成,接下來我將繼續撰寫第二部分:無障礙設計和多語系支援。 1.4 無障礙設計(WCAG 2.1 AA 級) 色彩對比度要求 正常文字: 對比度至少 4.5:1 大文字 (18pt 以上): 對比度至少 3:1 互動元件: 對比度至少 3:1 /* Tailwind CSS 無障礙色彩配置 */ .text-primary { @apply text-gray-900; /* 對比度 21:1 */ } .text-secondary { @apply text-gray-700; /* 對比度 9.2:1 */ } .bg-interactive { @apply bg-blue-600 hover:bg-blue-700 focus:bg-blue-700; } .bg-interactive:focus { @apply ring-2 ring-blue-500 ring-offset-2; } 鍵盤操作支援 Tab 順序: 邏輯性的焦點移動順序 快捷鍵: 主要功能提供鍵盤快捷鍵 焦點指示: 清晰的焦點視覺回饋 <template> <!-- 無障礙表單範例 --> <form @submit.prevent="handleSubmit" class="space-y-6"> <div> <label for="account-number" class="block text-sm font-medium text-gray-700"> 帳戶號碼 <span class="text-red-500" aria-label="必填欄位">*</span> </label> <input id="account-number" v-model="accountNumber" type="text" required aria-describedby="account-help account-error" class="mt-1 block w-full rounded-md border-gray-300 shadow-sm focus:border-blue-500 focus:ring-blue-500" :aria-invalid="hasError" /> <p id="account-help" class="mt-2 text-sm text-gray-500"> 請輸入 12 位數字帳戶號碼 </p> <p id="account-error" v-if="errorMessage" class="mt-2 text-sm text-red-600" role="alert"> {{ errorMessage }} </p> </div> </form> </template> 螢幕閱讀器支援 語義化 HTML: 使用適當的 HTML 語義標籤 ARIA 標籤: 補充無障礙資訊 替代文字: 圖片提供有意義的 alt 文字 <template> <!-- 無障礙數據表格 --> <table role="table" aria-label="帳戶交易記錄"> <caption class="sr-only"> 最近 10 筆交易記錄,包含日期、摘要、金額和餘額 </caption> <thead> <tr> <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> 交易日期 </th> <th scope="col" class="px-6 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider"> 摘要 </th> <th scope="col" class="px-6 py-3 text-right text-xs font-medium text-gray-500 uppercase tracking-wider"> 金額 </th> </tr> </thead> <tbody> <tr v-for="transaction in transactions" :key="transaction.id"> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900"> {{ formatDate(transaction.date) }} </td> <td class="px-6 py-4 whitespace-nowrap text-sm text-gray-900"> {{ transaction.description }} </td> <td class="px-6 py-4 whitespace-nowrap text-sm text-right"> <span :class="transaction.amount >= 0 ? 'text-green-600' : 'text-red-600'"> {{ formatCurrency(transaction.amount) }} </span> </td> </tr> </tbody> </table> </template> 1.5 多語系支援 語言切換機制 // i18n 配置 import { createI18n } from 'vue-i18n'; interface Messages { 'zh-TW': Record<string, any>; 'en-US': Record<string, any>; 'ja-JP': Record<string, any>; } const messages: Messages = { 'zh-TW': { common: { confirm: '確認', cancel: '取消', save: '儲存', delete: '刪除', loading: '載入中...' }, account: { balance: '帳戶餘額', transfer: '轉帳', history: '交易記錄' } }, 'en-US': { common: { confirm: 'Confirm', cancel: 'Cancel', save: 'Save', delete: 'Delete', loading: 'Loading...' }, account: { balance: 'Account Balance', transfer: 'Transfer', history: 'Transaction History' } } }; export const i18n = createI18n({ locale: 'zh-TW', fallbackLocale: 'en-US', messages }); 文字方向支援 (RTL/LTR) <template> <div :dir="currentLocale.dir" class="app-container"> <!-- 語言切換器 --> <div class="language-selector"> <select v-model="currentLanguage" @change="changeLanguage" class="block w-32 rounded-md border-gray-300" :aria-label="$t('common.selectLanguage')" > <option value="zh-TW">繁體中文</option> <option value="en-US">English</option> <option value="ar-SA">العربية</option> </select> </div> </div> </template> <script setup lang="ts"> import { computed } from 'vue'; import { useI18n } from 'vue-i18n'; const { locale, t } = useI18n(); const localeConfig = { 'zh-TW': { dir: 'ltr', name: '繁體中文' }, 'en-US': { dir: 'ltr', name: 'English' }, 'ar-SA': { dir: 'rtl', name: 'العربية' } }; const currentLocale = computed(() => localeConfig[locale.value]); </script> <style> /* RTL 支援樣式 */ [dir="rtl"] .text-left { text-align: right; } [dir="rtl"] .text-right { text-align: left; } [dir="rtl"] .ml-4 { margin-left: 0; margin-right: 1rem; } </style> 數字與日期本地化 // 本地化格式化工具 export class LocaleFormatter { private locale: string; constructor(locale: string) { this.locale = locale; } // 金額格式化 formatCurrency(amount: number, currency: string = 'TWD'): string { const currencyMap = { 'zh-TW': 'TWD', 'en-US': 'USD', 'ja-JP': 'JPY', 'ko-KR': 'KRW' }; return new Intl.NumberFormat(this.locale, { style: 'currency', currency: currencyMap[this.locale] || currency, minimumFractionDigits: currency === 'JPY' ? 0 : 2 }).format(amount); } // 日期格式化 formatDate(date: Date, options?: Intl.DateTimeFormatOptions): string { const defaultOptions: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'short', day: 'numeric' }; return new Intl.DateTimeFormat(this.locale, options || defaultOptions).format(date); } // 時間格式化 formatTime(date: Date): string { return new Intl.DateTimeFormat(this.locale, { hour: '2-digit', minute: '2-digit', hour12: this.locale === 'en-US' }).format(date); } // 百分比格式化 formatPercentage(value: number): string { return new Intl.NumberFormat(this.locale, { style: 'percent', minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(value); } } 進階無障礙設計實作 <template> <!-- 高對比模式切換 --> <div class="accessibility-controls fixed top-4 right-4 z-50"> <button @click="toggleHighContrast" :aria-pressed="isHighContrast" class="p-2 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" :class="isHighContrast ? 'bg-yellow-400 text-black' : 'bg-white text-gray-700 shadow-md'" aria-label="切換高對比模式" > <EyeIcon class="h-5 w-5" /> </button> <!-- 字體大小調整 --> <div class="mt-2 flex flex-col space-y-1"> <button @click="increaseFontSize" class="p-1 text-xs bg-white rounded shadow-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500" aria-label="增大字體" > A+ </button> <button @click="decreaseFontSize" class="p-1 text-xs bg-white rounded shadow-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500" aria-label="縮小字體" > A- </button> <button @click="resetFontSize" class="p-1 text-xs bg-white rounded shadow-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500" aria-label="重設字體大小" > A </button> </div> </div> <!-- 跳至主要內容連結 --> <a href="#main-content" class="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 bg-blue-600 text-white px-4 py-2 rounded-md z-50" > 跳至主要內容 </a> <!-- 焦點陷阱對話框範例 --> <div v-if="showModal" class="fixed inset-0 z-10 overflow-y-auto" role="dialog" aria-modal="true" :aria-labelledby="modalTitleId" @keydown.esc="closeModal" > <div class="flex items-end justify-center min-h-screen pt-4 px-4 pb-20 text-center sm:block sm:p-0"> <!-- 背景遮罩 --> <div class="fixed inset-0 transition-opacity" aria-hidden="true" @click="closeModal" > <div class="absolute inset-0 bg-gray-500 opacity-75"></div> </div> <!-- 對話框內容 --> <div ref="modalContent" class="inline-block align-bottom bg-white rounded-lg text-left overflow-hidden shadow-xl transform transition-all sm:my-8 sm:align-middle sm:max-w-lg sm:w-full" @keydown="handleModalKeydown" > <div class="bg-white px-4 pt-5 pb-4 sm:p-6 sm:pb-4"> <h3 :id="modalTitleId" class="text-lg leading-6 font-medium text-gray-900 mb-4"> 確認操作 </h3> <p class="text-sm text-gray-500 mb-4"> 您確定要執行此操作嗎?此動作無法復原。 </p> <!-- 焦點陷阱內的可互動元素 --> <div class="flex justify-end space-x-3"> <button ref="cancelButton" @click="closeModal" class="px-4 py-2 text-sm font-medium text-gray-700 bg-white border border-gray-300 rounded-md hover:bg-gray-50 focus:outline-none focus:ring-2 focus:ring-blue-500" > 取消 </button> <button ref="confirmButton" @click="handleConfirm" class="px-4 py-2 text-sm font-medium text-white bg-red-600 border border-transparent rounded-md hover:bg-red-700 focus:outline-none focus:ring-2 focus:ring-red-500" > 確認 </button> </div> </div> </div> </div> </div> </template> <script setup lang="ts"> import { ref, nextTick, onMounted, onUnmounted } from 'vue'; import { EyeIcon } from '@heroicons/vue/24/outline'; // 無障礙狀態管理 const isHighContrast = ref(false); const fontSize = ref(16); const showModal = ref(false); const modalTitleId = ref(`modal-title-${Math.random().toString(36).substr(2, 9)}`); // 高對比模式 const toggleHighContrast = () => { isHighContrast.value = !isHighContrast.value; document.documentElement.classList.toggle('high-contrast', isHighContrast.value); // 儲存使用者偏好 localStorage.setItem('high-contrast', isHighContrast.value.toString()); // 通知螢幕閱讀器 announceToScreenReader( isHighContrast.value ? '已開啟高對比模式' : '已關閉高對比模式' ); }; // 字體大小調整 const increaseFontSize = () => { if (fontSize.value < 24) { fontSize.value += 2; updateFontSize(); } }; const decreaseFontSize = () => { if (fontSize.value > 12) { fontSize.value -= 2; updateFontSize(); } }; const resetFontSize = () => { fontSize.value = 16; updateFontSize(); }; const updateFontSize = () => { document.documentElement.style.fontSize = `${fontSize.value}px`; localStorage.setItem('font-size', fontSize.value.toString()); announceToScreenReader(`字體大小已調整為 ${fontSize.value} 像素`); }; // 螢幕閱讀器公告 const announceToScreenReader = (message: string) => { const announcement = document.createElement('div'); announcement.setAttribute('aria-live', 'polite'); announcement.setAttribute('aria-atomic', 'true'); announcement.className = 'sr-only'; announcement.textContent = message; document.body.appendChild(announcement); setTimeout(() => { document.body.removeChild(announcement); }, 1000); }; // 焦點陷阱管理 const modalContent = ref<HTMLElement>(); const cancelButton = ref<HTMLButtonElement>(); const confirmButton = ref<HTMLButtonElement>(); const focusableElements: HTMLElement[] = []; let previousFocusedElement: HTMLElement | null = null; const openModal = async () => { previousFocusedElement = document.activeElement as HTMLElement; showModal.value = true; await nextTick(); // 收集可聚焦元素 const selector = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'; focusableElements.splice(0); focusableElements.push( ...Array.from(modalContent.value?.querySelectorAll(selector) || []) ); // 聚焦第一個元素 if (focusableElements.length > 0) { focusableElements[0].focus(); } }; const closeModal = () => { showModal.value = false; // 恢復之前的焦點 if (previousFocusedElement) { previousFocusedElement.focus(); } }; const handleModalKeydown = (event: KeyboardEvent) => { if (event.key === 'Tab') { const currentIndex = focusableElements.indexOf(event.target as HTMLElement); if (event.shiftKey) { // Shift + Tab (向後) if (currentIndex <= 0) { event.preventDefault(); focusableElements[focusableElements.length - 1].focus(); } } else { // Tab (向前) if (currentIndex >= focusableElements.length - 1) { event.preventDefault(); focusableElements[0].focus(); } } } }; const handleConfirm = () => { // 處理確認邏輯 announceToScreenReader('操作已確認'); closeModal(); }; // 鍵盤快捷鍵 const handleGlobalKeydown = (event: KeyboardEvent) => { // Alt + H: 開啟高對比模式 if (event.altKey && event.key === 'h') { event.preventDefault(); toggleHighContrast(); } // Alt + +: 增大字體 if (event.altKey && event.key === '+') { event.preventDefault(); increaseFontSize(); } // Alt + -: 縮小字體 if (event.altKey && event.key === '-') { event.preventDefault(); decreaseFontSize(); } }; // 初始化無障礙設定 onMounted(() => { // 載入儲存的偏好設定 const savedHighContrast = localStorage.getItem('high-contrast'); if (savedHighContrast === 'true') { isHighContrast.value = true; document.documentElement.classList.add('high-contrast'); } const savedFontSize = localStorage.getItem('font-size'); if (savedFontSize) { fontSize.value = parseInt(savedFontSize); document.documentElement.style.fontSize = `${fontSize.value}px`; } // 監聽全域鍵盤事件 document.addEventListener('keydown', handleGlobalKeydown); // 檢測用戶偏好的配色方案 if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { document.documentElement.classList.add('dark-mode'); } // 檢測用戶偏好的動畫設定 if (window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches) { document.documentElement.classList.add('reduce-motion'); } }); onUnmounted(() => { document.removeEventListener('keydown', handleGlobalKeydown); }); </script> <style> /* 高對比模式樣式 */ .high-contrast { filter: contrast(150%) saturate(200%); } .high-contrast button { border: 2px solid currentColor !important; } .high-contrast a { text-decoration: underline !important; } /* 減少動畫模式 */ .reduce-motion *, .reduce-motion *::before, .reduce-motion *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; } /* 深色模式支援 */ .dark-mode { color-scheme: dark; } /* 螢幕閱讀器專用隱藏 */ .sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; } .sr-only.focus:focus { position: static; width: auto; height: auto; padding: inherit; margin: inherit; overflow: visible; clip: auto; white-space: normal; } </style> 多語系內容管理策略 // 多語系內容管理 interface TranslationKey { key: string; context?: string; pluralization?: boolean; interpolation?: string[]; } interface TranslationContent { [locale: string]: { [namespace: string]: { [key: string]: string | object; }; }; } // 翻譯內容結構化管理 export const translationStructure: TranslationContent = { 'zh-TW': { common: { // 通用操作 actions: { save: '儲存', cancel: '取消', confirm: '確認', delete: '刪除', edit: '編輯', add: '新增', search: '搜尋', filter: '篩選', sort: '排序', refresh: '重新整理', loading: '載入中...', noData: '無資料', error: '發生錯誤' }, // 表單相關 form: { required: '此欄位為必填', invalid: '格式不正確', tooShort: '長度不足', tooLong: '長度過長', emailInvalid: '請輸入有效的電子郵件地址', phoneInvalid: '請輸入有效的手機號碼', passwordMismatch: '密碼不一致' }, // 時間相關 time: { just_now: '剛剛', minutes_ago: '{count} 分鐘前', hours_ago: '{count} 小時前', days_ago: '{count} 天前', weeks_ago: '{count} 週前', months_ago: '{count} 個月前', years_ago: '{count} 年前' } }, // 金融業務相關 banking: { account: { balance: '帳戶餘額', available: '可用餘額', accountNumber: '帳戶號碼', accountType: '帳戶類型', status: '狀態', openDate: '開戶日期', lastTransaction: '最近交易' }, transaction: { transfer: '轉帳', deposit: '存款', withdrawal: '提款', payment: '付款', refund: '退款', fee: '手續費', amount: '金額', recipient: '收款人', description: '摘要', reference: '參考號碼', date: '交易日期', status: '交易狀態' }, status: { active: '啟用', inactive: '停用', pending: '處理中', completed: '已完成', failed: '失敗', cancelled: '已取消', expired: '已過期' } } }, 'en-US': { common: { actions: { save: 'Save', cancel: 'Cancel', confirm: 'Confirm', delete: 'Delete', edit: 'Edit', add: 'Add', search: 'Search', filter: 'Filter', sort: 'Sort', refresh: 'Refresh', loading: 'Loading...', noData: 'No Data', error: 'Error Occurred' }, form: { required: 'This field is required', invalid: 'Invalid format', tooShort: 'Too short', tooLong: 'Too long', emailInvalid: 'Please enter a valid email address', phoneInvalid: 'Please enter a valid phone number', passwordMismatch: 'Passwords do not match' }, time: { just_now: 'Just now', minutes_ago: '{count} minutes ago', hours_ago: '{count} hours ago', days_ago: '{count} days ago', weeks_ago: '{count} weeks ago', months_ago: '{count} months ago', years_ago: '{count} years ago' } }, banking: { account: { balance: 'Account Balance', available: 'Available Balance', accountNumber: 'Account Number', accountType: 'Account Type', status: 'Status', openDate: 'Open Date', lastTransaction: 'Last Transaction' }, transaction: { transfer: 'Transfer', deposit: 'Deposit', withdrawal: 'Withdrawal', payment: 'Payment', refund: 'Refund', fee: 'Fee', amount: 'Amount', recipient: 'Recipient', description: 'Description', reference: 'Reference Number', date: 'Transaction Date', status: 'Transaction Status' }, status: { active: 'Active', inactive: 'Inactive', pending: 'Pending', completed: 'Completed', failed: 'Failed', cancelled: 'Cancelled', expired: 'Expired' } } } }; // 進階翻譯函數 export class I18nManager { private currentLocale: string = 'zh-TW'; private fallbackLocale: string = 'en-US'; // 設定當前語言 setLocale(locale: string) { this.currentLocale = locale; document.documentElement.setAttribute('lang', locale); // 更新頁面方向 const direction = this.getTextDirection(locale); document.documentElement.setAttribute('dir', direction); // 觸發語言變更事件 window.dispatchEvent(new CustomEvent('localeChanged', { detail: { locale, direction } })); } // 取得文字方向 private getTextDirection(locale: string): 'ltr' | 'rtl' { const rtlLocales = ['ar', 'he', 'fa', 'ur']; return rtlLocales.some(rtl => locale.startsWith(rtl)) ? 'rtl' : 'ltr'; } // 翻譯函數 translate(key: string, options?: { interpolation?: Record<string, any>; count?: number; defaultValue?: string; }): string { const keys = key.split('.'); let value = this.getNestedValue(translationStructure[this.currentLocale], keys); // 如果找不到翻譯,嘗試備用語言 if (!value) { value = this.getNestedValue(translationStructure[this.fallbackLocale], keys); } // 如果還是找不到,返回預設值或 key if (!value) { return options?.defaultValue || key; } // 處理插值 if (options?.interpolation) { Object.entries(options.interpolation).forEach(([placeholder, replacement]) => { value = value.replace(new RegExp(`{${placeholder}}`, 'g'), replacement); }); } // 處理複數形式 if (options?.count !== undefined) { value = this.handlePluralization(value, options.count); } return value; } // 取得巢狀物件值 private getNestedValue(obj: any, keys: string[]): string | null { return keys.reduce((current, key) => current?.[key], obj) || null; } // 處理複數形式 private handlePluralization(value: string, count: number): string { // 簡化的複數處理,實際應用中可能需要更複雜的邏輯 if (this.currentLocale === 'en-US') { if (count === 1) { return value.replace(/\{count\}/, count.toString()); } else { // 處理英文複數規則 return value.replace(/\{count\}/, count.toString()); } } return value.replace(/\{count\}/, count.toString()); } // 格式化相對時間 formatRelativeTime(date: Date): string { const now = new Date(); const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000); if (diffInSeconds < 60) { return this.translate('common.time.just_now'); } else if (diffInSeconds < 3600) { const minutes = Math.floor(diffInSeconds / 60); return this.translate('common.time.minutes_ago', { interpolation: { count: minutes.toString() } }); } else if (diffInSeconds < 86400) { const hours = Math.floor(diffInSeconds / 3600); return this.translate('common.time.hours_ago', { interpolation: { count: hours.toString() } }); } else if (diffInSeconds < 604800) { const days = Math.floor(diffInSeconds / 86400); return this.translate('common.time.days_ago', { interpolation: { count: days.toString() } }); } else if (diffInSeconds < 2419200) { const weeks = Math.floor(diffInSeconds / 604800); return this.translate('common.time.weeks_ago', { interpolation: { count: weeks.toString() } }); } else if (diffInSeconds < 29030400) { const months = Math.floor(diffInSeconds / 2419200); return this.translate('common.time.months_ago', { interpolation: { count: months.toString() } }); } else { const years = Math.floor(diffInSeconds / 29030400); return this.translate('common.time.years_ago', { interpolation: { count: years.toString() } }); } } } // 全域 i18n 實例 export const i18nManager = new I18nManager(); // Vue 組合函數 export function useI18n() { const t = (key: string, options?: any) => i18nManager.translate(key, options); const setLocale = (locale: string) => i18nManager.setLocale(locale); const formatRelativeTime = (date: Date) => i18nManager.formatRelativeTime(date); return { t, setLocale, formatRelativeTime }; } 無障礙設計和多語系支援擴充完成,接下來我將繼續撰寫第三部分:UI 元件規範。 ...

October 31, 2025 · 34 min · 7053 words · Eric Cheng