feat: 本次提交更新内容如下
流量分发的功能构建完成
This commit is contained in:
@@ -204,7 +204,7 @@ export const updateDistributionRule = async (id: string, params: any): Promise<A
|
|||||||
* @returns 删除结果
|
* @returns 删除结果
|
||||||
*/
|
*/
|
||||||
export const deleteDistributionRule = async (id: string): Promise<ApiResponse<any>> => {
|
export const deleteDistributionRule = async (id: string): Promise<ApiResponse<any>> => {
|
||||||
return del<ApiResponse<any>>(`/v1/workbench/delete/${id}`);
|
return del<ApiResponse<any>>(`/v1/workbench/delete?id=${id}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,13 +1,11 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect, useCallback } from 'react';
|
||||||
import { Search, Database } from 'lucide-react';
|
import { Search, Database } from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Input } from '@/components/ui/input';
|
import { Input } from '@/components/ui/input';
|
||||||
import { ScrollArea } from '@/components/ui/scroll-area';
|
import { ScrollArea } from '@/components/ui/scroll-area';
|
||||||
import { Checkbox } from '@/components/ui/checkbox';
|
import { Dialog, DialogContent, DialogTitle } from '@/components/ui/dialog';
|
||||||
import { Dialog, DialogContent, DialogHeader, DialogTitle } from '@/components/ui/dialog';
|
|
||||||
import { useToast } from '@/components/ui/toast';
|
import { useToast } from '@/components/ui/toast';
|
||||||
import { fetchDeviceLabels, type TrafficPool, type TrafficPoolListResponse } from '@/api/trafficDistribution';
|
import { fetchDeviceLabels, type TrafficPool } from '@/api/trafficDistribution';
|
||||||
import type { ApiResponse } from '@/types/common';
|
|
||||||
|
|
||||||
// 组件属性接口
|
// 组件属性接口
|
||||||
interface TrafficPoolSelectionProps {
|
interface TrafficPoolSelectionProps {
|
||||||
@@ -34,34 +32,8 @@ export default function TrafficPoolSelection({
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
|
|
||||||
// 当弹窗打开时获取流量池列表
|
|
||||||
useEffect(() => {
|
|
||||||
if (dialogOpen && deviceIds.length > 0) {
|
|
||||||
// 弹窗打开时重置搜索和页码,然后立即请求第一页数据
|
|
||||||
setSearchQuery('');
|
|
||||||
setCurrentPage(1);
|
|
||||||
fetchPools(1, '');
|
|
||||||
}
|
|
||||||
}, [dialogOpen, deviceIds]);
|
|
||||||
|
|
||||||
// 监听页码变化,重新请求数据
|
|
||||||
useEffect(() => {
|
|
||||||
if (dialogOpen && deviceIds.length > 0 && currentPage > 1) {
|
|
||||||
fetchPools(currentPage, searchQuery);
|
|
||||||
}
|
|
||||||
}, [currentPage]);
|
|
||||||
|
|
||||||
// 当设备ID变化时,清空已选择的流量池(如果需要的话)
|
|
||||||
useEffect(() => {
|
|
||||||
if (deviceIds.length === 0) {
|
|
||||||
setPools([]);
|
|
||||||
setTotalPools(0);
|
|
||||||
setTotalPages(1);
|
|
||||||
}
|
|
||||||
}, [deviceIds]);
|
|
||||||
|
|
||||||
// 获取流量池列表API
|
// 获取流量池列表API
|
||||||
const fetchPools = async (page: number, keyword: string = '') => {
|
const fetchPools = useCallback(async (page: number, keyword: string = '') => {
|
||||||
if (deviceIds.length === 0) return;
|
if (deviceIds.length === 0) return;
|
||||||
|
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -136,7 +108,33 @@ export default function TrafficPoolSelection({
|
|||||||
} finally {
|
} finally {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
}
|
}
|
||||||
};
|
}, [deviceIds, toast]);
|
||||||
|
|
||||||
|
// 当弹窗打开时获取流量池列表
|
||||||
|
useEffect(() => {
|
||||||
|
if (dialogOpen && deviceIds.length > 0) {
|
||||||
|
// 弹窗打开时重置搜索和页码,然后立即请求第一页数据
|
||||||
|
setSearchQuery('');
|
||||||
|
setCurrentPage(1);
|
||||||
|
fetchPools(1, '');
|
||||||
|
}
|
||||||
|
}, [dialogOpen, deviceIds, fetchPools]);
|
||||||
|
|
||||||
|
// 监听页码变化,重新请求数据
|
||||||
|
useEffect(() => {
|
||||||
|
if (dialogOpen && deviceIds.length > 0 && currentPage > 1) {
|
||||||
|
fetchPools(currentPage, searchQuery);
|
||||||
|
}
|
||||||
|
}, [currentPage, dialogOpen, deviceIds.length, fetchPools, searchQuery]);
|
||||||
|
|
||||||
|
// 当设备ID变化时,清空已选择的流量池(如果需要的话)
|
||||||
|
useEffect(() => {
|
||||||
|
if (deviceIds.length === 0) {
|
||||||
|
setPools([]);
|
||||||
|
setTotalPools(0);
|
||||||
|
setTotalPages(1);
|
||||||
|
}
|
||||||
|
}, [deviceIds]);
|
||||||
|
|
||||||
// 处理搜索
|
// 处理搜索
|
||||||
const handleSearch = (keyword: string) => {
|
const handleSearch = (keyword: string) => {
|
||||||
@@ -167,16 +165,6 @@ export default function TrafficPoolSelection({
|
|||||||
setDialogOpen(false);
|
setDialogOpen(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
// 处理弹窗关闭
|
|
||||||
const handleDialogClose = (open: boolean) => {
|
|
||||||
setDialogOpen(open);
|
|
||||||
if (!open) {
|
|
||||||
// 弹窗关闭时可以选择性清理状态,这里保留搜索状态以便下次打开时使用
|
|
||||||
// setSearchQuery('');
|
|
||||||
// setCurrentPage(1);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 处理输入框点击
|
// 处理输入框点击
|
||||||
const handleInputClick = () => {
|
const handleInputClick = () => {
|
||||||
if (deviceIds.length === 0) {
|
if (deviceIds.length === 0) {
|
||||||
@@ -207,9 +195,9 @@ export default function TrafficPoolSelection({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 流量池选择弹窗 */}
|
{/* 流量池选择弹窗 */}
|
||||||
<Dialog open={dialogOpen} onOpenChange={handleDialogClose}>
|
<Dialog open={dialogOpen} onOpenChange={setDialogOpen}>
|
||||||
<DialogContent className="max-w-sm w-[90vw] max-h-[90vh] flex flex-col p-0 gap-0 overflow-hidden">
|
<DialogContent className="max-w-sm w-[90vw] max-h-[90vh] flex flex-col p-0 gap-0 overflow-hidden">
|
||||||
<div className="p-6">
|
<div>
|
||||||
<DialogTitle className="text-center text-xl font-medium mb-6">选择流量池</DialogTitle>
|
<DialogTitle className="text-center text-xl font-medium mb-6">选择流量池</DialogTitle>
|
||||||
|
|
||||||
<div className="relative mb-4">
|
<div className="relative mb-4">
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { Steps, StepItem } from 'tdesign-mobile-react';
|
|||||||
import {
|
import {
|
||||||
Users,
|
Users,
|
||||||
Search,
|
Search,
|
||||||
Database,
|
|
||||||
X,
|
X,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
@@ -17,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 } from '@/api/trafficDistribution';
|
import { fetchAccountList, Account, createDistributionRule } from '@/api/trafficDistribution';
|
||||||
import '@/components/Layout.css';
|
import '@/components/Layout.css';
|
||||||
import TrafficPoolSelection from '@/components/TrafficPoolSelection';
|
import TrafficPoolSelection from '@/components/TrafficPoolSelection';
|
||||||
|
|
||||||
@@ -45,13 +44,6 @@ interface FormData {
|
|||||||
trafficPool: Partial<TrafficPoolData>;
|
trafficPool: Partial<TrafficPoolData>;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface TrafficPool {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
count: number;
|
|
||||||
description: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 账号选择对话框组件
|
// 账号选择对话框组件
|
||||||
const AccountSelectionDialog = ({
|
const AccountSelectionDialog = ({
|
||||||
open,
|
open,
|
||||||
@@ -289,6 +281,20 @@ export default function NewDistribution() {
|
|||||||
return `流量分发 ${year}${month}${day} ${hour}${minute}`;
|
return `流量分发 ${year}${month}${day} ${hour}${minute}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 转换分配方式为后端需要的格式
|
||||||
|
const getDistributeType = (distributionMethod: string | undefined) => {
|
||||||
|
switch (distributionMethod) {
|
||||||
|
case 'equal':
|
||||||
|
return 1; // 均分配
|
||||||
|
case 'priority':
|
||||||
|
return 2; // 优先级分配
|
||||||
|
case 'ratio':
|
||||||
|
return 3; // 比例分配
|
||||||
|
default:
|
||||||
|
return 1; // 默认均分配
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleBasicInfoNext = (data: BasicInfoData) => {
|
const handleBasicInfoNext = (data: BasicInfoData) => {
|
||||||
setFormData(prev => ({ ...prev, basicInfo: data }));
|
setFormData(prev => ({ ...prev, basicInfo: data }));
|
||||||
setCurrentStep(1);
|
setCurrentStep(1);
|
||||||
@@ -314,19 +320,48 @@ export default function NewDistribution() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
console.log('提交的数据:', finalData);
|
// 构造API请求参数 - 按照后端要求的格式
|
||||||
|
const apiParams = {
|
||||||
|
type: 5, // 流量分发类型
|
||||||
|
name: finalData.basicInfo.name || '',
|
||||||
|
distributeType: getDistributeType(finalData.basicInfo.distributionMethod),
|
||||||
|
maxPerDay: finalData.basicInfo.dailyLimit || 50,
|
||||||
|
timeType: finalData.basicInfo.timeRestriction === 'allDay' ? 1 : 2,
|
||||||
|
startTime: finalData.basicInfo.timeRestriction === 'custom'
|
||||||
|
? (finalData.basicInfo.startTime || "09:00")
|
||||||
|
: "00:00",
|
||||||
|
endTime: finalData.basicInfo.timeRestriction === 'custom'
|
||||||
|
? (finalData.basicInfo.endTime || "18:00")
|
||||||
|
: "23:59",
|
||||||
|
devices: finalData.targetSettings?.selectedDevices || [],
|
||||||
|
account: finalData.basicInfo?.selectedAccounts || [],
|
||||||
|
pools: finalData.trafficPool?.selectedPools || [],
|
||||||
|
enabled: true // 默认启用
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('提交的数据:', apiParams);
|
||||||
|
|
||||||
toast({
|
const response = await createDistributionRule(apiParams);
|
||||||
title: "创建成功",
|
|
||||||
description: "流量分发规则已成功创建"
|
|
||||||
});
|
|
||||||
|
|
||||||
navigate('/workspace/traffic-distribution');
|
if (response.code === 200) {
|
||||||
|
toast({
|
||||||
|
title: "创建成功",
|
||||||
|
description: "流量分发规则已成功创建"
|
||||||
|
});
|
||||||
|
|
||||||
|
navigate('/workspace/traffic-distribution');
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: "创建失败",
|
||||||
|
description: response.msg || "请稍后重试",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('提交失败:', error);
|
console.error('提交失败:', error);
|
||||||
toast({
|
toast({
|
||||||
title: "创建失败",
|
title: "创建失败",
|
||||||
description: "请稍后重试",
|
description: "网络错误,请稍后重试",
|
||||||
variant: "destructive"
|
variant: "destructive"
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -589,14 +624,6 @@ export default function NewDistribution() {
|
|||||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||||
|
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (selectedPools.length === 0) {
|
|
||||||
toast({
|
|
||||||
title: "请选择至少一个流量池",
|
|
||||||
variant: "destructive"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSubmitting(true);
|
setIsSubmitting(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -634,7 +661,7 @@ export default function NewDistribution() {
|
|||||||
<Button variant="outline" onClick={onBack}>
|
<Button variant="outline" onClick={onBack}>
|
||||||
← 上一步
|
← 上一步
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={handleSubmit} disabled={isSubmitting || selectedPools.length === 0}>
|
<Button onClick={handleSubmit} disabled={isSubmitting}>
|
||||||
{isSubmitting ? "提交中..." : "完成"}
|
{isSubmitting ? "提交中..." : "完成"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user