@@ -2148,5 +2148,295 @@ src/services/
21482148
21492149---
21502150
2151- * 文档版本: 2.0 (强类型安全版)*
2151+ ## 12. Platform Services(平台服务层)
2152+
2153+ ### 12.1 概述
2154+
2155+ Platform Services 是与运行平台相关的服务,提供设备能力抽象,支持 Web 和 DWEB 两种运行环境。
2156+
2157+ ** 设计目标:**
2158+ - ** 编译时实现选择** :通过 Vite alias 实现完美 tree-shaking
2159+ - ** 统一 API** :上层代码无需关心运行环境
2160+ - ** Mock 支持** :开发和测试时可使用 Mock 实现
2161+
2162+ ### 12.2 服务列表
2163+
2164+ | 服务 | 说明 | Web 实现 | DWEB 实现 |
2165+ | -----| ------| ---------| ----------|
2166+ | BiometricService | 生物识别认证 | WebAuthn API | @plaoc/plugins biometricsPlugin |
2167+ | SecureStorageService | 安全存储 | localStorage + AES | @plaoc/plugins keyValuePlugin |
2168+ | ClipboardService | 剪贴板操作 | Clipboard API | @plaoc/plugins clipboardPlugin |
2169+ | ToastService | 轻提示 | DOM 注入 | @plaoc/plugins toastPlugin |
2170+ | CameraService | 相机/扫码 | MediaDevices API | @plaoc/plugins barcodeScannerPlugin |
2171+ | HapticsService | 触觉反馈 | Vibration API | @plaoc/plugins hapticsPlugin |
2172+
2173+ ### 12.3 目标架构(每服务独立文件夹)
2174+
2175+ ```
2176+ src/services/
2177+ ├── biometric/
2178+ │ ├── index.ts # 统一导出 + ServiceProvider
2179+ │ ├── types.ts # 接口定义
2180+ │ ├── web.ts # Web 实现
2181+ │ ├── dweb.ts # DWEB 实现
2182+ │ └── mock.ts # Mock 实现
2183+ │
2184+ ├── storage/
2185+ │ ├── index.ts
2186+ │ ├── types.ts
2187+ │ ├── web.ts
2188+ │ ├── dweb.ts
2189+ │ └── mock.ts
2190+ │
2191+ ├── clipboard/
2192+ │ ├── index.ts
2193+ │ ├── types.ts
2194+ │ ├── web.ts
2195+ │ ├── dweb.ts
2196+ │ └── mock.ts
2197+ │
2198+ ├── toast/
2199+ │ ├── index.ts
2200+ │ ├── types.ts
2201+ │ ├── web.ts
2202+ │ ├── dweb.ts
2203+ │ └── mock.ts
2204+ │
2205+ ├── camera/
2206+ │ ├── index.ts
2207+ │ ├── types.ts
2208+ │ ├── web.ts
2209+ │ ├── dweb.ts
2210+ │ └── mock.ts
2211+ │
2212+ ├── haptics/
2213+ │ ├── index.ts
2214+ │ ├── types.ts
2215+ │ ├── web.ts
2216+ │ ├── dweb.ts
2217+ │ └── mock.ts
2218+ │
2219+ └── index.ts # 统一导出所有服务
2220+ ```
2221+
2222+ ### 12.4 接口定义示例
2223+
2224+ ``` typescript
2225+ // src/services/biometric/types.ts
2226+
2227+ export interface BiometricOptions {
2228+ reason? : string
2229+ fallbackToPassword? : boolean
2230+ }
2231+
2232+ export interface BiometricResult {
2233+ success: boolean
2234+ error? : string
2235+ }
2236+
2237+ export interface IBiometricService {
2238+ /** 检查是否可用 */
2239+ isAvailable(): Promise <boolean >
2240+
2241+ /** 执行认证 */
2242+ authenticate(options ? : BiometricOptions ): Promise <BiometricResult >
2243+ }
2244+ ```
2245+
2246+ ``` typescript
2247+ // src/services/clipboard/types.ts
2248+
2249+ export interface IClipboardService {
2250+ /** 写入文本 */
2251+ writeText(text : string ): Promise <void >
2252+
2253+ /** 读取文本 */
2254+ readText(): Promise <string >
2255+ }
2256+ ```
2257+
2258+ ``` typescript
2259+ // src/services/toast/types.ts
2260+
2261+ export type ToastType = ' success' | ' error' | ' warning' | ' info'
2262+
2263+ export interface ToastOptions {
2264+ message: string
2265+ type? : ToastType
2266+ duration? : number
2267+ }
2268+
2269+ export interface IToastService {
2270+ /** 显示 toast */
2271+ show(options : ToastOptions ): void
2272+
2273+ /** 快捷方法 */
2274+ success(message : string ): void
2275+ error(message : string ): void
2276+ warning(message : string ): void
2277+ info(message : string ): void
2278+ }
2279+ ```
2280+
2281+ ### 12.5 编译时实现选择
2282+
2283+ ** Vite 配置:**
2284+
2285+ ``` typescript
2286+ // vite.config.ts
2287+ export default defineConfig ({
2288+ resolve: {
2289+ alias: {
2290+ // 根据 SERVICE_IMPL 环境变量选择实现
2291+ ' #biometric-impl' : ` ./src/services/biometric/${process .env .SERVICE_IMPL || ' web' }.ts ` ,
2292+ ' #storage-impl' : ` ./src/services/storage/${process .env .SERVICE_IMPL || ' web' }.ts ` ,
2293+ ' #clipboard-impl' : ` ./src/services/clipboard/${process .env .SERVICE_IMPL || ' web' }.ts ` ,
2294+ ' #toast-impl' : ` ./src/services/toast/${process .env .SERVICE_IMPL || ' web' }.ts ` ,
2295+ ' #camera-impl' : ` ./src/services/camera/${process .env .SERVICE_IMPL || ' web' }.ts ` ,
2296+ ' #haptics-impl' : ` ./src/services/haptics/${process .env .SERVICE_IMPL || ' web' }.ts ` ,
2297+ }
2298+ }
2299+ })
2300+ ```
2301+
2302+ ** 服务入口文件:**
2303+
2304+ ``` typescript
2305+ // src/services/biometric/index.ts
2306+ import type { IBiometricService } from ' ./types'
2307+ import { BiometricService } from ' #biometric-impl'
2308+
2309+ export type { IBiometricService , BiometricOptions , BiometricResult } from ' ./types'
2310+ export { BiometricService }
2311+
2312+ // 创建单例
2313+ export const biometricService: IBiometricService = new BiometricService ()
2314+ ```
2315+
2316+ ** 构建脚本:**
2317+
2318+ ``` json
2319+ {
2320+ "scripts" : {
2321+ "dev" : " SERVICE_IMPL=web vite" ,
2322+ "dev:mock" : " SERVICE_IMPL=mock vite" ,
2323+ "build:web" : " SERVICE_IMPL=web vite build" ,
2324+ "build:dweb" : " SERVICE_IMPL=dweb vite build"
2325+ }
2326+ }
2327+ ```
2328+
2329+ ### 12.6 React Hooks
2330+
2331+ ``` typescript
2332+ // src/services/hooks.ts
2333+ import { biometricService } from ' ./biometric'
2334+ import { clipboardService } from ' ./clipboard'
2335+ import { toastService } from ' ./toast'
2336+ import { hapticsService } from ' ./haptics'
2337+
2338+ export function useBiometric() {
2339+ return biometricService
2340+ }
2341+
2342+ export function useClipboard() {
2343+ return clipboardService
2344+ }
2345+
2346+ export function useToast() {
2347+ return toastService
2348+ }
2349+
2350+ export function useHaptics() {
2351+ return hapticsService
2352+ }
2353+ ```
2354+
2355+ ** 使用示例:**
2356+
2357+ ``` typescript
2358+ function WalletAddress({ address }: { address: string }) {
2359+ const clipboard = useClipboard ()
2360+ const toast = useToast ()
2361+ const haptics = useHaptics ()
2362+
2363+ const handleCopy = async () => {
2364+ await clipboard .writeText (address )
2365+ await haptics .impact (' light' )
2366+ toast .success (' 地址已复制' )
2367+ }
2368+
2369+ return (
2370+ < button onClick = {handleCopy }>
2371+ {address }
2372+ < / button >
2373+ )
2374+ }
2375+ ```
2376+
2377+ ### 12.7 Mock 实现(测试用)
2378+
2379+ ``` typescript
2380+ // src/services/biometric/mock.ts
2381+ import type { IBiometricService , BiometricOptions , BiometricResult } from ' ./types'
2382+
2383+ // 通过 window 暴露控制接口,供 E2E 测试使用
2384+ declare global {
2385+ interface Window {
2386+ __MOCK_BIOMETRIC__? : {
2387+ available: boolean
2388+ shouldSucceed: boolean
2389+ }
2390+ }
2391+ }
2392+
2393+ export class BiometricService implements IBiometricService {
2394+ async isAvailable(): Promise <boolean > {
2395+ return window .__MOCK_BIOMETRIC__ ?.available ?? true
2396+ }
2397+
2398+ async authenticate(options ? : BiometricOptions ): Promise <BiometricResult > {
2399+ const shouldSucceed = window .__MOCK_BIOMETRIC__ ?.shouldSucceed ?? true
2400+ return {
2401+ success: shouldSucceed ,
2402+ error: shouldSucceed ? undefined : ' Mock authentication failed'
2403+ }
2404+ }
2405+ }
2406+ ```
2407+
2408+ ### 12.8 E2E 测试集成
2409+
2410+ ``` typescript
2411+ // e2e/services.spec.ts
2412+ import { test , expect } from ' @playwright/test'
2413+
2414+ test (' biometric authentication' , async ({ page }) => {
2415+ // 配置 mock
2416+ await page .evaluate (() => {
2417+ window .__MOCK_BIOMETRIC__ = { available: true , shouldSucceed: true }
2418+ })
2419+
2420+ // 触发认证
2421+ await page .click (' [data-testid="biometric-auth-button"]' )
2422+
2423+ // 验证结果
2424+ await expect (page .locator (' [data-testid="auth-success"]' )).toBeVisible ()
2425+ })
2426+
2427+ test (' clipboard copy' , async ({ page }) => {
2428+ await page .click (' [data-testid="copy-address-button"]' )
2429+
2430+ // 验证 toast 提示
2431+ await expect (page .locator (' .toast-success' )).toContainText (' 已复制' )
2432+
2433+ // 验证剪贴板内容(通过 mock)
2434+ const clipboardContent = await page .evaluate (() => window .__CLIPBOARD__ )
2435+ expect (clipboardContent ).toBe (' 0x...' )
2436+ })
2437+ ```
2438+
2439+ ---
2440+
2441+ * 文档版本: 3.0 (强类型安全版 + Platform Services)*
21522442* 配套文档: PDR.md (产品需求) / TDD.md (技术设计)*
0 commit comments