|
@@ -0,0 +1,490 @@
|
|
|
|
|
+import React, { useState } from 'react';
|
|
|
|
|
+import { Plus, Trash2, Copy, Save, X, Package } 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';
|
|
|
|
|
+import { Input } from '@d8d/shared-ui-components/components/ui/input';
|
|
|
|
|
+import { Label } from '@d8d/shared-ui-components/components/ui/label';
|
|
|
|
|
+import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@d8d/shared-ui-components/components/ui/table';
|
|
|
|
|
+import { Badge } from '@d8d/shared-ui-components/components/ui/badge';
|
|
|
|
|
+
|
|
|
|
|
+interface BatchSpecCreatorInlineProps {
|
|
|
|
|
+ // 初始规格模板
|
|
|
|
|
+ initialSpecs?: Array<{
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ price: number;
|
|
|
|
|
+ costPrice: number;
|
|
|
|
|
+ stock: number;
|
|
|
|
|
+ sort: number;
|
|
|
|
|
+ }>;
|
|
|
|
|
+
|
|
|
|
|
+ // 回调函数
|
|
|
|
|
+ onSpecsChange?: (specs: Array<{ name: string; price: number; costPrice: number; stock: number; sort: number }>) => void;
|
|
|
|
|
+ onSaveTemplate?: (templateName: string, specs: Array<{ name: string; price: number; costPrice: number; stock: number; sort: number }>) => void;
|
|
|
|
|
+
|
|
|
|
|
+ // 其他
|
|
|
|
|
+ className?: string;
|
|
|
|
|
+ disabled?: boolean;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+export const BatchSpecCreatorInline: React.FC<BatchSpecCreatorInlineProps> = ({
|
|
|
|
|
+ initialSpecs = [],
|
|
|
|
|
+ onSpecsChange,
|
|
|
|
|
+ onSaveTemplate,
|
|
|
|
|
+ className = '',
|
|
|
|
|
+ disabled = false
|
|
|
|
|
+}) => {
|
|
|
|
|
+ const [specs, setSpecs] = useState<Array<{
|
|
|
|
|
+ id: number;
|
|
|
|
|
+ name: string;
|
|
|
|
|
+ price: number;
|
|
|
|
|
+ costPrice: number;
|
|
|
|
|
+ stock: number;
|
|
|
|
|
+ sort: number;
|
|
|
|
|
+ }>>(
|
|
|
|
|
+ initialSpecs.map((spec, index) => ({
|
|
|
|
|
+ id: Date.now() + index,
|
|
|
|
|
+ ...spec
|
|
|
|
|
+ }))
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const [newSpec, setNewSpec] = useState({
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ price: 0,
|
|
|
|
|
+ costPrice: 0,
|
|
|
|
|
+ stock: 0,
|
|
|
|
|
+ sort: 0
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const [templateName, setTemplateName] = useState('');
|
|
|
|
|
+ const [showSaveTemplate, setShowSaveTemplate] = useState(false);
|
|
|
|
|
+
|
|
|
|
|
+ const handleAddSpec = () => {
|
|
|
|
|
+ if (!newSpec.name.trim()) {
|
|
|
|
|
+ toast.error('请输入规格名称');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (newSpec.price < 0) {
|
|
|
|
|
+ toast.error('价格不能为负数');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (newSpec.costPrice < 0) {
|
|
|
|
|
+ toast.error('成本价不能为负数');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (newSpec.stock < 0) {
|
|
|
|
|
+ toast.error('库存不能为负数');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ const newSpecWithId = {
|
|
|
|
|
+ id: Date.now(),
|
|
|
|
|
+ ...newSpec
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const updatedSpecs = [...specs, newSpecWithId];
|
|
|
|
|
+ setSpecs(updatedSpecs);
|
|
|
|
|
+
|
|
|
|
|
+ // 通知父组件
|
|
|
|
|
+ if (onSpecsChange) {
|
|
|
|
|
+ onSpecsChange(updatedSpecs.map(({ id, ...rest }) => rest));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 重置表单
|
|
|
|
|
+ setNewSpec({
|
|
|
|
|
+ name: '',
|
|
|
|
|
+ price: 0,
|
|
|
|
|
+ costPrice: 0,
|
|
|
|
|
+ stock: 0,
|
|
|
|
|
+ sort: specs.length
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ toast.success('规格已添加');
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleRemoveSpec = (id: number) => {
|
|
|
|
|
+ const updatedSpecs = specs.filter(spec => spec.id !== id);
|
|
|
|
|
+ setSpecs(updatedSpecs);
|
|
|
|
|
+
|
|
|
|
|
+ // 通知父组件
|
|
|
|
|
+ if (onSpecsChange) {
|
|
|
|
|
+ onSpecsChange(updatedSpecs.map(({ id, ...rest }) => rest));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ toast.success('规格已删除');
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleDuplicateSpec = (index: number) => {
|
|
|
|
|
+ const specToDuplicate = specs[index];
|
|
|
|
|
+ const duplicatedSpec = {
|
|
|
|
|
+ ...specToDuplicate,
|
|
|
|
|
+ id: Date.now(),
|
|
|
|
|
+ name: `${specToDuplicate.name} (副本)`,
|
|
|
|
|
+ sort: specs.length
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const updatedSpecs = [...specs, duplicatedSpec];
|
|
|
|
|
+ setSpecs(updatedSpecs);
|
|
|
|
|
+
|
|
|
|
|
+ // 通知父组件
|
|
|
|
|
+ if (onSpecsChange) {
|
|
|
|
|
+ onSpecsChange(updatedSpecs.map(({ id, ...rest }) => rest));
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ toast.success('规格已复制');
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleUpdateSpec = (id: number, field: string, value: any) => {
|
|
|
|
|
+ const updatedSpecs = specs.map(spec => {
|
|
|
|
|
+ if (spec.id === id) {
|
|
|
|
|
+ return { ...spec, [field]: value };
|
|
|
|
|
+ }
|
|
|
|
|
+ return spec;
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ setSpecs(updatedSpecs);
|
|
|
|
|
+
|
|
|
|
|
+ // 通知父组件
|
|
|
|
|
+ if (onSpecsChange) {
|
|
|
|
|
+ onSpecsChange(updatedSpecs.map(({ id, ...rest }) => rest));
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleSaveTemplate = () => {
|
|
|
|
|
+ if (!templateName.trim()) {
|
|
|
|
|
+ toast.error('请输入模板名称');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (specs.length === 0) {
|
|
|
|
|
+ toast.error('请先添加规格');
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (onSaveTemplate) {
|
|
|
|
|
+ onSaveTemplate(templateName, specs.map(({ id, ...rest }) => rest));
|
|
|
|
|
+ setTemplateName('');
|
|
|
|
|
+ setShowSaveTemplate(false);
|
|
|
|
|
+ toast.success('模板保存成功');
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ const handleLoadTemplate = (templateSpecs: Array<{ name: string; price: number; costPrice: number; stock: number; sort: number }>) => {
|
|
|
|
|
+ const newSpecs = templateSpecs.map((spec, index) => ({
|
|
|
|
|
+ id: Date.now() + index,
|
|
|
|
|
+ ...spec
|
|
|
|
|
+ }));
|
|
|
|
|
+
|
|
|
|
|
+ setSpecs(newSpecs);
|
|
|
|
|
+
|
|
|
|
|
+ // 通知父组件
|
|
|
|
|
+ if (onSpecsChange) {
|
|
|
|
|
+ onSpecsChange(templateSpecs);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ toast.success('模板已加载');
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ // 预定义模板
|
|
|
|
|
+ const predefinedTemplates = [
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '颜色规格模板',
|
|
|
|
|
+ specs: [
|
|
|
|
|
+ { name: '红色', price: 100, costPrice: 80, stock: 100, sort: 1 },
|
|
|
|
|
+ { name: '蓝色', price: 100, costPrice: 80, stock: 100, sort: 2 },
|
|
|
|
|
+ { name: '绿色', price: 100, costPrice: 80, stock: 100, sort: 3 },
|
|
|
|
|
+ { name: '黑色', price: 100, costPrice: 80, stock: 100, sort: 4 },
|
|
|
|
|
+ { name: '白色', price: 100, costPrice: 80, stock: 100, sort: 5 }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '尺寸规格模板',
|
|
|
|
|
+ specs: [
|
|
|
|
|
+ { name: 'S码', price: 100, costPrice: 80, stock: 100, sort: 1 },
|
|
|
|
|
+ { name: 'M码', price: 110, costPrice: 85, stock: 100, sort: 2 },
|
|
|
|
|
+ { name: 'L码', price: 120, costPrice: 90, stock: 100, sort: 3 },
|
|
|
|
|
+ { name: 'XL码', price: 130, costPrice: 95, stock: 100, sort: 4 }
|
|
|
|
|
+ ]
|
|
|
|
|
+ },
|
|
|
|
|
+ {
|
|
|
|
|
+ name: '容量规格模板',
|
|
|
|
|
+ specs: [
|
|
|
|
|
+ { name: '64GB', price: 2999, costPrice: 2500, stock: 50, sort: 1 },
|
|
|
|
|
+ { name: '128GB', price: 3499, costPrice: 2800, stock: 50, sort: 2 },
|
|
|
|
|
+ { name: '256GB', price: 3999, costPrice: 3200, stock: 50, sort: 3 },
|
|
|
|
|
+ { name: '512GB', price: 4999, costPrice: 4000, stock: 30, sort: 4 }
|
|
|
|
|
+ ]
|
|
|
|
|
+ }
|
|
|
|
|
+ ];
|
|
|
|
|
+
|
|
|
|
|
+ const totalStock = specs.reduce((sum, spec) => sum + spec.stock, 0);
|
|
|
|
|
+ const totalValue = specs.reduce((sum, spec) => sum + (spec.price * spec.stock), 0);
|
|
|
|
|
+ const avgPrice = specs.length > 0 ? specs.reduce((sum, spec) => sum + spec.price, 0) / specs.length : 0;
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <Card className={className}>
|
|
|
|
|
+ <CardHeader>
|
|
|
|
|
+ <CardTitle>批量创建规格</CardTitle>
|
|
|
|
|
+ <CardDescription>
|
|
|
|
|
+ 添加多个商品规格,创建后将作为子商品批量生成
|
|
|
|
|
+ </CardDescription>
|
|
|
|
|
+ </CardHeader>
|
|
|
|
|
+ <CardContent className="space-y-4">
|
|
|
|
|
+ {/* 添加新规格表单 */}
|
|
|
|
|
+ <div className="grid grid-cols-1 md:grid-cols-6 gap-4 p-4 border rounded-lg">
|
|
|
|
|
+ <div className="md:col-span-2">
|
|
|
|
|
+ <Label htmlFor="spec-name">规格名称 *</Label>
|
|
|
|
|
+ <Input
|
|
|
|
|
+ id="spec-name"
|
|
|
|
|
+ placeholder="例如:红色、64GB、S码"
|
|
|
|
|
+ value={newSpec.name}
|
|
|
|
|
+ onChange={(e) => setNewSpec({ ...newSpec, name: e.target.value })}
|
|
|
|
|
+ disabled={disabled}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <Label htmlFor="spec-price">价格</Label>
|
|
|
|
|
+ <Input
|
|
|
|
|
+ id="spec-price"
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ min="0"
|
|
|
|
|
+ step="0.01"
|
|
|
|
|
+ placeholder="0.00"
|
|
|
|
|
+ value={newSpec.price}
|
|
|
|
|
+ onChange={(e) => setNewSpec({ ...newSpec, price: parseFloat(e.target.value) || 0 })}
|
|
|
|
|
+ disabled={disabled}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <Label htmlFor="spec-cost-price">成本价</Label>
|
|
|
|
|
+ <Input
|
|
|
|
|
+ id="spec-cost-price"
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ min="0"
|
|
|
|
|
+ step="0.01"
|
|
|
|
|
+ placeholder="0.00"
|
|
|
|
|
+ value={newSpec.costPrice}
|
|
|
|
|
+ onChange={(e) => setNewSpec({ ...newSpec, costPrice: parseFloat(e.target.value) || 0 })}
|
|
|
|
|
+ disabled={disabled}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <Label htmlFor="spec-stock">库存</Label>
|
|
|
|
|
+ <Input
|
|
|
|
|
+ id="spec-stock"
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ min="0"
|
|
|
|
|
+ step="1"
|
|
|
|
|
+ placeholder="0"
|
|
|
|
|
+ value={newSpec.stock}
|
|
|
|
|
+ onChange={(e) => setNewSpec({ ...newSpec, stock: parseInt(e.target.value) || 0 })}
|
|
|
|
|
+ disabled={disabled}
|
|
|
|
|
+ />
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex items-end">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ onClick={handleAddSpec}
|
|
|
|
|
+ disabled={disabled || !newSpec.name.trim()}
|
|
|
|
|
+ className="w-full"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Plus className="mr-2 h-4 w-4" />
|
|
|
|
|
+ 添加
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 预定义模板 */}
|
|
|
|
|
+ <div>
|
|
|
|
|
+ <Label className="mb-2 block">快速模板</Label>
|
|
|
|
|
+ <div className="flex flex-wrap gap-2">
|
|
|
|
|
+ {predefinedTemplates.map((template, index) => (
|
|
|
|
|
+ <Badge
|
|
|
|
|
+ key={index}
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ className="cursor-pointer hover:bg-accent"
|
|
|
|
|
+ onClick={() => !disabled && handleLoadTemplate(template.specs)}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Copy className="mr-1 h-3 w-3" />
|
|
|
|
|
+ {template.name}
|
|
|
|
|
+ </Badge>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 规格列表 */}
|
|
|
|
|
+ {specs.length > 0 ? (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <div className="overflow-x-auto">
|
|
|
|
|
+ <Table>
|
|
|
|
|
+ <TableHeader>
|
|
|
|
|
+ <TableRow>
|
|
|
|
|
+ <TableHead>规格名称</TableHead>
|
|
|
|
|
+ <TableHead>价格</TableHead>
|
|
|
|
|
+ <TableHead>成本价</TableHead>
|
|
|
|
|
+ <TableHead>库存</TableHead>
|
|
|
|
|
+ <TableHead>排序</TableHead>
|
|
|
|
|
+ <TableHead className="text-right">操作</TableHead>
|
|
|
|
|
+ </TableRow>
|
|
|
|
|
+ </TableHeader>
|
|
|
|
|
+ <TableBody>
|
|
|
|
|
+ {specs.map((spec, index) => (
|
|
|
|
|
+ <TableRow key={spec.id}>
|
|
|
|
|
+ <TableCell className="font-medium">
|
|
|
|
|
+ <Input
|
|
|
|
|
+ value={spec.name}
|
|
|
|
|
+ onChange={(e) => handleUpdateSpec(spec.id, 'name', e.target.value)}
|
|
|
|
|
+ disabled={disabled}
|
|
|
|
|
+ />
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ <TableCell>
|
|
|
|
|
+ <Input
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ min="0"
|
|
|
|
|
+ step="0.01"
|
|
|
|
|
+ value={spec.price}
|
|
|
|
|
+ onChange={(e) => handleUpdateSpec(spec.id, 'price', parseFloat(e.target.value) || 0)}
|
|
|
|
|
+ disabled={disabled}
|
|
|
|
|
+ />
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ <TableCell>
|
|
|
|
|
+ <Input
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ min="0"
|
|
|
|
|
+ step="0.01"
|
|
|
|
|
+ value={spec.costPrice}
|
|
|
|
|
+ onChange={(e) => handleUpdateSpec(spec.id, 'costPrice', parseFloat(e.target.value) || 0)}
|
|
|
|
|
+ disabled={disabled}
|
|
|
|
|
+ />
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ <TableCell>
|
|
|
|
|
+ <Input
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ min="0"
|
|
|
|
|
+ step="1"
|
|
|
|
|
+ value={spec.stock}
|
|
|
|
|
+ onChange={(e) => handleUpdateSpec(spec.id, 'stock', parseInt(e.target.value) || 0)}
|
|
|
|
|
+ disabled={disabled}
|
|
|
|
|
+ />
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ <TableCell>
|
|
|
|
|
+ <Input
|
|
|
|
|
+ type="number"
|
|
|
|
|
+ min="0"
|
|
|
|
|
+ step="1"
|
|
|
|
|
+ value={spec.sort}
|
|
|
|
|
+ onChange={(e) => handleUpdateSpec(spec.id, 'sort', parseInt(e.target.value) || 0)}
|
|
|
|
|
+ disabled={disabled}
|
|
|
|
|
+ />
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ <TableCell className="text-right">
|
|
|
|
|
+ <div className="flex justify-end gap-2">
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="icon"
|
|
|
|
|
+ onClick={() => handleDuplicateSpec(index)}
|
|
|
|
|
+ disabled={disabled}
|
|
|
|
|
+ title="复制"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Copy className="h-4 w-4" />
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="icon"
|
|
|
|
|
+ onClick={() => handleRemoveSpec(spec.id)}
|
|
|
|
|
+ disabled={disabled}
|
|
|
|
|
+ title="删除"
|
|
|
|
|
+ className="text-destructive hover:text-destructive"
|
|
|
|
|
+ >
|
|
|
|
|
+ <Trash2 className="h-4 w-4" />
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </TableCell>
|
|
|
|
|
+ </TableRow>
|
|
|
|
|
+ ))}
|
|
|
|
|
+ </TableBody>
|
|
|
|
|
+ </Table>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 统计信息 */}
|
|
|
|
|
+ <div className="grid grid-cols-2 md:grid-cols-4 gap-4 p-4 border rounded-lg">
|
|
|
|
|
+ <div className="space-y-1">
|
|
|
|
|
+ <div className="text-sm text-muted-foreground">规格数量</div>
|
|
|
|
|
+ <div className="text-lg font-semibold">{specs.length}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="space-y-1">
|
|
|
|
|
+ <div className="text-sm text-muted-foreground">总库存</div>
|
|
|
|
|
+ <div className="text-lg font-semibold">{totalStock}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="space-y-1">
|
|
|
|
|
+ <div className="text-sm text-muted-foreground">平均价格</div>
|
|
|
|
|
+ <div className="text-lg font-semibold">¥{avgPrice.toFixed(2)}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="space-y-1">
|
|
|
|
|
+ <div className="text-sm text-muted-foreground">总货值</div>
|
|
|
|
|
+ <div className="text-lg font-semibold">¥{totalValue.toFixed(2)}</div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+
|
|
|
|
|
+ {/* 保存模板 */}
|
|
|
|
|
+ <div className="flex justify-between items-center">
|
|
|
|
|
+ <div className="text-sm text-muted-foreground">
|
|
|
|
|
+ 共 {specs.length} 个规格,将在创建商品后批量生成子商品
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div className="flex gap-2">
|
|
|
|
|
+ {!showSaveTemplate ? (
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ onClick={() => setShowSaveTemplate(true)}
|
|
|
|
|
+ disabled={disabled}
|
|
|
|
|
+ >
|
|
|
|
|
+ <Save className="mr-2 h-4 w-4" />
|
|
|
|
|
+ 保存为模板
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <div className="flex gap-2">
|
|
|
|
|
+ <Input
|
|
|
|
|
+ placeholder="输入模板名称"
|
|
|
|
|
+ value={templateName}
|
|
|
|
|
+ onChange={(e) => setTemplateName(e.target.value)}
|
|
|
|
|
+ className="w-40"
|
|
|
|
|
+ disabled={disabled}
|
|
|
|
|
+ />
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="outline"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ onClick={handleSaveTemplate}
|
|
|
|
|
+ disabled={disabled || !templateName.trim()}
|
|
|
|
|
+ >
|
|
|
|
|
+ 保存
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ <Button
|
|
|
|
|
+ variant="ghost"
|
|
|
|
|
+ size="sm"
|
|
|
|
|
+ onClick={() => setShowSaveTemplate(false)}
|
|
|
|
|
+ disabled={disabled}
|
|
|
|
|
+ >
|
|
|
|
|
+ <X className="h-4 w-4" />
|
|
|
|
|
+ </Button>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ </>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <div className="text-center py-8 border rounded-lg">
|
|
|
|
|
+ <Package className="h-12 w-12 mx-auto mb-2 text-muted-foreground" />
|
|
|
|
|
+ <p className="text-muted-foreground">暂无规格</p>
|
|
|
|
|
+ <p className="text-sm text-muted-foreground mt-1">
|
|
|
|
|
+ 添加规格后,将在创建商品时批量生成子商品
|
|
|
|
|
+ </p>
|
|
|
|
|
+ </div>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </CardContent>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ );
|
|
|
|
|
+};
|