瀏覽代碼

✨ feat(shared-ui): add hc utility for hono client with axios adapter

- 创建 axios 适配器实现 Hono 客户端请求
- 新增 hc 工具函数用于创建 Hono RPC 客户端
- 添加 axios 和 hono 依赖包

✨ feat(user-management): implement user client manager with singleton pattern

- 创建 UserClientManager 单例类管理客户端实例
- 支持动态初始化和获取 Hono RPC 客户端
- 修改 UserManagement 组件使用客户端管理器获取实例

✨ feat(web): add api initialization for user client

- 创建 api_init.ts 初始化用户客户端基础 URL
- 在 admin 路由中导入 api 初始化脚本
- 设置用户客户端基础路径为 '/api/v1/users'
yourname 1 月之前
父節點
當前提交
3f1a3f519f

+ 8 - 1
packages/shared-ui-components/package.json

@@ -276,6 +276,11 @@
       "import": "./src/utils/cn.ts",
       "require": "./src/utils/cn.ts"
     },
+    "./utils/hc": {
+      "types": "./src/utils/hc.ts",
+      "import": "./src/utils/hc.ts",
+      "require": "./src/utils/hc.ts"
+    },
     "./types": {
       "types": "./src/types/index.ts",
       "import": "./src/types/index.ts",
@@ -342,7 +347,9 @@
     "tailwind-merge": "^3.3.1",
     "tw-animate-css": "^1.3.6",
     "vaul": "^1.1.2",
-    "zod": "^4.0.15"
+    "zod": "^4.0.15",
+    "axios": "^1.11.0",
+    "hono": "^4.8.5"
   },
   "devDependencies": {
     "@testing-library/jest-dom": "^6.8.0",

+ 65 - 0
packages/shared-ui-components/src/utils/hc.ts

@@ -0,0 +1,65 @@
+import axios, { isAxiosError } from 'axios';
+import { hc } from 'hono/client'
+
+// 创建 axios 适配器
+const axiosFetch = async (url: RequestInfo | URL, init?: RequestInit) => {
+  const requestHeaders: Record<string, string> = {};
+
+  if (init?.headers instanceof Headers) {
+    init.headers.forEach((value, key) => {
+      requestHeaders[key] = value;
+    })
+  }
+
+  const response = await axios.request({
+    url: url.toString(),
+    method: init?.method || 'GET',
+    headers: requestHeaders,
+    data: init?.body,
+  }).catch((error) => {
+    console.log('axiosFetch error', error)
+
+    if (isAxiosError(error)) {
+      return {
+        status: error.response?.status,
+        statusText: error.response?.statusText,
+        data: error.response?.data,
+        headers: error.response?.headers
+      }
+    }
+    throw error;
+  })
+
+  const responseHeaders = new Headers();
+  if (response.headers) {
+    for (const [key, value] of Object.entries(response.headers)) {
+      responseHeaders.set(key, value);
+    }
+  }
+
+
+  // 处理204 No Content响应,不设置body
+  const body = response.status === 204
+    ? null
+    : responseHeaders.get('content-type')?.includes('application/json')
+      ? JSON.stringify(response.data)
+      : response.data;
+
+  return new Response(
+    body,
+    {
+      status: response.status,
+      statusText: response.statusText,
+      headers: responseHeaders
+    }
+  )
+}
+
+// 创建Hono RPC客户端
+// @ts-types="node_modules/hono/dist/types/client/types"
+export const rpcClient = <T extends any>(aptBaseUrl: string) => {
+  // @ts-ignore
+  return hc<T>(aptBaseUrl, {
+    fetch: axiosFetch
+  })
+}

+ 1 - 1
packages/user-management-ui/src/api/index.ts

@@ -1 +1 @@
-export { userClient } from './userClient';
+export { userClient , userClientManager } from './userClient';

+ 42 - 3
packages/user-management-ui/src/api/userClient.ts

@@ -1,5 +1,44 @@
-import { hc } from 'hono/client';
 import { userRoutes } from '@d8d/user-module';
+import { rpcClient } from '@d8d/shared-ui-components/utils/hc'
 
-// 创建用户模块的RPC客户端
-export const userClient = hc<typeof userRoutes>('/');
+class UserClientManager {
+  private static instance: UserClientManager;
+  private client: ReturnType<typeof rpcClient<typeof userRoutes>> | null = null;
+
+  private constructor() {}
+
+  public static getInstance(): UserClientManager {
+    if (!UserClientManager.instance) {
+      UserClientManager.instance = new UserClientManager();
+    }
+    return UserClientManager.instance;
+  }
+
+  // 初始化客户端
+  public init(baseUrl: string = '/'): ReturnType<typeof rpcClient<typeof userRoutes>> {
+    return this.client = rpcClient<typeof userRoutes>(baseUrl);
+  }
+
+  // 获取客户端实例
+  public get(): ReturnType<typeof rpcClient<typeof userRoutes>> {
+    if (!this.client) {
+      return this.init()
+    }
+    return this.client;
+  }
+
+  // 重置客户端(用于测试或重新初始化)
+  public reset(): void {
+    this.client = null;
+  }
+}
+
+// 导出单例实例
+const userClientManager = UserClientManager.getInstance();
+
+// 导出默认客户端实例(延迟初始化)
+export const userClient = userClientManager.get()
+
+export {
+  userClientManager
+}

+ 5 - 5
packages/user-management-ui/src/components/UserManagement.tsx

@@ -2,7 +2,7 @@ import React, { useState, useMemo, useCallback } from 'react';
 import { useQuery } from '@tanstack/react-query';
 import { format } from 'date-fns';
 import { Plus, Search, Edit, Trash2, Filter, X } from 'lucide-react';
-import { userClient } from '../api/userClient';
+import { userClient, userClientManager } from '../api/userClient';
 import type { InferRequestType, InferResponseType } from 'hono/client';
 import { Button } from '@d8d/shared-ui-components/components/ui/button';
 import { Input } from '@d8d/shared-ui-components/components/ui/input';
@@ -97,7 +97,7 @@ export const UserManagement = () => {
         filterParams.createdAt = filters.createdAt;
       }
 
-      const res = await userClient.index.$get({
+      const res = await userClientManager.get().index.$get({
         query: {
           page: searchParams.page,
           pageSize: searchParams.limit,
@@ -208,7 +208,7 @@ export const UserManagement = () => {
   // 处理创建表单提交
   const handleCreateSubmit = async (data: CreateUserFormData) => {
     try {
-      const res = await userClient.index.$post({
+      const res = await userClientManager.get().index.$post({
         json: data
       });
       if (res.status !== 201) {
@@ -227,7 +227,7 @@ export const UserManagement = () => {
     if (!editingUser) return;
 
     try {
-      const res = await userClient[':id']['$put']({
+      const res = await userClientManager.get()[':id']['$put']({
         param: { id: editingUser.id },
         json: data
       });
@@ -252,7 +252,7 @@ export const UserManagement = () => {
     if (!userToDelete) return;
 
     try {
-      const res = await userClient[':id']['$delete']({
+      const res = await userClientManager.get()[':id']['$delete']({
         param: { id: userToDelete }
       });
       if (res.status !== 204) {

+ 6 - 0
pnpm-lock.yaml

@@ -1645,6 +1645,9 @@ importers:
       '@tanstack/react-query':
         specifier: ^5.90.9
         version: 5.90.9(react@19.2.0)
+      axios:
+        specifier: ^1.11.0
+        version: 1.12.2(debug@4.4.3)
       class-variance-authority:
         specifier: ^0.7.1
         version: 0.7.1
@@ -1663,6 +1666,9 @@ importers:
       embla-carousel-react:
         specifier: ^8.6.0
         version: 8.6.0(react@19.2.0)
+      hono:
+        specifier: ^4.8.5
+        version: 4.8.5
       input-otp:
         specifier: ^1.4.2
         version: 1.4.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)

+ 1 - 0
web/src/client/admin/routes.tsx

@@ -15,6 +15,7 @@ import { MerchantsPage } from './pages/Merchants'
 import { OrdersPage } from './pages/Orders';
 import { DeliveryAddressesPage } from './pages/DeliveryAddresses';
 import { UserManagement } from '@d8d/user-management-ui';
+import "@/client/api_init"
 
 export const router = createBrowserRouter([
   {

+ 3 - 0
web/src/client/api_init.ts

@@ -0,0 +1,3 @@
+import { userClientManager } from '@d8d/user-management-ui/api';
+
+userClientManager.init('/api/v1/users');