專案結構
本文檔說明基於 AppFuse Web 的專案結構,包含應用程式結構和框架內部結構。
應用程式結構
以下是使用 AppFuse Web 建構的應用程式推薦結構:
my-web-app/
├── public/ # 靜態資源
│ ├── favicon.ico
│ └── mockServiceWorker.js # MSW Service Worker
├── src/
│ ├── applets/ # 業務功能模組
│ ├── components/ # 共用組件
│ ├── layouts/ # 佈局組件
│ ├── features/ # Redux slices
│ ├── services/ # API 服務層
│ ├── mocks/ # MSW mock handlers
│ ├── hooks/ # 自訂 hooks
│ ├── utils/ # 工具函數
│ ├── config/ # 應用程式配置
│ ├── routes/ # 路由配置
│ ├── nls/ # 國際化資源
│ ├── types/ # TypeScript 類型定義
│ ├── App.tsx # 主應用程式組件
│ ├── main.tsx # 應用程式入口
│ └── tailwind.css # Tailwind CSS
├── index.html # HTML 模板
├── package.json # 專案配置
├── vite.config.ts # Vite 配置
├── tailwind.config.ts # Tailwind 配置
└── tsconfig.json # TypeScript 配置
核心目錄說明
applets/ - 業務功能模組
Applet 是獨立的業務功能模組,包含完整的 UI、狀態、邏輯:
applets/
├── products/ # 商品管理 Applet
│ ├── ProductListApplet.tsx
│ ├── ProductDetailApplet.tsx
│ └── types.ts
├── orders/ # 訂單管理 Applet
│ ├── OrderListApplet.tsx
│ └── OrderDetailApplet.tsx
└── settings/ # 設定 Applet
└── SettingsApplet.tsx
Applet 特性:
- 獨立的路由入口
- 可包含子路由
- 使用
<AppletShell>提供標準佈局 - 透過 Application Launcher 註冊
範例:參考 app-web/src/applets
components/ - 共用組件
存放專案內跨 Applet 共用的組件:
components/
├── PageHeader/ # 頁面標題組件
│ ├── PageHeader.tsx
│ └── PageHeader.test.tsx
├── DataCard/ # 資料卡片組件
│ └── DataCard.tsx
└── ErrorBoundary/ # 錯誤邊界組件
└── ErrorBoundary.tsx
使用時機:
- 框架元件庫未提供
- 多個 Applet 需要使用
- 專案特定的業務組件
原則:
- 優先使用
@appfuse/appfuse-web的組件 - 僅建立必要的自訂組件
- 避免過度抽象
layouts/ - 佈局組件
定義應用程式的整體佈局:
layouts/
├── MainLayout.tsx # 主要佈局(含 Header、Sidebar)
├── AuthLayout.tsx # 認證頁面佈局
└── ErrorLayout.tsx # 錯誤頁面佈局
職責:
- 定義頁面框架
- 整合導航元件
- 處理佈局狀態(sidebar 展開/收合)
features/ - Redux Slices
使用 Redux Toolkit 管理全域狀態:
features/
├── auth/ # 認證狀態
│ ├── authSlice.ts
│ └── authThunks.ts
├── tenant/ # 租戶狀態
│ └── tenantSlice.ts
└── ui/ # UI 狀態(sidebar、theme)
└── uiSlice.ts
設計原則:
- 僅存放真正全域的狀態
- 局部狀態使用 React state 或 URL state
- 使用 RTK Query 處理 API 快取
services/ - API 服務層
封裝與後端的通訊:
services/
├── api.ts # API 客戶端配置
├── auth.service.ts # 認證相關 API
├── product.service.ts # 商品相關 API
└── order.service.ts # 訂單相關 API
職責:
- 定義 API 端點
- 處理請求/回應轉換
- 統一錯誤處理
範例:
// services/product.service.ts
import { api } from './api';
import type { Product, ProductCreateRequest } from '@/types';
export const productService = {
findAll: () => api.get<Product[]>('/api/products'),
findById: (id: string) => api.get<Product>(`/api/products/${id}`),
create: (data: ProductCreateRequest) =>
api.post<Product>('/api/products', data),
};
mocks/ - MSW Mock Handlers
定義 Mock API 回應:
mocks/
├── browser.ts # MSW 瀏覽器設定
├── handlers/ # Mock handlers
│ ├── auth.handlers.ts
│ ├── product.handlers.ts
│ └── order.handlers.ts
└── data/ # Mock 資料
├── products.ts
└── orders.ts
使用方式:
// mocks/handlers/product.handlers.ts
import { http, HttpResponse } from 'msw';
import { products } from '../data/products';
export const productHandlers = [
http.get('/api/products', () => {
return HttpResponse.json(products);
}),
http.post('/api/products', async ({ request }) => {
const newProduct = await request.json();
return HttpResponse.json(newProduct, { status: 201 });
}),
];
Mock → 真實 API 切換:參考 Mock API 指南
config/ - 應用程式配置
config/
├── app.config.ts # 應用程式配置
├── routes.config.ts # 路由配置
└── theme.config.ts # 主題配置
範例:
// config/app.config.ts
export const appConfig = {
name: 'My Web App',
version: '0.0.1',
apiBaseUrl: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080',
enableMockAPI: import.meta.env.DEV,
};
routes/ - 路由配置
定義應用程式路由:
routes/
├── index.tsx # 主路由配置
├── AppRoutes.tsx # 應用程式路由
└── AuthRoutes.tsx # 認證路由
範例:
// routes/AppRoutes.tsx
import { Routes, Route } from 'react-router-dom';
import { ProductListApplet } from '@/applets/products';
export function AppRoutes() {
return (
<Routes>
<Route path="/products" element={<ProductListApplet />} />
{/* 其他路由 */}
</Routes>
);
}
nls/ - 國際化資源
存放多語言資源檔:
nls/
├── zh-TW/ # 繁體中文
│ ├── common.json
│ ├── products.json
│ └── orders.json
└── en-US/ # 英文
├── common.json
├── products.json
└── orders.json
使用方式:參考 國際化指南
配置檔案
vite.config.ts
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';
import tailwindcss from '@tailwindcss/vite';
import path from 'path';
export default defineConfig({
plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
server: {
port: 5173,
},
});
tailwind.config.ts
import type { Config } from 'tailwindcss';
export default {
content: [
'./index.html',
'./src/**/*.{js,ts,jsx,tsx}',
'./node_modules/@appfuse/appfuse-web/lib/**/*.{ts,tsx}',
],
theme: {
extend: {
// 自訂主題
},
},
plugins: [],
} satisfies Config;
src/tailwind.css
@import "tailwindcss";
/*
* 掃描元件庫的原始碼,讓 Tailwind 統一生成 CSS
*/
@source "../node_modules/@appfuse/appfuse-web/lib/**/*.{ts,tsx}";
/* 自訂樣式 */
最佳實踐
1. Import 路徑
使用路徑別名提高可讀性:
✅ 推薦:
import { Button } from '@appfuse/appfuse-web';
import { ProductCard } from '@/components/ProductCard';
import { useAuth } from '@/hooks/useAuth';
❌ 避免:
import { Button } from '../../../node_modules/@appfuse/appfuse-web';
import { ProductCard } from '../../../components/ProductCard';
2. 狀態管理策略
局部狀態 → React useState
URL 狀態 → useSearchParams
全域狀態 → Redux Toolkit
伺服器狀態 → RTK Query
3. 檔案命名
- 組件:
PascalCase.tsx(如ProductCard.tsx) - Hooks:
camelCase.ts(如useAuth.ts) - 工具函數:
camelCase.ts(如formatDate.ts) - 類型定義:
types.ts或*.types.ts
框架內部結構
以下是 @appfuse/appfuse-web 框架本身的結構,供進階參考:
appfuse-web/
├── lib/ # 框架層(發布為 @appfuse/appfuse-web)
│ ├── components/ # UI 元件庫(30+ 元件)
│ ├── form/ # 表單元件(react-hook-form 整合)
│ ├── utils/ # 工具函數(http, i18n, time 等)
│ ├── hooks/ # React Hooks
│ ├── messaging/ # 全域訊息系統(Toast + Dialog)
│ ├── main.ts # 框架進入點
│ └── tailwind.css # 基礎 Tailwind CSS
│
├── src/ # 原型層(參考實作)
│ ├── applets/ # 功能模組(product, order, customer 等)
│ ├── services/ # API 服務層
│ ├── components/ # 共用元件
│ ├── layouts/ # 版面配置
│ ├── routes/ # React Router 路由
│ ├── mocks/ # MSW Mock API
│ ├── features/ # Redux Slices
│ ├── store/ # Redux Store
│ ├── config/ # 應用程式配置
│ ├── types/ # TypeScript 型別
│ ├── nls/ # 國際化資源
│ └── assets/ # 靜態資源
│
├── tests/ # E2E 測試(Playwright)
├── docs/ # 框架文檔
└── .storybook/ # Storybook 配置
框架元件庫 (lib/components/)
按功能分類的 UI 元件:
| 分類 | 路徑 | 包含元件 |
|---|---|---|
| 資料輸入 | data-input/ | Input, Select, Checkbox, Radio, Switch, Textarea, TagInput, FileInput, MediaInput, RichTextEditor |
| 資料顯示 | data-display/ | DataTable, VirtualTable, Icon, MediaViewer, AuthImage |
| 動作 | actions/ | Button |
| 圖表 | charts/ | LineChart, AreaChart, BarChart, PieChart, RadarChart, FunnelChart, ScatterPlot |
| 版面 | layout/ | CollapsibleCard |
| 導航 | navigation/ | Tabs |
| 回饋 | feedback/ | Toast, ToastContainer, Dialog |
| 覆蓋 | overlays/ | Dropdown |
框架工具函數 (lib/utils/)
| 模組 | 用途 |
|---|---|
http | HTTP 客戶端(axios 封裝,自動日期轉換) |
filter | 查詢過濾器建構(RSQL/FIQL 標準) |
browser | 瀏覽器偵測、語言偵測 |
cn | CSS 類別合併(Tailwind CSS) |
cookie | Cookie 管理 |
environ | 環境配置 |
i18n | 國際化 |
logger | 日誌系統 |
numeral | 數字格式化 |
template | 字串模板 |
time | 日期時間處理 |
框架匯入路徑
// 元件
import { Button, Input } from '@appfuse/appfuse-web/components';
// 表單元件
import { Input, Select } from '@appfuse/appfuse-web/form';
// 工具函數
import { createHttpClient } from '@appfuse/appfuse-web/utils';
import { time } from '@appfuse/appfuse-web/utils';
// Hooks
import { useTimeout } from '@appfuse/appfuse-web/hooks';
// 訊息系統
import { prompt } from '@appfuse/appfuse-web/messaging';
下一步
- 第一個 Applet - 建立你的第一個業務模組
- 狀態管理 - 深入了解狀態管理策略
- 表單處理 - 學習表單開發模式
參考資源
- app-web 專案結構 - 參考實作完整範例