|
@@ -4,7 +4,7 @@ import { useForm } from 'react-hook-form';
|
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import { zodResolver } from '@hookform/resolvers/zod';
|
|
|
import { format } from 'date-fns';
|
|
import { format } from 'date-fns';
|
|
|
import { toast } from 'sonner';
|
|
import { toast } from 'sonner';
|
|
|
-import { Search, Edit, Eye, Package } from 'lucide-react';
|
|
|
|
|
|
|
+import { Search, Edit, Eye, Package, Truck } from 'lucide-react';
|
|
|
|
|
|
|
|
// 使用共享UI组件包的具体路径导入
|
|
// 使用共享UI组件包的具体路径导入
|
|
|
import { Button } from '@d8d/shared-ui-components/components/ui/button';
|
|
import { Button } from '@d8d/shared-ui-components/components/ui/button';
|
|
@@ -62,12 +62,14 @@ const DataTablePagination = ({
|
|
|
);
|
|
);
|
|
|
};
|
|
};
|
|
|
import { adminOrderClient, orderClientManager } from '../api';
|
|
import { adminOrderClient, orderClientManager } from '../api';
|
|
|
-import type { InferRequestType, InferResponseType } from 'hono/client';
|
|
|
|
|
|
|
+import type { InferResponseType } from 'hono/client';
|
|
|
import { UpdateOrderDto } from '@d8d/orders-module-mt/schemas';
|
|
import { UpdateOrderDto } from '@d8d/orders-module-mt/schemas';
|
|
|
|
|
|
|
|
// 类型定义
|
|
// 类型定义
|
|
|
type OrderResponse = InferResponseType<typeof adminOrderClient.index.$get, 200>['data'][0];
|
|
type OrderResponse = InferResponseType<typeof adminOrderClient.index.$get, 200>['data'][0];
|
|
|
-type UpdateRequest = InferRequestType<typeof adminOrderClient[':id']['$put']>['json'];
|
|
|
|
|
|
|
+type UpdateRequest = any;
|
|
|
|
|
+type DeliveryRequest = any;
|
|
|
|
|
+type DeliveryResponse = any;
|
|
|
|
|
|
|
|
// 状态映射
|
|
// 状态映射
|
|
|
const orderStatusMap = {
|
|
const orderStatusMap = {
|
|
@@ -91,6 +93,14 @@ const orderTypeMap = {
|
|
|
2: { label: '虚拟订单', color: 'secondary' },
|
|
2: { label: '虚拟订单', color: 'secondary' },
|
|
|
} as const;
|
|
} as const;
|
|
|
|
|
|
|
|
|
|
+const deliveryTypeMap = {
|
|
|
|
|
+ 0: { label: '未发货', color: 'warning' },
|
|
|
|
|
+ 1: { label: '物流快递', color: 'info' },
|
|
|
|
|
+ 2: { label: '同城配送', color: 'info' },
|
|
|
|
|
+ 3: { label: '用户自提', color: 'info' },
|
|
|
|
|
+ 4: { label: '虚拟发货', color: 'info' },
|
|
|
|
|
+} as const;
|
|
|
|
|
+
|
|
|
export const OrderManagement = () => {
|
|
export const OrderManagement = () => {
|
|
|
const [searchParams, setSearchParams] = useState({
|
|
const [searchParams, setSearchParams] = useState({
|
|
|
page: 1,
|
|
page: 1,
|
|
@@ -103,6 +113,8 @@ export const OrderManagement = () => {
|
|
|
const [editingOrder, setEditingOrder] = useState<OrderResponse | null>(null);
|
|
const [editingOrder, setEditingOrder] = useState<OrderResponse | null>(null);
|
|
|
const [detailModalOpen, setDetailModalOpen] = useState(false);
|
|
const [detailModalOpen, setDetailModalOpen] = useState(false);
|
|
|
const [selectedOrder, setSelectedOrder] = useState<OrderResponse | null>(null);
|
|
const [selectedOrder, setSelectedOrder] = useState<OrderResponse | null>(null);
|
|
|
|
|
+ const [deliveryModalOpen, setDeliveryModalOpen] = useState(false);
|
|
|
|
|
+ const [deliveringOrder, setDeliveringOrder] = useState<OrderResponse | null>(null);
|
|
|
|
|
|
|
|
// 表单实例
|
|
// 表单实例
|
|
|
const updateForm = useForm<UpdateRequest>({
|
|
const updateForm = useForm<UpdateRequest>({
|
|
@@ -110,6 +122,16 @@ export const OrderManagement = () => {
|
|
|
defaultValues: {},
|
|
defaultValues: {},
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
|
|
+ // 发货表单实例
|
|
|
|
|
+ const deliveryForm = useForm<DeliveryRequest>({
|
|
|
|
|
+ defaultValues: {
|
|
|
|
|
+ deliveryType: 1,
|
|
|
|
|
+ deliveryCompany: '',
|
|
|
|
|
+ deliveryNo: '',
|
|
|
|
|
+ deliveryRemark: '',
|
|
|
|
|
+ },
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
// 数据查询 - 60秒自动刷新
|
|
// 数据查询 - 60秒自动刷新
|
|
|
const { data, isLoading, refetch } = useQuery({
|
|
const { data, isLoading, refetch } = useQuery({
|
|
|
queryKey: ['orders', searchParams],
|
|
queryKey: ['orders', searchParams],
|
|
@@ -162,12 +184,49 @@ export const OrderManagement = () => {
|
|
|
setDetailModalOpen(true);
|
|
setDetailModalOpen(true);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
|
|
+ // 处理发货
|
|
|
|
|
+ const handleDeliveryOrder = (order: OrderResponse) => {
|
|
|
|
|
+ setDeliveringOrder(order);
|
|
|
|
|
+ deliveryForm.reset({
|
|
|
|
|
+ deliveryType: 1,
|
|
|
|
|
+ deliveryCompany: '',
|
|
|
|
|
+ deliveryNo: '',
|
|
|
|
|
+ deliveryRemark: '',
|
|
|
|
|
+ });
|
|
|
|
|
+ setDeliveryModalOpen(true);
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 处理发货提交
|
|
|
|
|
+ const handleDeliverySubmit = async (data: DeliveryRequest) => {
|
|
|
|
|
+ if (!deliveringOrder || !deliveringOrder.id) return;
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ const res = await (orderClientManager.getAdminDeliveryClient() as any)[':id']['delivery']['$post']({
|
|
|
|
|
+ param: { id: deliveringOrder.id },
|
|
|
|
|
+ json: data,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ if (res.status === 200) {
|
|
|
|
|
+ const result = await res.json() as DeliveryResponse;
|
|
|
|
|
+ toast.success(result.message || '发货成功');
|
|
|
|
|
+ setDeliveryModalOpen(false);
|
|
|
|
|
+ refetch();
|
|
|
|
|
+ } else {
|
|
|
|
|
+ const error = await res.json();
|
|
|
|
|
+ toast.error(error.message || '发货失败');
|
|
|
|
|
+ }
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ console.error('发货失败:', error);
|
|
|
|
|
+ toast.error('发货失败,请重试');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
// 处理更新订单
|
|
// 处理更新订单
|
|
|
const handleUpdateSubmit = async (data: UpdateRequest) => {
|
|
const handleUpdateSubmit = async (data: UpdateRequest) => {
|
|
|
if (!editingOrder || !editingOrder.id) return;
|
|
if (!editingOrder || !editingOrder.id) return;
|
|
|
|
|
|
|
|
try {
|
|
try {
|
|
|
- const res = await orderClientManager.getAdminOrderClient()[':id']['$put']({
|
|
|
|
|
|
|
+ const res = await (orderClientManager.getAdminOrderClient() as any)[':id']['$put']({
|
|
|
param: { id: editingOrder.id },
|
|
param: { id: editingOrder.id },
|
|
|
json: data,
|
|
json: data,
|
|
|
});
|
|
});
|
|
@@ -192,8 +251,8 @@ export const OrderManagement = () => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// 获取状态颜色
|
|
// 获取状态颜色
|
|
|
- const getStatusBadge = (status: number, type: 'order' | 'pay') => {
|
|
|
|
|
- const map = type === 'order' ? orderStatusMap : payStatusMap;
|
|
|
|
|
|
|
+ const getStatusBadge = (status: number, type: 'order' | 'pay' | 'delivery') => {
|
|
|
|
|
+ const map = type === 'order' ? orderStatusMap : type === 'pay' ? payStatusMap : deliveryTypeMap;
|
|
|
const config = map[status as keyof typeof map] || { label: '未知', color: 'default' };
|
|
const config = map[status as keyof typeof map] || { label: '未知', color: 'default' };
|
|
|
|
|
|
|
|
return <Badge variant={config.color as any}>{config.label}</Badge>;
|
|
return <Badge variant={config.color as any}>{config.label}</Badge>;
|
|
@@ -466,6 +525,16 @@ export const OrderManagement = () => {
|
|
|
>
|
|
>
|
|
|
<Edit className="h-4 w-4" />
|
|
<Edit className="h-4 w-4" />
|
|
|
</Button>
|
|
</Button>
|
|
|
|
|
+ {order.state === 0 && order.payState === 2 && (
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="icon"
|
|
|
|
|
+ onClick={() => handleDeliveryOrder(order)}
|
|
|
|
|
+ data-testid="order-delivery-button"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Truck className="h-4 w-4" />
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ )}
|
|
|
</div>
|
|
</div>
|
|
|
</TableCell>
|
|
</TableCell>
|
|
|
</TableRow>
|
|
</TableRow>
|
|
@@ -702,9 +771,6 @@ export const OrderManagement = () => {
|
|
|
)}
|
|
)}
|
|
|
<div>
|
|
<div>
|
|
|
<p className="font-medium text-sm">{item.goodsName}</p>
|
|
<p className="font-medium text-sm">{item.goodsName}</p>
|
|
|
- {item.specification && (
|
|
|
|
|
- <p className="text-xs text-muted-foreground">{item.specification}</p>
|
|
|
|
|
- )}
|
|
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
<div className="col-span-2 text-center text-sm">
|
|
<div className="col-span-2 text-center text-sm">
|
|
@@ -738,6 +804,136 @@ export const OrderManagement = () => {
|
|
|
)}
|
|
)}
|
|
|
</DialogContent>
|
|
</DialogContent>
|
|
|
</Dialog>
|
|
</Dialog>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 发货模态框 */}
|
|
|
|
|
+ <Dialog open={deliveryModalOpen} onOpenChange={setDeliveryModalOpen}>
|
|
|
|
|
+ <DialogContent className="sm:max-w-[500px] max-h-[90vh] overflow-y-auto">
|
|
|
|
|
+ <DialogHeader>
|
|
|
|
|
+ <DialogTitle>订单发货</DialogTitle>
|
|
|
|
|
+ <DialogDescription>选择发货方式并填写发货信息</DialogDescription>
|
|
|
|
|
+ </DialogHeader>
|
|
|
|
|
+
|
|
|
|
|
+ {deliveringOrder && (
|
|
|
|
|
+ <div className="space-y-4">
|
|
|
|
|
+ <div className="bg-muted p-3 rounded-md">
|
|
|
|
|
+ <h4 className="font-medium mb-2">订单信息</h4>
|
|
|
|
|
+ <div className="text-sm space-y-1">
|
|
|
|
|
+ <div className="flex justify-between">
|
|
|
|
|
+ <span className="text-muted-foreground">订单号:</span>
|
|
|
|
|
+ <span>{deliveringOrder.orderNo}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex justify-between">
|
|
|
|
|
+ <span className="text-muted-foreground">收货人:</span>
|
|
|
|
|
+ <span>{deliveringOrder.recevierName || '-'}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex justify-between">
|
|
|
|
|
+ <span className="text-muted-foreground">手机号:</span>
|
|
|
|
|
+ <span>{deliveringOrder.receiverMobile || '-'}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex justify-between">
|
|
|
|
|
+ <span className="text-muted-foreground">地址:</span>
|
|
|
|
|
+ <span>{deliveringOrder.address || '-'}</span>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ <Form {...deliveryForm}>
|
|
|
|
|
+ <form onSubmit={deliveryForm.handleSubmit(handleDeliverySubmit)} className="space-y-4">
|
|
|
|
|
+ <FormField
|
|
|
|
|
+ control={deliveryForm.control}
|
|
|
|
|
+ name="deliveryType"
|
|
|
|
|
+ render={({ field }) => (
|
|
|
|
|
+ <FormItem>
|
|
|
|
|
+ <FormLabel>发货方式</FormLabel>
|
|
|
|
|
+ <Select onValueChange={(value) => field.onChange(parseInt(value))} value={field.value?.toString()}>
|
|
|
|
|
+ <FormControl>
|
|
|
|
|
+ <SelectTrigger data-testid="delivery-type-select">
|
|
|
|
|
+ <SelectValue placeholder="选择发货方式" />
|
|
|
|
|
+ </SelectTrigger>
|
|
|
|
|
+ </FormControl>
|
|
|
|
|
+ <SelectContent>
|
|
|
|
|
+ <SelectItem value="1">物流快递</SelectItem>
|
|
|
|
|
+ <SelectItem value="2">同城配送</SelectItem>
|
|
|
|
|
+ <SelectItem value="3">用户自提</SelectItem>
|
|
|
|
|
+ <SelectItem value="4">虚拟发货</SelectItem>
|
|
|
|
|
+ </SelectContent>
|
|
|
|
|
+ </Select>
|
|
|
|
|
+ <FormMessage />
|
|
|
|
|
+ </FormItem>
|
|
|
|
|
+ )}
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ {deliveryForm.watch('deliveryType') === 1 && (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <FormField
|
|
|
|
|
+ control={deliveryForm.control}
|
|
|
|
|
+ name="deliveryCompany"
|
|
|
|
|
+ render={({ field }) => (
|
|
|
|
|
+ <FormItem>
|
|
|
|
|
+ <FormLabel>快递公司</FormLabel>
|
|
|
|
|
+ <FormControl>
|
|
|
|
|
+ <Input
|
|
|
|
|
+ placeholder="输入快递公司名称"
|
|
|
|
|
+ data-testid="delivery-company-input"
|
|
|
|
|
+ {...field}
|
|
|
|
|
+ />
|
|
|
|
|
+ </FormControl>
|
|
|
|
|
+ <FormMessage />
|
|
|
|
|
+ </FormItem>
|
|
|
|
|
+ )}
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <FormField
|
|
|
|
|
+ control={deliveryForm.control}
|
|
|
|
|
+ name="deliveryNo"
|
|
|
|
|
+ render={({ field }) => (
|
|
|
|
|
+ <FormItem>
|
|
|
|
|
+ <FormLabel>快递单号</FormLabel>
|
|
|
|
|
+ <FormControl>
|
|
|
|
|
+ <Input
|
|
|
|
|
+ placeholder="输入快递单号"
|
|
|
|
|
+ data-testid="delivery-no-input"
|
|
|
|
|
+ {...field}
|
|
|
|
|
+ />
|
|
|
|
|
+ </FormControl>
|
|
|
|
|
+ <FormMessage />
|
|
|
|
|
+ </FormItem>
|
|
|
|
|
+ )}
|
|
|
|
|
+ />
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ <FormField
|
|
|
|
|
+ control={deliveryForm.control}
|
|
|
|
|
+ name="deliveryRemark"
|
|
|
|
|
+ render={({ field }) => (
|
|
|
|
|
+ <FormItem>
|
|
|
|
|
+ <FormLabel>发货备注</FormLabel>
|
|
|
|
|
+ <FormControl>
|
|
|
|
|
+ <Textarea
|
|
|
|
|
+ placeholder="输入发货备注信息..."
|
|
|
|
|
+ className="resize-none"
|
|
|
|
|
+ data-testid="delivery-remark-textarea"
|
|
|
|
|
+ {...field}
|
|
|
|
|
+ />
|
|
|
|
|
+ </FormControl>
|
|
|
|
|
+ <FormMessage />
|
|
|
|
|
+ </FormItem>
|
|
|
|
|
+ )}
|
|
|
|
|
+ />
|
|
|
|
|
+
|
|
|
|
|
+ <DialogFooter>
|
|
|
|
|
+ <Button type="button" variant="outline" onClick={() => setDeliveryModalOpen(false)}>
|
|
|
|
|
+ 取消
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button type="submit" data-testid="delivery-submit-button">确认发货</Button>
|
|
|
|
|
+ </DialogFooter>
|
|
|
|
|
+ </form>
|
|
|
|
|
+ </Form>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </DialogContent>
|
|
|
|
|
+ </Dialog>
|
|
|
</div>
|
|
</div>
|
|
|
);
|
|
);
|
|
|
};
|
|
};
|