这块改完了

This commit is contained in:
云电脑的永平
2025-07-16 14:19:47 +08:00
parent 1bb34b4932
commit d9ef245018
8 changed files with 1742 additions and 473 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,243 @@
import React, { useState } from 'react';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Switch } from '@/components/ui/switch';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Minus, Plus } from 'lucide-react';
interface BasicSettingsProps {
defaultValues?: {
name: string;
pushTimeStart: string;
pushTimeEnd: string;
dailyPushCount: number;
pushOrder: 'earliest' | 'latest';
isLoopPush: boolean;
isImmediatePush: boolean;
isEnabled: boolean;
};
onNext: (values: any) => void;
onSave: (values: any) => void;
onCancel: () => void;
loading?: boolean;
}
export default function BasicSettings({
defaultValues = {
name: '',
pushTimeStart: '06:00',
pushTimeEnd: '23:59',
dailyPushCount: 20,
pushOrder: 'latest',
isLoopPush: false,
isImmediatePush: false,
isEnabled: false,
},
onNext,
onSave,
onCancel,
loading = false,
}: BasicSettingsProps) {
const [values, setValues] = useState(defaultValues);
const handleChange = (field: string, value: any) => {
setValues((prev) => ({ ...prev, [field]: value }));
};
const handleCountChange = (increment: boolean) => {
setValues((prev) => ({
...prev,
dailyPushCount: increment ? prev.dailyPushCount + 1 : Math.max(1, prev.dailyPushCount - 1),
}));
};
return (
<div className="space-y-6">
<Card>
<CardContent className="p-4 sm:p-6">
<div className="space-y-4">
{/* 任务名称 */}
<div className="space-y-2">
<Label htmlFor="taskName" className="flex items-center text-sm font-medium">
<span className="text-red-500 mr-1">*</span>:
</Label>
<Input
id="taskName"
value={values.name}
onChange={(e) => handleChange('name', e.target.value)}
placeholder="请输入任务名称"
/>
</div>
{/* 允许推送的时间段 */}
<div className="space-y-2">
<Label className="text-sm font-medium">:</Label>
<div className="flex items-center space-x-2">
<Input
type="time"
value={values.pushTimeStart}
onChange={(e) => handleChange('pushTimeStart', e.target.value)}
className="w-full"
/>
<span className="text-gray-500"></span>
<Input
type="time"
value={values.pushTimeEnd}
onChange={(e) => handleChange('pushTimeEnd', e.target.value)}
className="w-full"
/>
</div>
</div>
{/* 每日推送 */}
<div className="space-y-2">
<Label className="text-sm font-medium">:</Label>
<div className="flex items-center space-x-2">
<Button
variant="outline"
size="icon"
type="button"
onClick={() => handleCountChange(false)}
className="h-9 w-9"
disabled={loading}
>
<Minus className="h-4 w-4" />
</Button>
<Input
type="number"
value={values.dailyPushCount.toString()}
onChange={(e) => handleChange('dailyPushCount', Number.parseInt(e.target.value) || 1)}
className="w-20 text-center"
min={1}
disabled={loading}
/>
<Button
variant="outline"
size="icon"
type="button"
onClick={() => handleCountChange(true)}
className="h-9 w-9"
disabled={loading}
>
<Plus className="h-4 w-4" />
</Button>
<span className="text-gray-500"></span>
</div>
</div>
{/* 推送顺序 */}
<div className="space-y-2">
<Label className="text-sm font-medium">:</Label>
<div className="flex">
<Button
type="button"
variant={values.pushOrder === 'earliest' ? 'default' : 'outline'}
className={`rounded-r-none flex-1 ${values.pushOrder === 'earliest' ? '' : 'text-gray-500'}`}
onClick={() => handleChange('pushOrder', 'earliest')}
disabled={loading}
>
</Button>
<Button
type="button"
variant={values.pushOrder === 'latest' ? 'default' : 'outline'}
className={`rounded-l-none flex-1 ${values.pushOrder === 'latest' ? '' : 'text-gray-500'}`}
onClick={() => handleChange('pushOrder', 'latest')}
disabled={loading}
>
</Button>
</div>
</div>
{/* 是否循环推送 */}
<div className="flex items-center justify-between">
<Label htmlFor="isLoopPush" className="flex items-center text-sm font-medium">
<span className="text-red-500 mr-1">*</span>:
</Label>
<div className="flex items-center space-x-2">
<span className={values.isLoopPush ? 'text-gray-400' : 'text-gray-900'}></span>
<Switch
id="isLoopPush"
checked={values.isLoopPush}
onCheckedChange={(checked) => handleChange('isLoopPush', checked)}
disabled={loading}
/>
<span className={values.isLoopPush ? 'text-gray-900' : 'text-gray-400'}></span>
</div>
</div>
{/* 是否立即推送 */}
<div className="flex items-center justify-between">
<Label htmlFor="isImmediatePush" className="flex items-center text-sm font-medium">
<span className="text-red-500 mr-1">*</span>:
</Label>
<div className="flex items-center space-x-2">
<span className={values.isImmediatePush ? 'text-gray-400' : 'text-gray-900'}></span>
<Switch
id="isImmediatePush"
checked={values.isImmediatePush}
onCheckedChange={(checked) => handleChange('isImmediatePush', checked)}
disabled={loading}
/>
<span className={values.isImmediatePush ? 'text-gray-900' : 'text-gray-400'}></span>
</div>
</div>
{values.isImmediatePush && (
<div className="bg-yellow-50 border border-yellow-200 rounded-md p-3 text-sm text-yellow-700">
</div>
)}
{/* 是否启用 */}
<div className="flex items-center justify-between">
<Label htmlFor="isEnabled" className="flex items-center text-sm font-medium">
<span className="text-red-500 mr-1">*</span>:
</Label>
<div className="flex items-center space-x-2">
<span className={values.isEnabled ? 'text-gray-400' : 'text-gray-900'}></span>
<Switch
id="isEnabled"
checked={values.isEnabled}
onCheckedChange={(checked) => handleChange('isEnabled', checked)}
disabled={loading}
/>
<span className={values.isEnabled ? 'text-gray-900' : 'text-gray-400'}></span>
</div>
</div>
</div>
</CardContent>
</Card>
<div className="flex space-x-2 justify-center sm:justify-end">
<Button
type="button"
onClick={() => onNext(values)}
className="flex-1 sm:flex-none"
disabled={loading}
>
</Button>
<Button
type="button"
variant="outline"
onClick={() => onSave(values)}
className="flex-1 sm:flex-none"
disabled={loading}
>
{loading ? '保存中...' : '保存'}
</Button>
<Button
type="button"
variant="outline"
onClick={onCancel}
className="flex-1 sm:flex-none"
disabled={loading}
>
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,237 @@
import React, { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import { Search, FileText } from 'lucide-react';
interface ContentLibrary {
id: string;
name: string;
targets: Array<{
id: string;
avatar: string;
}>;
}
interface ContentSelectorProps {
selectedLibraries: ContentLibrary[];
onLibrariesChange: (libraries: ContentLibrary[]) => void;
onPrevious: () => void;
onNext: () => void;
onSave: () => void;
onCancel: () => void;
loading?: boolean;
}
// 模拟内容库数据
const mockLibraries: ContentLibrary[] = [
{
id: '1',
name: '产品推广内容库',
targets: [
{ id: '1', avatar: 'https://via.placeholder.com/32' },
{ id: '2', avatar: 'https://via.placeholder.com/32' },
{ id: '3', avatar: 'https://via.placeholder.com/32' },
],
},
{
id: '2',
name: '活动宣传内容库',
targets: [
{ id: '4', avatar: 'https://via.placeholder.com/32' },
{ id: '5', avatar: 'https://via.placeholder.com/32' },
],
},
{
id: '3',
name: '客户服务内容库',
targets: [
{ id: '6', avatar: 'https://via.placeholder.com/32' },
{ id: '7', avatar: 'https://via.placeholder.com/32' },
{ id: '8', avatar: 'https://via.placeholder.com/32' },
{ id: '9', avatar: 'https://via.placeholder.com/32' },
],
},
{
id: '4',
name: '节日问候内容库',
targets: [
{ id: '10', avatar: 'https://via.placeholder.com/32' },
{ id: '11', avatar: 'https://via.placeholder.com/32' },
],
},
{
id: '5',
name: '新品发布内容库',
targets: [
{ id: '12', avatar: 'https://via.placeholder.com/32' },
{ id: '13', avatar: 'https://via.placeholder.com/32' },
{ id: '14', avatar: 'https://via.placeholder.com/32' },
],
},
];
export default function ContentSelector({
selectedLibraries,
onLibrariesChange,
onPrevious,
onNext,
onSave,
onCancel,
loading = false,
}: ContentSelectorProps) {
const [searchTerm, setSearchTerm] = useState('');
const [libraries, setLibraries] = useState<ContentLibrary[]>(mockLibraries);
const filteredLibraries = libraries.filter((library) =>
library.name.toLowerCase().includes(searchTerm.toLowerCase())
);
const handleLibraryToggle = (library: ContentLibrary, checked: boolean) => {
if (checked) {
onLibrariesChange([...selectedLibraries, library]);
} else {
onLibrariesChange(selectedLibraries.filter((l) => l.id !== library.id));
}
};
const handleSelectAll = () => {
if (selectedLibraries.length === filteredLibraries.length) {
onLibrariesChange([]);
} else {
onLibrariesChange(filteredLibraries);
}
};
const isLibrarySelected = (libraryId: string) => {
return selectedLibraries.some((library) => library.id === libraryId);
};
return (
<div className="space-y-6">
<Card>
<CardContent className="p-4 sm:p-6">
<div className="space-y-4">
{/* 搜索框 */}
<div className="space-y-2">
<Label className="text-sm font-medium">:</Label>
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Input
placeholder="搜索内容库名称"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-9"
disabled={loading}
/>
</div>
</div>
{/* 全选按钮 */}
<div className="flex items-center space-x-2">
<Checkbox
id="selectAll"
checked={selectedLibraries.length === filteredLibraries.length && filteredLibraries.length > 0}
onCheckedChange={handleSelectAll}
disabled={loading}
/>
<Label htmlFor="selectAll" className="text-sm font-medium">
({selectedLibraries.length}/{filteredLibraries.length})
</Label>
</div>
{/* 内容库列表 */}
<div className="space-y-2 max-h-96 overflow-y-auto">
{filteredLibraries.map((library) => (
<div
key={library.id}
className="flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50"
>
<Checkbox
id={library.id}
checked={isLibrarySelected(library.id)}
onCheckedChange={(checked) => handleLibraryToggle(library, checked as boolean)}
disabled={loading}
/>
<div className="flex items-center space-x-3 flex-1">
<div className="w-10 h-10 bg-blue-100 rounded-lg flex items-center justify-center">
<FileText className="h-5 w-5 text-blue-600" />
</div>
<div className="flex-1">
<div className="font-medium text-sm">{library.name}</div>
<div className="text-xs text-gray-500">
{library.targets.length}
</div>
</div>
<div className="flex -space-x-1">
{library.targets.slice(0, 3).map((target) => (
<img
key={target.id}
src={target.avatar}
alt=""
className="w-6 h-6 rounded-full border border-white"
/>
))}
{library.targets.length > 3 && (
<div className="w-6 h-6 rounded-full bg-gray-200 border border-white flex items-center justify-center">
<span className="text-xs text-gray-600">+{library.targets.length - 3}</span>
</div>
)}
</div>
</div>
</div>
))}
</div>
{filteredLibraries.length === 0 && (
<div className="text-center py-8 text-gray-500">
<FileText className="h-12 w-12 mx-auto mb-2 text-gray-300" />
<p></p>
</div>
)}
</div>
</CardContent>
</Card>
<div className="flex space-x-2 justify-center sm:justify-end">
<Button
type="button"
variant="outline"
onClick={onPrevious}
className="flex-1 sm:flex-none"
disabled={loading}
>
</Button>
<Button
type="button"
onClick={onNext}
className="flex-1 sm:flex-none"
disabled={loading || selectedLibraries.length === 0}
>
</Button>
<Button
type="button"
variant="outline"
onClick={onSave}
className="flex-1 sm:flex-none"
disabled={loading}
>
{loading ? '保存中...' : '保存'}
</Button>
<Button
type="button"
variant="outline"
onClick={onCancel}
className="flex-1 sm:flex-none"
disabled={loading}
>
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,248 @@
import React, { useState, useEffect } from 'react';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import { Search, Users } from 'lucide-react';
interface WechatGroup {
id: string;
name: string;
avatar: string;
serviceAccount: {
id: string;
name: string;
avatar: string;
};
}
interface GroupSelectorProps {
selectedGroups: WechatGroup[];
onGroupsChange: (groups: WechatGroup[]) => void;
onPrevious: () => void;
onNext: () => void;
onSave: () => void;
onCancel: () => void;
loading?: boolean;
}
// 模拟群组数据
const mockGroups: WechatGroup[] = [
{
id: '1',
name: 'VIP客户群',
avatar: 'https://via.placeholder.com/40',
serviceAccount: {
id: '1',
name: '客服小美',
avatar: 'https://via.placeholder.com/32',
},
},
{
id: '2',
name: '潜在客户群',
avatar: 'https://via.placeholder.com/40',
serviceAccount: {
id: '1',
name: '客服小美',
avatar: 'https://via.placeholder.com/32',
},
},
{
id: '3',
name: '活动群',
avatar: 'https://via.placeholder.com/40',
serviceAccount: {
id: '2',
name: '推广专员',
avatar: 'https://via.placeholder.com/32',
},
},
{
id: '4',
name: '推广群',
avatar: 'https://via.placeholder.com/40',
serviceAccount: {
id: '2',
name: '推广专员',
avatar: 'https://via.placeholder.com/32',
},
},
{
id: '5',
name: '新客户群',
avatar: 'https://via.placeholder.com/40',
serviceAccount: {
id: '3',
name: '销售小王',
avatar: 'https://via.placeholder.com/32',
},
},
{
id: '6',
name: '体验群',
avatar: 'https://via.placeholder.com/40',
serviceAccount: {
id: '3',
name: '销售小王',
avatar: 'https://via.placeholder.com/32',
},
},
];
export default function GroupSelector({
selectedGroups,
onGroupsChange,
onPrevious,
onNext,
onSave,
onCancel,
loading = false,
}: GroupSelectorProps) {
const [searchTerm, setSearchTerm] = useState('');
const [groups, setGroups] = useState<WechatGroup[]>(mockGroups);
const filteredGroups = groups.filter((group) =>
group.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
group.serviceAccount.name.toLowerCase().includes(searchTerm.toLowerCase())
);
const handleGroupToggle = (group: WechatGroup, checked: boolean) => {
if (checked) {
onGroupsChange([...selectedGroups, group]);
} else {
onGroupsChange(selectedGroups.filter((g) => g.id !== group.id));
}
};
const handleSelectAll = () => {
if (selectedGroups.length === filteredGroups.length) {
onGroupsChange([]);
} else {
onGroupsChange(filteredGroups);
}
};
const isGroupSelected = (groupId: string) => {
return selectedGroups.some((group) => group.id === groupId);
};
return (
<div className="space-y-6">
<Card>
<CardContent className="p-4 sm:p-6">
<div className="space-y-4">
{/* 搜索框 */}
<div className="space-y-2">
<Label className="text-sm font-medium">:</Label>
<div className="relative">
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 h-4 w-4 text-gray-400" />
<Input
placeholder="搜索群组名称或客服名称"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
className="pl-9"
disabled={loading}
/>
</div>
</div>
{/* 全选按钮 */}
<div className="flex items-center space-x-2">
<Checkbox
id="selectAll"
checked={selectedGroups.length === filteredGroups.length && filteredGroups.length > 0}
onCheckedChange={handleSelectAll}
disabled={loading}
/>
<Label htmlFor="selectAll" className="text-sm font-medium">
({selectedGroups.length}/{filteredGroups.length})
</Label>
</div>
{/* 群组列表 */}
<div className="space-y-2 max-h-96 overflow-y-auto">
{filteredGroups.map((group) => (
<div
key={group.id}
className="flex items-center space-x-3 p-3 border rounded-lg hover:bg-gray-50"
>
<Checkbox
id={group.id}
checked={isGroupSelected(group.id)}
onCheckedChange={(checked) => handleGroupToggle(group, checked as boolean)}
disabled={loading}
/>
<div className="flex items-center space-x-3 flex-1">
<img
src={group.avatar}
alt={group.name}
className="w-10 h-10 rounded-full"
/>
<div className="flex-1">
<div className="font-medium text-sm">{group.name}</div>
<div className="text-xs text-gray-500 flex items-center">
<img
src={group.serviceAccount.avatar}
alt={group.serviceAccount.name}
className="w-4 h-4 rounded-full mr-1"
/>
{group.serviceAccount.name}
</div>
</div>
</div>
</div>
))}
</div>
{filteredGroups.length === 0 && (
<div className="text-center py-8 text-gray-500">
<Users className="h-12 w-12 mx-auto mb-2 text-gray-300" />
<p></p>
</div>
)}
</div>
</CardContent>
</Card>
<div className="flex space-x-2 justify-center sm:justify-end">
<Button
type="button"
variant="outline"
onClick={onPrevious}
className="flex-1 sm:flex-none"
disabled={loading}
>
</Button>
<Button
type="button"
onClick={onNext}
className="flex-1 sm:flex-none"
disabled={loading || selectedGroups.length === 0}
>
</Button>
<Button
type="button"
variant="outline"
onClick={onSave}
className="flex-1 sm:flex-none"
disabled={loading}
>
{loading ? '保存中...' : '保存'}
</Button>
<Button
type="button"
variant="outline"
onClick={onCancel}
className="flex-1 sm:flex-none"
disabled={loading}
>
</Button>
</div>
</div>
);
}

View File

@@ -0,0 +1,19 @@
import React from 'react';
import { Steps, StepItem } from 'tdesign-mobile-react';
interface StepIndicatorProps {
currentStep: number;
steps: { id: number; title: string; subtitle: string }[];
}
export default function StepIndicator({ currentStep, steps }: StepIndicatorProps) {
return (
<div className="mb-6">
<Steps current={currentStep - 1}>
{steps.map((step) => (
<StepItem key={step.id} title={step.subtitle} />
))}
</Steps>
</div>
);
}

View File

@@ -0,0 +1,260 @@
import React, { useState } from 'react';
import { useNavigate } from 'react-router-dom';
import { ArrowLeft } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { useToast } from '@/components/ui/toast';
import { createGroupPushTask } from '@/api/groupPush';
import Layout from '@/components/Layout';
import PageHeader from '@/components/PageHeader';
import StepIndicator from './components/StepIndicator';
import BasicSettings from './components/BasicSettings';
import GroupSelector from './components/GroupSelector';
import ContentSelector from './components/ContentSelector';
// 类型定义
interface WechatGroup {
id: string;
name: string;
avatar: string;
serviceAccount: {
id: string;
name: string;
avatar: string;
};
}
interface ContentLibrary {
id: string;
name: string;
targets: Array<{
id: string;
avatar: string;
}>;
}
interface FormData {
name: string;
pushTimeStart: string;
pushTimeEnd: string;
dailyPushCount: number;
pushOrder: 'earliest' | 'latest';
isLoopPush: boolean;
isImmediatePush: boolean;
isEnabled: boolean;
groups: WechatGroup[];
contentLibraries: ContentLibrary[];
}
const steps = [
{ id: 1, title: '步骤 1', subtitle: '基础设置' },
{ id: 2, title: '步骤 2', subtitle: '选择社群' },
{ id: 3, title: '步骤 3', subtitle: '选择内容库' },
{ id: 4, title: '步骤 4', subtitle: '京东联盟' },
];
export default function NewGroupPush() {
const navigate = useNavigate();
const { toast } = useToast();
const [currentStep, setCurrentStep] = useState(1);
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState<FormData>({
name: '',
pushTimeStart: '06:00',
pushTimeEnd: '23:59',
dailyPushCount: 20,
pushOrder: 'latest',
isLoopPush: false,
isImmediatePush: false,
isEnabled: false,
groups: [],
contentLibraries: [],
});
const handleBasicSettingsNext = (values: Partial<FormData>) => {
setFormData((prev) => ({ ...prev, ...values }));
setCurrentStep(2);
};
const handleGroupsChange = (groups: WechatGroup[]) => {
setFormData((prev) => ({ ...prev, groups }));
};
const handleLibrariesChange = (contentLibraries: ContentLibrary[]) => {
setFormData((prev) => ({ ...prev, contentLibraries }));
};
const handleSave = async () => {
if (!formData.name.trim()) {
toast({
title: '请输入任务名称',
variant: 'destructive',
});
return;
}
if (formData.groups.length === 0) {
toast({
title: '请选择至少一个社群',
variant: 'destructive',
});
return;
}
if (formData.contentLibraries.length === 0) {
toast({
title: '请选择至少一个内容库',
variant: 'destructive',
});
return;
}
setLoading(true);
try {
// 转换数据格式以匹配API
const apiData = {
name: formData.name,
timeRange: {
start: formData.pushTimeStart,
end: formData.pushTimeEnd,
},
maxPushPerDay: formData.dailyPushCount,
pushOrder: formData.pushOrder,
isLoopPush: formData.isLoopPush,
isImmediatePush: formData.isImmediatePush,
isEnabled: formData.isEnabled,
targetGroups: formData.groups.map(g => g.name),
contentLibraries: formData.contentLibraries.map(c => c.name),
pushMode: formData.isImmediatePush ? 'immediate' as const : 'scheduled' as const,
messageType: 'text' as const,
messageContent: '',
targetTags: [],
pushInterval: 60,
};
const response = await createGroupPushTask(apiData);
if (response.code === 200) {
toast({
title: '保存成功',
description: `社群推送任务"${formData.name}"已保存`,
});
navigate('/workspace/group-push');
} else {
toast({
title: '保存失败',
description: response.message || '请稍后重试',
variant: 'destructive',
});
}
} catch (error) {
console.error('保存任务失败:', error);
toast({
title: '保存失败',
description: '请稍后重试',
variant: 'destructive',
});
} finally {
setLoading(false);
}
};
const handleCancel = () => {
navigate('/workspace/group-push');
};
return (
<Layout
header={
<PageHeader
title="新建社群推送任务"
defaultBackPath="/workspace/group-push"
/>
}
>
<div className="container mx-auto py-4 px-4 sm:px-6 md:py-6">
<StepIndicator currentStep={currentStep} steps={steps} />
<div className="mt-8">
{currentStep === 1 && (
<BasicSettings
defaultValues={{
name: formData.name,
pushTimeStart: formData.pushTimeStart,
pushTimeEnd: formData.pushTimeEnd,
dailyPushCount: formData.dailyPushCount,
pushOrder: formData.pushOrder,
isLoopPush: formData.isLoopPush,
isImmediatePush: formData.isImmediatePush,
isEnabled: formData.isEnabled,
}}
onNext={handleBasicSettingsNext}
onSave={handleSave}
onCancel={handleCancel}
loading={loading}
/>
)}
{currentStep === 2 && (
<GroupSelector
selectedGroups={formData.groups}
onGroupsChange={handleGroupsChange}
onPrevious={() => setCurrentStep(1)}
onNext={() => setCurrentStep(3)}
onSave={handleSave}
onCancel={handleCancel}
loading={loading}
/>
)}
{currentStep === 3 && (
<ContentSelector
selectedLibraries={formData.contentLibraries}
onLibrariesChange={handleLibrariesChange}
onPrevious={() => setCurrentStep(2)}
onNext={() => setCurrentStep(4)}
onSave={handleSave}
onCancel={handleCancel}
loading={loading}
/>
)}
{currentStep === 4 && (
<div className="space-y-6">
<div className="border rounded-md p-8 text-center text-gray-500">
</div>
<div className="flex space-x-2 justify-center sm:justify-end">
<Button
type="button"
variant="outline"
onClick={() => setCurrentStep(3)}
className="flex-1 sm:flex-none"
disabled={loading}
>
</Button>
<Button
type="button"
onClick={handleSave}
className="flex-1 sm:flex-none"
disabled={loading}
>
{loading ? '保存中...' : '完成'}
</Button>
<Button
type="button"
variant="outline"
onClick={handleCancel}
className="flex-1 sm:flex-none"
disabled={loading}
>
</Button>
</div>
</div>
)}
</div>
</div>
</Layout>
);
}