|
|
@@ -1,6 +1,7 @@
|
|
|
-import React from 'react';
|
|
|
+import React, { useState } from 'react';
|
|
|
import { useQuery } from '@tanstack/react-query';
|
|
|
import { Edit, Trash2, Package, ExternalLink } from 'lucide-react';
|
|
|
+import { toast } from 'sonner';
|
|
|
|
|
|
import { Button } from '@d8d/shared-ui-components/components/ui/button';
|
|
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@d8d/shared-ui-components/components/ui/card';
|
|
|
@@ -8,6 +9,18 @@ import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@
|
|
|
import { Badge } from '@d8d/shared-ui-components/components/ui/badge';
|
|
|
import { Skeleton } from '@d8d/shared-ui-components/components/ui/skeleton';
|
|
|
import { goodsClientManager } from '../api/goodsClient';
|
|
|
+import { ChildGoodsInlineEditForm } from './ChildGoodsInlineEditForm';
|
|
|
+
|
|
|
+interface ChildGoods {
|
|
|
+ id: number;
|
|
|
+ name: string;
|
|
|
+ price: number;
|
|
|
+ costPrice?: number;
|
|
|
+ stock: number;
|
|
|
+ sort: number;
|
|
|
+ state: number;
|
|
|
+ createdAt: string;
|
|
|
+}
|
|
|
|
|
|
interface ChildGoodsListProps {
|
|
|
// 父商品ID
|
|
|
@@ -24,6 +37,8 @@ interface ChildGoodsListProps {
|
|
|
// 其他
|
|
|
className?: string;
|
|
|
showActions?: boolean;
|
|
|
+ // 是否启用行内编辑(默认启用)
|
|
|
+ enableInlineEdit?: boolean;
|
|
|
}
|
|
|
|
|
|
export const ChildGoodsList: React.FC<ChildGoodsListProps> = ({
|
|
|
@@ -33,8 +48,13 @@ export const ChildGoodsList: React.FC<ChildGoodsListProps> = ({
|
|
|
onDeleteChild,
|
|
|
onViewChild,
|
|
|
className = '',
|
|
|
- showActions = true
|
|
|
+ showActions = true,
|
|
|
+ enableInlineEdit = true
|
|
|
}) => {
|
|
|
+ // 行内编辑状态管理
|
|
|
+ const [editingChildId, setEditingChildId] = useState<number | null>(null);
|
|
|
+ const [isSaving, setIsSaving] = useState(false);
|
|
|
+
|
|
|
// 获取子商品列表
|
|
|
const { data: childrenData, isLoading, refetch } = useQuery({
|
|
|
queryKey: ['goods', 'children', 'list', parentGoodsId, tenantId],
|
|
|
@@ -42,7 +62,6 @@ export const ChildGoodsList: React.FC<ChildGoodsListProps> = ({
|
|
|
try {
|
|
|
const client = goodsClientManager.get();
|
|
|
if (!client || !client[':id'] || !client[':id'].children) {
|
|
|
- console.error('商品客户端未正确初始化');
|
|
|
return [];
|
|
|
}
|
|
|
|
|
|
@@ -52,14 +71,12 @@ export const ChildGoodsList: React.FC<ChildGoodsListProps> = ({
|
|
|
});
|
|
|
|
|
|
if (!res || res.status !== 200) {
|
|
|
- console.error('获取子商品列表失败,响应状态:', res?.status);
|
|
|
return [];
|
|
|
}
|
|
|
|
|
|
const result = await res.json();
|
|
|
return result.data || [];
|
|
|
- } catch (error) {
|
|
|
- console.error('获取子商品列表失败:', error);
|
|
|
+ } catch {
|
|
|
return [];
|
|
|
}
|
|
|
},
|
|
|
@@ -67,11 +84,68 @@ export const ChildGoodsList: React.FC<ChildGoodsListProps> = ({
|
|
|
});
|
|
|
|
|
|
const handleEdit = (childId: number) => {
|
|
|
+ // 如果启用了行内编辑,启用行内编辑模式
|
|
|
+ if (enableInlineEdit) {
|
|
|
+ setEditingChildId(childId);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果提供了回调函数,调用它
|
|
|
if (onEditChild) {
|
|
|
onEditChild(childId);
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+ const handleCancelEdit = () => {
|
|
|
+ setEditingChildId(null);
|
|
|
+ };
|
|
|
+
|
|
|
+ const handleSaveEdit = async (childId: number, updateData: {
|
|
|
+ name: string;
|
|
|
+ price: number;
|
|
|
+ costPrice?: number;
|
|
|
+ stock: number;
|
|
|
+ sort: number;
|
|
|
+ state: number;
|
|
|
+ }) => {
|
|
|
+ setIsSaving(true);
|
|
|
+ try {
|
|
|
+ const client = goodsClientManager.get();
|
|
|
+ if (!client || !client[':id']) {
|
|
|
+ toast.error('商品客户端未正确初始化');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 调用更新API
|
|
|
+ const res = await client[':id'].$put({
|
|
|
+ param: { id: childId },
|
|
|
+ json: updateData
|
|
|
+ });
|
|
|
+
|
|
|
+ if (!res || res.status !== 200) {
|
|
|
+ const errorText = await res.text().catch(() => '未知错误');
|
|
|
+ toast.error(`更新子商品失败: ${res.status} - ${errorText}`);
|
|
|
+ throw new Error(`更新失败: ${res.status}`);
|
|
|
+ }
|
|
|
+
|
|
|
+ await res.json();
|
|
|
+
|
|
|
+ // 显示成功消息
|
|
|
+ toast.success('子商品更新成功');
|
|
|
+
|
|
|
+ // 关闭编辑模式并刷新列表
|
|
|
+ setEditingChildId(null);
|
|
|
+ refetch(); // 保存成功后刷新列表
|
|
|
+ } catch (error) {
|
|
|
+ if (error instanceof Error) {
|
|
|
+ toast.error(`保存失败: ${error.message}`);
|
|
|
+ } else {
|
|
|
+ toast.error('保存失败,请稍后重试');
|
|
|
+ }
|
|
|
+ } finally {
|
|
|
+ setIsSaving(false);
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
const handleDelete = (childId: number) => {
|
|
|
if (onDeleteChild) {
|
|
|
onDeleteChild(childId);
|
|
|
@@ -137,58 +211,75 @@ export const ChildGoodsList: React.FC<ChildGoodsListProps> = ({
|
|
|
</TableRow>
|
|
|
</TableHeader>
|
|
|
<TableBody>
|
|
|
- {children.map((child: any) => (
|
|
|
- <TableRow key={child.id}>
|
|
|
- <TableCell className="font-medium">
|
|
|
- <div className="flex items-center gap-2">
|
|
|
- <Package className="h-4 w-4 text-muted-foreground" />
|
|
|
- {child.name}
|
|
|
- </div>
|
|
|
- </TableCell>
|
|
|
- <TableCell>¥{child.price.toFixed(2)}</TableCell>
|
|
|
- <TableCell>¥{child.costPrice?.toFixed(2) || '0.00'}</TableCell>
|
|
|
- <TableCell>{child.stock}</TableCell>
|
|
|
- <TableCell>{child.sort}</TableCell>
|
|
|
- <TableCell>
|
|
|
- <Badge variant={child.state === 1 ? 'default' : 'secondary'}>
|
|
|
- {child.state === 1 ? '可用' : '不可用'}
|
|
|
- </Badge>
|
|
|
- </TableCell>
|
|
|
- <TableCell>
|
|
|
- {new Date(child.createdAt).toLocaleDateString('zh-CN')}
|
|
|
- </TableCell>
|
|
|
- {showActions && (
|
|
|
- <TableCell className="text-right">
|
|
|
- <div className="flex justify-end gap-2">
|
|
|
- <Button
|
|
|
- variant="ghost"
|
|
|
- size="icon"
|
|
|
- onClick={() => handleView(child.id)}
|
|
|
- title="查看详情"
|
|
|
- >
|
|
|
- <ExternalLink className="h-4 w-4" />
|
|
|
- </Button>
|
|
|
- <Button
|
|
|
- variant="ghost"
|
|
|
- size="icon"
|
|
|
- onClick={() => handleEdit(child.id)}
|
|
|
- title="编辑"
|
|
|
- >
|
|
|
- <Edit className="h-4 w-4" />
|
|
|
- </Button>
|
|
|
- <Button
|
|
|
- variant="ghost"
|
|
|
- size="icon"
|
|
|
- onClick={() => handleDelete(child.id)}
|
|
|
- title="删除"
|
|
|
- className="text-destructive hover:text-destructive"
|
|
|
- >
|
|
|
- <Trash2 className="h-4 w-4" />
|
|
|
- </Button>
|
|
|
- </div>
|
|
|
- </TableCell>
|
|
|
+ {children.map((child: ChildGoods) => (
|
|
|
+ <React.Fragment key={child.id}>
|
|
|
+ {/* 编辑模式 */}
|
|
|
+ {editingChildId === child.id ? (
|
|
|
+ <TableRow className="bg-muted/50">
|
|
|
+ <TableCell colSpan={showActions ? 8 : 7}>
|
|
|
+ <ChildGoodsInlineEditForm
|
|
|
+ child={child}
|
|
|
+ onSave={handleSaveEdit}
|
|
|
+ onCancel={handleCancelEdit}
|
|
|
+ isLoading={isSaving}
|
|
|
+ />
|
|
|
+ </TableCell>
|
|
|
+ </TableRow>
|
|
|
+ ) : (
|
|
|
+ /* 正常显示模式 */
|
|
|
+ <TableRow>
|
|
|
+ <TableCell className="font-medium">
|
|
|
+ <div className="flex items-center gap-2">
|
|
|
+ <Package className="h-4 w-4 text-muted-foreground" />
|
|
|
+ {child.name}
|
|
|
+ </div>
|
|
|
+ </TableCell>
|
|
|
+ <TableCell>¥{child.price.toFixed(2)}</TableCell>
|
|
|
+ <TableCell>¥{child.costPrice?.toFixed(2) || '0.00'}</TableCell>
|
|
|
+ <TableCell>{child.stock}</TableCell>
|
|
|
+ <TableCell>{child.sort}</TableCell>
|
|
|
+ <TableCell>
|
|
|
+ <Badge variant={child.state === 1 ? 'default' : 'secondary'}>
|
|
|
+ {child.state === 1 ? '可用' : '不可用'}
|
|
|
+ </Badge>
|
|
|
+ </TableCell>
|
|
|
+ <TableCell>
|
|
|
+ {new Date(child.createdAt).toLocaleDateString('zh-CN')}
|
|
|
+ </TableCell>
|
|
|
+ {showActions && (
|
|
|
+ <TableCell className="text-right">
|
|
|
+ <div className="flex justify-end gap-2">
|
|
|
+ <Button
|
|
|
+ variant="ghost"
|
|
|
+ size="icon"
|
|
|
+ onClick={() => handleView(child.id)}
|
|
|
+ title="查看详情"
|
|
|
+ >
|
|
|
+ <ExternalLink className="h-4 w-4" />
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ variant="ghost"
|
|
|
+ size="icon"
|
|
|
+ onClick={() => handleEdit(child.id)}
|
|
|
+ title="编辑"
|
|
|
+ >
|
|
|
+ <Edit className="h-4 w-4" />
|
|
|
+ </Button>
|
|
|
+ <Button
|
|
|
+ variant="ghost"
|
|
|
+ size="icon"
|
|
|
+ onClick={() => handleDelete(child.id)}
|
|
|
+ title="删除"
|
|
|
+ className="text-destructive hover:text-destructive"
|
|
|
+ >
|
|
|
+ <Trash2 className="h-4 w-4" />
|
|
|
+ </Button>
|
|
|
+ </div>
|
|
|
+ </TableCell>
|
|
|
+ )}
|
|
|
+ </TableRow>
|
|
|
)}
|
|
|
- </TableRow>
|
|
|
+ </React.Fragment>
|
|
|
))}
|
|
|
</TableBody>
|
|
|
</Table>
|
|
|
@@ -202,26 +293,26 @@ export const ChildGoodsList: React.FC<ChildGoodsListProps> = ({
|
|
|
<div className="space-y-1">
|
|
|
<div className="text-muted-foreground">总库存</div>
|
|
|
<div className="font-medium">
|
|
|
- {children.reduce((sum: number, child: any) => sum + child.stock, 0)}
|
|
|
+ {children.reduce((sum: number, child: ChildGoods) => sum + child.stock, 0)}
|
|
|
</div>
|
|
|
</div>
|
|
|
<div className="space-y-1">
|
|
|
<div className="text-muted-foreground">平均价格</div>
|
|
|
<div className="font-medium">
|
|
|
- ¥{(children.reduce((sum: number, child: any) => sum + child.price, 0) / children.length).toFixed(2)}
|
|
|
+ ¥{(children.reduce((sum: number, child: ChildGoods) => sum + child.price, 0) / children.length).toFixed(2)}
|
|
|
</div>
|
|
|
</div>
|
|
|
<div className="space-y-1">
|
|
|
<div className="text-muted-foreground">可用商品</div>
|
|
|
<div className="font-medium">
|
|
|
- {children.filter((child: any) => child.state === 1).length} / {children.length}
|
|
|
+ {children.filter((child: ChildGoods) => child.state === 1).length} / {children.length}
|
|
|
</div>
|
|
|
</div>
|
|
|
<div className="space-y-1">
|
|
|
<div className="text-muted-foreground">最后更新</div>
|
|
|
<div className="font-medium">
|
|
|
{children.length > 0
|
|
|
- ? new Date(Math.max(...children.map((child: any) => new Date(child.createdAt).getTime()))).toLocaleDateString('zh-CN')
|
|
|
+ ? new Date(Math.max(...children.map((child: ChildGoods) => new Date(child.createdAt).getTime()))).toLocaleDateString('zh-CN')
|
|
|
: '-'}
|
|
|
</div>
|
|
|
</div>
|