dramaling-app/docs/04_technical/03_frontend/vue-project-structure.md

24 KiB

Vue.js 項目結構和配置範例

📁 完整項目結構

dramaling-web/
├── .env.development               # 開發環境變數
├── .env.production               # 生產環境變數
├── .eslintrc.js                  # ESLint配置
├── .gitignore                    # Git忽略檔案
├── .prettierrc                   # Prettier配置
├── index.html                    # HTML入口
├── package.json                  # 專案依賴和腳本
├── README.md                     # 專案說明
├── tsconfig.json                 # TypeScript配置
├── vite.config.ts               # Vite配置
├── vitest.config.ts             # Vitest測試配置
├── cypress.config.ts            # E2E測試配置
├── docker-compose.yml           # Docker開發環境
├── Dockerfile                   # Docker部署
├── nginx.conf                   # Nginx配置
│
├── public/                      # 公開靜態資源
│   ├── favicon.ico
│   ├── manifest.json            # PWA配置
│   ├── robots.txt
│   └── icons/                   # PWA圖標
│       ├── icon-192x192.png
│       └── icon-512x512.png
│
├── src/                         # 源代碼
│   ├── App.vue                  # 根組件
│   ├── main.ts                  # 應用入口
│   │
│   ├── assets/                  # 資源檔案
│   │   ├── images/              # 圖片資源
│   │   │   ├── logo.svg
│   │   │   └── backgrounds/
│   │   ├── audio/               # 音頻檔案
│   │   │   └── pronunciation/
│   │   └── styles/              # 全域樣式
│   │       ├── main.scss
│   │       ├── variables.scss
│   │       └── components.scss
│   │
│   ├── components/              # 共用組件
│   │   ├── base/                # 基礎組件
│   │   │   ├── BaseButton.vue
│   │   │   ├── BaseCard.vue
│   │   │   ├── BaseInput.vue
│   │   │   └── BaseModal.vue
│   │   ├── business/            # 業務組件
│   │   │   ├── VocabularyCard.vue
│   │   │   ├── DialogueMessage.vue
│   │   │   ├── ProgressBar.vue
│   │   │   └── AudioPlayer.vue
│   │   └── layout/              # 佈局組件
│   │       ├── AppHeader.vue
│   │       ├── AppFooter.vue
│   │       ├── NavigationDrawer.vue
│   │       └── Breadcrumb.vue
│   │
│   ├── composables/             # 組合式函數
│   │   ├── useAuth.ts           # 認證相關
│   │   ├── useAudio.ts          # 音頻處理
│   │   ├── useApi.ts            # API調用
│   │   ├── useCache.ts          # 快取管理
│   │   ├── useNotification.ts   # 通知系統
│   │   └── useLocalStorage.ts   # 本地存儲
│   │
│   ├── layouts/                 # 佈局模板
│   │   ├── MainLayout.vue       # 主要佈局
│   │   ├── AuthLayout.vue       # 認證佈局
│   │   ├── EmptyLayout.vue      # 空白佈局
│   │   └── AdminLayout.vue      # 管理佈局
│   │
│   ├── modules/                 # 功能模組
│   │   ├── auth/                # 認證模組
│   │   │   ├── components/
│   │   │   │   ├── LoginForm.vue
│   │   │   │   ├── RegisterForm.vue
│   │   │   │   └── PasswordResetForm.vue
│   │   │   ├── composables/
│   │   │   │   └── useAuthValidation.ts
│   │   │   ├── services/
│   │   │   │   └── authService.ts
│   │   │   ├── stores/
│   │   │   │   └── authStore.ts
│   │   │   ├── types/
│   │   │   │   └── auth.types.ts
│   │   │   ├── views/
│   │   │   │   ├── LoginPage.vue
│   │   │   │   ├── RegisterPage.vue
│   │   │   │   └── ProfilePage.vue
│   │   │   └── router.ts
│   │   │
│   │   ├── vocabulary/          # 詞彙學習模組
│   │   │   ├── components/
│   │   │   │   ├── WordCard.vue
│   │   │   │   ├── PracticeCard.vue
│   │   │   │   ├── ResultsPanel.vue
│   │   │   │   └── ProgressTracker.vue
│   │   │   ├── composables/
│   │   │   │   ├── useVocabulary.ts
│   │   │   │   └── usePractice.ts
│   │   │   ├── services/
│   │   │   │   └── vocabularyService.ts
│   │   │   ├── stores/
│   │   │   │   └── vocabularyStore.ts
│   │   │   ├── types/
│   │   │   │   └── vocabulary.types.ts
│   │   │   ├── views/
│   │   │   │   ├── IntroductionPage.vue
│   │   │   │   ├── PracticePage.vue
│   │   │   │   ├── ResultsPage.vue
│   │   │   │   └── ReviewPage.vue
│   │   │   └── router.ts
│   │   │
│   │   ├── dialogue/            # 情境對話模組
│   │   │   ├── components/
│   │   │   │   ├── ChatInterface.vue
│   │   │   │   ├── MessageBubble.vue
│   │   │   │   ├── VoiceRecorder.vue
│   │   │   │   └── ScenarioSelector.vue
│   │   │   ├── composables/
│   │   │   │   ├── useDialogue.ts
│   │   │   │   └── useSpeechRecognition.ts
│   │   │   ├── services/
│   │   │   │   └── dialogueService.ts
│   │   │   ├── stores/
│   │   │   │   └── dialogueStore.ts
│   │   │   ├── types/
│   │   │   │   └── dialogue.types.ts
│   │   │   ├── views/
│   │   │   │   ├── DialogueMainPage.vue
│   │   │   │   ├── ScenarioPage.vue
│   │   │   │   └── TimedChallengePage.vue
│   │   │   └── router.ts
│   │   │
│   │   ├── learning-map/        # 學習地圖模組
│   │   │   └── ... (類似結構)
│   │   │
│   │   └── shop/                # 商店模組
│   │       └── ... (類似結構)
│   │
│   ├── router/                  # 路由配置
│   │   ├── index.ts             # 主路由
│   │   ├── guards.ts            # 路由守衛
│   │   └── types.ts             # 路由類型
│   │
│   ├── stores/                  # Pinia狀態管理
│   │   ├── index.ts             # Store註冊
│   │   ├── auth.ts              # 認證Store
│   │   ├── user.ts              # 用戶Store
│   │   ├── ui.ts                # UI狀態Store
│   │   ├── learning.ts          # 學習進度Store
│   │   └── notification.ts      # 通知Store
│   │
│   ├── services/                # 服務層
│   │   ├── api/                 # API服務
│   │   │   ├── index.ts         # API客戶端
│   │   │   ├── auth.api.ts
│   │   │   ├── vocabulary.api.ts
│   │   │   ├── dialogue.api.ts
│   │   │   └── payment.api.ts
│   │   ├── storage/             # 存儲服務
│   │   │   ├── localStorage.ts
│   │   │   └── sessionStorage.ts
│   │   ├── notification/        # 通知服務
│   │   │   └── pushNotification.ts
│   │   └── analytics/           # 分析服務
│   │       └── tracking.ts
│   │
│   ├── types/                   # 全域類型定義
│   │   ├── index.ts             # 導出所有類型
│   │   ├── api.types.ts         # API相關類型
│   │   ├── user.types.ts        # 用戶相關類型
│   │   ├── learning.types.ts    # 學習相關類型
│   │   └── common.types.ts      # 通用類型
│   │
│   ├── utils/                   # 工具函數
│   │   ├── index.ts             # 導出所有工具
│   │   ├── format.ts            # 格式化工具
│   │   ├── validation.ts        # 驗證工具
│   │   ├── storage.ts           # 存儲工具
│   │   ├── audio.ts             # 音頻工具
│   │   ├── date.ts              # 日期工具
│   │   └── constants.ts         # 常數定義
│   │
│   └── plugins/                 # Vue插件
│       ├── index.ts             # 插件註冊
│       ├── quasar.ts            # Quasar配置
│       ├── pwa.ts               # PWA配置
│       └── i18n.ts              # 國際化配置
│
├── tests/                       # 測試檔案
│   ├── unit/                    # 單元測試
│   │   ├── components/
│   │   ├── composables/
│   │   ├── stores/
│   │   └── utils/
│   ├── integration/             # 整合測試
│   │   └── api/
│   ├── e2e/                     # E2E測試
│   │   ├── specs/
│   │   ├── fixtures/
│   │   └── support/
│   └── __mocks__/               # Mock檔案
│
├── docs/                        # 專案文檔
│   ├── api.md                   # API文檔
│   ├── components.md            # 組件文檔
│   ├── deployment.md            # 部署指南
│   └── development.md           # 開發指南
│
└── scripts/                     # 建構腳本
    ├── build.sh                 # 建構腳本
    ├── deploy.sh                # 部署腳本
    └── test.sh                  # 測試腳本

📋 核心配置檔案

package.json

{
  "name": "dramaling-web",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "vite --host",
    "build": "vue-tsc --noEmit && vite build",
    "preview": "vite preview",
    "test": "vitest",
    "test:ui": "vitest --ui",
    "test:coverage": "vitest --coverage",
    "test:e2e": "cypress run",
    "test:e2e:dev": "cypress open",
    "lint": "eslint . --ext .vue,.ts,.tsx --fix",
    "lint:style": "stylelint **/*.{css,scss,vue} --fix",
    "type-check": "vue-tsc --noEmit",
    "prepare": "husky install"
  },
  "dependencies": {
    "vue": "^3.4.21",
    "vue-router": "^4.3.0",
    "pinia": "^2.1.7",
    "pinia-plugin-persistedstate": "^3.2.1",
    "quasar": "^2.16.0",
    "@quasar/extras": "^1.16.4",
    "axios": "^1.6.8",
    "vee-validate": "^4.12.6",
    "yup": "^1.4.0",
    "lodash-es": "^4.17.21",
    "dayjs": "^1.11.10",
    "dompurify": "^3.0.11",
    "@vueuse/core": "^10.9.0",
    "workbox-window": "^7.0.0"
  },
  "devDependencies": {
    "@vitejs/plugin-vue": "^5.0.4",
    "vite": "^5.2.0",
    "vue-tsc": "^2.0.6",
    "typescript": "^5.4.0",
    "@types/node": "^20.12.7",
    "@types/lodash-es": "^4.17.12",
    "@types/dompurify": "^3.0.5",
    "vitest": "^1.5.0",
    "@vue/test-utils": "^2.4.5",
    "happy-dom": "^14.7.1",
    "@vitest/coverage-v8": "^1.5.0",
    "@vitest/ui": "^1.5.0",
    "cypress": "^13.7.2",
    "eslint": "^9.1.1",
    "@vue/eslint-config-typescript": "^13.0.0",
    "@vue/eslint-config-prettier": "^9.0.0",
    "prettier": "^3.2.5",
    "stylelint": "^16.4.0",
    "stylelint-config-standard-scss": "^13.1.0",
    "stylelint-config-standard-vue": "^1.0.0",
    "husky": "^9.0.11",
    "lint-staged": "^15.2.2",
    "unplugin-vue-components": "^0.27.0",
    "unplugin-auto-import": "^0.17.5",
    "@vite/plugin-pwa": "^0.20.0"
  }
}

vite.config.ts

import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { VitePWA } from 'vite-plugin-pwa'
import Components from 'unplugin-vue-components/vite'
import AutoImport from 'unplugin-auto-import/vite'
import { quasar, transformAssetUrls } from '@quasar/vite-plugin'
import path from 'path'

export default defineConfig({
  plugins: [
    vue({
      template: { transformAssetUrls }
    }),
    
    quasar({
      sassVariables: 'src/assets/styles/quasar-variables.sass'
    }),
    
    Components({
      resolvers: [
        (componentName) => {
          if (componentName.startsWith('Q'))
            return { name: componentName, from: 'quasar' }
        }
      ],
      dts: true,
      dirs: ['src/components'],
      extensions: ['vue'],
      deep: true
    }),
    
    AutoImport({
      imports: [
        'vue',
        'vue-router',
        'pinia',
        {
          'quasar': ['useQuasar', '$q', 'Notify', 'Loading', 'Dialog'],
          '@vueuse/core': ['useLocalStorage', 'useSessionStorage', 'useFetch']
        }
      ],
      dts: true,
      dirs: ['src/composables', 'src/stores'],
      vueTemplate: true
    }),
    
    VitePWA({
      registerType: 'autoUpdate',
      workbox: {
        globPatterns: ['**/*.{js,css,html,ico,png,svg,woff2}'],
        runtimeCaching: [
          {
            urlPattern: /^https:\/\/api\.dramaling\.com\/.*/i,
            handler: 'NetworkFirst',
            options: {
              cacheName: 'api-cache',
              expiration: {
                maxEntries: 50,
                maxAgeSeconds: 5 * 60 // 5分鐘
              }
            }
          }
        ]
      },
      manifest: {
        name: 'Drama Ling - AI語言學習',
        short_name: 'Drama Ling',
        description: 'AI驅動的情境式語言學習應用',
        theme_color: '#1976d2',
        background_color: '#ffffff',
        display: 'standalone',
        orientation: 'portrait-primary',
        scope: '/',
        start_url: '/',
        icons: [
          {
            src: '/icons/icon-192x192.png',
            sizes: '192x192',
            type: 'image/png'
          },
          {
            src: '/icons/icon-512x512.png',
            sizes: '512x512',
            type: 'image/png'
          }
        ]
      }
    })
  ],
  
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '~': path.resolve(__dirname, 'src'),
      '@components': path.resolve(__dirname, 'src/components'),
      '@modules': path.resolve(__dirname, 'src/modules'),
      '@stores': path.resolve(__dirname, 'src/stores'),
      '@services': path.resolve(__dirname, 'src/services'),
      '@utils': path.resolve(__dirname, 'src/utils'),
      '@assets': path.resolve(__dirname, 'src/assets')
    }
  },
  
  css: {
    preprocessorOptions: {
      scss: {
        additionalData: `@import "@/assets/styles/variables.scss";`
      }
    }
  },
  
  build: {
    target: 'es2020',
    rollupOptions: {
      output: {
        manualChunks: {
          // 框架核心
          'vue-vendor': ['vue', 'vue-router', 'pinia'],
          // UI框架
          'quasar-vendor': ['quasar'],
          // 工具庫
          'utils-vendor': ['axios', 'lodash-es', 'dayjs', '@vueuse/core'],
          // 驗證相關
          'validation-vendor': ['vee-validate', 'yup'],
          // 各模組
          'auth-module': ['src/modules/auth'],
          'vocabulary-module': ['src/modules/vocabulary'],
          'dialogue-module': ['src/modules/dialogue']
        }
      }
    }
  },
  
  server: {
    host: '0.0.0.0',
    port: 3000,
    proxy: {
      '/api': {
        target: 'http://localhost:5000',
        changeOrigin: true
      }
    }
  }
})

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "skipLibCheck": true,
    
    /* 模組解析選項 */
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    
    /* 嚴格性檢查選項 */
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    
    /* 路徑對應 */
    "baseUrl": ".",
    "paths": {
      "@/*": ["src/*"],
      "~/*": ["src/*"],
      "@components/*": ["src/components/*"],
      "@modules/*": ["src/modules/*"],
      "@stores/*": ["src/stores/*"],
      "@services/*": ["src/services/*"],
      "@utils/*": ["src/utils/*"],
      "@assets/*": ["src/assets/*"]
    },
    
    /* Vue 相關 */
    "types": ["node", "vue/ref-macros"],
    "allowJs": true
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
    "tests/**/*.ts"
  ],
  "exclude": [
    "node_modules",
    "dist"
  ]
}

.eslintrc.js

module.exports = {
  root: true,
  env: {
    node: true,
    browser: true,
    es2021: true
  },
  extends: [
    'plugin:vue/vue3-essential',
    'eslint:recommended',
    '@vue/typescript/recommended',
    '@vue/prettier',
    '@vue/prettier/@typescript-eslint'
  ],
  parser: 'vue-eslint-parser',
  parserOptions: {
    parser: '@typescript-eslint/parser',
    ecmaVersion: 2021,
    sourceType: 'module'
  },
  plugins: ['@typescript-eslint'],
  rules: {
    // Vue規則
    'vue/multi-word-component-names': 'off',
    'vue/no-unused-vars': 'error',
    'vue/component-definition-name-casing': ['error', 'PascalCase'],
    'vue/component-name-in-template-casing': ['error', 'PascalCase'],
    
    // TypeScript規則
    '@typescript-eslint/no-unused-vars': ['error', { 
      argsIgnorePattern: '^_',
      varsIgnorePattern: '^_'
    }],
    '@typescript-eslint/no-explicit-any': 'warn',
    '@typescript-eslint/explicit-function-return-type': 'off',
    '@typescript-eslint/explicit-module-boundary-types': 'off',
    
    // 一般規則
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'prefer-const': 'error',
    'no-var': 'error',
    'object-shorthand': 'error',
    'prefer-template': 'error'
  },
  globals: {
    defineProps: 'readonly',
    defineEmits: 'readonly',
    defineExpose: 'readonly',
    withDefaults: 'readonly'
  },
  overrides: [
    {
      files: ['**/*.test.{js,ts}', '**/tests/**/*'],
      env: {
        jest: true,
        vitest: true
      }
    }
  ]
}

.env.development

# API配置
VITE_API_BASE_URL=http://localhost:5000/api
VITE_WS_BASE_URL=ws://localhost:5000/ws

# 第三方服務
VITE_OPENAI_API_KEY=your_openai_key_here
VITE_STRIPE_PUBLIC_KEY=pk_test_your_stripe_key

# 分析服務
VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX
VITE_MIXPANEL_TOKEN=your_mixpanel_token

# 功能開關
VITE_ENABLE_PWA=true
VITE_ENABLE_DEV_TOOLS=true
VITE_ENABLE_MOCK_API=false

# 除錯設定
VITE_DEBUG_MODE=true
VITE_LOG_LEVEL=debug

.env.production

# API配置
VITE_API_BASE_URL=https://api.dramaling.com/api
VITE_WS_BASE_URL=wss://api.dramaling.com/ws

# 第三方服務
VITE_STRIPE_PUBLIC_KEY=pk_live_your_stripe_key

# 分析服務
VITE_GA_MEASUREMENT_ID=G-XXXXXXXXXX
VITE_MIXPANEL_TOKEN=your_mixpanel_token

# 功能開關
VITE_ENABLE_PWA=true
VITE_ENABLE_DEV_TOOLS=false
VITE_ENABLE_MOCK_API=false

# 除錯設定
VITE_DEBUG_MODE=false
VITE_LOG_LEVEL=error

vitest.config.ts

import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import path from 'path'

export default defineConfig({
  plugins: [vue()],
  test: {
    globals: true,
    environment: 'happy-dom',
    setupFiles: ['tests/setup.ts'],
    coverage: {
      provider: 'v8',
      reporter: ['text', 'json-summary', 'html'],
      exclude: [
        'node_modules/',
        'tests/',
        '**/*.d.ts',
        '**/*.config.*',
        'src/main.ts'
      ],
      threshold: {
        global: {
          branches: 80,
          functions: 80,
          lines: 80,
          statements: 80
        }
      }
    }
  },
  resolve: {
    alias: {
      '@': path.resolve(__dirname, 'src'),
      '~': path.resolve(__dirname, 'src')
    }
  }
})

docker-compose.yml

version: '3.8'

services:
  # 開發環境前端服務
  web:
    build:
      context: .
      dockerfile: Dockerfile.dev
    ports:
      - "3000:3000"
    volumes:
      - .:/app
      - /app/node_modules
    environment:
      - NODE_ENV=development
      - VITE_API_BASE_URL=http://api:5000/api
    depends_on:
      - api
    networks:
      - dramaling_network

  # Mock API服務 (開發時使用)
  api:
    image: mockserver/mockserver:latest
    ports:
      - "5000:1080"
    environment:
      MOCKSERVER_PROPERTY_FILE: /config/mockserver.properties
    volumes:
      - ./mock-api:/config
    networks:
      - dramaling_network

  # Redis (用於開發環境快取)
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    networks:
      - dramaling_network

networks:
  dramaling_network:
    driver: bridge

Dockerfile

# 多階段建構
# 建構階段
FROM node:18-alpine AS builder

WORKDIR /app

# 複製依賴檔案
COPY package*.json ./

# 安裝依賴
RUN npm ci --only=production

# 複製源代碼
COPY . .

# 建構應用
RUN npm run build

# 生產階段
FROM nginx:alpine AS production

# 複製建構結果
COPY --from=builder /app/dist /usr/share/nginx/html

# 複製Nginx配置
COPY nginx.conf /etc/nginx/conf.d/default.conf

# 健康檢查
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
  CMD wget --no-verbose --tries=1 --spider http://localhost/ || exit 1

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]

nginx.conf

server {
    listen 80;
    server_name _;
    root /usr/share/nginx/html;
    index index.html;

    # 安全頭
    add_header X-Frame-Options DENY;
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";
    add_header Referrer-Policy "strict-origin-when-cross-origin";

    # 靜態資源快取
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg|woff|woff2|ttf|eot)$ {
        expires 1y;
        add_header Cache-Control "public, immutable";
    }

    # API代理
    location /api/ {
        proxy_pass http://api-server:5000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }

    # SPA路由支援
    location / {
        try_files $uri $uri/ /index.html;
    }

    # Gzip壓縮
    gzip on;
    gzip_vary on;
    gzip_types
        text/plain
        text/css
        text/xml
        text/javascript
        application/javascript
        application/xml+rss
        application/json;
}

📋 專案初始化腳本

setup.sh

#!/bin/bash

echo "🚀 初始化 Drama Ling Vue.js 專案..."

# 創建專案目錄
mkdir -p dramaling-web
cd dramaling-web

# 初始化 package.json
npm init -y

# 安裝依賴
echo "📦 安裝依賴..."
npm install vue@^3.4.21 vue-router@^4.3.0 pinia@^2.1.7 quasar@^2.16.0

# 安裝開發依賴
npm install -D @vitejs/plugin-vue vite typescript vue-tsc @types/node

# 創建基本檔案結構
echo "📁 創建檔案結構..."
mkdir -p src/{components,modules,stores,services,utils,assets,router}
mkdir -p src/assets/{styles,images,audio}
mkdir -p src/components/{base,business,layout}
mkdir -p public/icons
mkdir -p tests/{unit,integration,e2e}

# 創建基本檔案
touch src/main.ts src/App.vue
touch src/assets/styles/main.scss
touch vite.config.ts tsconfig.json

echo "✅ 專案初始化完成!"
echo "下一步:"
echo "1. 配置 vite.config.ts"
echo "2. 設定 TypeScript"
echo "3. 安裝 UI 框架"
echo "4. 開始開發 🎉"

📊 專案規模估算

文件數量預估

總計約 300-400 個檔案:
├── 組件檔案: ~80個
├── 頁面檔案: ~40個
├── 服務檔案: ~20個
├── Store檔案: ~15個
├── 工具檔案: ~25個
├── 測試檔案: ~100個
├── 配置檔案: ~15個
└── 其他檔案: ~20個

開發時間估算

階段性開發預估:
├── 專案初始化和環境配置: 1週
├── 基礎組件和佈局: 2週
├── 認證模組: 1週
├── 詞彙學習模組: 3週
├── 情境對話模組: 3週
├── 學習地圖模組: 2週
├── 商店模組: 2週
├── 整合測試和優化: 2週
└── 部署和上線: 1週

總計: ~17週 (約4個月)

文檔狀態: 🟢 完整項目結構規劃
最後更新: 2025-09-09
下次更新: 根據開發進度調整