跳至主要内容

工具函數

:::info 內容來源 本頁內容源自 appfuse-web/lib/utils/README.md,如有差異以原始碼為準。 :::

AppFuse Web 提供 10+ 工具模組,涵蓋 HTTP 請求、日期處理、國際化等常見需求。

匯入方式

所有工具函數統一從 @appfuse/appfuse-web/utils 匯入:

import { createHttpClient, time, logger, cn, i18n, cookie, environ, browser, numeral, template } from '@appfuse/appfuse-web/utils'

模組總覽

模組用途
createHttpClient / HttpClientProvider / useHttpClientHTTP 客戶端
Filter查詢過濾器(RSQL/FIQL)
time / format / toDateString日期時間處理
i18n / useTranslation / I18nProvider國際化
numeral數字格式化
cnCSS 類別合併
cookieCookie 管理
browser瀏覽器偵測
environ環境配置
logger日誌系統
template字串模板

http - HTTP 客戶端

基於 axios 的 HTTP 客戶端,支援自動日期轉換和檔案上傳。

建立客戶端

import { createHttpClient } from '@appfuse/appfuse-web/utils';

const http = createHttpClient({
baseURL: '/api',
timeout: 30000,
});

基本請求

// GET
const users = await http.get<User[]>('/users');

// GET with params
const users = await http.get<User[]>('/users', {
params: { status: 'active', page: 1 }
});

// POST
const user = await http.post<User>('/users', {
name: 'John',
email: 'john@example.com'
});

// PUT
await http.put(`/users/${id}`, userData);

// DELETE
await http.delete(`/users/${id}`);

自動日期轉換

HTTP 客戶端會自動將 ISO 8601 日期字串轉換為 Date 物件:

// API 回傳
{ "createdAt": "2024-01-15T10:30:00Z" }

// 自動轉換為
{ createdAt: Date } // JavaScript Date 物件

檔案上傳

HTTP 客戶端會自動偵測請求資料中的 File 物件並處理上傳,支援多種策略:

// 自動處理:資料中包含 File 時,自動轉為 multipart/form-data
const product = await http.post('/products', {
name: 'Product',
image: file // File 物件,自動處理
});

可透過 createHttpClientfileUpload 選項配置上傳策略:

  • binary(預設):使用 multipart/form-data
  • base64:檔案轉 base64 內嵌於 JSON

filter - 查詢過濾器

使用 Filter 類別建構 RSQL/FIQL 標準的查詢字串。

基本用法

import { Filter } from '@appfuse/appfuse-web/utils';

// 簡單查詢
const query = Filter.eq('status', 'active').toString();
// → 'status=="active"'

// 模糊查詢
const query = Filter.like('name', 'John').toString();
// → 'name=="*John*"'

支援的運算子

靜態方法運算子說明
Filter.eq(field, value)==等於
Filter.notEq(field, value)!=不等於
Filter.gt(field, value)=gt=大於
Filter.ge(field, value)=ge=大於等於
Filter.lt(field, value)=lt=小於
Filter.le(field, value)=le=小於等於
Filter.like(field, value)== + 萬用字元模糊比對
Filter.in(field, values)=in=包含於
Filter.notIn(field, values)=out=不包含於
Filter.has(field, value)=has=包含
Filter.is(field, value)=is=是(null 或 boolean)
Filter.isNot(field, value)=isnt=不是(null 或 boolean)

組合查詢

// AND 條件
const query = Filter.eq('status', 'active')
.and(Filter.ge('price', 100))
.toString();
// → '(status=="active";price=ge=100)'

// OR 條件
const query = Filter.eq('category', 'A')
.or(Filter.eq('category', 'B'))
.toString();
// → '(category=="A",category=="B")'

// 空值自動忽略
Filter.eq('name', '').toString(); // → null

time - 日期時間

基於 date-fns 的日期時間處理工具,提供格式化、解析、計算與 Duration 操作。

格式化

import { format, toDateString } from '@appfuse/appfuse-web/utils';

const date = new Date();

// 使用自訂格式
format(date, 'yyyy-MM-dd'); // "2024-01-15"
format(date, 'HH:mm:ss'); // "10:30:00"
format(date, 'yyyy-MM-dd HH:mm'); // "2024-01-15 10:30"

// 使用預設格式常數
import { TimeFormat } from '@appfuse/appfuse-web/utils';
format(date, TimeFormat.LOCAL_DATE); // "2024-01-15"
format(date, TimeFormat.LOCAL_DATETIME); // "2024-01-15T10:30:00"

// API 日期欄位轉換
toDateString(date); // "2024-01-15"(YYYY-MM-DD 格式)

解析

import { parse, TimeFormat } from '@appfuse/appfuse-web/utils';

parse('2024-01-15', TimeFormat.LOCAL_DATE); // Date 物件
parse('2024-01-15T10:30:00+08:00', TimeFormat.DATETIME); // Date 物件

計算

import { plus, minus, Duration, isBefore, isAfter } from '@appfuse/appfuse-web/utils';

const today = new Date();

// 加減時間
const tomorrow = plus(today, Duration.days(1));
const lastWeek = minus(today, Duration.days(7));
const later = plus(today, Duration.hours(2));

// 比較
isBefore(date1, date2); // date1 < date2
isAfter(date1, date2); // date1 > date2

Duration

import { Duration } from '@appfuse/appfuse-web/utils';

// 建立 Duration
const d = Duration.minutes(90);
d.toHours(); // 1.5
d.toMinutes(); // 90
d.hours(); // 1(小時部分)
d.minutes(); // 30(分鐘部分)

// 靜態工廠方法
Duration.hours(2);
Duration.days(7);
Duration.seconds(30);

// 運算
const total = Duration.hours(1).add(Duration.minutes(30));

// 格式化(ISO 8601)
Duration.minutes(90).toISOString(); // "PT1H30M"

i18n - 國際化

多語言支援工具,提供翻譯、參數插值與語言回退。

初始化

import { i18n } from '@appfuse/appfuse-web/utils';

// 新增翻譯
i18n.addTranslation('zh-TW', {
welcome: '歡迎,${name}!',
items: '你有 ${count} 個項目',
});

i18n.addTranslation('en', {
welcome: 'Welcome, ${name}!',
items: 'You have ${count} items',
});

// 設定語言
i18n.language = 'zh-TW';

// 或使用 configure 一次配置
i18n.configure({
language: 'zh-TW',
fallback: 'en',
translations: {
'zh-TW': { welcome: '歡迎' },
'en': { welcome: 'Welcome' },
},
});

翻譯

// 簡單翻譯
i18n.t('welcome'); // "歡迎"

// 帶具名參數
i18n.t('welcome', { name: 'John' }); // "歡迎,John!"

// 找不到翻譯時回傳原文
i18n.t('Not translated'); // "Not translated"

語言回退

// 設定回退語言
i18n.fallback = 'en';

// zh-TW 找不到時會嘗試 zh,再嘗試 en

React 整合

import { useTranslation } from '@appfuse/appfuse-web/utils';

function MyComponent() {
const { t, language } = useTranslation();

return <button>{t('Save')}</button>;
}

numeral - 數字格式化

使用 Intl API 的數字格式化工具,所有方法皆為 numeral 實例的方法。

數字格式化

import { numeral } from '@appfuse/appfuse-web/utils';

// 十進位數字
numeral.formatDecimal(1234567.89); // "1,234,567.89"
numeral.formatDecimal(1234567.89, 0); // "1,234,568"(無小數)

// 貨幣
numeral.formatCurrency(1234.56, 'USD'); // "$1,234.56"
numeral.formatCurrency(1234.56, 'TWD'); // "NT$1,235"

// 百分比
numeral.formatPercent(0.1234); // "12%"
numeral.formatPercent(0.1234, 2); // "12.34%"

// 通用格式化(使用模式字串)
numeral.format(1234.56, 'currency:usd'); // "$1,234.56"
numeral.format(0.1234, 'percent'); // "12%"

cn - CSS 類別合併

智慧合併 Tailwind CSS 類別。

基本用法

import { cn } from '@appfuse/appfuse-web/utils';

// 合併類別
cn('px-4 py-2', 'bg-blue-500');
// "px-4 py-2 bg-blue-500"

// 條件類別
cn('btn', isActive && 'btn-active');
// isActive ? "btn btn-active" : "btn"

// 覆蓋衝突類別
cn('px-4', 'px-8');
// "px-8"(後者覆蓋前者)

// 物件語法
cn({
'btn': true,
'btn-primary': isPrimary,
'btn-disabled': isDisabled,
});

透過 cookie 實例進行類型安全的 Cookie 操作。

基本操作

import { cookie } from '@appfuse/appfuse-web/utils';

// 讀取
const token = cookie.get('token');
const theme = cookie.get('theme', 'light'); // 含預設值

// 讀取為布林值
const isAuth = cookie.getAsBoolean('isAuth', false);

// 設定
cookie.set('token', 'abc123', {
expiredInDays: 7,
path: '/',
secure: true,
sameSite: 'Strict',
});

// 檢查存在
if (cookie.has('sessionId')) {
// ...
}

// 刪除
cookie.delete('sessionId');

browser - 瀏覽器偵測

透過 browser 實例進行瀏覽器和語言偵測。

語言偵測

import { browser } from '@appfuse/appfuse-web/utils';

// 取得瀏覽器語言
const lang = browser.language; // "zh-TW"

// 取得所有偏好語言
const langs = browser.languages; // ["zh-TW", "en-US", ...]

// 取得解析後的 Locale
const locale = browser.locale;
// { language: 'zh', country: 'TW' }

environ - 環境配置

透過 environ 實例進行類型安全的環境配置管理。配置只能設定一次,設定後不可變。

配置與讀取

import { environ } from '@appfuse/appfuse-web/utils';

// 初始化配置(只能呼叫一次)
environ.configure({
app: {
name: 'My App',
version: '1.0.0',
stage: 'development',
basename: '/',
baseURL: '/api',
},
i18n: {
languages: ['zh-TW', 'en'],
},
});

// 讀取配置
environ.baseURL; // '/api'
environ.basename; // '/'
environ.app.name; // 'My App'
environ.app.version; // '1.0.0'
environ.isDev; // true
environ.isProd; // false
environ.isConfigured; // true

logger - 日誌系統

基於 loglevel 的日誌工具,透過 logger 實例操作。

基本用法

import { logger } from '@appfuse/appfuse-web/utils';

// 不同等級
logger.debug('Debug message');
logger.info('Info message');
logger.warn('Warning message');
logger.error('Error message');

// 條件式日誌
logger.warnIf(items.length > 100, 'Large dataset detected', items.length);

// 效能計時
logger.time('fetchData');
await fetchData();
logger.timeEnd('fetchData');

建立子 logger

// 建立模組專用 logger
const authLogger = logger.getLogger('auth');
authLogger.info('User logged in'); // 帶時間戳輸出

動態設定等級

// 透過 setter 設定等級
logger.level = 'debug'; // 開發環境
logger.level = 'warn'; // 生產環境

template - 字串模板

透過 template 實例進行字串插值,使用 ${...} 佔位符語法。

位置參數

import { template } from '@appfuse/appfuse-web/utils';

template.format('Hello, ${0}!', 'World');
// "Hello, World!"

template.format('${0} + ${1} = ${2}', 1, 2, 3);
// "1 + 2 = 3"

命名參數

template.format('Hello, ${name}! You have ${count} messages.', {
name: 'John',
count: 5
});
// "Hello, John! You have 5 messages."

Tree-shaking 匯入

除了統一匯入,也支援個別模組匯入以優化打包體積:

// 統一匯入
import { createHttpClient, logger } from '@appfuse/appfuse-web/utils'

// 個別模組匯入(僅打包使用的模組)
import { createHttpClient } from '@appfuse/appfuse-web/utils/http'
import { logger } from '@appfuse/appfuse-web/utils/logger'

最佳實踐

  1. 統一 HTTP 客戶端 - 應用程式只建立一個 http 實例
  2. 使用 Filter - 透過 Filter 靜態方法建構查詢,避免手動拼接字串
  3. 日期處理 - 使用 format()plus()minus() 等 time 工具函數
  4. CSS 合併 - 使用 cn() 處理條件樣式

下一步