Thymeleaf使用教學

Thymeleaf 使用教學手冊 目錄 基礎概念 1.1 什麼是 Thymeleaf? 1.2 核心特色 1.2.1 自然模板特性 1.2.2 表達式豐富性 1.3 與其他模板引擎比較 1.3.1 詳細比較表 1.4 適用場景 1.5 工作流程 1.6 實務注意事項 環境建置 2.1 Spring Boot 專案整合 2.1.1 Maven 設定 2.1.2 Gradle 設定 2.2 目錄結構設定 2.3 應用程式設定 2.3.1 基本設定 (application.yml) 2.3.2 生產環境設定 (application-prod.yml) 2.4 IDE 設定 2.4.1 IntelliJ IDEA 設定 2.4.2 VS Code 設定 2.5 驗證安裝 2.5.1 建立控制器 2.5.2 建立模板 2.5.3 啟動應用程式 2.6 開發環境最佳化 2.6.1 熱重載設定 2.6.2 除錯設定 2.7 常見安裝問題 語法教學 3.1 基本標籤語法 3.1.1 文字輸出 (th:text) 3.1.2 HTML 輸出 (th:utext) 3.1.3 條件判斷 (th:if, th:unless) 3.1.4 迴圈遍歷 (th:each) 3.1.5 條件選擇 (th:switch, th:case) 3.2 屬性處理 3.2.1 設定屬性 (th:attr) 3.2.2 常用屬性快捷方式 3.2.3 CSS 類別處理 (th:classappend) 3.3 表達式語法 3.3.1 變數表達式 (${…}) 3.3.2 選擇表達式 (*{…}) 3.3.3 連結表達式 (@{…}) 3.3.4 訊息表達式 (#{…}) 3.3.5 片段表達式 (~{…}) 3.4 內建工具物件 3.4.1 日期工具 (#dates) 3.4.2 數字工具 (#numbers) 3.4.3 字串工具 (#strings) 3.4.4 集合工具 (#lists, #sets, #maps) 3.5 實務注意事項 3.6 模板繼承與片段 3.6.1 片段定義與使用 (th:fragment) 3.6.2 片段插入方式 3.6.3 佈局模板系統 3.6.4 參數化片段 3.6.5 片段選擇器 3.7 實務注意事項 3.8 表單處理 3.8.1 基本表單綁定 3.8.2 下拉選單處理 3.8.3 核取方塊處理 3.8.4 單選按鈕處理 3.8.5 檔案上傳處理 3.8.6 表單驗證錯誤處理 3.9 國際化 (i18n) 支援 3.9.1 設定國際化 3.9.2 訊息資源檔案 3.9.3 在模板中使用國際化 3.9.4 Java 程式碼中的國際化 3.9.5 日期和數字本地化 3.9.6 進階國際化實作 3.9.7 多語言最佳實務 3.10 實務注意事項 實務應用範例 ...

October 31, 2025 · 45 min · 9455 words · Eric Cheng

UI_UX設計指引範本

UI/UX 設計指引範本 Prompt 目標 指導 AI 進行用戶體驗和使用者介面設計,建立以使用者為中心的設計規範和原型。 角色設定 你是一位資深 UI/UX 設計師,具備豐富的使用者體驗設計經驗,熟悉設計思維、使用者研究方法和現代化介面設計原則。 任務描述 請協助我完成 {專案名稱} 的 UI/UX 設計工作。 專案設計背景 專案名稱: {填入專案名稱} 產品類型: {填入產品類型,如:Web應用、Mobile App、桌面應用} 目標使用者: {填入主要使用者群體} 使用情境: {填入主要使用場景} 設計風格偏好: {填入設計風格,如:簡約、現代、傳統} 品牌色彩: {填入品牌主色調} UI/UX 設計要求 請按照以下結構進行設計: 1. 使用者研究 使用者角色建立 使用者旅程地圖 痛點分析 需求優先級排序 2. 資訊架構設計 內容結構規劃 導航系統設計 資訊層級設計 搜尋和篩選機制 3. 互動設計 使用者流程設計 互動原型設計 微互動設計 回饋機制設計 4. 視覺設計 設計系統建立 色彩配置方案 字體系統設計 圖示和插圖風格 5. 響應式設計 多裝置適配策略 斷點設計規劃 彈性佈局設計 觸控優化設計 6. 無障礙設計 可及性標準遵循 色彩對比度檢查 鍵盤導航支援 螢幕閱讀器相容 輸出格式 # {專案名稱} UI/UX 設計規格 ## 1. 使用者研究 ### 1.1 使用者角色 (Personas) #### 主要使用者角色: [角色名稱] **基本資訊:** - 年齡: [年齡範圍] - 職業: [職業類型] - 技術熟悉度: [初級/中級/高級] - 使用裝置: [主要使用的裝置] **目標和需求:** - 主要目標: [使用者想要達成的目標] - 次要需求: [附加需求清單] - 成功指標: [如何衡量目標達成] **行為特徵:** - 使用習慣: [典型的使用模式] - 偏好: [介面和功能偏好] - 挫折點: [常見的困擾] **情境描述:** "[一段描述使用者在什麼情況下會使用這個產品的情境故事]" ### 1.2 使用者旅程地圖 #### 旅程階段1: 發現階段 **使用者行為:** [使用者在此階段的行為] **想法和感受:** [使用者的心理狀態] **觸點:** [與產品的接觸點] **痛點:** [遇到的問題] **機會點:** [改善的機會] #### 旅程階段2: 探索階段 **使用者行為:** [行為描述] **想法和感受:** [心理狀態] **觸點:** [接觸點] **痛點:** [問題點] **機會點:** [改善機會] #### 旅程階段3: 使用階段 **使用者行為:** [行為描述] **想法和感受:** [心理狀態] **觸點:** [接觸點] **痛點:** [問題點] **機會點:** [改善機會] ### 1.3 痛點分析矩陣 | 痛點 | 頻率 | 嚴重性 | 影響範圍 | 解決優先級 | |------|------|--------|----------|------------| | [痛點1] | [高/中/低] | [高/中/低] | [影響的使用者比例] | [高/中/低] | | [痛點2] | [高/中/低] | [高/中/低] | [影響的使用者比例] | [高/中/低] | ## 2. 資訊架構設計 ### 2.1 網站地圖 首頁 ├── 產品/服務 │ ├── 產品分類A │ ├── 產品分類B │ └── 產品詳細頁 ├── 關於我們 │ ├── 公司介紹 │ ├── 團隊介紹 │ └── 聯絡我們 ├── 支援中心 │ ├── 常見問題 │ ├── 使用指南 │ └── 技術支援 └── 使用者帳戶 ├── 個人資料 ├── 訂單記錄 └── 設定 ...

October 31, 2025 · 5 min · 994 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

Vim使用教學

Vim 使用教學手冊 目錄 前言 Vim 在專案中的角色 為什麼要學習 Vim 本手冊的學習方式與使用建議 第一篇:Vim 基礎入門 1. Vim 簡介 2. 安裝與環境設定 3. Vim 的操作模式 4. 文字編輯基礎 5. 檔案操作 第二篇:進階編輯技巧 6. 搜尋與取代 7. 巨集與自動化 8. 多檔案編輯與快速導覽 9. 文本處理進階 第三篇:專案開發實務 10. Vim 與程式開發 11. 插件管理 12. Git 與版本控制整合 13. 日常開發案例 第四篇:考試與認證準備 14. Vim 認證簡介 15. 模擬練習題 16. 學習路線圖 附錄 Vim 常用快捷鍵速查表 常見錯誤排解 推薦書籍與網站 練習建議 檢查清單(Checklist) 新進成員 Vim 技能檢查清單 團隊協作檢查清單 系統管理檢查清單 持續改進檢查清單 前言 Vim 在專案中的角色 在現代軟體開發專案中,Vim 扮演著重要的角色: Linux 伺服器管理必備工具:在產品環境中進行設定檔編輯、日誌查看、緊急修復 高效率文字編輯器:相較於圖形界面編輯器,Vim 在純文字環境下具有絕對優勢 跨平台一致性:無論在 Linux、macOS 或 Windows 環境,Vim 都能提供相同的操作體驗 與開發工具整合:許多現代 IDE 都提供 Vim 模式,學會 Vim 能提升整體開發效率 為什麼要學習 Vim mindmap root((為什麼學 Vim)) 效率提升 快速編輯 鍵盤操作 減少滑鼠依賴 專業需求 Linux 系統管理 遠端作業 伺服器維護 認證考試 LPIC RHCE CompTIA Linux+ 技能發展 文字處理專精 自動化能力 工具整合 本手冊的學習方式與使用建議 循序漸進:建議按章節順序學習,每章都有實作練習 動手實作:理論與實務並重,務必完成每章的練習題 日常應用:將學會的技巧應用到實際專案開發中 認證導向:標註的認證重點可作為考試準備參考 第一篇:Vim 基礎入門 1. Vim 簡介 簡介 Vim(Vi IMproved)是基於經典的 Vi 編輯器所改良的文字編輯器,是 Unix/Linux 系統中最重要的編輯工具之一。 ...

October 31, 2025 · 35 min · 7268 words · Eric Cheng

Visual Studio Code使用教學

Visual Studio Code 使用教學手冊 完整的 VS Code 開發環境設定與實戰指南 涵蓋前端 (Vue 3 + TypeScript) 與後端 (Spring Boot) 開發,適用於團隊協作與企業級專案開發 📋 目錄 1. VS Code 安裝與環境設定 1.1 安裝步驟 1.2 推薦字型與主題 1.3 專案必要的 Extensions 清單 1.3.1 基礎開發工具 1.3.2 前端開發 1.3.3 後端開發 1.3.4 Python 開發 1.3.5 AI 輔助開發 1.3.6 一鍵安裝指令 1.4 設定同步功能 1.5 實務案例與注意事項 2. 專案開發環境配置 2.1 如何開啟專案 2.2 前端、後端工作區設定 2.3 編碼規範設定 2.3.1 前端編碼規範 (ESLint + Prettier) 2.3.2 後端編碼規範 (Checkstyle) 2.3.3 Maven 獨立安裝設定 2.4 容器化開發環境 (Dev Containers) 2.5 實務案例與注意事項 3. 日常開發操作 3.1 Git 與 GitHub/GitLab 整合 3.2 常用快捷鍵 3.3 偵錯與斷點設定 3.4 終端機與多工作區使用 3.5 程式碼片段 (Snippets) 使用 3.6 AI 輔助開發 — GitHub Copilot 3.6.1 程式碼自動完成與 Next Edit Suggestions 3.6.2 Copilot Chat 對話式助手 3.6.3 Inline Chat(行內聊天) 3.6.4 智慧動作 3.6.5 Agent 模式與 Agent Sessions 3.6.6 Autopilot 與 Agent 權限控制 3.6.7 Plan Agent(計畫代理) 3.6.8 自訂指示檔 3.6.9 MCP 伺服器整合 3.6.10 Custom Agents(自訂代理) 3.6.11 Agent Skills(代理技能) 3.6.12 Prompt Files(提示檔案) 3.6.13 Hooks(生命週期鉤子) 3.6.14 語言模型選擇 3.7 實務案例與注意事項 4. 專案特定開發流程指引 4.1 前端開發流程 4.2 後端開發流程 4.3 全端開發工作流程 4.4 程式碼品質檢查 4.5 效能監控與分析 4.6 實務案例與注意事項 4.7 Python 開發環境設定 4.7.1 Python 專案結構 4.7.2 Python 環境設定 4.7.3 Python 開發工具設定 4.7.4 Python 偵錯設定 4.7.5 Python 任務設定 4.7.6 Python 專案範例 4.7.7 Python 開發最佳實務 5. 協作開發功能 5.1 Live Share 即時協作 5.2 多人開發設定 5.3 程式碼審查工具 5.3.1 GitHub Pull Request 整合 5.3.2 GitLab Merge Request 整合 5.3.3 程式碼審查檢查清單 5.4 團隊協作最佳實務 6. 進階功能與擴充 6.1 自訂程式碼片段 6.2 擴充功能開發入門 6.3 工作流程自動化 6.3.1 Task 自動化 6.3.2 GitHub Actions 整合 6.3.3 GitLab CI/CD 整合 6.4 效能優化進階技巧 6.5 遠端開發與 SSH 6.6 工作區管理進階技巧 6.7 設定檔 (Profiles) 管理 6.8 Chat Customizations 編輯器 7. 最佳實務 7.1 常見問題 (FAQ) 與解決方式 7.2 建議的工作習慣 7.3 效能最佳化 7.4 安全性最佳實務 7.5 團隊協作規範 8. 檢查清單 8.1 新進成員快速上手檢查清單 8.2 日常開發檢查清單 8.3 部署前檢查清單 8.4 故障排除檢查清單 9. 附錄 9.1 參考資源 9.2 版本歷程 1. VS Code 安裝與環境設定 1.1 安裝步驟 1.1.1 下載與安裝 前往 Visual Studio Code 官方網站 點擊 “Download for Windows” 下載安裝檔 執行安裝檔,建議勾選以下選項: ✅ 新增至 PATH (在重新啟動後可用) ✅ 在檔案總管中的檔案上顯示「使用 Code 開啟」動作 ✅ 在檔案總管中的目錄上顯示「使用 Code 開啟」動作 ✅ 將 Code 註冊為支援的檔案類型的編輯器 1.1.2 首次啟動設定 啟動 VS Code 選擇適合的色彩主題 登入 Microsoft 帳戶(可選,用於同步設定) 1.2 推薦字型與主題 1.2.1 推薦字型 建議安裝並使用以下等寬字型: ...

October 31, 2025 · 43 min · 9002 words · Eric Cheng

Vue3 前端framework教學

Vue 3.x 前端 Framework 教學手冊 📘 適用對象:完全沒有學過 Vue 3 的新進開發同仁 🎯 學習目標:循序漸進掌握 Vue 3.x 開發技能,並具備專案實戰能力 🏆 認證準備:涵蓋 Vue 3 官方認證考試重點 📖 目錄 第一章:Vue 3 基礎入門 1.1 什麼是 Vue.js? 1.2 開發環境建置 1.3 第一個 Vue 3 應用 1.4 專案應用指引 1.5 認證考點提示 第二章:Composition API 深入 2.1 setup() 函數詳解 2.2 組合式函數 (Composables) 2.3 進階組合式函數範例 2.4 專案應用指引 2.5 認證考點提示 第三章:響應式系統 3.1 ref() 與 reactive() 詳解 3.2 深度響應式與淺層響應式 3.3 computed() 計算屬性 3.4 watch() 與 watchEffect() 3.5 響應式工具函數 3.6 專案應用指引 3.7 認證考點提示 第四章:模板語法與指令 ...

October 31, 2025 · 56 min · 11798 words · Eric Cheng

使用指南

SSDLC Prompt 範本使用指南 概述 本文檔提供完整的 SSDLC (Secure Software Development Life Cycle) Prompt 範本使用指南,協助團隊透過 AI 輔助完成各階段的開發任務。 範本結構說明 目錄組織 .github/prompts/ ├── SSDLC_專案範本指南.md # 主要指南文檔 ├── 需求分析/ │ ├── 業務需求收集範本.md │ ├── 功能需求分析範本.md │ ├── 安全需求識別範本.md │ └── 使用者故事撰寫範本.md ├── 設計開發/ │ ├── 系統架構設計範本.md │ ├── API設計範本.md │ └── [其他設計範本] ├── 測試驗收/ │ ├── 測試策略制定範本.md │ ├── 自動化測試範本.md │ └── [其他測試範本] └── 部署運維/ ├── CI_CD流程範本.md └── [其他運維範本] 快速開始指南 步驟 1: 選擇適當的範本 根據當前專案階段選擇對應的範本: 需求分析階段: 從業務需求收集開始 設計開發階段: 從系統架構設計開始 測試驗收階段: 從測試策略制定開始 部署運維階段: 從 CI/CD 流程設計開始 步驟 2: 填寫專案資訊 每個範本都包含專案背景資訊區塊,需要填入: ...

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

使用者故事撰寫範本

使用者故事撰寫範本 Prompt 目標 指導 AI 撰寫高品質的使用者故事,包含完整的驗收標準和估算資訊。 角色設定 你是一位敏捷開發專家和產品負責人,具備豐富的使用者故事撰寫經驗,熟悉敏捷開發方法論和最佳實務。 任務描述 請協助我為 {專案名稱} 撰寫完整的使用者故事集合。 專案背景資訊 專案名稱: {填入專案名稱} 產品類型: {填入產品類型} 目標使用者: {填入主要使用者群體} 業務目標: {填入主要業務目標} Sprint 週期: {填入 Sprint 長度,如:2週} 故事撰寫要求 請按照以下標準撰寫使用者故事: 1. 故事結構 使用標準的「作為…我希望…以便…」格式 包含明確的角色定義 描述具體的功能需求 說明清楚的價值目標 2. 驗收標準 使用 Given-When-Then 格式 涵蓋正常流程和例外情況 包含可測試的條件 定義明確的完成標準 3. 故事估算 使用故事點數進行估算 考慮複雜度、工作量和風險 提供估算理由說明 建議任務分解方式 4. 優先級排序 定義業務價值優先級 考慮技術相依性 評估風險和不確定性 建議開發順序 輸出格式 # {專案名稱} 使用者故事清單 ## 產品願景 {產品願景陳述} ## 使用者角色 (Personas) ### 角色1: [角色名稱] **角色描述:** [詳細描述] **主要目標:** [目標列表] **技術能力:** [技術水平] **使用情境:** [典型使用場景] **痛點問題:** [主要困擾] ## Epic 史詩故事 ### Epic 1: [Epic 名稱] **Epic 描述:** [Epic 整體描述] **業務價值:** [價值說明] **成功指標:** [衡量標準] **相關使用者:** [涉及的使用者角色] ## 使用者故事清單 ### 故事 ID: US001 **故事標題:** [簡短描述性標題] **使用者故事:** 作為 [使用者角色] 我希望 [功能描述] 以便 [價值/目標] **商業價值:** [具體的商業價值描述] **驗收標準:** #### 場景1: [正常流程場景] **Given** [前置條件] **When** [執行動作] **Then** [預期結果] #### 場景2: [替代流程場景] **Given** [前置條件] **When** [執行動作] **Then** [預期結果] #### 場景3: [例外處理場景] **Given** [前置條件] **When** [執行動作] **Then** [預期結果] **定義完成 (Definition of Done):** - [ ] [完成條件1] - [ ] [完成條件2] - [ ] [完成條件3] - [ ] 通過所有自動化測試 - [ ] 通過程式碼審查 - [ ] 更新相關文檔 **故事點數:** [點數] 點 **估算理由:** [估算考量因素] **優先級:** [高/中/低] **優先級理由:** [排序理由] **相依性:** - 前置需求: [相依的其他故事] - 阻擋因素: [可能的阻礙] **備註:** [額外的技術或業務考量] --- ### 故事 ID: US002 [重複上述格式...] ## 故事地圖 (Story Map) ### 使用者旅程階段1: [階段名稱] - US001: [故事標題] - US002: [故事標題] ### 使用者旅程階段2: [階段名稱] - US003: [故事標題] - US004: [故事標題] ## Release 規劃 ### Release 1.0 (MVP) **發布目標:** [MVP 目標] **包含故事:** US001, US002, US003 **預計時間:** [時間範圍] **風險評估:** [主要風險點] ### Release 2.0 **發布目標:** [下一版本目標] **包含故事:** US004, US005, US006 **預計時間:** [時間範圍] ## 故事估算總結 | 故事ID | 故事標題 | 故事點數 | 優先級 | Sprint | |--------|----------|----------|--------|--------| | US001 | [標題] | [點數] | [優先級] | [建議Sprint] | | US002 | [標題] | [點數] | [優先級] | [建議Sprint] | **總估算:** [總點數] 故事點數 **預估 Sprint 數:** [Sprint 數量] 故事撰寫最佳實務 INVEST 原則 Independent (獨立): 故事應該盡可能獨立 Negotiable (可協商): 細節可以協商調整 Valuable (有價值): 對使用者有明確價值 Estimable (可估算): 團隊能夠估算工作量 Small (小): 在一個 Sprint 內完成 Testable (可測試): 有明確的驗收標準 3C 模型 Card (卡片): 故事的簡短描述 Conversation (對話): 團隊間的討論 Confirmation (確認): 驗收標準 故事拆分技巧 按工作流程拆分: 將大故事按步驟分解 按資料變化拆分: 根據不同資料類型分解 按操作拆分: 增加、修改、刪除、查詢 按使用者角色拆分: 不同角色的需求 按介面拆分: Web、Mobile、API 品質檢查清單 使用標準的故事格式 包含明確的使用者角色 描述具體的功能需求 說明清楚的價值目標 提供完整的驗收標準 包含正常和例外流程 故事大小適中 (1-8 點) 具備可測試性 標示優先級和相依性 符合 INVEST 原則 使用範例 範例:線上書店購物車功能 使用者角色 角色: 線上購書者 描述: 25-45歲的上班族,喜歡閱讀,經常在線上購買書籍 目標: 方便快速地選購和購買書籍 技術能力: 中等,熟悉基本網路操作 ...

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

前端開發指引

前端開發指引 文件資訊 版本: 1.0.0 建立日期: 2025-08-11 適用專案: 大型金融級 Web 專案 技術棧: Vue 3.x + TypeScript + Tailwind CSS 目錄 專案目錄與檔案結構規範 命名規範 程式撰寫風格與 Lint 設定 元件開發規範 樣式與 Tailwind CSS 規範 API 串接與資料存取規範 狀態管理規範 多語系處理規範 測試規範 安全性考量 效能優化規範 無障礙設計規範 版本控制與分支策略 專案建置與部署流程 程式碼審查規範 常見錯誤處理與 Debug 流程 開發工具與環境設定 團隊協作與溝通規範 1. 專案目錄與檔案結構規範 1.1 標準專案結構 frontend-project/ ├── public/ # 靜態資源 │ ├── favicon.ico │ ├── index.html │ └── manifest.json ├── src/ # 原始碼 │ ├── api/ # API 相關 │ │ ├── modules/ # 依功能模組分類 │ │ │ ├── auth.ts │ │ │ └── user.ts │ │ ├── interceptors/ # 攔截器 │ │ └── types/ # API 型別定義 │ ├── assets/ # 靜態資源 │ │ ├── images/ │ │ ├── icons/ │ │ └── fonts/ │ ├── components/ # 共用元件 │ │ ├── base/ # 基礎元件 │ │ │ ├── BaseButton.vue │ │ │ ├── BaseInput.vue │ │ │ └── BaseModal.vue │ │ ├── business/ # 業務元件 │ │ └── layout/ # 版面元件 │ │ ├── Header.vue │ │ ├── Sidebar.vue │ │ └── Footer.vue │ ├── composables/ # Vue 3 Composition API │ │ ├── useAuth.ts │ │ ├── useApi.ts │ │ └── useLocalStorage.ts │ ├── constants/ # 常數定義 │ │ ├── api.ts │ │ ├── routes.ts │ │ └── config.ts │ ├── directives/ # 自定義指令 │ ├── i18n/ # 多語系 │ │ ├── locales/ │ │ │ ├── zh-TW.json │ │ │ ├── en-US.json │ │ │ └── ja-JP.json │ │ └── index.ts │ ├── layouts/ # 版面配置 │ │ ├── DefaultLayout.vue │ │ ├── AuthLayout.vue │ │ └── EmptyLayout.vue │ ├── middleware/ # 中間件 │ │ ├── auth.ts │ │ └── permission.ts │ ├── pages/ # 頁面元件 │ │ ├── auth/ │ │ │ ├── Login.vue │ │ │ └── Register.vue │ │ ├── dashboard/ │ │ └── user/ │ ├── plugins/ # 插件 │ │ ├── axios.ts │ │ ├── i18n.ts │ │ └── router.ts │ ├── router/ # 路由設定 │ │ ├── modules/ # 路由模組 │ │ │ ├── auth.ts │ │ │ └── dashboard.ts │ │ └── index.ts │ ├── stores/ # Pinia 狀態管理 │ │ ├── modules/ │ │ │ ├── auth.ts │ │ │ └── user.ts │ │ └── index.ts │ ├── styles/ # 樣式檔案 │ │ ├── globals.css │ │ ├── variables.css │ │ └── components/ │ ├── types/ # TypeScript 型別定義 │ │ ├── api.ts │ │ ├── auth.ts │ │ └── global.ts │ ├── utils/ # 工具函式 │ │ ├── format.ts │ │ ├── validation.ts │ │ └── storage.ts │ ├── App.vue # 根元件 │ └── main.ts # 應用程式進入點 ├── tests/ # 測試檔案 │ ├── unit/ # 單元測試 │ ├── e2e/ # E2E 測試 │ └── __mocks__/ # Mock 檔案 ├── .env # 環境變數 ├── .env.development ├── .env.production ├── .eslintrc.js # ESLint 設定 ├── .prettierrc # Prettier 設定 ├── tailwind.config.js # Tailwind CSS 設定 ├── vite.config.ts # Vite 設定 ├── tsconfig.json # TypeScript 設定 └── package.json # 套件管理 1.2 檔案命名原則 檔案類型對應命名方式 Vue 元件: PascalCase (如 UserProfile.vue) TypeScript 檔案: camelCase (如 userService.ts) CSS/SCSS 檔案: kebab-case (如 user-profile.scss) 測試檔案: 與被測檔案同名 + .test 或 .spec (如 UserProfile.test.ts) 型別定義檔案: camelCase + .d.ts (如 userTypes.d.ts) 特殊檔案命名 頁面元件: PascalCase,通常以頁面功能命名 (如 UserManagement.vue) Layout 元件: PascalCase + Layout 後綴 (如 DashboardLayout.vue) Store 檔案: camelCase,以業務領域命名 (如 userStore.ts) API 檔案: camelCase,以 API 服務命名 (如 userApi.ts) 2. 命名規範 2.1 檔案與資料夾命名 資料夾命名 使用 kebab-case (小寫字母 + 連字號) 名稱應簡潔且具描述性 ✅ 正確範例 user-management/ auth-service/ api-client/ ❌ 錯誤範例 UserManagement/ authService/ API_Client/ 檔案命名 Vue 元件檔案: PascalCase JavaScript/TypeScript 檔案: camelCase 樣式檔案: kebab-case 設定檔案: kebab-case 或 camelCase (依慣例) // ✅ 正確範例 UserProfile.vue userService.ts user-profile.scss vite.config.ts // ❌ 錯誤範例 userprofile.vue UserService.ts user_profile.scss vite_config.ts 2.2 變數與函式命名 JavaScript/TypeScript 變數 使用 camelCase 常數使用 UPPER_SNAKE_CASE 私有變數以 _ 開頭 布林值變數使用 is、has、can、should 等前綴 // ✅ 正確範例 const userName = 'John Doe'; const API_BASE_URL = 'https://api.example.com'; const _privateVariable = 'private'; const isLoggedIn = true; const hasPermission = false; const canEdit = true; const shouldUpdate = false; // ❌ 錯誤範例 const user_name = 'John Doe'; const apiBaseUrl = 'https://api.example.com'; // 常數應使用大寫 const privateVariable = 'private'; // 私有變數缺少前綴 const loggedIn = true; // 布林值缺少前綴 函式命名 使用 camelCase 動詞開頭,描述函式的動作 事件處理器使用 handle 前綴 取得資料使用 get、fetch 前綴 設定資料使用 set、update 前綴 // ✅ 正確範例 function getUserData() { } function handleButtonClick() { } function validateEmail() { } function formatCurrency() { } function updateUserProfile() { } function fetchUserList() { } // ❌ 錯誤範例 function userData() { } // 缺少動詞 function buttonClick() { } // 事件處理器缺少 handle 前綴 function email() { } // 不明確的命名 function currency() { } // 不明確的命名 2.3 Vue 元件命名 元件名稱 使用 PascalCase 多個單字組合,避免單一單字 基礎元件使用 Base 前綴 業務元件使用具體的業務領域命名 <!-- ✅ 正確範例 --> <script setup lang="ts"> // 元件檔案: UserProfile.vue </script> <script setup lang="ts"> // 元件檔案: BaseButton.vue </script> <script setup lang="ts"> // 元件檔案: PaymentForm.vue </script> <!-- ❌ 錯誤範例 --> <script setup lang="ts"> // 元件檔案: user.vue - 命名太簡短 </script> <script setup lang="ts"> // 元件檔案: button.vue - 應使用 BaseButton </script> Props 命名 定義時使用 camelCase HTML 模板中使用 kebab-case <script setup lang="ts"> // ✅ 正確範例 interface Props { userName: string; isVisible: boolean; maxLength?: number; } const props = withDefaults(defineProps<Props>(), { maxLength: 100 }); </script> <template> <!-- HTML 模板中使用 kebab-case --> <UserProfile :user-name="currentUser" :is-visible="showProfile" :max-length="200" /> </template> Event 命名 使用 kebab-case 動詞開頭,描述事件的動作 <script setup lang="ts"> // ✅ 正確範例 const emit = defineEmits<{ 'update:modelValue': [value: string]; 'user-created': [user: User]; 'form-submitted': [data: FormData]; 'item-selected': [item: Item]; }>(); // 觸發事件 emit('user-created', newUser); emit('form-submitted', formData); </script> 3. 程式撰寫風格與 Lint 設定 3.1 ESLint 設定 eslint.config.js 範例 import { defineConfig } from 'eslint-define-config'; import vue from 'eslint-plugin-vue'; import typescript from '@typescript-eslint/eslint-plugin'; import typescriptParser from '@typescript-eslint/parser'; import prettier from 'eslint-plugin-prettier'; export default defineConfig([ { files: ['**/*.{js,ts,vue}'], languageOptions: { parser: typescriptParser, parserOptions: { ecmaVersion: 2022, sourceType: 'module', extraFileExtensions: ['.vue'] } }, plugins: { vue, '@typescript-eslint': typescript, prettier }, rules: { // Vue 規則 'vue/multi-word-component-names': 'error', 'vue/component-name-in-template-casing': ['error', 'PascalCase'], 'vue/no-unused-vars': 'error', 'vue/no-multiple-template-root': 'off', // Vue 3 支援多個根元素 'vue/script-setup-uses-vars': 'error', // TypeScript 規則 '@typescript-eslint/no-unused-vars': 'error', '@typescript-eslint/no-explicit-any': 'warn', '@typescript-eslint/explicit-function-return-type': 'off', '@typescript-eslint/no-non-null-assertion': 'warn', // 通用規則 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'warn', 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'warn', 'prefer-const': 'error', 'no-var': 'error', 'object-shorthand': 'error', 'prefer-template': 'error', // Prettier 規則 'prettier/prettier': 'error' } } ]); 3.2 Prettier 設定 .prettierrc 範例 { "semi": true, "trailingComma": "es5", "singleQuote": true, "printWidth": 80, "tabWidth": 2, "useTabs": false, "bracketSpacing": true, "bracketSameLine": false, "arrowParens": "avoid", "endOfLine": "lf", "vueIndentScriptAndStyle": true, "htmlWhitespaceSensitivity": "css" } 3.3 TypeScript 撰寫規範 型別定義規範 // ✅ 正確範例 - 使用 interface 定義物件型別 interface User { id: number; name: string; email: string; isActive: boolean; createdAt: Date; updatedAt?: Date; // 選擇性屬性 } // ✅ 正確範例 - 使用 type 定義聯合型別 type Status = 'pending' | 'approved' | 'rejected'; type Theme = 'light' | 'dark' | 'auto'; // ✅ 正確範例 - 泛型使用 interface ApiResponse<T> { data: T; message: string; success: boolean; code: number; } // ✅ 正確範例 - 函式型別定義 type EventHandler<T = Event> = (event: T) => void; type AsyncFunction<T> = () => Promise<T>; 函式撰寫規範 // ✅ 正確範例 - 明確的參數和回傳型別 async function fetchUserData(userId: number): Promise<User | null> { try { const response = await api.get<ApiResponse<User>>(`/users/${userId}`); return response.data.data; } catch (error) { console.error('Failed to fetch user data:', error); return null; } } // ✅ 正確範例 - 箭頭函式與型別推斷 const formatCurrency = (amount: number, currency = 'TWD'): string => { return new Intl.NumberFormat('zh-TW', { style: 'currency', currency, }).format(amount); }; // ✅ 正確範例 - 高階函式 const createValidator = <T>( validator: (value: T) => boolean, errorMessage: string ) => { return (value: T): ValidationResult => ({ isValid: validator(value), message: validator(value) ? '' : errorMessage, }); }; 3.4 Vue 3 Composition API 撰寫規範 <script setup> 結構規範 <script setup lang="ts"> // 1. 導入 Vue 相關 API import { ref, computed, watch, onMounted } from 'vue'; // 2. 導入 Composables import { useAuth } from '@/composables/useAuth'; import { useApi } from '@/composables/useApi'; // 3. 導入其他模組 import { formatDate } from '@/utils/format'; import type { User } from '@/types/user'; // 4. 定義 Props 介面 interface Props { userId: number; isEditable?: boolean; } // 5. 定義 Emits 介面 interface Emits { 'user-updated': [user: User]; 'error': [error: string]; } // 6. 宣告 Props 和 Emits const props = withDefaults(defineProps<Props>(), { isEditable: false, }); const emit = defineEmits<Emits>(); // 7. 響應式資料 const user = ref<User | null>(null); const loading = ref(false); const error = ref<string>(''); // 8. 計算屬性 const displayName = computed(() => { return user.value ? `${user.value.firstName} ${user.value.lastName}` : ''; }); const canEdit = computed(() => { return props.isEditable && user.value?.isActive; }); // 9. 監聽器 watch( () => props.userId, async (newUserId) => { if (newUserId) { await loadUser(); } }, { immediate: true } ); // 10. 方法 const loadUser = async (): Promise<void> => { loading.value = true; error.value = ''; try { const userData = await fetchUserData(props.userId); user.value = userData; } catch (err) { error.value = '載入使用者資料失敗'; emit('error', error.value); } finally { loading.value = false; } }; const updateUser = async (userData: Partial<User>): Promise<void> => { if (!user.value) return; try { const updatedUser = await updateUserData(user.value.id, userData); user.value = updatedUser; emit('user-updated', updatedUser); } catch (err) { error.value = '更新使用者資料失敗'; emit('error', error.value); } }; // 11. 生命週期鉤子 onMounted(() => { console.log('Component mounted'); }); // 12. 暴露給父元件的方法/屬性 (如需要) defineExpose({ loadUser, updateUser, }); </script> 3.5 程式碼品質規範 註解撰寫規範 /** * 使用者資料服務類別 * * 提供使用者相關的 API 操作方法,包含: * - 取得使用者資料 * - 更新使用者資料 * - 刪除使用者 * * @example * ```typescript * const userService = new UserService(); * const user = await userService.getUser(123); * ``` */ export class UserService { /** * 根據 ID 取得使用者資料 * * @param userId - 使用者 ID * @returns 使用者資料,如果找不到則回傳 null * @throws {ApiError} 當 API 請求失敗時拋出錯誤 * * @example * ```typescript * const user = await userService.getUser(123); * if (user) { * console.log(user.name); * } * ``` */ async getUser(userId: number): Promise<User | null> { // TODO: 實作快取機制 // FIXME: 處理網路錯誤重試邏輯 try { const response = await this.api.get(`/users/${userId}`); return response.data; } catch (error) { // 記錄錯誤但不拋出,讓呼叫方決定如何處理 console.error(`Failed to fetch user ${userId}:`, error); return null; } } } 錯誤處理規範 // ✅ 正確範例 - 統一的錯誤處理 export class ApiError extends Error { constructor( message: string, public status: number, public code?: string ) { super(message); this.name = 'ApiError'; } } // ✅ 正確範例 - 錯誤邊界處理 const handleApiError = (error: unknown): never => { if (error instanceof ApiError) { switch (error.status) { case 401: // 重新導向到登入頁面 router.push('/login'); break; case 403: // 顯示權限不足訊息 showErrorMessage('您沒有權限執行此操作'); break; case 500: // 顯示伺服器錯誤訊息 showErrorMessage('伺服器發生錯誤,請稍後再試'); break; default: showErrorMessage(error.message); } } else { // 未知錯誤 console.error('Unexpected error:', error); showErrorMessage('發生未知錯誤'); } throw error; }; 4. 元件開發規範 4.1 Vue 單檔元件 (SFC) 結構 標準 SFC 結構順序 <!-- 1. 模板區域 --> <template> <div class="user-profile"> <!-- 內容 --> </div> </template> <!-- 2. 邏輯區域 --> <script setup lang="ts"> // 邏輯代碼 </script> <!-- 3. 樣式區域 --> <style scoped lang="scss"> // 樣式代碼 </style> 完整元件範例 <template> <div class="user-card" :class="cardClasses"> <!-- 頭像區域 --> <div class="user-card__avatar"> <img :src="user.avatar || defaultAvatar" :alt="`${user.name} 的頭像`" class="user-card__avatar-img" @error="handleImageError" /> <div v-if="showStatus" class="user-card__status" :class="statusClass"> {{ statusText }} </div> </div> <!-- 使用者資訊 --> <div class="user-card__content"> <h3 class="user-card__name">{{ user.name }}</h3> <p class="user-card__email">{{ user.email }}</p> <!-- 標籤 --> <div v-if="user.tags?.length" class="user-card__tags"> <span v-for="tag in user.tags" :key="tag" class="user-card__tag" > {{ tag }} </span> </div> <!-- 動作按鈕 --> <div class="user-card__actions"> <BaseButton variant="primary" size="small" :disabled="loading" @click="handleEdit" > 編輯 </BaseButton> <BaseButton variant="secondary" size="small" :disabled="loading" @click="handleView" > 查看 </BaseButton> </div> </div> <!-- 載入狀態 --> <div v-if="loading" class="user-card__loading"> <LoadingSpinner size="small" /> </div> </div> </template> <script setup lang="ts"> import { computed, ref } from 'vue'; import BaseButton from '@/components/base/BaseButton.vue'; import LoadingSpinner from '@/components/base/LoadingSpinner.vue'; import { useI18n } from 'vue-i18n'; import type { User } from '@/types/user'; // Props 定義 interface Props { user: User; variant?: 'default' | 'compact' | 'detailed'; showStatus?: boolean; interactive?: boolean; } // Emits 定義 interface Emits { edit: [user: User]; view: [user: User]; 'avatar-error': [user: User]; } const props = withDefaults(defineProps<Props>(), { variant: 'default', showStatus: true, interactive: true, }); const emit = defineEmits<Emits>(); // Composables const { t } = useI18n(); // 響應式資料 const loading = ref(false); const defaultAvatar = '/images/default-avatar.png'; // 計算屬性 const cardClasses = computed(() => ({ [`user-card--${props.variant}`]: true, 'user-card--interactive': props.interactive, 'user-card--loading': loading.value, })); const statusClass = computed(() => ({ 'user-card__status--online': props.user.isOnline, 'user-card__status--offline': !props.user.isOnline, })); const statusText = computed(() => { return props.user.isOnline ? t('common.online') : t('common.offline'); }); // 方法 const handleEdit = (): void => { if (!props.interactive || loading.value) return; emit('edit', props.user); }; const handleView = (): void => { if (!props.interactive || loading.value) return; emit('view', props.user); }; const handleImageError = (): void => { emit('avatar-error', props.user); }; </script> <style scoped lang="scss"> .user-card { @apply bg-white rounded-lg shadow-md p-4 transition-all duration-200; &--interactive { @apply hover:shadow-lg cursor-pointer; } &--loading { @apply opacity-50 pointer-events-none; } &__avatar { @apply relative flex-shrink-0; } &__avatar-img { @apply w-12 h-12 rounded-full object-cover; } &__status { @apply absolute -bottom-1 -right-1 px-2 py-1 text-xs rounded-full text-white; &--online { @apply bg-green-500; } &--offline { @apply bg-gray-400; } } &__content { @apply flex-1 ml-4; } &__name { @apply text-lg font-semibold text-gray-900 mb-1; } &__email { @apply text-sm text-gray-600 mb-2; } &__tags { @apply flex flex-wrap gap-1 mb-3; } &__tag { @apply px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded; } &__actions { @apply flex gap-2; } &__loading { @apply absolute inset-0 flex items-center justify-center bg-white bg-opacity-75; } // 變體樣式 &--compact { @apply p-2; .user-card__avatar-img { @apply w-8 h-8; } .user-card__name { @apply text-base; } } &--detailed { @apply p-6; .user-card__avatar-img { @apply w-16 h-16; } } } </style> 4.2 Props 設計規範 Props 型別定義 // ✅ 正確範例 - 完整的 Props 介面 interface ButtonProps { // 必要屬性 label: string; // 選擇性屬性with default values variant?: 'primary' | 'secondary' | 'danger' | 'ghost'; size?: 'small' | 'medium' | 'large'; disabled?: boolean; loading?: boolean; // 複雜型別 icon?: { name: string; position: 'left' | 'right'; }; // 函式型別 onClick?: (event: MouseEvent) => void; } const props = withDefaults(defineProps<ButtonProps>(), { variant: 'primary', size: 'medium', disabled: false, loading: false, }); Props 驗證 // ✅ 正確範例 - 執行時驗證 interface FormInputProps { modelValue: string; type?: 'text' | 'email' | 'password' | 'number'; placeholder?: string; required?: boolean; maxLength?: number; pattern?: string; validator?: (value: string) => boolean | string; } const props = withDefaults(defineProps<FormInputProps>(), { type: 'text', required: false, }); // 自定義驗證邏輯 const isValid = computed(() => { if (props.required && !props.modelValue) { return false; } if (props.maxLength && props.modelValue.length > props.maxLength) { return false; } if (props.pattern && !new RegExp(props.pattern).test(props.modelValue)) { return false; } if (props.validator) { const result = props.validator(props.modelValue); return result === true; } return true; }); 4.3 Emits 事件規範 事件定義與觸發 // ✅ 正確範例 - 型別安全的事件定義 interface FormEmits { // v-model 雙向綁定 'update:modelValue': [value: string]; // 表單事件 submit: [data: FormData]; cancel: []; // 驗證事件 'validation-error': [errors: ValidationError[]]; 'validation-success': []; // 使用者互動事件 'field-focus': [fieldName: string]; 'field-blur': [fieldName: string, value: string]; } const emit = defineEmits<FormEmits>(); // 事件觸發範例 const handleSubmit = (formData: FormData): void => { // 驗證表單 const errors = validateForm(formData); if (errors.length > 0) { emit('validation-error', errors); return; } emit('validation-success'); emit('submit', formData); }; const handleCancel = (): void => { emit('cancel'); }; // v-model 實作 const updateValue = (newValue: string): void => { emit('update:modelValue', newValue); }; 4.4 Slots 使用規範 具名插槽設計 <template> <div class="card"> <!-- 標題插槽 --> <header v-if="$slots.header" class="card__header"> <slot name="header" :title="title" :subtitle="subtitle" /> </header> <!-- 預設內容插槽 --> <main class="card__content"> <slot :data="data" :loading="loading" /> </main> <!-- 動作按鈕插槽 --> <footer v-if="$slots.actions" class="card__actions"> <slot name="actions" :save="handleSave" :cancel="handleCancel" :canSave="canSave" /> </footer> <!-- 條件式插槽 --> <div v-if="$slots.sidebar" class="card__sidebar"> <slot name="sidebar" /> </div> </div> </template> <script setup lang="ts"> interface Props { title?: string; subtitle?: string; data: any; loading?: boolean; } const props = defineProps<Props>(); // 計算屬性 const canSave = computed(() => { return !props.loading && isDataValid(props.data); }); // 提供給插槽的方法 const handleSave = (): void => { // 儲存邏輯 }; const handleCancel = (): void => { // 取消邏輯 }; </script> 插槽使用範例 <template> <Card :data="userData" :loading="loading"> <!-- 標題插槽 --> <template #header="{ title, subtitle }"> <h2>{{ title || '使用者資料' }}</h2> <p v-if="subtitle">{{ subtitle }}</p> </template> <!-- 預設內容插槽 --> <template #default="{ data, loading }"> <div v-if="!loading"> <UserForm :user="data" @update="handleUserUpdate" /> </div> <LoadingSpinner v-else /> </template> <!-- 動作插槽 --> <template #actions="{ save, cancel, canSave }"> <BaseButton variant="primary" :disabled="!canSave" @click="save" > 儲存 </BaseButton> <BaseButton variant="secondary" @click="cancel" > 取消 </BaseButton> </template> </Card> </template> 4.5 元件組合與複用 高階元件 (HOC) 模式 <!-- withLoading.vue - 高階元件 --> <template> <div class="with-loading"> <div v-if="loading" class="loading-overlay"> <LoadingSpinner :size="loadingSize" /> <p v-if="loadingText">{{ loadingText }}</p> </div> <div :class="{ 'is-loading': loading }"> <slot :loading="loading" /> </div> </div> </template> <script setup lang="ts"> interface Props { loading: boolean; loadingText?: string; loadingSize?: 'small' | 'medium' | 'large'; } withDefaults(defineProps<Props>(), { loadingSize: 'medium', }); </script> 組合式元件使用 <template> <WithLoading :loading="isLoading" loading-text="載入使用者資料中..."> <UserProfile :user="userData" @edit="handleEdit" @delete="handleDelete" /> </WithLoading> </template> <script setup lang="ts"> import WithLoading from '@/components/hoc/WithLoading.vue'; import UserProfile from '@/components/UserProfile.vue'; const isLoading = ref(false); const userData = ref(null); const handleEdit = (user: User): void => { // 編輯邏輯 }; const handleDelete = (user: User): void => { // 刪除邏輯 }; </script> 5. 樣式與 Tailwind CSS 規範 5.1 Tailwind CSS 設定 tailwind.config.js 範例 /** @type {import('tailwindcss').Config} */ export default { content: [ './index.html', './src/**/*.{vue,js,ts,jsx,tsx}', ], theme: { extend: { // 顏色系統 colors: { primary: { 50: '#eff6ff', 100: '#dbeafe', 200: '#bfdbfe', 300: '#93c5fd', 400: '#60a5fa', 500: '#3b82f6', // 主要品牌色 600: '#2563eb', 700: '#1d4ed8', 800: '#1e40af', 900: '#1e3a8a', 950: '#172554', }, secondary: { 50: '#f8fafc', 500: '#64748b', 900: '#0f172a', }, success: { 50: '#f0fdf4', 500: '#22c55e', 900: '#14532d', }, warning: { 50: '#fffbeb', 500: '#f59e0b', 900: '#78350f', }, danger: { 50: '#fef2f2', 500: '#ef4444', 900: '#7f1d1d', }, }, // 字型設定 fontFamily: { sans: [ 'Noto Sans TC', 'Microsoft JhengHei', 'PingFang TC', 'Helvetica Neue', 'Arial', 'sans-serif', ], }, // 響應式斷點 screens: { 'xs': '475px', 'sm': '640px', 'md': '768px', 'lg': '1024px', 'xl': '1280px', '2xl': '1536px', }, }, }, plugins: [ require('@tailwindcss/forms'), require('@tailwindcss/typography'), ], }; 5.2 RWD 響應式設計原則 Mobile-First 設計策略 <template> <!-- 響應式網格系統 --> <div class="container mx-auto px-4"> <div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-4"> <div v-for="item in items" :key="item.id" class="card"> <!-- 響應式圖片 --> <img :src="item.image" :alt="item.title" class="w-full h-32 sm:h-40 lg:h-48 object-cover" /> <!-- 響應式文字 --> <div class="p-4"> <h3 class="text-lg sm:text-xl lg:text-2xl font-semibold"> {{ item.title }} </h3> <p class="text-sm sm:text-base text-gray-600"> {{ item.description }} </p> </div> </div> </div> </div> <!-- 響應式導航 --> <nav class="bg-white shadow"> <!-- 桌面版導航 --> <div class="hidden lg:flex items-center space-x-8 px-6 py-4"> <a v-for="link in navLinks" :key="link.path" :href="link.path"> {{ link.title }} </a> </div> <!-- 行動版選單 --> <div class="lg:hidden"> <button @click="toggleMobileMenu" class="p-4"> <MenuIcon class="w-6 h-6" /> </button> </div> </nav> </template> 5.3 顏色與主題設計 CSS 變數主題系統 :root { /* 品牌色 */ --color-primary: #3b82f6; --color-secondary: #64748b; /* 語意化顏色 */ --color-success: #22c55e; --color-warning: #f59e0b; --color-danger: #ef4444; /* 背景色 */ --bg-primary: #ffffff; --bg-secondary: #f8fafc; /* 文字色 */ --text-primary: #111827; --text-secondary: #4b5563; } /* 暗色主題 */ [data-theme="dark"] { --bg-primary: #111827; --bg-secondary: #1f2937; --text-primary: #f9fafb; --text-secondary: #d1d5db; } 5.4 共用樣式元件 基礎元件樣式 @layer components { /* 按鈕樣式 */ .btn { @apply inline-flex items-center px-4 py-2 text-sm font-medium rounded-md transition-colors focus:outline-none focus:ring-2; } .btn-primary { @apply btn bg-primary-500 text-white hover:bg-primary-600; } .btn-secondary { @apply btn bg-gray-200 text-gray-900 hover:bg-gray-300; } /* 表單樣式 */ .form-input { @apply w-full px-3 py-2 border border-gray-300 rounded-md focus:ring-1 focus:ring-primary-500 focus:border-primary-500; } /* 卡片樣式 */ .card { @apply bg-white rounded-lg shadow-md p-4; } } 總結 本前端開發指引提供了完整的開發規範,涵蓋了專案結構、命名規範、程式撰寫風格、元件開發和樣式設計等核心面向。請開發團隊嚴格遵循這些規範,以確保程式碼品質和專案的可維護性。 ...

October 31, 2025 · 81 min · 17161 words · Eric Cheng

功能需求分析範本

功能需求分析範本 Prompt 目標 指導 AI 進行系統化的功能需求分析,產生詳細的功能需求規格文檔。 角色設定 你是一位資深系統分析師,具備豐富的功能需求分析經驗,能夠將業務需求轉換為詳細的功能規格。 任務描述 請協助我完成 {專案名稱} 的功能需求分析工作。 專案背景資訊 專案名稱: {填入專案名稱} 系統類型: {填入系統類型} 主要使用者角色: {填入使用者角色列表} 核心業務流程: {填入核心業務流程} 分析要求 請按照以下結構進行功能需求分析: 1. 使用者故事分析 識別主要使用者角色 撰寫使用者故事 定義驗收標準 設定故事點數估算 2. 功能分解結構 系統功能模組劃分 子功能識別 功能相依關係分析 功能優先級排序 3. 介面需求定義 使用者介面需求 系統介面需求 外部整合介面 API 需求規格 4. 資料流程分析 輸入資料定義 處理邏輯描述 輸出結果規格 資料驗證規則 5. 效能需求 響應時間要求 吞吐量需求 並發使用者數 資源使用限制 6. 安全需求 身份驗證需求 授權控制要求 資料保護需求 稽核日誌需求 輸出格式 # {專案名稱} 功能需求規格文檔 ## 1. 使用者故事 ### 1.1 使用者角色定義 | 角色 | 描述 | 主要職責 | 技術能力 | |------|------|----------|----------| | [角色名稱] | [角色描述] | [主要職責] | [技術能力評估] | ### 1.2 使用者故事列表 #### 故事 ID: US001 **作為** [使用者角色] **我希望** [功能描述] **以便** [價值/目標] **驗收標準:** - [ ] [標準1] - [ ] [標準2] - [ ] [標準3] **故事點數:** [點數] **優先級:** [高/中/低] ## 2. 功能分解結構 ### 2.1 功能模組圖 系統名稱 ├── 模組A │ ├── 子功能A1 │ ├── 子功能A2 │ └── 子功能A3 ├── 模組B │ ├── 子功能B1 │ └── 子功能B2 └── 模組C ├── 子功能C1 └── 子功能C2 ...

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