ソースを参照

🗑️ chore(admin): 移除未使用的管理页面和组件

- 删除代理商选择器组件 (AgentSelector)
- 删除城市级联选择器组件 (CityCascadeSelector)
- 删除用户卡选择器组件 (UserCardSelector)
- 删除代理商管理页面 (Agents)
- 删除快递公司管理页面 (ExpressCompanies)
- 删除用户卡余额记录页面 (UserCardBalanceRecords)
- 删除用户卡管理页面 (UserCards)
- 从路由配置中移除相关页面路径
- 从API客户端移除对应的服务接口
yourname 1 ヶ月 前
コミット
ffe6af9c57

+ 0 - 59
web/src/client/admin/components/AgentSelector.tsx

@@ -1,59 +0,0 @@
-import React from 'react';
-import { useQuery } from '@tanstack/react-query';
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/client/components/ui/select';
-import { agentClient } from '@/client/api';
-
-interface AgentSelectorProps {
-  value?: number;
-  onChange?: (value: number) => void;
-  placeholder?: string;
-  disabled?: boolean;
-}
-
-export const AgentSelector: React.FC<AgentSelectorProps> = ({
-  value,
-  onChange,
-  placeholder = "选择代理商",
-  disabled
-}) => {
-  const { data: agents, isLoading } = useQuery({
-    queryKey: ['agents'],
-    queryFn: async () => {
-      const res = await agentClient.$get({
-        query: {
-          page: 1,
-          pageSize: 100
-        }
-      });
-      
-      if (res.status !== 200) throw new Error('获取代理商列表失败');
-      const result = await res.json();
-      return result.data;
-    }
-  });
-
-  return (
-    <Select 
-      value={value?.toString() || ''} 
-      onValueChange={(val) => onChange?.(parseInt(val))}
-      disabled={disabled || isLoading}
-    >
-      <SelectTrigger>
-        <SelectValue placeholder={placeholder} />
-      </SelectTrigger>
-      <SelectContent>
-        {isLoading ? (
-          <SelectItem value="loading" disabled>加载中...</SelectItem>
-        ) : agents && agents.length > 0 ? (
-          agents.map((agent) => (
-            <SelectItem key={agent.id} value={agent.id.toString()}>
-              {agent.name || agent.username} ({agent.realname || agent.phone || '无联系方式'})
-            </SelectItem>
-          ))
-        ) : (
-          <SelectItem value="no-agents" disabled>暂无代理商</SelectItem>
-        )}
-      </SelectContent>
-    </Select>
-  );
-};

+ 0 - 226
web/src/client/admin/components/CityCascadeSelector.tsx

@@ -1,226 +0,0 @@
-import React, { useState, useEffect } from 'react';
-import { useQuery } from '@tanstack/react-query';
-import {
-  Select,
-  SelectContent,
-  SelectItem,
-  SelectTrigger,
-  SelectValue,
-} from '@/client/components/ui/select';
-import { FormItem, FormLabel, FormControl, FormMessage } from '@/client/components/ui/form';
-import { cityClient } from '@/client/api';
-
-interface CityCascadeSelectorProps {
-  provinceValue?: number;
-  cityValue?: number;
-  districtValue?: number;
-  townValue?: number;
-  onProvinceChange?: (value: number) => void;
-  onCityChange?: (value: number) => void;
-  onDistrictChange?: (value: number) => void;
-  onTownChange?: (value: number) => void;
-  disabled?: boolean;
-  showLabels?: boolean;
-}
-
-interface City {
-  id: number;
-  name: string;
-  level: number;
-  parentId: number;
-}
-
-export const CityCascadeSelector: React.FC<CityCascadeSelectorProps> = ({
-  provinceValue,
-  cityValue,
-  districtValue,
-  townValue,
-  onProvinceChange,
-  onCityChange,
-  onDistrictChange,
-  onTownChange,
-  disabled = false,
-  showLabels = true,
-}) => {
-  // 获取省份数据 (level=1)
-  const { data: provinces } = useQuery({
-    queryKey: ['cities', 'provinces'],
-    queryFn: async () => {
-      const res = await cityClient.$get({
-        query: {
-          page: 1,
-          pageSize: 100,
-          filters: JSON.stringify({ level: 1 }),
-          sortOrder: 'ASC',
-          sortBy: 'id'
-        },
-      });
-      if (res.status !== 200) throw new Error('获取省份数据失败');
-      const data = await res.json();
-      return data.data as City[];
-    },
-  });
-
-  // 获取城市数据 (level=2)
-  const { data: cities } = useQuery({
-    queryKey: ['cities', 'cities', provinceValue],
-    queryFn: async () => {
-      if (!provinceValue) return [];
-      const res = await cityClient.$get({
-        query: {
-          page: 1,
-          pageSize: 100,
-          filters: JSON.stringify({ level: 2, parentId: provinceValue }),
-          sortOrder: 'ASC',
-        },
-      });
-      if (res.status !== 200) throw new Error('获取城市数据失败');
-      const data = await res.json();
-      return data.data as City[];
-    },
-    enabled: !!provinceValue,
-  });
-
-  // 获取区县数据 (level=3)
-  const { data: districts } = useQuery({
-    queryKey: ['cities', 'districts', cityValue],
-    queryFn: async () => {
-      if (!cityValue) return [];
-      const res = await cityClient.$get({
-        query: {
-          page: 1,
-          pageSize: 100,
-          filters: JSON.stringify({ level: 3, parentId: cityValue }),
-          sortOrder: 'ASC',
-        },
-      });
-      if (res.status !== 200) throw new Error('获取区县数据失败');
-      const data = await res.json();
-      return data.data as City[];
-    },
-    enabled: !!cityValue,
-  });
-
-  // 获取街道数据 (level=4)
-  const { data: towns } = useQuery({
-    queryKey: ['cities', 'towns', districtValue],
-    queryFn: async () => {
-      if (!districtValue) return [];
-      const res = await cityClient.$get({
-        query: {
-          page: 1,
-          pageSize: 100,
-          filters: JSON.stringify({ level: 4, parentId: districtValue }),
-          sortOrder: 'ASC',
-        },
-      });
-      if (res.status !== 200) throw new Error('获取街道数据失败');
-      const data = await res.json();
-      return data.data as City[];
-    },
-    enabled: !!districtValue,
-  });
-
-  // 清除下级选择器
-  useEffect(() => {
-    // 当省份变化时,清除城市、区县、街道
-    if (provinceValue !== undefined) {
-      if (cityValue !== undefined) {
-        onCityChange?.(0);
-      }
-      if (districtValue !== undefined) {
-        onDistrictChange?.(0);
-      }
-      if (townValue !== undefined) {
-        onTownChange?.(0);
-      }
-    }
-  }, [provinceValue]);
-
-  useEffect(() => {
-    // 当城市变化时,清除区县、街道
-    if (cityValue !== undefined) {
-      if (districtValue !== undefined) {
-        onDistrictChange?.(0);
-      }
-      if (townValue !== undefined) {
-        onTownChange?.(0);
-      }
-    }
-  }, [cityValue]);
-
-  useEffect(() => {
-    // 当区县变化时,清除街道
-    if (districtValue !== undefined) {
-      if (townValue !== undefined) {
-        onTownChange?.(0);
-      }
-    }
-  }, [districtValue]);
-
-  const renderSelect = (
-    label: string,
-    value: number | undefined,
-    onChange: ((value: number) => void) | undefined,
-    options: City[] | undefined,
-    placeholder: string
-  ) => (
-    <FormItem>
-      {showLabels && <FormLabel>{label}</FormLabel>}
-      <FormControl>
-        <Select
-          value={value?.toString() || ''}
-          onValueChange={(val) => onChange?.(parseInt(val))}
-          disabled={disabled || !options?.length}
-        >
-          <SelectTrigger>
-            <SelectValue placeholder={placeholder} />
-          </SelectTrigger>
-          <SelectContent>
-            {options?.map((option) => (
-              <SelectItem key={option.id} value={option.id.toString()}>
-                {option.name}
-              </SelectItem>
-            ))}
-          </SelectContent>
-        </Select>
-      </FormControl>
-    </FormItem>
-  );
-
-  return (
-    <div className="grid grid-cols-4 gap-4">
-      {renderSelect(
-        '省份',
-        provinceValue,
-        onProvinceChange,
-        provinces,
-        '选择省份'
-      )}
-      
-      {renderSelect(
-        '城市',
-        cityValue,
-        onCityChange,
-        cities,
-        provinceValue ? '选择城市' : '请先选择省份'
-      )}
-      
-      {renderSelect(
-        '区县',
-        districtValue,
-        onDistrictChange,
-        districts,
-        cityValue ? '选择区县' : '请先选择城市'
-      )}
-      
-      {renderSelect(
-        '街道',
-        townValue,
-        onTownChange,
-        towns,
-        districtValue ? '选择街道' : '请先选择区县'
-      )}
-    </div>
-  );
-};

+ 0 - 65
web/src/client/admin/components/UserCardSelector.tsx

@@ -1,65 +0,0 @@
-import React from 'react';
-import { useQuery } from '@tanstack/react-query';
-import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '@/client/components/ui/select';
-import { userCardClient } from '@/client/api';
-
-interface UserCardSelectorProps {
-  userId?: number;
-  value?: string;
-  onChange?: (value: string) => void;
-  placeholder?: string;
-  disabled?: boolean;
-}
-
-export const UserCardSelector: React.FC<UserCardSelectorProps> = ({
-  userId,
-  value,
-  onChange,
-  placeholder = "选择用户卡",
-  disabled
-}) => {
-  const { data: cards, isLoading } = useQuery({
-    queryKey: ['userCards', userId],
-    queryFn: async () => {
-      if (!userId) return [];
-      
-      const res = await userCardClient.$get({
-        query: {
-          page: 1,
-          pageSize: 100,
-          filters: JSON.stringify({ userId })
-        }
-      });
-      
-      if (res.status !== 200) throw new Error('获取用户卡列表失败');
-      const result = await res.json();
-      return result.data;
-    },
-    enabled: !!userId
-  });
-
-  return (
-    <Select 
-      value={value || ''} 
-      onValueChange={onChange}
-      disabled={disabled || isLoading}
-    >
-      <SelectTrigger>
-        <SelectValue placeholder={placeholder} />
-      </SelectTrigger>
-      <SelectContent>
-        {isLoading ? (
-          <SelectItem value="loading" disabled>加载中...</SelectItem>
-        ) : cards && cards.length > 0 ? (
-          cards.map((card) => (
-            <SelectItem key={card.id} value={card.cardNo}>
-              {card.cardNo} (余额: ¥{card.balance.toFixed(2)})
-            </SelectItem>
-          ))
-        ) : (
-          <SelectItem value="no-cards" disabled>暂无用户卡</SelectItem>
-        )}
-      </SelectContent>
-    </Select>
-  );
-};

+ 0 - 578
web/src/client/admin/pages/Agents.tsx

@@ -1,578 +0,0 @@
-import React, { useState } from 'react';
-import { useQuery } from '@tanstack/react-query';
-import { format } from 'date-fns';
-import { Plus, Search, Edit, Trash2 } from 'lucide-react';
-import { agentClient } from '@/client/api';
-import type { InferRequestType, InferResponseType } from 'hono/client';
-import { Button } from '@/client/components/ui/button';
-import { Input } from '@/client/components/ui/input';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/client/components/ui/table';
-import { Badge } from '@/client/components/ui/badge';
-import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/client/components/ui/dialog';
-import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
-import { DataTablePagination } from '@/client/admin/components/DataTablePagination';
-import { useForm } from 'react-hook-form';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { toast } from 'sonner';
-import { Skeleton } from '@/client/components/ui/skeleton';
-import { Switch } from '@/client/components/ui/switch';
-import { CreateAgentDto, UpdateAgentDto } from '@d8d/server/modules/agent/agent.schema';
-
-// 使用RPC方式提取类型
-type CreateAgentRequest = InferRequestType<typeof agentClient.$post>['json'];
-type UpdateAgentRequest = InferRequestType<typeof agentClient[':id']['$put']>['json'];
-type AgentResponse = InferResponseType<typeof agentClient.$get, 200>['data'][0];
-
-// 直接使用后端定义的 schema
-const createAgentFormSchema = CreateAgentDto;
-const updateAgentFormSchema = UpdateAgentDto;
-
-type CreateAgentFormData = CreateAgentRequest;
-type UpdateAgentFormData = UpdateAgentRequest;
-
-export const AgentsPage = () => {
-  const [searchParams, setSearchParams] = useState({
-    page: 1,
-    limit: 10,
-    search: ''
-  });
-  const [isModalOpen, setIsModalOpen] = useState(false);
-  const [editingAgent, setEditingAgent] = useState<any>(null);
-  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
-  const [agentToDelete, setAgentToDelete] = useState<number | null>(null);
-  const [isCreateForm, setIsCreateForm] = useState(true);
-  
-  const createForm = useForm<CreateAgentFormData>({
-    resolver: zodResolver(createAgentFormSchema),
-    defaultValues: {
-      username: '',
-      name: null,
-      phone: null,
-      realname: null,
-      password: '',
-      state: 2,
-    },
-  });
-
-  const updateForm = useForm<UpdateAgentFormData>({
-    resolver: zodResolver(updateAgentFormSchema),
-    defaultValues: {
-      username: undefined,
-      name: undefined,
-      phone: null,
-      realname: null,
-      password: undefined,
-      state: undefined,
-    },
-  });
-
-  const { data: agentsData, isLoading, refetch } = useQuery({
-    queryKey: ['agents', searchParams],
-    queryFn: async () => {
-      const res = await agentClient.$get({
-        query: {
-          page: searchParams.page,
-          pageSize: searchParams.limit,
-          keyword: searchParams.search
-        }
-      });
-      if (res.status !== 200) {
-        throw new Error('获取代理商列表失败');
-      }
-      return await res.json();
-    }
-  });
-
-  const agents = agentsData?.data || [];
-  const totalCount = agentsData?.pagination?.total || 0;
-
-  // 处理搜索
-  const handleSearch = (e: React.FormEvent) => {
-    e.preventDefault();
-    setSearchParams(prev => ({ ...prev, page: 1 }));
-  };
-
-  // 处理分页
-  const handlePageChange = (page: number, limit: number) => {
-    setSearchParams(prev => ({ ...prev, page, limit }));
-  };
-
-  // 打开创建代理商对话框
-  const handleCreateAgent = () => {
-    setEditingAgent(null);
-    setIsCreateForm(true);
-    createForm.reset({
-      username: '',
-      name: null,
-      phone: null,
-      realname: null,
-      password: '',
-      state: 2,
-    });
-    setIsModalOpen(true);
-  };
-
-  // 打开编辑代理商对话框
-  const handleEditAgent = (agent: AgentResponse) => {
-    setEditingAgent(agent);
-    setIsCreateForm(false);
-    updateForm.reset({
-      username: agent.username,
-      name: agent.name,
-      phone: agent.phone,
-      realname: agent.realname,
-      state: agent.state,
-    });
-    setIsModalOpen(true);
-  };
-
-  // 处理创建表单提交
-  const handleCreateSubmit = async (data: CreateAgentFormData) => {
-    try {
-      const res = await agentClient.$post({
-        json: data
-      });
-      if (res.status !== 201) {
-        throw new Error('创建代理商失败');
-      }
-      toast.success('代理商创建成功');
-      setIsModalOpen(false);
-      refetch();
-    } catch (error) {
-      console.error('创建代理商失败:', error);
-      toast.error('创建失败,请重试');
-    }
-  };
-
-  // 处理更新表单提交
-  const handleUpdateSubmit = async (data: UpdateAgentFormData) => {
-    if (!editingAgent) return;
-    
-    try {
-      const res = await agentClient[':id']['$put']({
-        param: { id: editingAgent.id },
-        json: data
-      });
-      if (res.status !== 200) {
-        throw new Error('更新代理商失败');
-      }
-      toast.success('代理商更新成功');
-      setIsModalOpen(false);
-      refetch();
-    } catch (error) {
-      console.error('更新代理商失败:', error);
-      toast.error('更新失败,请重试');
-    }
-  };
-
-  // 处理删除代理商
-  const handleDeleteAgent = (id: number) => {
-    setAgentToDelete(id);
-    setDeleteDialogOpen(true);
-  };
-
-  const confirmDelete = async () => {
-    if (!agentToDelete) return;
-    
-    try {
-      const res = await agentClient[':id']['$delete']({
-        param: { id: agentToDelete }
-      });
-      if (res.status !== 204) {
-        throw new Error('删除代理商失败');
-      }
-      toast.success('代理商删除成功');
-      refetch();
-    } catch (error) {
-      console.error('删除代理商失败:', error);
-      toast.error('删除失败,请重试');
-    } finally {
-      setDeleteDialogOpen(false);
-      setAgentToDelete(null);
-    }
-  };
-
-  // 渲染加载骨架
-  if (isLoading) {
-    return (
-      <div className="space-y-4">
-        <div className="flex justify-between items-center">
-          <h1 className="text-2xl font-bold">代理商管理</h1>
-          <Button disabled>
-            <Plus className="mr-2 h-4 w-4" />
-            创建代理商
-          </Button>
-        </div>
-        
-        <Card>
-          <CardHeader>
-            <Skeleton className="h-6 w-1/4" />
-          </CardHeader>
-          <CardContent>
-            <div className="space-y-2">
-              <Skeleton className="h-4 w-full" />
-              <Skeleton className="h-4 w-full" />
-              <Skeleton className="h-4 w-full" />
-            </div>
-          </CardContent>
-        </Card>
-      </div>
-    );
-  }
-
-  return (
-    <div className="space-y-4">
-      <div className="flex justify-between items-center">
-        <h1 className="text-2xl font-bold">代理商管理</h1>
-        <Button onClick={handleCreateAgent}>
-          <Plus className="mr-2 h-4 w-4" />
-          创建代理商
-        </Button>
-      </div>
-
-      <Card>
-        <CardHeader>
-          <CardTitle>代理商列表</CardTitle>
-          <CardDescription>
-            管理系统中的所有代理商,共 {totalCount} 位代理商
-          </CardDescription>
-        </CardHeader>
-        <CardContent>
-          <div className="mb-4">
-            <form onSubmit={handleSearch} className="flex gap-2">
-              <div className="relative flex-1 max-w-sm">
-                <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>
-              <Button type="submit" variant="outline">
-                搜索
-              </Button>
-            </form>
-          </div>
-
-          <div className="rounded-md border">
-            <Table>
-              <TableHeader>
-                <TableRow>
-                  <TableHead>用户名</TableHead>
-                  <TableHead>代理商名称</TableHead>
-                  <TableHead>姓名</TableHead>
-                  <TableHead>手机号</TableHead>
-                  <TableHead>登录次数</TableHead>
-                  <TableHead>状态</TableHead>
-                  <TableHead>创建时间</TableHead>
-                  <TableHead className="text-right">操作</TableHead>
-                </TableRow>
-              </TableHeader>
-              <TableBody>
-                {agents.map((agent) => (
-                  <TableRow key={agent.id}>
-                    <TableCell className="font-medium">{agent.username}</TableCell>
-                    <TableCell>{agent.name || '-'}</TableCell>
-                    <TableCell>{agent.realname || '-'}</TableCell>
-                    <TableCell>{agent.phone || '-'}</TableCell>
-                    <TableCell>{agent.loginNum}</TableCell>
-                    <TableCell>
-                      <Badge
-                        variant={agent.state === 1 ? 'default' : 'secondary'}
-                      >
-                        {agent.state === 1 ? '启用' : '禁用'}
-                      </Badge>
-                    </TableCell>
-                    <TableCell>
-                      {format(new Date(agent.createdAt), 'yyyy-MM-dd HH:mm')}
-                    </TableCell>
-                    <TableCell className="text-right">
-                      <div className="flex justify-end gap-2">
-                        <Button
-                          variant="ghost"
-                          size="icon"
-                          onClick={() => handleEditAgent(agent)}
-                        >
-                          <Edit className="h-4 w-4" />
-                        </Button>
-                        <Button
-                          variant="ghost"
-                          size="icon"
-                          onClick={() => handleDeleteAgent(agent.id)}
-                        >
-                          <Trash2 className="h-4 w-4" />
-                        </Button>
-                      </div>
-                    </TableCell>
-                  </TableRow>
-                ))}
-              </TableBody>
-            </Table>
-          </div>
-
-          <DataTablePagination
-            currentPage={searchParams.page}
-            totalCount={totalCount}
-            pageSize={searchParams.limit}
-            onPageChange={handlePageChange}
-          />
-        </CardContent>
-      </Card>
-
-      {/* 创建/编辑代理商对话框 */}
-      <Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
-        <DialogContent className="sm:max-w-[500px] max-h-[90vh] overflow-y-auto">
-          <DialogHeader>
-            <DialogTitle>
-              {editingAgent ? '编辑代理商' : '创建代理商'}
-            </DialogTitle>
-            <DialogDescription>
-              {editingAgent ? '编辑现有代理商信息' : '创建一个新的代理商账户'}
-            </DialogDescription>
-          </DialogHeader>
-          
-          {isCreateForm ? (
-            <Form {...createForm}>
-              <form onSubmit={createForm.handleSubmit(handleCreateSubmit)} className="space-y-4">
-                <FormField
-                  control={createForm.control}
-                  name="username"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        用户名
-                        <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入用户名" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="name"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>代理商名称</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入代理商名称" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="realname"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>姓名</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入姓名" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="phone"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>手机号</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入手机号" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="password"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        密码
-                        <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input type="password" placeholder="请输入密码" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="state"
-                  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 : 2)}
-                        />
-                      </FormControl>
-                    </FormItem>
-                  )}
-                />
-
-                <DialogFooter>
-                  <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
-                    取消
-                  </Button>
-                  <Button type="submit">
-                    创建代理商
-                  </Button>
-                </DialogFooter>
-              </form>
-            </Form>
-          ) : (
-            <Form {...updateForm}>
-              <form onSubmit={updateForm.handleSubmit(handleUpdateSubmit)} className="space-y-4">
-                <FormField
-                  control={updateForm.control}
-                  name="username"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        用户名
-                        <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入用户名" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="name"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>代理商名称</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入代理商名称" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="realname"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>姓名</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入姓名" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="phone"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>手机号</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入手机号" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="password"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>新密码</FormLabel>
-                      <FormControl>
-                        <Input type="password" placeholder="留空则不修改密码" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="state"
-                  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 : 2)}
-                        />
-                      </FormControl>
-                    </FormItem>
-                  )}
-                />
-
-                <DialogFooter>
-                  <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
-                    取消
-                  </Button>
-                  <Button type="submit">
-                    更新代理商
-                  </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}>
-              删除
-            </Button>
-          </DialogFooter>
-        </DialogContent>
-      </Dialog>
-    </div>
-  );
-};

+ 0 - 525
web/src/client/admin/pages/ExpressCompanies.tsx

@@ -1,525 +0,0 @@
-import { useState } from 'react'
-import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
-import { zodResolver } from '@hookform/resolvers/zod'
-import { useForm } from 'react-hook-form'
-import { Plus, Search, Edit, Trash2 } from 'lucide-react'
-import { toast } from 'react-toastify'
-
-import { Button } from '@/client/components/ui/button'
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card'
-import { Input } from '@/client/components/ui/input'
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/client/components/ui/table'
-import { Badge } from '@/client/components/ui/badge'
-import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/client/components/ui/dialog'
-import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form'
-import { DataTablePagination } from '@/client/admin/components/DataTablePagination'
-
-import { expressCompanyClient } from '@/client/api'
-import type { InferRequestType, InferResponseType } from 'hono/client'
-import { CreateExpressCompanyDto, UpdateExpressCompanyDto } from '@d8d/server/modules/logistics/express-company.schema'
-
-// 类型定义
-type CreateRequest = InferRequestType<typeof expressCompanyClient.$post>['json']
-type UpdateRequest = InferRequestType<typeof expressCompanyClient[':id']['$put']>['json']
-type ExpressCompanyResponse = InferResponseType<typeof expressCompanyClient.$get, 200>['data'][0]
-
-// 表单Schema直接使用后端定义
-const createFormSchema = CreateExpressCompanyDto
-const updateFormSchema = UpdateExpressCompanyDto
-
-export const ExpressCompaniesPage = () => {
-  const queryClient = useQueryClient()
-  
-  // 状态管理
-  const [searchParams, setSearchParams] = useState({ page: 1, limit: 10, search: '' })
-  const [isModalOpen, setIsModalOpen] = useState(false)
-  const [editingCompany, setEditingCompany] = useState<ExpressCompanyResponse | null>(null)
-  const [isCreateForm, setIsCreateForm] = useState(true)
-  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
-  const [companyToDelete, setCompanyToDelete] = useState<number | null>(null)
-
-  // 表单实例
-  const createForm = useForm<CreateRequest>({
-    resolver: zodResolver(createFormSchema),
-    defaultValues: {
-      name: '',
-      code: '',
-      state: 1,
-      sort: 100
-    },
-  })
-
-  const updateForm = useForm<UpdateRequest>({
-    resolver: zodResolver(updateFormSchema),
-  })
-
-  // 数据查询
-  const { data, isLoading, refetch } = useQuery({
-    queryKey: ['express-companies', searchParams],
-    queryFn: async () => {
-      const res = await expressCompanyClient.$get({
-        query: {
-          page: searchParams.page,
-          pageSize: searchParams.limit,
-          keyword: searchParams.search,
-        }
-      })
-      if (res.status !== 200) throw new Error('获取快递公司列表失败')
-      return await res.json()
-    }
-  })
-
-  // 创建快递公司
-  const createMutation = useMutation({
-    mutationFn: async (data: CreateRequest) => {
-      const res = await expressCompanyClient.$post({ json: data })
-      if (res.status !== 201) throw new Error('创建快递公司失败')
-      return await res.json()
-    },
-    onSuccess: () => {
-      toast.success('快递公司创建成功')
-      setIsModalOpen(false)
-      queryClient.invalidateQueries({ queryKey: ['express-companies'] })
-      createForm.reset()
-    },
-    onError: (error) => {
-      toast.error(error instanceof Error ? error.message : '创建失败,请重试')
-    }
-  })
-
-  // 更新快递公司
-  const updateMutation = useMutation({
-    mutationFn: async ({ id, data }: { id: number; data: UpdateRequest }) => {
-      const res = await expressCompanyClient[':id']['$put']({
-        param: { id: id.toString() },
-        json: data
-      })
-      if (res.status !== 200) throw new Error('更新快递公司失败')
-      return await res.json()
-    },
-    onSuccess: () => {
-      toast.success('快递公司更新成功')
-      setIsModalOpen(false)
-      queryClient.invalidateQueries({ queryKey: ['express-companies'] })
-    },
-    onError: (error) => {
-      toast.error(error instanceof Error ? error.message : '更新失败,请重试')
-    }
-  })
-
-  // 删除快递公司
-  const deleteMutation = useMutation({
-    mutationFn: async (id: number) => {
-      const res = await expressCompanyClient[':id']['$delete']({
-        param: { id: id.toString() }
-      })
-      if (res.status !== 204) throw new Error('删除快递公司失败')
-      return res
-    },
-    onSuccess: () => {
-      toast.success('快递公司删除成功')
-      setDeleteDialogOpen(false)
-      queryClient.invalidateQueries({ queryKey: ['express-companies'] })
-    },
-    onError: (error) => {
-      toast.error(error instanceof Error ? error.message : '删除失败,请重试')
-    }
-  })
-
-  // 业务逻辑函数
-  const handleSearch = () => {
-    setSearchParams(prev => ({ ...prev, page: 1 }))
-  }
-
-  const handleCreateCompany = () => {
-    setIsCreateForm(true)
-    setEditingCompany(null)
-    createForm.reset()
-    setIsModalOpen(true)
-  }
-
-  const handleEditCompany = (company: ExpressCompanyResponse) => {
-    setIsCreateForm(false)
-    setEditingCompany(company)
-    updateForm.reset({
-      name: company.name,
-      code: company.code,
-      state: company.state,
-      sort: company.sort || undefined
-    })
-    setIsModalOpen(true)
-  }
-
-  const handleDeleteCompany = (id: number) => {
-    setCompanyToDelete(id)
-    setDeleteDialogOpen(true)
-  }
-
-  const handleCreateSubmit = (data: CreateRequest) => {
-    createMutation.mutate(data)
-  }
-
-  const handleUpdateSubmit = (data: UpdateRequest) => {
-    if (editingCompany) {
-      updateMutation.mutate({ id: editingCompany.id, data })
-    }
-  }
-
-  const confirmDelete = () => {
-    if (companyToDelete) {
-      deleteMutation.mutate(companyToDelete)
-    }
-  }
-
-  // 渲染页面
-  if (isLoading) {
-    return (
-      <div className="space-y-4">
-        <div className="flex justify-between items-center">
-          <h1 className="text-2xl font-bold">快递公司管理</h1>
-          <Button disabled>
-            <Plus className="mr-2 h-4 w-4" />
-            创建快递公司
-          </Button>
-        </div>
-        
-        <Card>
-          <CardHeader>
-            <Skeleton className="h-6 w-1/4" />
-          </CardHeader>
-          <CardContent>
-            <div className="space-y-2">
-              <Skeleton className="h-4 w-full" />
-              <Skeleton className="h-4 w-full" />
-              <Skeleton className="h-4 w-full" />
-            </div>
-          </CardContent>
-        </Card>
-      </div>
-    )
-  }
-
-  return (
-    <div className="space-y-4">
-      {/* 页面标题区域 */}
-      <div className="flex justify-between items-center">
-        <h1 className="text-2xl font-bold">快递公司管理</h1>
-        <Button onClick={handleCreateCompany}>
-          <Plus className="mr-2 h-4 w-4" />
-          创建快递公司
-        </Button>
-      </div>
-
-      {/* 搜索区域 */}
-      <Card>
-        <CardHeader>
-          <CardTitle>快递公司列表</CardTitle>
-          <CardDescription>管理系统中的快递公司信息</CardDescription>
-        </CardHeader>
-        <CardContent>
-          <div className="mb-4">
-            <form onSubmit={(e) => { e.preventDefault(); handleSearch() }} className="flex gap-2">
-              <div className="relative flex-1 max-w-sm">
-                <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>
-              <Button type="submit" variant="outline">
-                搜索
-              </Button>
-            </form>
-          </div>
-
-          {/* 数据表格 */}
-          <div className="rounded-md border">
-            <Table>
-              <TableHeader>
-                <TableRow>
-                  <TableHead>ID</TableHead>
-                  <TableHead>公司名称</TableHead>
-                  <TableHead>编号</TableHead>
-                  <TableHead>状态</TableHead>
-                  <TableHead>排序</TableHead>
-                  <TableHead>创建时间</TableHead>
-                  <TableHead className="text-right">操作</TableHead>
-                </TableRow>
-              </TableHeader>
-              <TableBody>
-                {data?.data.map((company) => (
-                  <TableRow key={company.id}>
-                    <TableCell>{company.id}</TableCell>
-                    <TableCell>{company.name}</TableCell>
-                    <TableCell>{company.code}</TableCell>
-                    <TableCell>
-                      <Badge variant={company.state === 1 ? 'default' : 'secondary'}>
-                        {company.state === 1 ? '启用' : '禁用'}
-                      </Badge>
-                    </TableCell>
-                    <TableCell>{company.sort || '-'}</TableCell>
-                    <TableCell>{new Date(company.createdAt).toLocaleDateString()}</TableCell>
-                    <TableCell className="text-right">
-                      <div className="flex justify-end gap-2">
-                        <Button 
-                          variant="ghost" 
-                          size="icon" 
-                          onClick={() => handleEditCompany(company)}
-                          title="编辑"
-                        >
-                          <Edit className="h-4 w-4" />
-                        </Button>
-                        <Button 
-                          variant="ghost" 
-                          size="icon" 
-                          onClick={() => handleDeleteCompany(company.id)}
-                          title="删除"
-                        >
-                          <Trash2 className="h-4 w-4" />
-                        </Button>
-                      </div>
-                    </TableCell>
-                  </TableRow>
-                ))}
-              </TableBody>
-            </Table>
-          </div>
-
-          {data?.data.length === 0 && !isLoading && (
-            <div className="text-center py-8">
-              <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-[500px] 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="name"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        公司名称 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入快递公司名称" {...field} />
-                      </FormControl>
-                      <FormDescription>例如:顺丰速运、中通快递等</FormDescription>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-                
-                <FormField
-                  control={createForm.control}
-                  name="code"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        编号 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入快递公司编号" {...field} />
-                      </FormControl>
-                      <FormDescription>例如:SF、ZTO等唯一标识</FormDescription>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-                
-                <FormField
-                  control={createForm.control}
-                  name="state"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>状态</FormLabel>
-                      <FormControl>
-                        <select 
-                          className="w-full px-3 py-2 border rounded-md"
-                          value={field.value}
-                          onChange={(e) => field.onChange(parseInt(e.target.value))}
-                        >
-                          <option value={1}>启用</option>
-                          <option value={2}>禁用</option>
-                        </select>
-                      </FormControl>
-                      <FormDescription>选择快递公司的使用状态</FormDescription>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-                
-                <FormField
-                  control={createForm.control}
-                  name="sort"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>排序</FormLabel>
-                      <FormControl>
-                        <Input 
-                          type="number" 
-                          placeholder="请输入排序值" 
-                          {...field}
-                          onChange={(e) => field.onChange(e.target.value ? parseInt(e.target.value) : null)}
-                          value={field.value || ''}
-                        />
-                      </FormControl>
-                      <FormDescription>数字越大优先级越高</FormDescription>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-                
-                <DialogFooter>
-                  <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
-                    取消
-                  </Button>
-                  <Button type="submit" disabled={createMutation.isPending}>
-                    {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 className="flex items-center">
-                        公司名称 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入快递公司名称" {...field} />
-                      </FormControl>
-                      <FormDescription>例如:顺丰速运、中通快递等</FormDescription>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-                
-                <FormField
-                  control={updateForm.control}
-                  name="code"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        编号 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入快递公司编号" {...field} />
-                      </FormControl>
-                      <FormDescription>例如:SF、ZTO等唯一标识</FormDescription>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-                
-                <FormField
-                  control={updateForm.control}
-                  name="state"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>状态</FormLabel>
-                      <FormControl>
-                        <select 
-                          className="w-full px-3 py-2 border rounded-md"
-                          value={field.value}
-                          onChange={(e) => field.onChange(parseInt(e.target.value))}
-                        >
-                          <option value={1}>启用</option>
-                          <option value={2}>禁用</option>
-                        </select>
-                      </FormControl>
-                      <FormDescription>选择快递公司的使用状态</FormDescription>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-                
-                <FormField
-                  control={updateForm.control}
-                  name="sort"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>排序</FormLabel>
-                      <FormControl>
-                        <Input 
-                          type="number" 
-                          placeholder="请输入排序值" 
-                          {...field}
-                          onChange={(e) => field.onChange(e.target.value ? parseInt(e.target.value) : null)}
-                          value={field.value || ''}
-                        />
-                      </FormControl>
-                      <FormDescription>数字越大优先级越高</FormDescription>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-                
-                <DialogFooter>
-                  <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
-                    取消
-                  </Button>
-                  <Button type="submit" disabled={updateMutation.isPending}>
-                    {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}>
-              {deleteMutation.isPending ? '删除中...' : '删除'}
-            </Button>
-          </DialogFooter>
-        </DialogContent>
-      </Dialog>
-    </div>
-  )
-}
-
-// 骨架屏组件
-const Skeleton = ({ className }: { className?: string }) => (
-  <div className={`animate-pulse rounded-md bg-muted ${className}`} />
-)

+ 0 - 761
web/src/client/admin/pages/UserCardBalanceRecords.tsx

@@ -1,761 +0,0 @@
-import React, { useState } from 'react';
-import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
-import { Plus, Search, Edit, Trash2, CreditCard, TrendingUp, TrendingDown } from 'lucide-react';
-import { format } from 'date-fns';
-import { zhCN } from 'date-fns/locale';
-import { useForm } from 'react-hook-form';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { toast } from 'sonner';
-
-import { Button } from '@/client/components/ui/button';
-import { Input } from '@/client/components/ui/input';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/client/components/ui/table';
-import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/client/components/ui/dialog';
-import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
-import { Badge } from '@/client/components/ui/badge';
-import { Skeleton } from '@/client/components/ui/skeleton';
-import { DataTablePagination } from '@/client/admin/components/DataTablePagination';
-
-import { userCardBalanceRecordClient } from '@/client/api';
-import type { InferRequestType, InferResponseType } from 'hono/client';
-import { CreateUserCardBalanceRecordDto, UpdateUserCardBalanceRecordDto } from '@d8d/server/modules/user-card-balance-records/user-card-balance-record.schema';
-import { UserSelector } from '@/client/admin/components/UserSelector';
-import { UserCardSelector } from '@/client/admin/components/UserCardSelector';
-
-type CreateRequest = InferRequestType<typeof userCardBalanceRecordClient.$post>['json'];
-type UpdateRequest = InferRequestType<typeof userCardBalanceRecordClient[':id']['$put']>['json'];
-type UserCardBalanceRecordResponse = InferResponseType<typeof userCardBalanceRecordClient.$get, 200>['data'][0];
-
-const createFormSchema = CreateUserCardBalanceRecordDto;
-const updateFormSchema = UpdateUserCardBalanceRecordDto;
-
-export const UserCardBalanceRecordsPage = () => {
-  const queryClient = useQueryClient();
-  const [searchParams, setSearchParams] = useState({ page: 1, limit: 10, search: '', type: '' });
-  const [isModalOpen, setIsModalOpen] = useState(false);
-  const [isCreateForm, setIsCreateForm] = useState(true);
-  const [editingRecord, setEditingRecord] = useState<UserCardBalanceRecordResponse | null>(null);
-  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
-  const [recordToDelete, setRecordToDelete] = useState<number | null>(null);
-
-  // 表单实例
-  const createForm = useForm<CreateRequest>({
-    resolver: zodResolver(createFormSchema),
-    defaultValues: {
-      userId: 0,
-      cardNo: '',
-      amount: 0,
-      amountBefore: 0,
-      amountAfter: 0,
-      orderNo: '',
-      type: 1,
-      remark: ''
-    }
-  });
-
-  const updateForm = useForm<UpdateRequest>({
-    resolver: zodResolver(updateFormSchema),
-    defaultValues: {}
-  });
-
-  // 数据查询
-  const { data, isLoading, refetch } = useQuery({
-    queryKey: ['userCardBalanceRecords', searchParams],
-    queryFn: async () => {
-      const res = await userCardBalanceRecordClient.$get({
-        query: {
-          page: searchParams.page,
-          pageSize: searchParams.limit,
-          keyword: searchParams.search,
-          ...(searchParams.type && { filters: JSON.stringify({ type: parseInt(searchParams.type) }) })
-        }
-      });
-      if (res.status !== 200) throw new Error('获取余额记录列表失败');
-      return await res.json();
-    }
-  });
-
-  // 创建余额记录
-  const createMutation = useMutation({
-    mutationFn: async (data: CreateRequest) => {
-      const res = await userCardBalanceRecordClient.$post({ json: data });
-      if (res.status !== 201) throw new Error('创建余额记录失败');
-      return await res.json();
-    },
-    onSuccess: () => {
-      toast.success('余额记录创建成功');
-      setIsModalOpen(false);
-      createForm.reset();
-      refetch();
-    },
-    onError: (error) => {
-      toast.error(error.message || '创建余额记录失败');
-    }
-  });
-
-  // 更新余额记录
-  const updateMutation = useMutation({
-    mutationFn: async ({ id, data }: { id: number; data: UpdateRequest }) => {
-      const res = await userCardBalanceRecordClient[':id']['$put']({
-        param: { id: id.toString() },
-        json: data
-      });
-      if (res.status !== 200) throw new Error('更新余额记录失败');
-      return await res.json();
-    },
-    onSuccess: () => {
-      toast.success('余额记录更新成功');
-      setIsModalOpen(false);
-      updateForm.reset();
-      refetch();
-    },
-    onError: (error) => {
-      toast.error(error.message || '更新余额记录失败');
-    }
-  });
-
-  // 删除余额记录
-  const deleteMutation = useMutation({
-    mutationFn: async (id: number) => {
-      const res = await userCardBalanceRecordClient[':id']['$delete']({
-        param: { id: id.toString() }
-      });
-      if (res.status !== 204) throw new Error('删除余额记录失败');
-      return res;
-    },
-    onSuccess: () => {
-      toast.success('余额记录删除成功');
-      setDeleteDialogOpen(false);
-      refetch();
-    },
-    onError: (error) => {
-      toast.error(error.message || '删除余额记录失败');
-    }
-  });
-
-  // 处理搜索
-  const handleSearch = (e: React.FormEvent) => {
-    e.preventDefault();
-    setSearchParams(prev => ({ ...prev, page: 1 }));
-  };
-
-  // 处理创建余额记录
-  const handleCreateRecord = () => {
-    setIsCreateForm(true);
-    setEditingRecord(null);
-    createForm.reset();
-    setIsModalOpen(true);
-  };
-
-  // 处理编辑余额记录
-  const handleEditRecord = (record: UserCardBalanceRecordResponse) => {
-    setIsCreateForm(false);
-    setEditingRecord(record);
-    updateForm.reset({
-      userId: record.userId,
-      cardNo: record.cardNo,
-      amount: record.amount,
-      amountBefore: record.amountBefore,
-      amountAfter: record.amountAfter,
-      orderNo: record.orderNo,
-      type: record.type,
-      remark: record.remark || undefined
-    });
-    setIsModalOpen(true);
-  };
-
-  // 处理删除余额记录
-  const handleDeleteRecord = (id: number) => {
-    setRecordToDelete(id);
-    setDeleteDialogOpen(true);
-  };
-
-  // 确认删除
-  const confirmDelete = () => {
-    if (recordToDelete) {
-      deleteMutation.mutate(recordToDelete);
-    }
-  };
-
-  // 计算总计
-  const totalAmount = data?.data.reduce((sum, record) => sum + record.amount, 0) || 0;
-  const totalConsumption = data?.data.filter(r => r.type === 1).reduce((sum, record) => sum + record.amount, 0) || 0;
-  const totalRefund = data?.data.filter(r => r.type === 2).reduce((sum, record) => sum + record.amount, 0) || 0;
-
-  // 加载状态
-  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-muted-foreground">管理用户卡余额变动记录</p>
-        </div>
-        <Button onClick={handleCreateRecord}>
-          <Plus className="mr-2 h-4 w-4" />
-          创建记录
-        </Button>
-      </div>
-
-      {/* 统计卡片 */}
-      <div className="grid gap-4 md:grid-cols-3">
-        <Card>
-          <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
-            <CardTitle className="text-sm font-medium">总变动金额</CardTitle>
-            <CreditCard className="h-4 w-4 text-muted-foreground" />
-          </CardHeader>
-          <CardContent>
-            <div className="text-2xl font-bold">¥{totalAmount.toFixed(2)}</div>
-          </CardContent>
-        </Card>
-        <Card>
-          <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
-            <CardTitle className="text-sm font-medium">总消费</CardTitle>
-            <TrendingDown className="h-4 w-4 text-muted-foreground" />
-          </CardHeader>
-          <CardContent>
-            <div className="text-2xl font-bold text-red-600">¥{totalConsumption.toFixed(2)}</div>
-          </CardContent>
-        </Card>
-        <Card>
-          <CardHeader className="flex flex-row items-center justify-between space-y-0 pb-2">
-            <CardTitle className="text-sm font-medium">总退款</CardTitle>
-            <TrendingUp className="h-4 w-4 text-muted-foreground" />
-          </CardHeader>
-          <CardContent>
-            <div className="text-2xl font-bold text-green-600">¥{totalRefund.toFixed(2)}</div>
-          </CardContent>
-        </Card>
-      </div>
-
-      {/* 搜索区域 */}
-      <Card>
-        <CardHeader>
-          <CardTitle>余额记录列表</CardTitle>
-          <CardDescription>查看和管理所有余额变动记录</CardDescription>
-        </CardHeader>
-        <CardContent>
-          <form onSubmit={handleSearch} className="flex gap-2 flex-wrap">
-            <div className="relative flex-1 max-w-sm">
-              <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>
-            <select
-              className="px-3 py-2 border rounded-md"
-              value={searchParams.type}
-              onChange={(e) => setSearchParams(prev => ({ ...prev, type: e.target.value, page: 1 }))}
-            >
-              <option value="">全部类型</option>
-              <option value="1">消费</option>
-              <option value="2">退款</option>
-            </select>
-            <Button type="submit" variant="outline">
-              搜索
-            </Button>
-          </form>
-        </CardContent>
-      </Card>
-
-      {/* 数据表格 */}
-      <Card>
-        <CardContent className="p-0">
-          <Table>
-            <TableHeader>
-              <TableRow>
-                <TableHead>ID</TableHead>
-                <TableHead>用户</TableHead>
-                <TableHead>卡号</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((record) => (
-                <TableRow key={record.id}>
-                  <TableCell>{record.id}</TableCell>
-                  <TableCell>{record.user?.name || record.user?.username || '-'}</TableCell>
-                  <TableCell>{record.cardNo}</TableCell>
-                  <TableCell>
-                    <span className={record.type === 1 ? 'text-red-600' : 'text-green-600'}>
-                      {record.type === 1 ? '-' : '+'}¥{record.amount.toFixed(2)}
-                    </span>
-                  </TableCell>
-                  <TableCell>¥{record.amountBefore.toFixed(2)}</TableCell>
-                  <TableCell>¥{record.amountAfter.toFixed(2)}</TableCell>
-                  <TableCell className="font-mono text-xs">{record.orderNo}</TableCell>
-                  <TableCell>
-                    <Badge variant={record.type === 1 ? 'destructive' : 'default'}>
-                      {record.type === 1 ? (
-                        <>
-                          <TrendingDown className="h-3 w-3 mr-1" />
-                          消费
-                        </>
-                      ) : (
-                        <>
-                          <TrendingUp className="h-3 w-3 mr-1" />
-                          退款
-                        </>
-                      )}
-                    </Badge>
-                  </TableCell>
-                  <TableCell>{record.remark || '-'}</TableCell>
-                  <TableCell>
-                    {format(new Date(record.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={() => handleEditRecord(record)}
-                      >
-                        <Edit className="h-4 w-4" />
-                      </Button>
-                      <Button
-                        variant="ghost"
-                        size="icon"
-                        onClick={() => handleDeleteRecord(record.id)}
-                      >
-                        <Trash2 className="h-4 w-4" />
-                      </Button>
-                    </div>
-                  </TableCell>
-                </TableRow>
-              ))}
-            </TableBody>
-          </Table>
-          
-          {data?.data.length === 0 && (
-            <div className="text-center py-8">
-              <p className="text-muted-foreground">暂无余额记录数据</p>
-            </div>
-          )}
-        </CardContent>
-      </Card>
-
-      {/* 分页 */}
-      {data && data.data.length > 0 && (
-        <DataTablePagination
-          currentPage={searchParams.page}
-          pageSize={searchParams.limit}
-          totalCount={data.pagination.total || 0}
-          onPageChange={(page, limit) => setSearchParams(prev => ({ ...prev, page, limit }))}
-        />
-      )}
-
-      {/* 创建/编辑模态框 */}
-      <Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
-        <DialogContent className="sm:max-w-[500px] max-h-[90vh] overflow-y-auto">
-          <DialogHeader>
-            <DialogTitle>{isCreateForm ? '创建余额记录' : '编辑余额记录'}</DialogTitle>
-            <DialogDescription>
-              {isCreateForm ? '创建一个新的余额变动记录' : '编辑现有余额记录信息'}
-            </DialogDescription>
-          </DialogHeader>
-
-          {isCreateForm ? (
-            <Form {...createForm}>
-              <form onSubmit={createForm.handleSubmit((data) => createMutation.mutate(data))} className="space-y-4">
-                <FormField
-                  control={createForm.control}
-                  name="userId"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        用户 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <UserSelector
-                          value={field.value}
-                          onChange={field.onChange}
-                          placeholder="选择用户"
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="cardNo"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        卡号 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <UserCardSelector
-                          userId={createForm.watch('userId')}
-                          value={field.value}
-                          onChange={field.onChange}
-                          placeholder="选择用户卡"
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="amount"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        变动金额 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input
-                          type="number"
-                          step="0.01"
-                          placeholder="0.00"
-                          {...field}
-                          onChange={(e) => field.onChange(parseFloat(e.target.value))}
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="amountBefore"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        变动前金额 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input
-                          type="number"
-                          step="0.01"
-                          placeholder="0.00"
-                          {...field}
-                          onChange={(e) => field.onChange(parseFloat(e.target.value))}
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="amountAfter"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        变动后金额 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input
-                          type="number"
-                          step="0.01"
-                          placeholder="0.00"
-                          {...field}
-                          onChange={(e) => field.onChange(parseFloat(e.target.value))}
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="orderNo"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        订单号 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入订单号" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="type"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>类型</FormLabel>
-                      <FormControl>
-                        <select
-                          {...field}
-                          className="w-full px-3 py-2 border rounded-md"
-                          onChange={(e) => field.onChange(parseInt(e.target.value))}
-                        >
-                          <option value={1}>消费</option>
-                          <option value={2}>退款</option>
-                        </select>
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="remark"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>备注</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入备注" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <DialogFooter>
-                  <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
-                    取消
-                  </Button>
-                  <Button type="submit" disabled={createMutation.isPending}>
-                    {createMutation.isPending ? '创建中...' : '创建'}
-                  </Button>
-                </DialogFooter>
-              </form>
-            </Form>
-          ) : (
-            <Form {...updateForm}>
-              <form onSubmit={updateForm.handleSubmit((data) => updateMutation.mutate({ id: editingRecord!.id, data }))} className="space-y-4">
-                <FormField
-                  control={updateForm.control}
-                  name="userId"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        用户 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <UserSelector
-                          value={field.value || editingRecord?.userId || 0}
-                          onChange={field.onChange}
-                          placeholder="选择用户"
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="cardNo"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        卡号 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <UserCardSelector
-                          userId={updateForm.watch('userId') || editingRecord?.userId || 0}
-                          value={field.value || editingRecord?.cardNo || ''}
-                          onChange={field.onChange}
-                          placeholder="选择用户卡"
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="amount"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        变动金额 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input
-                          type="number"
-                          step="0.01"
-                          placeholder="0.00"
-                          {...field}
-                          onChange={(e) => field.onChange(parseFloat(e.target.value))}
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="amountBefore"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        变动前金额 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input
-                          type="number"
-                          step="0.01"
-                          placeholder="0.00"
-                          {...field}
-                          onChange={(e) => field.onChange(parseFloat(e.target.value))}
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="amountAfter"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        变动后金额 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input
-                          type="number"
-                          step="0.01"
-                          placeholder="0.00"
-                          {...field}
-                          onChange={(e) => field.onChange(parseFloat(e.target.value))}
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="orderNo"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        订单号 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入订单号" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="type"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>类型</FormLabel>
-                      <FormControl>
-                        <select
-                          {...field}
-                          className="w-full px-3 py-2 border rounded-md"
-                          onChange={(e) => field.onChange(parseInt(e.target.value))}
-                        >
-                          <option value={1}>消费</option>
-                          <option value={2}>退款</option>
-                        </select>
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="remark"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>备注</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入备注" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <DialogFooter>
-                  <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
-                    取消
-                  </Button>
-                  <Button type="submit" disabled={updateMutation.isPending}>
-                    {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}>
-              {deleteMutation.isPending ? '删除中...' : '删除'}
-            </Button>
-          </DialogFooter>
-        </DialogContent>
-      </Dialog>
-    </div>
-  );
-};

+ 0 - 678
web/src/client/admin/pages/UserCards.tsx

@@ -1,678 +0,0 @@
-import React, { useState } from 'react';
-import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query';
-import { Plus, Search, Edit, Trash2, CreditCard } from 'lucide-react';
-import { format } from 'date-fns';
-import { zhCN } from 'date-fns/locale';
-import { useForm } from 'react-hook-form';
-import { zodResolver } from '@hookform/resolvers/zod';
-import { toast } from 'sonner';
-
-import { Button } from '@/client/components/ui/button';
-import { Input } from '@/client/components/ui/input';
-import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/client/components/ui/card';
-import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/client/components/ui/table';
-import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle } from '@/client/components/ui/dialog';
-import { Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage } from '@/client/components/ui/form';
-import { Badge } from '@/client/components/ui/badge';
-import { Skeleton } from '@/client/components/ui/skeleton';
-import { DataTablePagination } from '@/client/admin/components/DataTablePagination';
-
-import { userCardClient } from '@/client/api';
-import type { InferRequestType, InferResponseType } from 'hono/client';
-import { CreateUserCardDto, UpdateUserCardDto } from '@d8d/server/modules/user-cards/user-card.schema';
-import { UserSelector } from '@/client/admin/components/UserSelector';
-import { AgentSelector } from '@/client/admin/components/AgentSelector';
-
-type CreateRequest = InferRequestType<typeof userCardClient.$post>['json'];
-type UpdateRequest = InferRequestType<typeof userCardClient[':id']['$put']>['json'];
-type UserCardResponse = InferResponseType<typeof userCardClient.$get, 200>['data'][0];
-
-const createFormSchema = CreateUserCardDto;
-const updateFormSchema = UpdateUserCardDto;
-
-export const UserCardsPage = () => {
-  const queryClient = useQueryClient();
-  const [searchParams, setSearchParams] = useState({ page: 1, limit: 10, search: '' });
-  const [isModalOpen, setIsModalOpen] = useState(false);
-  const [isCreateForm, setIsCreateForm] = useState(true);
-  const [editingCard, setEditingCard] = useState<UserCardResponse | null>(null);
-  const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
-  const [cardToDelete, setCardToDelete] = useState<number | null>(null);
-
-  // 表单实例
-  const createForm = useForm<CreateRequest>({
-    resolver: zodResolver(createFormSchema),
-    defaultValues: {
-      userId: 0,
-      cardNo: '',
-      password: '',
-      balance: 0,
-      state: 1,
-      isDefault: 2
-    }
-  });
-
-  const updateForm = useForm<UpdateRequest>({
-    resolver: zodResolver(updateFormSchema),
-    defaultValues: {}
-  });
-
-  // 数据查询
-  const { data, isLoading, refetch } = useQuery({
-    queryKey: ['userCards', searchParams],
-    queryFn: async () => {
-      const res = await userCardClient.$get({
-        query: {
-          page: searchParams.page,
-          pageSize: searchParams.limit,
-          keyword: searchParams.search
-        }
-      });
-      if (res.status !== 200) throw new Error('获取用户卡列表失败');
-      return await res.json();
-    }
-  });
-
-  // 创建用户卡
-  const createMutation = useMutation({
-    mutationFn: async (data: CreateRequest) => {
-      const res = await userCardClient.$post({ json: data });
-      if (res.status !== 201) throw new Error('创建用户卡失败');
-      return await res.json();
-    },
-    onSuccess: () => {
-      toast.success('用户卡创建成功');
-      setIsModalOpen(false);
-      createForm.reset();
-      refetch();
-    },
-    onError: (error) => {
-      toast.error(error.message || '创建用户卡失败');
-    }
-  });
-
-  // 更新用户卡
-  const updateMutation = useMutation({
-    mutationFn: async ({ id, data }: { id: number; data: UpdateRequest }) => {
-      const res = await userCardClient[':id']['$put']({
-        param: { id: id.toString() },
-        json: data
-      });
-      if (res.status !== 200) throw new Error('更新用户卡失败');
-      return await res.json();
-    },
-    onSuccess: () => {
-      toast.success('用户卡更新成功');
-      setIsModalOpen(false);
-      updateForm.reset();
-      refetch();
-    },
-    onError: (error) => {
-      toast.error(error.message || '更新用户卡失败');
-    }
-  });
-
-  // 删除用户卡
-  const deleteMutation = useMutation({
-    mutationFn: async (id: number) => {
-      const res = await userCardClient[':id']['$delete']({
-        param: { id: id.toString() }
-      });
-      if (res.status !== 204) throw new Error('删除用户卡失败');
-      return res;
-    },
-    onSuccess: () => {
-      toast.success('用户卡删除成功');
-      setDeleteDialogOpen(false);
-      refetch();
-    },
-    onError: (error) => {
-      toast.error(error.message || '删除用户卡失败');
-    }
-  });
-
-  // 处理搜索
-  const handleSearch = (e: React.FormEvent) => {
-    e.preventDefault();
-    setSearchParams(prev => ({ ...prev, page: 1 }));
-  };
-
-  // 处理创建用户卡
-  const handleCreateCard = () => {
-    setIsCreateForm(true);
-    setEditingCard(null);
-    createForm.reset();
-    setIsModalOpen(true);
-  };
-
-  // 处理编辑用户卡
-  const handleEditCard = (card: UserCardResponse) => {
-    setIsCreateForm(false);
-    setEditingCard(card);
-    updateForm.reset({
-      userId: card.userId,
-      agentId: card.agentId || undefined,
-      cardNo: card.cardNo,
-      sjtCardNo: card.sjtCardNo || undefined,
-      password: card.password,
-      authCode: card.authCode || undefined,
-      state: card.state,
-      balance: card.balance,
-      isDefault: card.isDefault
-    });
-    setIsModalOpen(true);
-  };
-
-  // 处理删除用户卡
-  const handleDeleteCard = (id: number) => {
-    setCardToDelete(id);
-    setDeleteDialogOpen(true);
-  };
-
-  // 确认删除
-  const confirmDelete = () => {
-    if (cardToDelete) {
-      deleteMutation.mutate(cardToDelete);
-    }
-  };
-
-  // 加载状态
-  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-muted-foreground">管理用户卡和余额信息</p>
-        </div>
-        <Button onClick={handleCreateCard}>
-          <Plus className="mr-2 h-4 w-4" />
-          创建用户卡
-        </Button>
-      </div>
-
-      {/* 搜索区域 */}
-      <Card>
-        <CardHeader>
-          <CardTitle>用户卡列表</CardTitle>
-          <CardDescription>查看和管理所有用户卡</CardDescription>
-        </CardHeader>
-        <CardContent>
-          <form onSubmit={handleSearch} className="flex gap-2">
-            <div className="relative flex-1 max-w-sm">
-              <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>
-            <Button type="submit" variant="outline">
-              搜索
-            </Button>
-          </form>
-        </CardContent>
-      </Card>
-
-      {/* 数据表格 */}
-      <Card>
-        <CardContent className="p-0">
-          <Table>
-            <TableHeader>
-              <TableRow>
-                <TableHead>ID</TableHead>
-                <TableHead>用户</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((card) => (
-                <TableRow key={card.id}>
-                  <TableCell>{card.id}</TableCell>
-                  <TableCell>{card.user?.name || card.user?.username || '-'}</TableCell>
-                  <TableCell>{card.cardNo}</TableCell>
-                  <TableCell>{card.sjtCardNo || '-'}</TableCell>
-                  <TableCell>{card.agent?.name || '-'}</TableCell>
-                  <TableCell>¥{card.balance.toFixed(2)}</TableCell>
-                  <TableCell>
-                    <Badge variant={card.state === 1 ? 'default' : 'secondary'}>
-                      {card.state === 1 ? '绑定' : '解绑'}
-                    </Badge>
-                  </TableCell>
-                  <TableCell>
-                    <Badge variant={card.isDefault === 1 ? 'default' : 'secondary'}>
-                      {card.isDefault === 1 ? '是' : '否'}
-                    </Badge>
-                  </TableCell>
-                  <TableCell>
-                    {format(new Date(card.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={() => handleEditCard(card)}
-                      >
-                        <Edit className="h-4 w-4" />
-                      </Button>
-                      <Button
-                        variant="ghost"
-                        size="icon"
-                        onClick={() => handleDeleteCard(card.id)}
-                      >
-                        <Trash2 className="h-4 w-4" />
-                      </Button>
-                    </div>
-                  </TableCell>
-                </TableRow>
-              ))}
-            </TableBody>
-          </Table>
-          
-          {data?.data.length === 0 && (
-            <div className="text-center py-8">
-              <p className="text-muted-foreground">暂无用户卡数据</p>
-            </div>
-          )}
-        </CardContent>
-      </Card>
-
-      {/* 分页 */}
-      {data && data.data.length > 0 && (
-        <DataTablePagination
-          currentPage={searchParams.page}
-          pageSize={searchParams.limit}
-          totalCount={data.pagination.total || 0}
-          onPageChange={(page, limit) => setSearchParams(prev => ({ ...prev, page, limit }))}
-        />
-      )}
-
-      {/* 创建/编辑模态框 */}
-      <Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
-        <DialogContent className="sm:max-w-[500px] max-h-[90vh] overflow-y-auto">
-          <DialogHeader>
-            <DialogTitle>{isCreateForm ? '创建用户卡' : '编辑用户卡'}</DialogTitle>
-            <DialogDescription>
-              {isCreateForm ? '创建一个新的用户卡' : '编辑现有用户卡信息'}
-            </DialogDescription>
-          </DialogHeader>
-
-          {isCreateForm ? (
-            <Form {...createForm}>
-              <form onSubmit={createForm.handleSubmit((data) => createMutation.mutate(data))} className="space-y-4">
-                <FormField
-                  control={createForm.control}
-                  name="userId"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        用户 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <UserSelector
-                          value={field.value}
-                          onChange={field.onChange}
-                          placeholder="选择用户"
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="cardNo"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        卡号 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入卡号" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="password"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        密码 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input type="password" placeholder="请输入密码" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="agentId"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>代理商</FormLabel>
-                      <FormControl>
-                        <AgentSelector
-                          value={field.value || undefined}
-                          onChange={field.onChange}
-                          placeholder="选择代理商"
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="sjtCardNo"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>盛京通卡号</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入盛京通卡号" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="balance"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>初始余额</FormLabel>
-                      <FormControl>
-                        <Input
-                          type="number"
-                          step="0.01"
-                          placeholder="0.00"
-                          {...field}
-                          onChange={(e) => field.onChange(parseFloat(e.target.value))}
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="state"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>状态</FormLabel>
-                      <FormControl>
-                        <select
-                          {...field}
-                          className="w-full px-3 py-2 border rounded-md"
-                          onChange={(e) => field.onChange(parseInt(e.target.value))}
-                        >
-                          <option value={1}>绑定</option>
-                          <option value={2}>解绑</option>
-                        </select>
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={createForm.control}
-                  name="isDefault"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>是否默认</FormLabel>
-                      <FormControl>
-                        <select
-                          {...field}
-                          className="w-full px-3 py-2 border rounded-md"
-                          onChange={(e) => field.onChange(parseInt(e.target.value))}
-                        >
-                          <option value={1}>是</option>
-                          <option value={2}>否</option>
-                        </select>
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <DialogFooter>
-                  <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
-                    取消
-                  </Button>
-                  <Button type="submit" disabled={createMutation.isPending}>
-                    {createMutation.isPending ? '创建中...' : '创建'}
-                  </Button>
-                </DialogFooter>
-              </form>
-            </Form>
-          ) : (
-            <Form {...updateForm}>
-              <form onSubmit={updateForm.handleSubmit((data) => updateMutation.mutate({ id: editingCard!.id, data }))} className="space-y-4">
-                <FormField
-                  control={updateForm.control}
-                  name="userId"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        用户 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <UserSelector
-                          value={field.value || editingCard?.userId || 0}
-                          onChange={field.onChange}
-                          placeholder="选择用户"
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="cardNo"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        卡号 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入卡号" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="password"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel className="flex items-center">
-                        密码 <span className="text-red-500 ml-1">*</span>
-                      </FormLabel>
-                      <FormControl>
-                        <Input type="password" placeholder="请输入密码" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="agentId"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>代理商</FormLabel>
-                      <FormControl>
-                        <AgentSelector
-                          value={field.value || editingCard?.agentId || undefined}
-                          onChange={field.onChange}
-                          placeholder="选择代理商"
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="sjtCardNo"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>盛京通卡号</FormLabel>
-                      <FormControl>
-                        <Input placeholder="请输入盛京通卡号" {...field} />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="balance"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>余额</FormLabel>
-                      <FormControl>
-                        <Input
-                          type="number"
-                          step="0.01"
-                          placeholder="0.00"
-                          {...field}
-                          onChange={(e) => field.onChange(parseFloat(e.target.value))}
-                        />
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="state"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>状态</FormLabel>
-                      <FormControl>
-                        <select
-                          {...field}
-                          className="w-full px-3 py-2 border rounded-md"
-                          onChange={(e) => field.onChange(parseInt(e.target.value))}
-                        >
-                          <option value={1}>绑定</option>
-                          <option value={2}>解绑</option>
-                        </select>
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <FormField
-                  control={updateForm.control}
-                  name="isDefault"
-                  render={({ field }) => (
-                    <FormItem>
-                      <FormLabel>是否默认</FormLabel>
-                      <FormControl>
-                        <select
-                          {...field}
-                          className="w-full px-3 py-2 border rounded-md"
-                          onChange={(e) => field.onChange(parseInt(e.target.value))}
-                        >
-                          <option value={1}>是</option>
-                          <option value={2}>否</option>
-                        </select>
-                      </FormControl>
-                      <FormMessage />
-                    </FormItem>
-                  )}
-                />
-
-                <DialogFooter>
-                  <Button type="button" variant="outline" onClick={() => setIsModalOpen(false)}>
-                    取消
-                  </Button>
-                  <Button type="submit" disabled={updateMutation.isPending}>
-                    {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}>
-              {deleteMutation.isPending ? '删除中...' : '删除'}
-            </Button>
-          </DialogFooter>
-        </DialogContent>
-      </Dialog>
-    </div>
-  );
-};

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

@@ -11,12 +11,8 @@ import { AdvertisementsPage } from './pages/Advertisements';
 import { AdvertisementTypesPage } from './pages/AdvertisementTypes';
 import { GoodsCategories } from './pages/GoodsCategories';
 import { GoodsPage } from './pages/Goods';
-import { ExpressCompaniesPage } from './pages/ExpressCompanies';
 import { SuppliersPage } from './pages/Suppliers';
 import { MerchantsPage } from './pages/Merchants'
-import { AgentsPage } from './pages/Agents';
-import { UserCardsPage } from './pages/UserCards';
-import { UserCardBalanceRecordsPage } from './pages/UserCardBalanceRecords';
 import { OrdersPage } from './pages/Orders';
 import { DeliveryAddressesPage } from './pages/DeliveryAddresses';
 
@@ -76,11 +72,6 @@ export const router = createBrowserRouter([
         element: <GoodsPage />,
         errorElement: <ErrorPage />
       },
-      {
-        path: 'express-companies',
-        element: <ExpressCompaniesPage />,
-        errorElement: <ErrorPage />
-      },
       {
         path: 'suppliers',
         element: <SuppliersPage />,
@@ -91,21 +82,6 @@ export const router = createBrowserRouter([
         element: <MerchantsPage />,
         errorElement: <ErrorPage />
       },
-      {
-        path: 'agents',
-        element: <AgentsPage />,
-        errorElement: <ErrorPage />
-      },
-      {
-        path: 'user-cards',
-        element: <UserCardsPage />,
-        errorElement: <ErrorPage />
-      },
-      {
-        path: 'user-card-balance-records',
-        element: <UserCardBalanceRecordsPage />,
-        errorElement: <ErrorPage />
-      },
       {
         path: 'delivery-addresses',
         element: <DeliveryAddressesPage />,

+ 1 - 34
web/src/client/api.ts

@@ -3,8 +3,7 @@ import { hc } from 'hono/client'
 import type {
   AuthRoutes, UserRoutes, RoleRoutes, FileRoutes,
   AdvertisementRoutes, AdvertisementTypeRoutes, GoodsCategoryRoutes, GoodsRoutes,
-  CityRoutes, ConfigRoutes, ExpressCompanyRoutes, OrganizationRoutes, SupplierRoutes,
-  CardRoutes, AgentRoutes, MerchantRoutes, UserCardRoutes, UserCardBalanceRecordRoutes,
+  SupplierRoutes, MerchantRoutes, 
   DeliveryAddressRoutes, OrderRoutes, OrderGoodsRoutes, OrderRefundRoutes
 } from '@d8d/server';
 
@@ -95,46 +94,14 @@ export const goodsClient = hc<GoodsRoutes>('/', {
   fetch: axiosFetch,
 }).api.v1.goods
 
-export const cityClient = hc<CityRoutes>('/', {
-  fetch: axiosFetch,
-}).api.v1.cities
-
-export const configClient = hc<ConfigRoutes>('/', {
-  fetch: axiosFetch,
-}).api.v1.configs
-
-export const expressCompanyClient = hc<ExpressCompanyRoutes>('/', {
-  fetch: axiosFetch,
-}).api.v1['express-companies']
-
-export const organizationClient = hc<OrganizationRoutes>('/', {
-  fetch: axiosFetch,
-}).api.v1.organizations
-
 export const supplierClient = hc<SupplierRoutes>('/', {
   fetch: axiosFetch,
 }).api.v1.suppliers
 
-export const cardClient = hc<CardRoutes>('/', {
-  fetch: axiosFetch,
-}).api.v1.cards
-
-export const agentClient = hc<AgentRoutes>('/', {
-  fetch: axiosFetch,
-}).api.v1.agents
-
 export const merchantClient = hc<MerchantRoutes>('/', {
   fetch: axiosFetch,
 }).api.v1.merchants
 
-export const userCardClient = hc<UserCardRoutes>('/', {
-  fetch: axiosFetch,
-}).api.v1['user-cards']
-
-export const userCardBalanceRecordClient = hc<UserCardBalanceRecordRoutes>('/', {
-  fetch: axiosFetch,
-}).api.v1['user-card-balance-records']
-
 export const deliveryAddressClient = hc<DeliveryAddressRoutes>('/', {
   fetch: axiosFetch,
 }).api.v1['delivery-addresses']