Просмотр исходного кода

✨ feat(delivery-address-management-ui): 实现单租户地址管理界面独立包

- 创建完整的地址管理UI包结构
- 实现单例模式的RPC客户端管理器
- 集成UserSelector和AreaSelect4Level组件
- 实现完整的CRUD操作和表单验证
- 添加四级区域选择器功能
- 创建集成测试套件
- 配置包导出接口

🤖 Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
yourname 1 месяц назад
Родитель
Сommit
e0dbca1835

+ 1 - 0
packages/delivery-address-management-ui/package.json

@@ -45,6 +45,7 @@
     "@d8d/delivery-address-module": "workspace:*",
     "@d8d/delivery-address-module": "workspace:*",
     "@d8d/geo-areas": "workspace:*",
     "@d8d/geo-areas": "workspace:*",
     "@d8d/user-management-ui": "workspace:*",
     "@d8d/user-management-ui": "workspace:*",
+    "@d8d/area-management-ui": "workspace:*",
     "@hookform/resolvers": "^5.2.1",
     "@hookform/resolvers": "^5.2.1",
     "@tanstack/react-query": "^5.90.9",
     "@tanstack/react-query": "^5.90.9",
     "axios": "^1.7.9",
     "axios": "^1.7.9",

+ 611 - 0
packages/delivery-address-management-ui/src/components/DeliveryAddressManagement.tsx

@@ -0,0 +1,611 @@
+import { useState } from 'react';
+import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
+import { format } from 'date-fns';
+import { zhCN } from 'date-fns/locale';
+import { toast } from 'sonner';
+import { Plus, Search, Edit, Trash2, MapPin } from 'lucide-react';
+import { useForm } from 'react-hook-form';
+import { zodResolver } from '@hookform/resolvers/zod';
+
+import { deliveryAddressClient } from '../api/deliveryAddressClient';
+import { CreateDeliveryAddressDto, UpdateDeliveryAddressDto } from '@d8d/delivery-address-module/schemas';
+import { Button } from '@d8d/shared-ui-components/components/ui/button';
+import { Input } from '@d8d/shared-ui-components/components/ui/input';
+import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@d8d/shared-ui-components/components/ui/card';
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@d8d/shared-ui-components/components/ui/table';
+import { Badge } from '@d8d/shared-ui-components/components/ui/badge';
+import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@d8d/shared-ui-components/components/ui/dialog';
+import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@d8d/shared-ui-components/components/ui/form';
+import { Switch } from '@d8d/shared-ui-components/components/ui/switch';
+import { Skeleton } from '@d8d/shared-ui-components/components/ui/skeleton';
+import { DataTablePagination } from '@d8d/shared-ui-components/components/admin/DataTablePagination';
+import { UserSelector } from '@d8d/user-management-ui/components';
+import { AreaSelect4Level } from '@d8d/area-management-ui/components';
+import type { DeliveryAddress, DeliveryAddressQueryParams } from '../types/delivery-address';
+import { DeliveryAddressState, DefaultAddressState } from '../types/delivery-address';
+
+// 表单schema
+const createFormSchema = CreateDeliveryAddressDto;
+const updateFormSchema = UpdateDeliveryAddressDto;
+
+export const DeliveryAddressManagement: React.FC = () => {
+  const queryClient = useQueryClient();
+
+  // 状态管理
+  const [searchParams, setSearchParams] = useState<DeliveryAddressQueryParams>({
+    page: 1,
+    limit: 10,
+    search: '',
+    userId: undefined,
+  });
+  const [isModalOpen, setIsModalOpen] = useState(false);
+  const [editingAddress, setEditingAddress] = useState<DeliveryAddress | null>(null);
+  const [isCreateForm, setIsCreateForm] = useState(true);
+  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
+  const [addressToDelete, setAddressToDelete] = useState<number | null>(null);
+
+  // 表单实例
+  const createForm = useForm({
+    resolver: zodResolver(createFormSchema),
+    defaultValues: {
+      userId: 0,
+      name: '',
+      phone: '',
+      address: '',
+      receiverProvince: 0,
+      receiverCity: 0,
+      receiverDistrict: 0,
+      receiverTown: 0,
+      isDefault: 0,
+    },
+  });
+
+  const updateForm = useForm({
+    resolver: zodResolver(updateFormSchema),
+  });
+
+  // 数据查询
+  const { data, isLoading, refetch } = useQuery({
+    queryKey: ['delivery-addresses', searchParams],
+    queryFn: async () => {
+      const res = await deliveryAddressClient.$get({
+        query: {
+          page: searchParams.page,
+          pageSize: searchParams.limit,
+          keyword: searchParams.search,
+          ...(searchParams.userId && { userId: searchParams.userId }),
+        }
+      });
+      if (res.status !== 200) throw new Error('获取收货地址列表失败');
+      return await res.json();
+    }
+  });
+
+  // 创建地址
+  const createMutation = useMutation({
+    mutationFn: async (data: any) => {
+      const res = await deliveryAddressClient.$post({ json: data });
+      if (res.status !== 201) throw new Error('创建失败');
+      return await res.json();
+    },
+    onSuccess: () => {
+      toast.success('收货地址创建成功');
+      setIsModalOpen(false);
+      refetch();
+      createForm.reset();
+    },
+    onError: (error) => {
+      toast.error(error.message || '创建失败');
+    }
+  });
+
+  // 更新地址
+  const updateMutation = useMutation({
+    mutationFn: async ({ id, data }: { id: number; data: any }) => {
+      const res = await deliveryAddressClient[':id']['$put']({
+        param: { id },
+        json: data,
+      });
+      if (res.status !== 200) throw new Error('更新失败');
+      return await res.json();
+    },
+    onSuccess: () => {
+      toast.success('收货地址更新成功');
+      setIsModalOpen(false);
+      refetch();
+    },
+    onError: (error) => {
+      toast.error(error.message || '更新失败');
+    }
+  });
+
+  // 删除地址
+  const deleteMutation = useMutation({
+    mutationFn: async (id: number) => {
+      const res = await deliveryAddressClient[':id']['$delete']({
+        param: { id },
+      });
+      if (res.status !== 204) throw new Error('删除失败');
+    },
+    onSuccess: () => {
+      toast.success('收货地址删除成功');
+      setDeleteDialogOpen(false);
+      refetch();
+    },
+    onError: (error) => {
+      toast.error(error.message || '删除失败');
+    }
+  });
+
+  // 业务逻辑函数
+  const handleSearch = () => {
+    setSearchParams(prev => ({ ...prev, page: 1 }));
+  };
+
+  const handleCreateAddress = () => {
+    setIsCreateForm(true);
+    setEditingAddress(null);
+    createForm.reset();
+    setIsModalOpen(true);
+  };
+
+  const handleEditAddress = (address: DeliveryAddress) => {
+    setIsCreateForm(false);
+    setEditingAddress(address);
+
+    updateForm.reset({
+      name: address.name,
+      phone: address.phone,
+      address: address.address,
+      receiverProvince: address.receiverProvince,
+      receiverCity: address.receiverCity,
+      receiverDistrict: address.receiverDistrict,
+      receiverTown: address.receiverTown,
+      isDefault: address.isDefault,
+    });
+
+    setIsModalOpen(true);
+  };
+
+  const handleDeleteAddress = (id: number) => {
+    setAddressToDelete(id);
+    setDeleteDialogOpen(true);
+  };
+
+  const confirmDelete = () => {
+    if (addressToDelete) {
+      deleteMutation.mutate(addressToDelete);
+    }
+  };
+
+  const handleCreateSubmit = (data: any) => {
+    createMutation.mutate(data);
+  };
+
+  const handleUpdateSubmit = (data: any) => {
+    if (editingAddress) {
+      updateMutation.mutate({ id: editingAddress.id, data });
+    }
+  };
+
+  // 状态显示
+  const getStatusBadge = (status: DeliveryAddressState) => {
+    switch (status) {
+      case DeliveryAddressState.ACTIVE:
+        return <Badge variant="default">正常</Badge>;
+      case DeliveryAddressState.DISABLED:
+        return <Badge variant="secondary">禁用</Badge>;
+      case DeliveryAddressState.DELETED:
+        return <Badge variant="destructive">删除</Badge>;
+      default:
+        return <Badge variant="outline">未知</Badge>;
+    }
+  };
+
+  const getIsDefaultBadge = (isDefault: DefaultAddressState) => {
+    return isDefault === DefaultAddressState.IS_DEFAULT ? (
+      <Badge variant="default">默认</Badge>
+    ) : (
+      <Badge variant="outline">非默认</Badge>
+    );
+  };
+
+  // 格式化地址显示
+  const formatAddressDisplay = (address: DeliveryAddress) => {
+    const parts = [
+      address.province?.name,
+      address.city?.name,
+      address.district?.name,
+      address.town?.name,
+      address.address
+    ].filter(Boolean);
+    return parts.join(' ');
+  };
+
+  // 加载状态
+  if (isLoading) {
+    return (
+      <div className="space-y-4">
+        <div className="flex justify-between items-center">
+          <Skeleton className="h-8 w-48" />
+          <Skeleton className="h-10 w-32" />
+        </div>
+
+        <Card>
+          <CardContent className="pt-6">
+            <div className="space-y-3">
+              {[...Array(5)].map((_, i) => (
+                <div key={i} className="flex gap-4">
+                  <Skeleton className="h-10 flex-1" />
+                  <Skeleton className="h-10 flex-1" />
+                  <Skeleton className="h-10 flex-1" />
+                  <Skeleton className="h-10 w-20" />
+                </div>
+              ))}
+            </div>
+          </CardContent>
+        </Card>
+      </div>
+    );
+  }
+
+  return (
+    <div className="space-y-4">
+      {/* 页面标题 */}
+      <div className="flex justify-between items-center">
+        <div>
+          <h1 className="text-2xl font-bold">用户收货地址</h1>
+          <p className="text-sm text-muted-foreground">管理用户的收货地址信息</p>
+        </div>
+        <Button onClick={handleCreateAddress}>
+          <Plus className="mr-2 h-4 w-4" />
+          创建收货地址
+        </Button>
+      </div>
+
+      {/* 搜索区域 */}
+      <Card>
+        <CardHeader>
+          <CardTitle>搜索筛选</CardTitle>
+          <CardDescription>根据条件筛选收货地址</CardDescription>
+        </CardHeader>
+        <CardContent>
+          <div className="flex gap-4">
+            <div className="flex-1">
+              <div className="relative">
+                <Search className="absolute left-2 top-2.5 h-4 w-4 text-muted-foreground" />
+                <Input
+                  placeholder="搜索姓名、手机号、地址..."
+                  value={searchParams.search}
+                  onChange={(e) => setSearchParams(prev => ({ ...prev, search: e.target.value }))}
+                  className="pl-8"
+                />
+              </div>
+            </div>
+            <div className="w-64">
+              <UserSelector
+                value={searchParams.userId}
+                onChange={(value) => setSearchParams(prev => ({ ...prev, userId: value }))}
+                placeholder="选择用户"
+              />
+            </div>
+            <Button onClick={handleSearch}>搜索</Button>
+          </div>
+        </CardContent>
+      </Card>
+
+      {/* 数据表格 */}
+      <Card>
+        <CardHeader>
+          <CardTitle>收货地址列表</CardTitle>
+          <CardDescription>
+            共 {data?.pagination.total || 0} 条记录
+          </CardDescription>
+        </CardHeader>
+        <CardContent>
+          <div className="rounded-md border">
+            <Table>
+              <TableHeader>
+                <TableRow>
+                  <TableHead>ID</TableHead>
+                  <TableHead>用户</TableHead>
+                  <TableHead>收货人</TableHead>
+                  <TableHead>手机号</TableHead>
+                  <TableHead>地址</TableHead>
+                  <TableHead>状态</TableHead>
+                  <TableHead>默认</TableHead>
+                  <TableHead>创建时间</TableHead>
+                  <TableHead className="text-right">操作</TableHead>
+                </TableRow>
+              </TableHeader>
+              <TableBody>
+                {data?.data.map((address: DeliveryAddress) => (
+                  <TableRow key={address.id}>
+                    <TableCell>{address.id}</TableCell>
+                    <TableCell>{address.user?.username || '-'}</TableCell>
+                    <TableCell>{address.name}</TableCell>
+                    <TableCell>{address.phone}</TableCell>
+                    <TableCell className="max-w-xs truncate" title={formatAddressDisplay(address)}>
+                      {formatAddressDisplay(address)}
+                    </TableCell>
+                    <TableCell>{getStatusBadge(address.state)}</TableCell>
+                    <TableCell>{getIsDefaultBadge(address.isDefault)}</TableCell>
+                    <TableCell>
+                      {format(new Date(address.createdAt), 'yyyy-MM-dd HH:mm', { locale: zhCN })}
+                    </TableCell>
+                    <TableCell className="text-right">
+                      <div className="flex justify-end gap-2">
+                        <Button
+                          variant="ghost"
+                          size="icon"
+                          onClick={() => handleEditAddress(address)}
+                        >
+                          <Edit className="h-4 w-4" />
+                        </Button>
+                        <Button
+                          variant="ghost"
+                          size="icon"
+                          onClick={() => handleDeleteAddress(address.id)}
+                        >
+                          <Trash2 className="h-4 w-4" />
+                        </Button>
+                      </div>
+                    </TableCell>
+                  </TableRow>
+                ))}
+              </TableBody>
+            </Table>
+          </div>
+
+          {data?.data.length === 0 && !isLoading && (
+            <div className="text-center py-8">
+              <MapPin className="h-12 w-12 mx-auto text-muted-foreground mb-4" />
+              <p className="text-muted-foreground">暂无收货地址数据</p>
+            </div>
+          )}
+
+          <DataTablePagination
+            currentPage={searchParams.page}
+            pageSize={searchParams.limit}
+            totalCount={data?.pagination.total || 0}
+            onPageChange={(page, limit) => setSearchParams(prev => ({ ...prev, page, limit }))}
+          />
+        </CardContent>
+      </Card>
+
+      {/* 创建/编辑模态框 */}
+      <Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
+        <DialogContent className="sm:max-w-[600px] max-h-[90vh] overflow-y-auto">
+          <DialogHeader>
+            <DialogTitle>
+              {isCreateForm ? '创建收货地址' : '编辑收货地址'}
+            </DialogTitle>
+            <DialogDescription>
+              {isCreateForm ? '创建一个新的收货地址' : '编辑现有收货地址信息'}
+            </DialogDescription>
+          </DialogHeader>
+
+          {isCreateForm ? (
+            <Form {...createForm}>
+              <form onSubmit={createForm.handleSubmit(handleCreateSubmit)} className="space-y-4">
+                <FormField
+                  control={createForm.control}
+                  name="userId"
+                  render={({ field }) => (
+                    <FormItem>
+                      <FormLabel>用户<span className="text-red-500 ml-1">*</span></FormLabel>
+                      <FormControl>
+                        <UserSelector
+                          value={field.value}
+                          onChange={field.onChange}
+                          placeholder="选择用户"
+                        />
+                      </FormControl>
+                      <FormMessage />
+                    </FormItem>
+                  )}
+                />
+
+                <div className="grid grid-cols-2 gap-4">
+                  <FormField
+                    control={createForm.control}
+                    name="name"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormLabel>收货人姓名<span className="text-red-500 ml-1">*</span></FormLabel>
+                        <FormControl>
+                          <Input placeholder="请输入收货人姓名" {...field} />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+
+                  <FormField
+                    control={createForm.control}
+                    name="phone"
+                    render={({ field }) => (
+                      <FormItem>
+                        <FormLabel>手机号<span className="text-red-500 ml-1">*</span></FormLabel>
+                        <FormControl>
+                          <Input placeholder="请输入手机号" {...field} />
+                        </FormControl>
+                        <FormMessage />
+                      </FormItem>
+                    )}
+                  />
+                </div>
+
+                <FormField
+                  control={createForm.control}
+                  name="address"
+                  render={({ field }) => (
+                    <FormItem>
+                      <FormLabel>详细地址<span className="text-red-500 ml-1">*</span></FormLabel>
+                      <FormControl>
+                        <Input placeholder="请输入详细地址" {...field} />
+                      </FormControl>
+                      <FormMessage />
+                    </FormItem>
+                  )}
+                />
+
+                <div className="space-y-2">
+                  <FormLabel>四级地址选择<span className="text-red-500 ml-1">*</span></FormLabel>
+                  <AreaSelect4Level
+                    provinceValue={createForm.watch('receiverProvince') || 0}
+                    cityValue={createForm.watch('receiverCity') || 0}
+                    districtValue={createForm.watch('receiverDistrict') || 0}
+                    townValue={createForm.watch('receiverTown') || 0}
+                    onProvinceChange={(value) => createForm.setValue('receiverProvince', value)}
+                    onCityChange={(value) => createForm.setValue('receiverCity', value)}
+                    onDistrictChange={(value) => createForm.setValue('receiverDistrict', value)}
+                    onTownChange={(value) => createForm.setValue('receiverTown', value)}
+                    showLabels={false}
+                  />
+                </div>
+
+                <FormField
+                  control={createForm.control}
+                  name="isDefault"
+                  render={({ field }) => (
+                    <FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
+                      <div className="space-y-0.5">
+                        <FormLabel className="text-base">设为默认地址</FormLabel>
+                        <FormDescription>将此地址设为用户的默认收货地址</FormDescription>
+                      </div>
+                      <FormControl>
+                        <Switch
+                          checked={field.value === 1}
+                          onCheckedChange={(checked) => field.onChange(checked ? 1 : 0)}
+                        />
+                      </FormControl>
+                    </FormItem>
+                  )}
+                />
+
+                <DialogFooter>
+                  <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
+                    取消
+                  </Button>
+                  <Button type="submit" disabled={createMutation.isPending}>
+                    创建
+                  </Button>
+                </DialogFooter>
+              </form>
+            </Form>
+          ) : (
+            <Form {...updateForm}>
+              <form onSubmit={updateForm.handleSubmit(handleUpdateSubmit)} className="space-y-4">
+                <FormField
+                  control={updateForm.control}
+                  name="name"
+                  render={({ field }) => (
+                    <FormItem>
+                      <FormLabel>收货人姓名<span className="text-red-500 ml-1">*</span></FormLabel>
+                      <FormControl>
+                        <Input placeholder="请输入收货人姓名" {...field} />
+                      </FormControl>
+                      <FormMessage />
+                    </FormItem>
+                  )}
+                />
+
+                <FormField
+                  control={updateForm.control}
+                  name="phone"
+                  render={({ field }) => (
+                    <FormItem>
+                      <FormLabel>手机号<span className="text-red-500 ml-1">*</span></FormLabel>
+                      <FormControl>
+                        <Input placeholder="请输入手机号" {...field} />
+                      </FormControl>
+                      <FormMessage />
+                    </FormItem>
+                  )}
+                />
+
+                <FormField
+                  control={updateForm.control}
+                  name="address"
+                  render={({ field }) => (
+                    <FormItem>
+                      <FormLabel>详细地址<span className="text-red-500 ml-1">*</span></FormLabel>
+                      <FormControl>
+                        <Input placeholder="请输入详细地址" {...field} />
+                      </FormControl>
+                      <FormMessage />
+                    </FormItem>
+                  )}
+                />
+
+                <div className="space-y-2">
+                  <FormLabel>四级地址选择<span className="text-red-500 ml-1">*</span></FormLabel>
+                  <AreaSelect4Level
+                    provinceValue={updateForm.watch('receiverProvince') || 0}
+                    cityValue={updateForm.watch('receiverCity') || 0}
+                    districtValue={updateForm.watch('receiverDistrict') || 0}
+                    townValue={updateForm.watch('receiverTown') || 0}
+                    onProvinceChange={(value) => updateForm.setValue('receiverProvince', value)}
+                    onCityChange={(value) => updateForm.setValue('receiverCity', value)}
+                    onDistrictChange={(value) => updateForm.setValue('receiverDistrict', value)}
+                    onTownChange={(value) => updateForm.setValue('receiverTown', value)}
+                    showLabels={false}
+                  />
+                </div>
+
+                <FormField
+                  control={updateForm.control}
+                  name="isDefault"
+                  render={({ field }) => (
+                    <FormItem className="flex flex-row items-center justify-between rounded-lg border p-4">
+                      <div className="space-y-0.5">
+                        <FormLabel className="text-base">设为默认地址</FormLabel>
+                        <FormDescription>将此地址设为用户的默认收货地址</FormDescription>
+                      </div>
+                      <FormControl>
+                        <Switch
+                          checked={field.value === 1}
+                          onCheckedChange={(checked) => field.onChange(checked ? 1 : 0)}
+                        />
+                      </FormControl>
+                    </FormItem>
+                  )}
+                />
+
+                <DialogFooter>
+                  <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
+                    取消
+                  </Button>
+                  <Button type="submit" disabled={updateMutation.isPending}>
+                    更新
+                  </Button>
+                </DialogFooter>
+              </form>
+            </Form>
+          )}
+        </DialogContent>
+      </Dialog>
+
+      {/* 删除确认对话框 */}
+      <Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
+        <DialogContent>
+          <DialogHeader>
+            <DialogTitle>确认删除</DialogTitle>
+            <DialogDescription>
+              确定要删除这个收货地址吗?此操作无法撤销。
+            </DialogDescription>
+          </DialogHeader>
+          <DialogFooter>
+            <Button variant="outline" onClick={() => setDeleteDialogOpen(false)}>
+              取消
+            </Button>
+            <Button variant="destructive" onClick={confirmDelete} disabled={deleteMutation.isPending}>
+              删除
+            </Button>
+          </DialogFooter>
+        </DialogContent>
+      </Dialog>
+    </div>
+  );
+};

+ 1 - 0
packages/delivery-address-management-ui/src/components/index.ts

@@ -0,0 +1 @@
+export { DeliveryAddressManagement } from './DeliveryAddressManagement';

+ 13 - 0
packages/delivery-address-management-ui/src/index.ts

@@ -0,0 +1,13 @@
+// 主包导出文件
+
+// 导出主组件
+export { DeliveryAddressManagement } from './components/DeliveryAddressManagement';
+
+// 导出API客户端
+export { deliveryAddressClient, deliveryAddressClientManager } from './api/deliveryAddressClient';
+
+// 导出类型定义
+export type { DeliveryAddress } from './types/deliveryAddress';
+
+// 默认导出主组件
+export default DeliveryAddressManagement;

+ 437 - 0
packages/delivery-address-management-ui/tests/integration/delivery-address-management.integration.test.tsx

@@ -0,0 +1,437 @@
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
+import { DeliveryAddressManagement } from '../../src/components/DeliveryAddressManagement';
+import { deliveryAddressClient, deliveryAddressClientManager } from '../../src/api/deliveryAddressClient';
+
+// 完整的mock响应对象
+const createMockResponse = (status: number, data?: any) => ({
+  status,
+  ok: status >= 200 && status < 300,
+  body: null,
+  bodyUsed: false,
+  statusText: status === 200 ? 'OK' : status === 201 ? 'Created' : status === 204 ? 'No Content' : 'Error',
+  headers: new Headers(),
+  url: '',
+  redirected: false,
+  type: 'basic' as ResponseType,
+  json: async () => data || {},
+  text: async () => '',
+  blob: async () => new Blob(),
+  arrayBuffer: async () => new ArrayBuffer(0),
+  formData: async () => new FormData(),
+  clone: function() { return this; }
+});
+
+// Mock API client
+vi.mock('../../src/api/deliveryAddressClient', () => {
+  const mockDeliveryAddressClient = {
+    $get: vi.fn(() => Promise.resolve({ status: 200, body: null })),
+    $post: vi.fn(() => Promise.resolve({ status: 201, body: null })),
+    ':id': {
+      $put: vi.fn(() => Promise.resolve({ status: 200, body: null })),
+      $delete: vi.fn(() => Promise.resolve({ status: 204, body: null })),
+    },
+  };
+
+  const mockDeliveryAddressClientManager = {
+    get: vi.fn(() => mockDeliveryAddressClient),
+  };
+
+  return {
+    deliveryAddressClientManager: mockDeliveryAddressClientManager,
+    deliveryAddressClient: mockDeliveryAddressClient,
+  };
+});
+
+// Mock toast
+vi.mock('sonner', () => ({
+  toast: {
+    success: vi.fn(() => {}),
+    error: vi.fn(() => {}),
+  },
+}));
+
+// Mock UserSelector
+vi.mock('@d8d/user-management-ui/components', () => ({
+  UserSelector: ({ value, onChange, placeholder }: any) => (
+    <select
+      data-testid="user-selector"
+      value={value || ''}
+      onChange={(e) => onChange(e.target.value ? Number(e.target.value) : undefined)}
+    >
+      <option value="">{placeholder}</option>
+      <option value="1">用户1</option>
+      <option value="2">用户2</option>
+    </select>
+  ),
+}));
+
+// Mock AreaSelect4Level
+vi.mock('@d8d/area-management-ui/components', () => ({
+  AreaSelect4Level: ({
+    provinceValue,
+    cityValue,
+    districtValue,
+    townValue,
+    onProvinceChange,
+    onCityChange,
+    onDistrictChange,
+    onTownChange,
+    showLabels
+  }: any) => (
+    <div data-testid="area-select-4-level">
+      <select
+        data-testid="province-select"
+        value={provinceValue || 0}
+        onChange={(e) => onProvinceChange(Number(e.target.value))}
+      >
+        <option value={0}>请选择省份</option>
+        <option value={1}>北京市</option>
+      </select>
+      <select
+        data-testid="city-select"
+        value={cityValue || 0}
+        onChange={(e) => onCityChange(Number(e.target.value))}
+      >
+        <option value={0}>请选择城市</option>
+        <option value={2}>北京市</option>
+      </select>
+      <select
+        data-testid="district-select"
+        value={districtValue || 0}
+        onChange={(e) => onDistrictChange(Number(e.target.value))}
+      >
+        <option value={0}>请选区县</option>
+        <option value={3}>朝阳区</option>
+      </select>
+      <select
+        data-testid="town-select"
+        value={townValue || 0}
+        onChange={(e) => onTownChange(Number(e.target.value))}
+      >
+        <option value={0}>请选择乡镇</option>
+        <option value={4}>三里屯街道</option>
+      </select>
+    </div>
+  ),
+}));
+
+const createTestQueryClient = () =>
+  new QueryClient({
+    defaultOptions: {
+      queries: {
+        retry: false,
+      },
+    },
+  });
+
+const renderWithProviders = (component: React.ReactElement) => {
+  const queryClient = createTestQueryClient();
+  return render(
+    <QueryClientProvider client={queryClient}>
+      {component as any}
+    </QueryClientProvider>
+  );
+};
+
+describe('地址管理集成测试', () => {
+  beforeEach(() => {
+    vi.clearAllMocks();
+  });
+
+  it('应该完成完整的地址CRUD流程', async () => {
+    const mockAddresses = {
+      data: [
+        {
+          id: 1,
+          userId: 1,
+          name: '张三',
+          phone: '13800138000',
+          address: '朝阳区三里屯街道',
+          receiverProvince: 1,
+          receiverCity: 2,
+          receiverDistrict: 3,
+          receiverTown: 4,
+          state: 1,
+          isDefault: 1,
+          createdBy: 1,
+          updatedBy: 1,
+          createdAt: '2024-01-01T00:00:00Z',
+          updatedAt: '2024-01-01T00:00:00Z',
+          user: {
+            id: 1,
+            username: 'user1',
+          },
+          province: {
+            id: 1,
+            name: '北京市',
+          },
+          city: {
+            id: 2,
+            name: '北京市',
+          },
+          district: {
+            id: 3,
+            name: '朝阳区',
+          },
+          town: {
+            id: 4,
+            name: '三里屯街道',
+          },
+        },
+      ],
+      pagination: {
+        total: 1,
+        page: 1,
+        pageSize: 10,
+      },
+    };
+
+    const { toast } = await import('sonner');
+
+    // Mock initial address list
+    (deliveryAddressClientManager.get().$get as any).mockResolvedValue(createMockResponse(200, mockAddresses));
+
+    renderWithProviders(<DeliveryAddressManagement />);
+
+    // Wait for initial data to load
+    await waitFor(() => {
+      expect(screen.getByText('张三')).toBeInTheDocument();
+    });
+
+    // Test create address
+    const createButton = screen.getByText('创建收货地址');
+    fireEvent.click(createButton);
+
+    // Fill create form
+    const nameInput = screen.getByPlaceholderText('请输入收货人姓名');
+    const phoneInput = screen.getByPlaceholderText('请输入手机号');
+    const addressInput = screen.getByPlaceholderText('请输入详细地址');
+
+    fireEvent.change(nameInput, { target: { value: '李四' } });
+    fireEvent.change(phoneInput, { target: { value: '13900139000' } });
+    fireEvent.change(addressInput, { target: { value: '详细地址信息' } });
+
+    // Select user
+    const userSelector = screen.getByTestId('user-selector');
+    fireEvent.change(userSelector, { target: { value: '1' } });
+
+    // Select area
+    const provinceSelect = screen.getByTestId('province-select');
+    fireEvent.change(provinceSelect, { target: { value: '1' } });
+
+    const citySelect = screen.getByTestId('city-select');
+    fireEvent.change(citySelect, { target: { value: '2' } });
+
+    const districtSelect = screen.getByTestId('district-select');
+    fireEvent.change(districtSelect, { target: { value: '3' } });
+
+    const townSelect = screen.getByTestId('town-select');
+    fireEvent.change(townSelect, { target: { value: '4' } });
+
+    // Mock successful creation
+    (deliveryAddressClient.$post as any).mockResolvedValue(createMockResponse(201, { id: 2, name: '李四' }));
+
+    const submitButton = screen.getByText('创建');
+    fireEvent.click(submitButton);
+
+    await waitFor(() => {
+      expect(deliveryAddressClient.$post).toHaveBeenCalledWith({
+        json: {
+          userId: 1,
+          name: '李四',
+          phone: '13900139000',
+          address: '详细地址信息',
+          receiverProvince: 1,
+          receiverCity: 2,
+          receiverDistrict: 3,
+          receiverTown: 4,
+          isDefault: 0,
+        },
+      });
+      expect(toast.success).toHaveBeenCalledWith('收货地址创建成功');
+    });
+
+    // Test edit address
+    const editButtons = screen.getAllByRole('button', { name: /编辑/ });
+    fireEvent.click(editButtons[0]);
+
+    // Verify edit form is populated
+    await waitFor(() => {
+      expect(screen.getByDisplayValue('张三')).toBeInTheDocument();
+    });
+
+    // Update address
+    const updateNameInput = screen.getByDisplayValue('张三');
+    fireEvent.change(updateNameInput, { target: { value: '王五' } });
+
+    // Mock successful update
+    (deliveryAddressClient[':id']['$put'] as any).mockResolvedValue(createMockResponse(200));
+
+    const updateButton = screen.getByText('更新');
+    fireEvent.click(updateButton);
+
+    await waitFor(() => {
+      expect(deliveryAddressClient[':id']['$put']).toHaveBeenCalledWith({
+        param: { id: 1 },
+        json: {
+          name: '王五',
+          phone: '13800138000',
+          address: '朝阳区三里屯街道',
+          receiverProvince: 1,
+          receiverCity: 2,
+          receiverDistrict: 3,
+          receiverTown: 4,
+          isDefault: 1,
+        },
+      });
+      expect(toast.success).toHaveBeenCalledWith('收货地址更新成功');
+    });
+
+    // Test delete address
+    const deleteButtons = screen.getAllByRole('button', { name: /删除/ });
+    fireEvent.click(deleteButtons[0]);
+
+    // Confirm deletion
+    expect(screen.getByText('确认删除')).toBeInTheDocument();
+
+    // Mock successful deletion
+    (deliveryAddressClient[':id']['$delete'] as any).mockResolvedValue({
+      status: 204,
+    });
+
+    const confirmDeleteButton = screen.getByText('删除');
+    fireEvent.click(confirmDeleteButton);
+
+    await waitFor(() => {
+      expect(deliveryAddressClient[':id']['$delete']).toHaveBeenCalledWith({
+        param: { id: 1 },
+      });
+      expect(toast.success).toHaveBeenCalledWith('收货地址删除成功');
+    });
+  });
+
+  it('应该优雅处理API错误', async () => {
+    const { toast } = await import('sonner');
+
+    // Mock API error
+    (deliveryAddressClientManager.get().$get as any).mockRejectedValue(new Error('API Error'));
+
+    renderWithProviders(<DeliveryAddressManagement />);
+
+    // Should handle error without crashing
+    await waitFor(() => {
+      expect(screen.getByText('用户收货地址')).toBeInTheDocument();
+    });
+
+    // Test create address error
+    const createButton = screen.getByText('创建收货地址');
+    fireEvent.click(createButton);
+
+    const nameInput = screen.getByPlaceholderText('请输入收货人姓名');
+    const phoneInput = screen.getByPlaceholderText('请输入手机号');
+
+    fireEvent.change(nameInput, { target: { value: '测试用户' } });
+    fireEvent.change(phoneInput, { target: { value: '13800138000' } });
+
+    // Mock creation error
+    (deliveryAddressClient.$post as any).mockRejectedValue(new Error('Creation failed'));
+
+    const submitButton = screen.getByText('创建');
+    fireEvent.click(submitButton);
+
+    await waitFor(() => {
+      expect(toast.error).toHaveBeenCalledWith('创建失败');
+    });
+  });
+
+  it('应该处理搜索和过滤器集成', async () => {
+    const mockAddresses = {
+      data: [],
+      pagination: { total: 0, page: 1, pageSize: 10 },
+    };
+
+    (deliveryAddressClientManager.get().$get as any).mockResolvedValue(createMockResponse(200, mockAddresses));
+
+    renderWithProviders(<DeliveryAddressManagement />);
+
+    // Test search
+    const searchInput = screen.getByPlaceholderText('搜索姓名、手机号、地址...');
+    fireEvent.change(searchInput, { target: { value: '张三' } });
+
+    await waitFor(() => {
+      expect(deliveryAddressClientManager.get().$get).toHaveBeenCalledWith({
+        query: {
+          page: 1,
+          pageSize: 10,
+          keyword: '张三',
+        },
+      });
+    });
+
+    // Test user filter
+    const userSelector = screen.getByTestId('user-selector');
+    fireEvent.change(userSelector, { target: { value: '1' } });
+
+    const searchButton = screen.getByText('搜索');
+    fireEvent.click(searchButton);
+
+    await waitFor(() => {
+      expect(deliveryAddressClientManager.get().$get).toHaveBeenCalledWith({
+        query: {
+          page: 1,
+          pageSize: 10,
+          keyword: '张三',
+          userId: 1,
+        },
+      });
+    });
+  });
+
+  it('应该正确显示地址状态和默认地址标记', async () => {
+    const mockAddresses = {
+      data: [
+        {
+          id: 1,
+          userId: 1,
+          name: '张三',
+          phone: '13800138000',
+          address: '朝阳区三里屯街道',
+          receiverProvince: 1,
+          receiverCity: 2,
+          receiverDistrict: 3,
+          receiverTown: 4,
+          state: 1,
+          isDefault: 1,
+          createdBy: 1,
+          updatedBy: 1,
+          createdAt: '2024-01-01T00:00:00Z',
+          updatedAt: '2024-01-01T00:00:00Z',
+          user: {
+            id: 1,
+            username: 'user1',
+          },
+        },
+      ],
+      pagination: {
+        total: 1,
+        page: 1,
+        pageSize: 10,
+      },
+    };
+
+    (deliveryAddressClientManager.get().$get as any).mockResolvedValue(createMockResponse(200, mockAddresses));
+
+    renderWithProviders(<DeliveryAddressManagement />);
+
+    // Wait for data to load
+    await waitFor(() => {
+      expect(screen.getByText('张三')).toBeInTheDocument();
+    });
+
+    // Verify status badges
+    expect(screen.getByText('正常')).toBeInTheDocument();
+    expect(screen.getByText('默认')).toBeInTheDocument();
+  });
+});