752 lines
25 KiB
TypeScript
752 lines
25 KiB
TypeScript
import React, { useState, useEffect, useCallback } from 'react';
|
||
import { useNavigate } from 'react-router-dom';
|
||
import { Steps, StepItem } from 'tdesign-mobile-react';
|
||
import {
|
||
Users,
|
||
Search,
|
||
Database,
|
||
X,
|
||
} from 'lucide-react';
|
||
import { Button } from '@/components/ui/button';
|
||
import { Input } from '@/components/ui/input';
|
||
import { Label } from '@/components/ui/label';
|
||
import { RadioGroup, RadioGroupItem } from '@/components/ui/radio-group';
|
||
import { Checkbox } from '@/components/ui/checkbox';
|
||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
||
import Layout from '@/components/Layout';
|
||
import PageHeader from '@/components/PageHeader';
|
||
import DeviceSelection from '@/components/DeviceSelection';
|
||
import { useToast } from '@/components/ui/toast';
|
||
import { fetchAccountList, Account } from '@/api/trafficDistribution';
|
||
import '@/components/Layout.css';
|
||
|
||
interface BasicInfoData {
|
||
name: string;
|
||
distributionMethod: 'equal' | 'priority' | 'ratio';
|
||
dailyLimit: number;
|
||
timeRestriction: 'allDay' | 'custom';
|
||
startTime: string;
|
||
endTime: string;
|
||
selectedAccounts: string[];
|
||
}
|
||
|
||
interface TargetSettingsData {
|
||
selectedDevices: string[];
|
||
}
|
||
|
||
interface TrafficPoolData {
|
||
selectedPools: string[];
|
||
}
|
||
|
||
interface FormData {
|
||
basicInfo: Partial<BasicInfoData>;
|
||
targetSettings: Partial<TargetSettingsData>;
|
||
trafficPool: Partial<TrafficPoolData>;
|
||
}
|
||
|
||
interface TrafficPool {
|
||
id: string;
|
||
name: string;
|
||
count: number;
|
||
description: string;
|
||
}
|
||
|
||
// 账号选择对话框组件
|
||
const AccountSelectionDialog = ({
|
||
open,
|
||
onClose,
|
||
selectedAccounts,
|
||
onConfirm
|
||
}: {
|
||
open: boolean;
|
||
onClose: () => void;
|
||
selectedAccounts: string[];
|
||
onConfirm: (accounts: string[]) => void;
|
||
}) => {
|
||
const [tempSelectedAccounts, setTempSelectedAccounts] = useState<string[]>(selectedAccounts);
|
||
const [accounts, setAccounts] = useState<Account[]>([]);
|
||
const [loading, setLoading] = useState(false);
|
||
const [page, setPage] = useState(1);
|
||
const [hasMore, setHasMore] = useState(true);
|
||
const { toast } = useToast();
|
||
|
||
// 获取账号列表
|
||
const fetchAccounts = useCallback(async (pageNum: number = 1, reset: boolean = true) => {
|
||
setLoading(true);
|
||
try {
|
||
const response = await fetchAccountList({
|
||
page: pageNum,
|
||
limit: 10
|
||
});
|
||
|
||
if (response.code === 200 && response.data) {
|
||
const accountList = response.data.list || [];
|
||
const total = response.data.total || 0;
|
||
|
||
if (reset) {
|
||
setAccounts(accountList);
|
||
} else {
|
||
setAccounts(prev => [...prev, ...accountList]);
|
||
}
|
||
|
||
// 计算是否还有更多数据
|
||
const currentTotal = reset ? accountList.length : accounts.length + accountList.length;
|
||
setHasMore(currentTotal < total);
|
||
} else {
|
||
toast({
|
||
title: "获取账号列表失败",
|
||
description: response.msg || "请稍后重试",
|
||
variant: "destructive"
|
||
});
|
||
|
||
// 如果API失败,使用模拟数据作为降级处理
|
||
const mockData = [
|
||
{ id: "1", userName: "user_001", realName: "张三", nickname: "游戏", memo: "游戏账号" },
|
||
{ id: "2", userName: "user_002", realName: "李四", nickname: "商务4", memo: "商务账号" },
|
||
{ id: "3", userName: "user_003", realName: "王五", nickname: "魔兽客服", memo: "客服账号" },
|
||
{ id: "4", userName: "user_004", realName: "赵六", nickname: "魔兽世界Kf", memo: "游戏客服" },
|
||
{ id: "5", userName: "user_005", realName: "孙七", nickname: "小羊网络", memo: "网络账号" },
|
||
];
|
||
|
||
if (reset) {
|
||
setAccounts(mockData);
|
||
}
|
||
setHasMore(false);
|
||
}
|
||
} catch (error) {
|
||
console.error('获取账号列表失败:', error);
|
||
toast({
|
||
title: "网络错误",
|
||
description: "请检查网络连接后重试",
|
||
variant: "destructive"
|
||
});
|
||
|
||
// 网络错误时使用模拟数据
|
||
const mockData = [
|
||
{ id: "1", userName: "user_001", realName: "张三", nickname: "游戏", memo: "游戏账号" },
|
||
{ id: "2", userName: "user_002", realName: "李四", nickname: "商务4", memo: "商务账号" },
|
||
{ id: "3", userName: "user_003", realName: "王五", nickname: "魔兽客服", memo: "客服账号" },
|
||
{ id: "4", userName: "user_004", realName: "赵六", nickname: "魔兽世界Kf", memo: "游戏客服" },
|
||
{ id: "5", userName: "user_005", realName: "孙七", nickname: "小羊网络", memo: "网络账号" },
|
||
];
|
||
|
||
if (reset) {
|
||
setAccounts(mockData);
|
||
}
|
||
setHasMore(false);
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
}, [accounts.length, toast]);
|
||
|
||
useEffect(() => {
|
||
if (open) {
|
||
setTempSelectedAccounts(selectedAccounts);
|
||
fetchAccounts(1, true);
|
||
setPage(1);
|
||
}
|
||
}, [open, selectedAccounts, fetchAccounts]);
|
||
|
||
const toggleAccount = (id: string) => {
|
||
setTempSelectedAccounts(prev =>
|
||
prev.includes(id) ? prev.filter(accountId => accountId !== id) : [...prev, id]
|
||
);
|
||
};
|
||
|
||
const handleConfirm = () => {
|
||
onConfirm(tempSelectedAccounts);
|
||
onClose();
|
||
};
|
||
|
||
const loadMore = () => {
|
||
if (!loading && hasMore) {
|
||
const nextPage = page + 1;
|
||
setPage(nextPage);
|
||
fetchAccounts(nextPage, false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<Dialog open={open} onOpenChange={onClose}>
|
||
<DialogContent className="max-w-sm w-[90vw] max-h-[80vh] p-0">
|
||
<DialogHeader className=" pb-4">
|
||
<div className="flex items-center justify-between">
|
||
<DialogTitle>选择账号</DialogTitle>
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
onClick={onClose}
|
||
className="h-6 w-6 p-0"
|
||
>
|
||
<X className="h-4 w-4" />
|
||
</Button>
|
||
</div>
|
||
</DialogHeader>
|
||
|
||
<div className=" flex-1 overflow-hidden">
|
||
<div className="space-y-3 max-h-96 overflow-y-auto">
|
||
{accounts.map(account => (
|
||
<div
|
||
key={account.id}
|
||
className={`cursor-pointer border rounded-lg p-4 ${
|
||
tempSelectedAccounts.includes(account.id)
|
||
? "border-blue-500 bg-blue-50"
|
||
: "border-gray-200"
|
||
}`}
|
||
onClick={() => toggleAccount(account.id)}
|
||
>
|
||
<div className="flex items-center justify-between">
|
||
<div className="flex items-center space-x-3">
|
||
<div className="w-12 h-12 rounded-lg bg-blue-100 flex items-center justify-center">
|
||
<span className="text-base font-medium text-blue-600">
|
||
{(account.nickname || account.realName || account.userName).charAt(0)}
|
||
</span>
|
||
</div>
|
||
<div className="flex-1">
|
||
<p className="font-medium text-base">{account.nickname || account.realName || account.userName}</p>
|
||
<p className="text-sm text-gray-500">账号: {account.userName}</p>
|
||
</div>
|
||
</div>
|
||
<div className="ml-2">
|
||
<Checkbox
|
||
checked={tempSelectedAccounts.includes(account.id)}
|
||
onCheckedChange={() => toggleAccount(account.id)}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
|
||
{loading && (
|
||
<div className="text-center py-4">
|
||
<div className="flex items-center justify-center space-x-2">
|
||
<div className="w-4 h-4 border-2 border-blue-500 border-t-transparent rounded-full animate-spin"></div>
|
||
<span className="text-sm text-gray-500">加载中...</span>
|
||
</div>
|
||
</div>
|
||
)}
|
||
|
||
{!loading && hasMore && (
|
||
<div className="text-center py-4">
|
||
<Button
|
||
variant="outline"
|
||
size="sm"
|
||
onClick={loadMore}
|
||
>
|
||
加载更多
|
||
</Button>
|
||
</div>
|
||
)}
|
||
|
||
{!loading && accounts.length === 0 && (
|
||
<div className="text-center py-8 text-gray-500">
|
||
<Users className="h-12 w-12 mx-auto mb-2 opacity-30" />
|
||
<p>暂无账号数据</p>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
|
||
<div className=" pt-4 border-t">
|
||
<Button
|
||
onClick={handleConfirm}
|
||
className="w-full h-12 text-base"
|
||
disabled={tempSelectedAccounts.length === 0}
|
||
>
|
||
确认
|
||
</Button>
|
||
</div>
|
||
</DialogContent>
|
||
</Dialog>
|
||
);
|
||
};
|
||
|
||
export default function NewDistribution() {
|
||
const navigate = useNavigate();
|
||
const { toast } = useToast();
|
||
const [currentStep, setCurrentStep] = useState(0);
|
||
const [formData, setFormData] = useState<FormData>({
|
||
basicInfo: {},
|
||
targetSettings: {},
|
||
trafficPool: {},
|
||
});
|
||
|
||
const steps = [
|
||
{ title: "基本信息", content: "step1" },
|
||
{ title: "目标设置", content: "step2" },
|
||
{ title: "流量池选择", content: "step3" },
|
||
];
|
||
|
||
// 生成默认计划名称
|
||
const generateDefaultName = () => {
|
||
const now = new Date();
|
||
const year = now.getFullYear();
|
||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||
const day = String(now.getDate()).padStart(2, '0');
|
||
const hour = String(now.getHours()).padStart(2, '0');
|
||
const minute = String(now.getMinutes()).padStart(2, '0');
|
||
return `流量分发 ${year}${month}${day} ${hour}${minute}`;
|
||
};
|
||
|
||
const handleBasicInfoNext = (data: BasicInfoData) => {
|
||
setFormData(prev => ({ ...prev, basicInfo: data }));
|
||
setCurrentStep(1);
|
||
};
|
||
|
||
const handleTargetSettingsNext = (data: TargetSettingsData) => {
|
||
setFormData(prev => ({ ...prev, targetSettings: data }));
|
||
setCurrentStep(2);
|
||
};
|
||
|
||
const handleTargetSettingsBack = () => {
|
||
setCurrentStep(0);
|
||
};
|
||
|
||
const handleTrafficPoolBack = () => {
|
||
setCurrentStep(1);
|
||
};
|
||
|
||
const handleSubmit = async (data: TrafficPoolData) => {
|
||
const finalData = {
|
||
...formData,
|
||
trafficPool: data,
|
||
};
|
||
|
||
try {
|
||
console.log('提交的数据:', finalData);
|
||
|
||
toast({
|
||
title: "创建成功",
|
||
description: "流量分发规则已成功创建"
|
||
});
|
||
|
||
navigate('/workspace/traffic-distribution');
|
||
} catch (error) {
|
||
console.error('提交失败:', error);
|
||
toast({
|
||
title: "创建失败",
|
||
description: "请稍后重试",
|
||
variant: "destructive"
|
||
});
|
||
}
|
||
};
|
||
|
||
// 基本信息步骤组件
|
||
const BasicInfoStep = ({ onNext, initialData = {} }: { onNext: (data: BasicInfoData) => void; initialData?: Partial<BasicInfoData> }) => {
|
||
const [formData, setFormData] = useState<BasicInfoData>({
|
||
name: initialData.name || generateDefaultName(),
|
||
distributionMethod: initialData.distributionMethod || "equal",
|
||
dailyLimit: initialData.dailyLimit || 50,
|
||
timeRestriction: initialData.timeRestriction || "custom",
|
||
startTime: initialData.startTime || "09:00",
|
||
endTime: initialData.endTime || "18:00",
|
||
selectedAccounts: initialData.selectedAccounts || [],
|
||
});
|
||
|
||
const [accountDialogOpen, setAccountDialogOpen] = useState(false);
|
||
|
||
const handleChange = (field: keyof BasicInfoData, value: string | number | string[]) => {
|
||
setFormData(prev => ({ ...prev, [field]: value }));
|
||
};
|
||
|
||
const handleAccountConfirm = (selectedAccounts: string[]) => {
|
||
handleChange("selectedAccounts", selectedAccounts);
|
||
};
|
||
|
||
const getSelectedAccountsText = () => {
|
||
if (formData.selectedAccounts.length === 0) {
|
||
return "请选择账号";
|
||
}
|
||
return `已选择 ${formData.selectedAccounts.length} 个账号`;
|
||
};
|
||
|
||
const handleSubmit = () => {
|
||
if (!formData.name.trim()) {
|
||
toast({
|
||
title: "请填写计划名称",
|
||
variant: "destructive"
|
||
});
|
||
return;
|
||
}
|
||
if (formData.selectedAccounts.length === 0) {
|
||
toast({
|
||
title: "请选择至少一个账号",
|
||
variant: "destructive"
|
||
});
|
||
return;
|
||
}
|
||
onNext(formData);
|
||
};
|
||
|
||
return (
|
||
<div className="bg-white rounded-lg p-6">
|
||
<h2 className="text-xl font-bold mb-6">基本信息</h2>
|
||
|
||
<div className="space-y-6">
|
||
<div className="space-y-2">
|
||
<Label htmlFor="name" className="flex items-center">
|
||
计划名称 <span className="text-red-500 ml-1">*</span>
|
||
</Label>
|
||
<Input
|
||
id="name"
|
||
value={formData.name}
|
||
onChange={(e) => handleChange("name", e.target.value)}
|
||
placeholder="请输入计划名称"
|
||
className="h-12"
|
||
/>
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
<Label className="flex items-center">
|
||
选择账号 <span className="text-red-500 ml-1">*</span>
|
||
</Label>
|
||
|
||
<div
|
||
className="relative cursor-pointer"
|
||
onClick={() => setAccountDialogOpen(true)}
|
||
>
|
||
<Input
|
||
value={getSelectedAccountsText()}
|
||
placeholder="请选择账号"
|
||
className="h-12 cursor-pointer"
|
||
readOnly
|
||
/>
|
||
<Search className="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={18} />
|
||
</div>
|
||
|
||
<div className="flex items-center text-sm text-gray-500">
|
||
<Users className="h-4 w-4 mr-1" />
|
||
已选账号:<span className="text-blue-600 font-medium ml-1">{formData.selectedAccounts.length} 个</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-2">
|
||
<Label>分配方式</Label>
|
||
<RadioGroup
|
||
value={formData.distributionMethod}
|
||
onValueChange={(value) => handleChange("distributionMethod", value as 'equal' | 'priority' | 'ratio')}
|
||
className="space-y-2"
|
||
>
|
||
<div className="flex items-center space-x-2">
|
||
<RadioGroupItem value="equal" id="equal" />
|
||
<Label htmlFor="equal" className="cursor-pointer">
|
||
均分配 <span className="text-gray-500 text-sm">(流量将均分配给所有客服)</span>
|
||
</Label>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<RadioGroupItem value="priority" id="priority" />
|
||
<Label htmlFor="priority" className="cursor-pointer">
|
||
优先级分配 <span className="text-gray-500 text-sm">(按客服优先级顺序分配)</span>
|
||
</Label>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<RadioGroupItem value="ratio" id="ratio" />
|
||
<Label htmlFor="ratio" className="cursor-pointer">
|
||
比例分配 <span className="text-gray-500 text-sm">(按设定比例分配流量)</span>
|
||
</Label>
|
||
</div>
|
||
</RadioGroup>
|
||
</div>
|
||
|
||
<div className="space-y-4">
|
||
<Label>分配限制</Label>
|
||
|
||
<div className="space-y-2">
|
||
<div className="flex justify-between items-center">
|
||
<span>每日最大分配量</span>
|
||
<span className="font-medium">{formData.dailyLimit} 人/天</span>
|
||
</div>
|
||
<input
|
||
type="range"
|
||
value={formData.dailyLimit}
|
||
min={1}
|
||
max={200}
|
||
step={1}
|
||
onChange={(e) => handleChange("dailyLimit", parseInt(e.target.value))}
|
||
className="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer"
|
||
style={{
|
||
background: `linear-gradient(to right, #3b82f6 0%, #3b82f6 ${(formData.dailyLimit / 200) * 100}%, #e5e7eb ${(formData.dailyLimit / 200) * 100}%, #e5e7eb 100%)`
|
||
}}
|
||
/>
|
||
<p className="text-sm text-gray-500">限制每天最多分配的流量数量</p>
|
||
</div>
|
||
|
||
<div className="space-y-4 pt-4">
|
||
<Label>时间限制</Label>
|
||
<RadioGroup
|
||
value={formData.timeRestriction}
|
||
onValueChange={(value) => handleChange("timeRestriction", value as 'allDay' | 'custom')}
|
||
className="space-y-4"
|
||
>
|
||
<div className="flex items-center space-x-2">
|
||
<RadioGroupItem value="allDay" id="allDay" />
|
||
<Label htmlFor="allDay" className="cursor-pointer">
|
||
全天分配
|
||
</Label>
|
||
</div>
|
||
<div className="flex items-center space-x-2">
|
||
<RadioGroupItem value="custom" id="custom" />
|
||
<Label htmlFor="custom" className="cursor-pointer">
|
||
自定义时间段
|
||
</Label>
|
||
</div>
|
||
</RadioGroup>
|
||
|
||
{formData.timeRestriction === "custom" && (
|
||
<div className="grid grid-cols-2 gap-4 pt-2">
|
||
<div>
|
||
<Label htmlFor="startTime" className="mb-2 block">
|
||
开始时间
|
||
</Label>
|
||
<Input
|
||
id="startTime"
|
||
type="time"
|
||
value={formData.startTime}
|
||
onChange={(e) => handleChange("startTime", e.target.value)}
|
||
className="h-12"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<Label htmlFor="endTime" className="mb-2 block">
|
||
结束时间
|
||
</Label>
|
||
<Input
|
||
id="endTime"
|
||
type="time"
|
||
value={formData.endTime}
|
||
onChange={(e) => handleChange("endTime", e.target.value)}
|
||
className="h-12"
|
||
/>
|
||
</div>
|
||
</div>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="mt-8 flex justify-end">
|
||
<Button onClick={handleSubmit} className="px-8">
|
||
下一步 →
|
||
</Button>
|
||
</div>
|
||
|
||
<AccountSelectionDialog
|
||
open={accountDialogOpen}
|
||
onClose={() => setAccountDialogOpen(false)}
|
||
selectedAccounts={formData.selectedAccounts}
|
||
onConfirm={handleAccountConfirm}
|
||
/>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// 目标设置步骤组件
|
||
const TargetSettingsStep = ({ onNext, onBack, initialData = {} }: { onNext: (data: TargetSettingsData) => void; onBack: () => void; initialData?: Partial<TargetSettingsData> }) => {
|
||
const [selectedDevices, setSelectedDevices] = useState<string[]>(initialData.selectedDevices || []);
|
||
|
||
const handleSubmit = () => {
|
||
if (selectedDevices.length === 0) {
|
||
toast({
|
||
title: "请选择至少一个设备",
|
||
variant: "destructive"
|
||
});
|
||
return;
|
||
}
|
||
|
||
onNext({
|
||
selectedDevices,
|
||
});
|
||
};
|
||
|
||
return (
|
||
<div className="bg-white rounded-lg p-6">
|
||
<h2 className="text-xl font-bold mb-6">目标设置</h2>
|
||
|
||
<div className="mb-6">
|
||
<DeviceSelection
|
||
selectedDevices={selectedDevices}
|
||
onSelect={setSelectedDevices}
|
||
placeholder="选择执行设备"
|
||
/>
|
||
</div>
|
||
|
||
<div className="mt-8 flex justify-between">
|
||
<Button variant="outline" onClick={onBack}>
|
||
← 上一步
|
||
</Button>
|
||
<Button onClick={handleSubmit} disabled={selectedDevices.length === 0}>
|
||
下一步 →
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
// 流量池选择步骤组件
|
||
const TrafficPoolStep = ({ onSubmit, onBack, initialData = {} }: { onSubmit: (data: TrafficPoolData) => void; onBack: () => void; initialData?: Partial<TrafficPoolData> }) => {
|
||
const [selectedPools, setSelectedPools] = useState<string[]>(initialData.selectedPools || []);
|
||
const [searchTerm, setSearchTerm] = useState("");
|
||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||
|
||
// 模拟流量池数据
|
||
const trafficPools: TrafficPool[] = [
|
||
{ id: "1", name: "新客流量池", count: 1250, description: "新获取的客户流量" },
|
||
{ id: "2", name: "高意向流量池", count: 850, description: "有购买意向的客户" },
|
||
{ id: "3", name: "复购流量池", count: 620, description: "已购买过产品的客户" },
|
||
{ id: "4", name: "活跃流量池", count: 1580, description: "近期活跃的客户" },
|
||
{ id: "5", name: "沉睡流量池", count: 2300, description: "长期未活跃的客户" },
|
||
];
|
||
|
||
const filteredPools = trafficPools.filter(
|
||
pool =>
|
||
pool.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||
pool.description.toLowerCase().includes(searchTerm.toLowerCase())
|
||
);
|
||
|
||
const togglePool = (id: string) => {
|
||
setSelectedPools(prev =>
|
||
prev.includes(id) ? prev.filter(poolId => poolId !== id) : [...prev, id]
|
||
);
|
||
};
|
||
|
||
const handleSubmit = async () => {
|
||
if (selectedPools.length === 0) {
|
||
toast({
|
||
title: "请选择至少一个流量池",
|
||
variant: "destructive"
|
||
});
|
||
return;
|
||
}
|
||
|
||
setIsSubmitting(true);
|
||
|
||
try {
|
||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||
onSubmit({ selectedPools });
|
||
} catch (error) {
|
||
console.error("提交失败:", error);
|
||
toast({
|
||
title: "创建失败",
|
||
description: "请稍后重试",
|
||
variant: "destructive"
|
||
});
|
||
} finally {
|
||
setIsSubmitting(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="bg-white rounded-lg p-6">
|
||
<h2 className="text-xl font-bold mb-6">流量池选择</h2>
|
||
|
||
<div className="mb-4">
|
||
<div className="relative">
|
||
<Search className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400" size={18} />
|
||
<Input
|
||
placeholder="搜索流量池"
|
||
value={searchTerm}
|
||
onChange={(e) => setSearchTerm(e.target.value)}
|
||
className="pl-10 h-12"
|
||
/>
|
||
</div>
|
||
</div>
|
||
|
||
<div className="space-y-3 mt-4">
|
||
{filteredPools.map(pool => (
|
||
<div
|
||
key={pool.id}
|
||
className={`cursor-pointer border rounded-lg ${selectedPools.includes(pool.id) ? "border-blue-500 bg-blue-50" : "border-gray-200"}`}
|
||
onClick={() => togglePool(pool.id)}
|
||
>
|
||
<div className="p-4 flex items-center justify-between">
|
||
<div className="flex items-center space-x-3">
|
||
<div className="w-10 h-10 rounded-full bg-blue-100 flex items-center justify-center">
|
||
<Database className="h-5 w-5 text-blue-600" />
|
||
</div>
|
||
<div>
|
||
<p className="font-medium">{pool.name}</p>
|
||
<p className="text-sm text-gray-500">{pool.description}</p>
|
||
</div>
|
||
</div>
|
||
<div className="flex items-center space-x-3">
|
||
<span className="text-sm text-gray-500">{pool.count} 人</span>
|
||
<Checkbox
|
||
checked={selectedPools.includes(pool.id)}
|
||
onCheckedChange={() => togglePool(pool.id)}
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
))}
|
||
</div>
|
||
|
||
<div className="mt-8 flex justify-between">
|
||
<Button variant="outline" onClick={onBack}>
|
||
← 上一步
|
||
</Button>
|
||
<Button onClick={handleSubmit} disabled={isSubmitting}>
|
||
{isSubmitting ? "提交中..." : "完成"}
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
);
|
||
};
|
||
|
||
const headerRightContent = (
|
||
<Button
|
||
variant="ghost"
|
||
size="sm"
|
||
className="text-gray-500"
|
||
onClick={() => navigate('/workspace/traffic-distribution')}
|
||
>
|
||
取消
|
||
</Button>
|
||
);
|
||
|
||
return (
|
||
<Layout
|
||
header={
|
||
<PageHeader
|
||
title="新建流量分发"
|
||
defaultBackPath="/workspace/traffic-distribution"
|
||
rightContent={headerRightContent}
|
||
/>
|
||
}
|
||
>
|
||
<div className="bg-gray-50 min-h-screen pb-16">
|
||
<div className="p-4">
|
||
<div className="mb-6">
|
||
<Steps current={currentStep}>
|
||
{steps.map((step, index) => (
|
||
<StepItem key={index} title={step.title} />
|
||
))}
|
||
</Steps>
|
||
</div>
|
||
|
||
{currentStep === 0 && (
|
||
<BasicInfoStep
|
||
onNext={handleBasicInfoNext}
|
||
initialData={formData.basicInfo}
|
||
/>
|
||
)}
|
||
|
||
{currentStep === 1 && (
|
||
<TargetSettingsStep
|
||
onNext={handleTargetSettingsNext}
|
||
onBack={handleTargetSettingsBack}
|
||
initialData={formData.targetSettings}
|
||
/>
|
||
)}
|
||
|
||
{currentStep === 2 && (
|
||
<TrafficPoolStep
|
||
onSubmit={handleSubmit}
|
||
onBack={handleTrafficPoolBack}
|
||
initialData={formData.trafficPool}
|
||
/>
|
||
)}
|
||
</div>
|
||
</div>
|
||
</Layout>
|
||
);
|
||
}
|