feat: 本次提交更新内容如下

流量分发构建完完成
This commit is contained in:
笔记本里的永平
2025-07-10 18:00:33 +08:00
parent cb253a568c
commit 3f43b6d388
4 changed files with 349 additions and 211 deletions

View File

@@ -65,6 +65,7 @@ function App() {
<Route path="/workspace/auto-like/:id/edit" element={<NewAutoLike />} /> <Route path="/workspace/auto-like/:id/edit" element={<NewAutoLike />} />
<Route path="/workspace/traffic-distribution" element={<TrafficDistribution />} /> <Route path="/workspace/traffic-distribution" element={<TrafficDistribution />} />
<Route path="/workspace/traffic-distribution/new" element={<NewDistribution />} /> <Route path="/workspace/traffic-distribution/new" element={<NewDistribution />} />
<Route path="/workspace/traffic-distribution/edit/:id" element={<NewDistribution />} />
<Route path="/workspace/auto-group" element={<AutoGroup />} /> <Route path="/workspace/auto-group" element={<AutoGroup />} />
<Route path="/workspace/auto-group/:id" element={<AutoGroupDetail />} /> <Route path="/workspace/auto-group/:id" element={<AutoGroupDetail />} />
<Route path="/workspace/group-push" element={<GroupPush />} /> <Route path="/workspace/group-push" element={<GroupPush />} />

View File

@@ -57,31 +57,39 @@ export interface TrafficPoolListResponse {
// 流量分发规则类型 // 流量分发规则类型
export interface DistributionRule { export interface DistributionRule {
id: string; id: number;
name: string; name: string;
type: number;
status: number; status: number;
deviceCount: number; autoStart: number;
totalTraffic: number;
distributedTraffic: number;
lastDistributionTime: string;
createTime: string; createTime: string;
creator: string; updateTime: string;
distributionInterval: number; companyId: number;
maxDistributionPerDay: number;
timeRange: { start: string; end: string };
targetChannels?: string[];
distributionRatio?: Record<string, number>;
priority?: 'high' | 'medium' | 'low';
filterConditions?: string[];
config?: { config?: {
total?: { id: number;
workbenchId: number;
distributeType: number; // 1-均分配, 2-优先级分配, 3-比例分配
maxPerDay: number; // 每日最大分配量
timeType: number; // 1-全天, 2-自定义时间段
startTime: string; // 开始时间
endTime: string; // 结束时间
account: string[]; // 账号列表
devices: string[]; // 设备列表
pools: string[]; // 流量池列表
createTime: string;
updateTime: string;
lastUpdated: string;
total: {
dailyAverage: number; // 日均分发量 dailyAverage: number; // 日均分发量
deviceCount: number; // 分发设备 totalAccounts: number; // 分发账户总数
poolCount: string; // 流量池 deviceCount: number; // 分发设备数量
totalAccounts: number; // 分发账户 poolCount: number; // 流量池数量
totalUsers: number; // 总用户数 totalUsers: number; // 总用户数
}
}; };
};
auto_like?: any;
moments_sync?: any;
group_push?: any;
} }
// 流量分发列表响应类型 // 流量分发列表响应类型
@@ -170,7 +178,7 @@ export const fetchDistributionRules = async (params: {
* @returns 流量分发规则详情 * @returns 流量分发规则详情
*/ */
export const fetchDistributionRuleDetail = async (id: string): Promise<ApiResponse<DistributionRule>> => { export const fetchDistributionRuleDetail = async (id: string): Promise<ApiResponse<DistributionRule>> => {
return get<ApiResponse<DistributionRule>>(`/v1/workbench/detail/${id}`); return get<ApiResponse<DistributionRule>>(`/v1/workbench/detail?id=${id}`);
}; };
/** /**
@@ -192,7 +200,8 @@ export const createDistributionRule = async (params: any): Promise<ApiResponse<{
* @returns 更新结果 * @returns 更新结果
*/ */
export const updateDistributionRule = async (id : string, params: any): Promise<ApiResponse<any>> => { export const updateDistributionRule = async (id : string, params: any): Promise<ApiResponse<any>> => {
return put<ApiResponse<any>>(`/v1/workbench/update/${id}`, { return post<ApiResponse<any>>(`/v1/workbench/update`, {
id: id,
...params, ...params,
type: WorkbenchTaskType.TRAFFIC_DISTRIBUTION type: WorkbenchTaskType.TRAFFIC_DISTRIBUTION
}); });

View File

@@ -1,5 +1,5 @@
import React, { useState, useEffect, useCallback } from 'react'; import React, { useState, useEffect, useCallback } from 'react';
import { useNavigate } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { Steps, StepItem } from 'tdesign-mobile-react'; import { Steps, StepItem } from 'tdesign-mobile-react';
import { import {
Users, Users,
@@ -16,7 +16,7 @@ import Layout from '@/components/Layout';
import PageHeader from '@/components/PageHeader'; import PageHeader from '@/components/PageHeader';
import DeviceSelection from '@/components/DeviceSelection'; import DeviceSelection from '@/components/DeviceSelection';
import { useToast } from '@/components/ui/toast'; import { useToast } from '@/components/ui/toast';
import { fetchAccountList, Account, createDistributionRule } from '@/api/trafficDistribution'; import { fetchAccountList, Account, createDistributionRule, updateDistributionRule, fetchDistributionRuleDetail } from '@/api/trafficDistribution';
import '@/components/Layout.css'; import '@/components/Layout.css';
import TrafficPoolSelection from '@/components/TrafficPoolSelection'; import TrafficPoolSelection from '@/components/TrafficPoolSelection';
@@ -256,8 +256,11 @@ const AccountSelectionDialog = ({
export default function NewDistribution() { export default function NewDistribution() {
const navigate = useNavigate(); const navigate = useNavigate();
const { id: ruleId } = useParams<{ id: string }>();
const { toast } = useToast(); const { toast } = useToast();
const [currentStep, setCurrentStep] = useState(0); const [currentStep, setCurrentStep] = useState(0);
const [isEditMode, setIsEditMode] = useState(false);
const [loading, setLoading] = useState(false);
const [formData, setFormData] = useState<FormData>({ const [formData, setFormData] = useState<FormData>({
basicInfo: {}, basicInfo: {},
targetSettings: {}, targetSettings: {},
@@ -270,6 +273,100 @@ export default function NewDistribution() {
{ title: "流量池选择", content: "step3" }, { title: "流量池选择", content: "step3" },
]; ];
// 加载规则数据
const loadRuleData = useCallback(async (id: string) => {
console.log('开始加载规则数据ID:', id);
setLoading(true);
try {
console.log('调用API: /v1/workbench/detail?id=' + id);
const response = await fetchDistributionRuleDetail(id);
if (response.code === 200 && response.data) {
const rule = response.data;
const config = rule.config as any || {};
console.log('接收到的详情数据:', rule);
console.log('config数据:', config);
// 转换分配类型1-均分配, 2-优先级分配, 3-比例分配
const getDistributionMethod = (distributeType: number) => {
switch (distributeType) {
case 2: return 'priority';
case 3: return 'ratio';
default: return 'equal';
}
};
// 转换时间限制类型1-全天, 2-自定义时间段
const timeRestriction = config?.timeType === 1 ? 'allDay' : 'custom';
// 转换后端数据为前端表单格式
setFormData({
basicInfo: {
name: rule.name || '',
distributionMethod: getDistributionMethod(config?.distributeType),
dailyLimit: config?.maxPerDay || 50,
timeRestriction: timeRestriction,
startTime: config?.startTime || '09:00',
endTime: config?.endTime || '18:00',
selectedAccounts: config?.account || [],
},
targetSettings: {
selectedDevices: config?.devices || [],
},
trafficPool: {
selectedPools: config?.pools || [],
},
});
console.log('转换后的表单数据:', {
basicInfo: {
name: rule.name || '',
distributionMethod: getDistributionMethod(config?.distributeType),
dailyLimit: config?.maxPerDay || 50,
timeRestriction: timeRestriction,
startTime: config?.startTime || '09:00',
endTime: config?.endTime || '18:00',
selectedAccounts: config?.account || [],
},
targetSettings: {
selectedDevices: config?.devices || [],
},
trafficPool: {
selectedPools: config?.pools || [],
},
});
} else {
toast({
title: "加载失败",
description: response.msg || "无法加载规则数据",
variant: "destructive"
});
navigate('/workspace/traffic-distribution');
}
} catch (error) {
console.error('加载规则数据失败:', error);
toast({
title: "加载失败",
description: "网络错误,请稍后重试",
variant: "destructive"
});
navigate('/workspace/traffic-distribution');
} finally {
setLoading(false);
}
}, [toast, navigate]);
// 检查是否为编辑模式并加载数据
useEffect(() => {
console.log('检查编辑模式 - ruleId:', ruleId);
if (ruleId) {
console.log('进入编辑模式,加载数据...');
setIsEditMode(true);
loadRuleData(ruleId);
}
}, [ruleId, loadRuleData]);
// 生成默认计划名称 // 生成默认计划名称
const generateDefaultName = () => { const generateDefaultName = () => {
const now = new Date(); const now = new Date();
@@ -341,18 +438,25 @@ export default function NewDistribution() {
console.log('提交的数据:', apiParams); console.log('提交的数据:', apiParams);
const response = await createDistributionRule(apiParams); let response;
if (isEditMode && ruleId) {
// 编辑模式 - 使用更新接口
response = await updateDistributionRule(ruleId, apiParams);
} else {
// 创建模式 - 使用创建接口
response = await createDistributionRule(apiParams);
}
if (response.code === 200) { if (response.code === 200) {
toast({ toast({
title: "创建成功", title: isEditMode ? "更新成功" : "创建成功",
description: "流量分发规则已成功创建" description: isEditMode ? "流量分发规则已成功更新" : "流量分发规则已成功创建"
}); });
navigate('/workspace/traffic-distribution'); navigate('/workspace/traffic-distribution');
} else { } else {
toast({ toast({
title: "创建失败", title: isEditMode ? "更新失败" : "创建失败",
description: response.msg || "请稍后重试", description: response.msg || "请稍后重试",
variant: "destructive" variant: "destructive"
}); });
@@ -360,7 +464,7 @@ export default function NewDistribution() {
} catch (error) { } catch (error) {
console.error('提交失败:', error); console.error('提交失败:', error);
toast({ toast({
title: "创建失败", title: isEditMode ? "更新失败" : "创建失败",
description: "网络错误,请稍后重试", description: "网络错误,请稍后重试",
variant: "destructive" variant: "destructive"
}); });
@@ -370,7 +474,7 @@ export default function NewDistribution() {
// 基本信息步骤组件 // 基本信息步骤组件
const BasicInfoStep = ({ onNext, initialData = {} }: { onNext: (data: BasicInfoData) => void; initialData?: Partial<BasicInfoData> }) => { const BasicInfoStep = ({ onNext, initialData = {} }: { onNext: (data: BasicInfoData) => void; initialData?: Partial<BasicInfoData> }) => {
const [formData, setFormData] = useState<BasicInfoData>({ const [formData, setFormData] = useState<BasicInfoData>({
name: initialData.name || generateDefaultName(), name: initialData.name || (isEditMode ? '' : generateDefaultName()),
distributionMethod: initialData.distributionMethod || "equal", distributionMethod: initialData.distributionMethod || "equal",
dailyLimit: initialData.dailyLimit || 50, dailyLimit: initialData.dailyLimit || 50,
timeRestriction: initialData.timeRestriction || "custom", timeRestriction: initialData.timeRestriction || "custom",
@@ -381,6 +485,22 @@ export default function NewDistribution() {
const [accountDialogOpen, setAccountDialogOpen] = useState(false); const [accountDialogOpen, setAccountDialogOpen] = useState(false);
// 当编辑模式下initialData变化时更新表单数据
useEffect(() => {
if (initialData.name) {
setFormData(prev => ({
...prev,
name: initialData.name || '',
distributionMethod: initialData.distributionMethod || prev.distributionMethod,
dailyLimit: initialData.dailyLimit || prev.dailyLimit,
timeRestriction: initialData.timeRestriction || prev.timeRestriction,
startTime: initialData.startTime || prev.startTime,
endTime: initialData.endTime || prev.endTime,
selectedAccounts: initialData.selectedAccounts || prev.selectedAccounts,
}));
}
}, [initialData]);
const handleChange = (field: keyof BasicInfoData, value: string | number | string[]) => { const handleChange = (field: keyof BasicInfoData, value: string | number | string[]) => {
setFormData(prev => ({ ...prev, [field]: value })); setFormData(prev => ({ ...prev, [field]: value }));
}; };
@@ -662,7 +782,7 @@ export default function NewDistribution() {
</Button> </Button>
<Button onClick={handleSubmit} disabled={isSubmitting}> <Button onClick={handleSubmit} disabled={isSubmitting}>
{isSubmitting ? "提交中..." : "完成"} {isSubmitting ? (isEditMode ? "更新中..." : "提交中...") : (isEditMode ? "更新完成" : "完成")}
</Button> </Button>
</div> </div>
</div> </div>
@@ -684,13 +804,21 @@ export default function NewDistribution() {
<Layout <Layout
header={ header={
<PageHeader <PageHeader
title="新建流量分发" title={isEditMode ? "编辑流量分发" : "新建流量分发"}
defaultBackPath="/workspace/traffic-distribution" defaultBackPath="/workspace/traffic-distribution"
rightContent={headerRightContent} rightContent={headerRightContent}
/> />
} }
> >
<div className="bg-gray-50 min-h-screen pb-16"> <div className="bg-gray-50 min-h-screen pb-16">
{loading ? (
<div className="flex items-center justify-center h-64">
<div className="text-center">
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mx-auto mb-2"></div>
<div className="text-gray-500">...</div>
</div>
</div>
) : (
<div className="p-4"> <div className="p-4">
<div className="mb-6"> <div className="mb-6">
<Steps current={currentStep}> <Steps current={currentStep}>
@@ -723,6 +851,7 @@ export default function NewDistribution() {
/> />
)} )}
</div> </div>
)}
</div> </div>
</Layout> </Layout>
); );

View File

@@ -10,7 +10,6 @@ import {
Trash2, Trash2,
Pause, Pause,
Play, Play,
Users,
Filter, Filter,
} from 'lucide-react'; } from 'lucide-react';
import { Card } from '@/components/ui/card'; import { Card } from '@/components/ui/card';
@@ -41,7 +40,7 @@ export default function TrafficDistribution() {
const [tasks, setTasks] = useState<DistributionRule[]>([]); const [tasks, setTasks] = useState<DistributionRule[]>([]);
// 处理删除 // 处理删除
const handleDelete = async (ruleId: string) => { const handleDelete = async (ruleId: number) => {
const ruleToDelete = tasks.find((rule) => rule.id === ruleId); const ruleToDelete = tasks.find((rule) => rule.id === ruleId);
if (!ruleToDelete) return; if (!ruleToDelete) return;
@@ -49,7 +48,7 @@ export default function TrafficDistribution() {
try { try {
setIsLoading(true); setIsLoading(true);
const response = await deleteDistributionRule(ruleId); const response = await deleteDistributionRule(ruleId.toString());
if (response.code === 200) { if (response.code === 200) {
setTasks(tasks.filter((rule) => rule.id !== ruleId)); setTasks(tasks.filter((rule) => rule.id !== ruleId));
@@ -76,15 +75,15 @@ export default function TrafficDistribution() {
} }
}; };
const handleEdit = (ruleId: string) => { const handleEdit = (ruleId: number) => {
navigate(`/workspace/traffic-distribution/${ruleId}/edit`); navigate(`/workspace/traffic-distribution/edit/${ruleId}`);
}; };
const handleView = (ruleId: string) => { const handleView = (ruleId: number) => {
navigate(`/workspace/traffic-distribution/${ruleId}`); navigate(`/workspace/traffic-distribution/${ruleId}`);
}; };
const toggleRuleStatus = async (ruleId: string) => { const toggleRuleStatus = async (ruleId: number) => {
const rule = tasks.find((r) => r.id === ruleId); const rule = tasks.find((r) => r.id === ruleId);
if (!rule) return; if (!rule) return;
@@ -93,7 +92,7 @@ export default function TrafficDistribution() {
// 根据当前状态决定新状态1表示开启0表示关闭 // 根据当前状态决定新状态1表示开启0表示关闭
const newStatus = rule.status === WorkbenchTaskStatus.RUNNING ? 0 : 1; const newStatus = rule.status === WorkbenchTaskStatus.RUNNING ? 0 : 1;
const response = await toggleDistributionRuleStatus(ruleId, newStatus as 0 | 1); const response = await toggleDistributionRuleStatus(ruleId.toString(), newStatus as 0 | 1);
if (response.code === 200) { if (response.code === 200) {
// 更新本地状态1对应RUNNING0对应PAUSED // 更新本地状态1对应RUNNING0对应PAUSED
@@ -363,9 +362,9 @@ export default function TrafficDistribution() {
<div className="mt-4 text-sm text-gray-500 flex items-center justify-between"> <div className="mt-4 text-sm text-gray-500 flex items-center justify-between">
<div className="flex items-center"> <div className="flex items-center">
<Clock className="h-4 w-4 mr-1" /> <Clock className="h-4 w-4 mr-1" />
: {rule.lastDistributionTime?.substring(0, 16) || '2025-07-02 09:00'} : {rule.config?.lastUpdated?.substring(0, 16) || '2025-07-02 09:00'}
</div> </div>
<div>: {rule.creator || '售前'}</div> <div>: {rule.createTime || '2025-07-02'}</div>
</div> </div>
</div> </div>
</Card> </Card>