From b4797425fc74cc0c63b6f7b8f624b38d4fbb8715 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=AC=94=E8=AE=B0=E6=9C=AC=E9=87=8C=E7=9A=84=E6=B0=B8?= =?UTF-8?q?=E5=B9=B3?= Date: Thu, 10 Jul 2025 10:57:26 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9C=AC=E6=AC=A1=E6=8F=90=E4=BA=A4?= =?UTF-8?q?=E6=9B=B4=E6=96=B0=E5=86=85=E5=AE=B9=E5=A6=82=E4=B8=8B=20?= =?UTF-8?q?=E9=80=89=E6=8B=A9=E8=AE=BE=E5=A4=87=E5=BC=B9=E7=AA=97=E6=9E=84?= =?UTF-8?q?=E5=BB=BA=E5=AE=8C=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nkebao/src/api/index.ts | 1 + nkebao/src/api/request.ts | 2 +- nkebao/src/api/trafficDistribution.ts | 125 ++++ nkebao/src/components/BottomNav.tsx | 16 +- .../src/components/DeviceSelectionDialog.tsx | 9 +- nkebao/src/components/ui/dialog.tsx | 2 +- .../src/pages/workspace/moments-sync/new.tsx | 562 ++++++++---------- .../TrafficDistribution.tsx | 502 +++++++++------- nkebao/src/types/device.ts | 1 + 9 files changed, 664 insertions(+), 556 deletions(-) create mode 100644 nkebao/src/api/trafficDistribution.ts diff --git a/nkebao/src/api/index.ts b/nkebao/src/api/index.ts index 9bfffc5a..2134d044 100644 --- a/nkebao/src/api/index.ts +++ b/nkebao/src/api/index.ts @@ -8,6 +8,7 @@ export * from './request'; export * from './devices'; export * from './scenarios'; export * from './wechat-accounts'; +export * from './trafficDistribution'; // 默认导出request实例 export { default as request } from './request'; \ No newline at end of file diff --git a/nkebao/src/api/request.ts b/nkebao/src/api/request.ts index af971f10..fc598b49 100644 --- a/nkebao/src/api/request.ts +++ b/nkebao/src/api/request.ts @@ -3,7 +3,7 @@ import { requestInterceptor, responseInterceptor, errorInterceptor } from './int // 创建axios实例 const request: AxiosInstance = axios.create({ - baseURL: process.env.REACT_APP_API_BASE_URL || 'http://www.yishi.com', + baseURL: process.env.REACT_APP_API_BASE_URL || 'https://ckbapi.quwanzhi.com', timeout: 20000, headers: { 'Content-Type': 'application/json', diff --git a/nkebao/src/api/trafficDistribution.ts b/nkebao/src/api/trafficDistribution.ts new file mode 100644 index 00000000..3514e70e --- /dev/null +++ b/nkebao/src/api/trafficDistribution.ts @@ -0,0 +1,125 @@ +import { get, post, put, del } from './request'; +import type { ApiResponse } from '@/types/common'; + +// 工作台任务类型 +export enum WorkbenchTaskType { + MOMENTS_SYNC = 1, // 朋友圈同步 + GROUP_PUSH = 2, // 社群推送 + AUTO_LIKE = 3, // 自动点赞 + AUTO_GROUP = 4, // 自动建群 + TRAFFIC_DISTRIBUTION = 5, // 流量分发 +} + +// 工作台任务状态 +export enum WorkbenchTaskStatus { + PENDING = 0, // 待处理 + RUNNING = 1, // 运行中 + PAUSED = 2, // 已暂停 + COMPLETED = 3, // 已完成 + FAILED = 4, // 失败 +} + +// 流量分发规则类型 +export interface DistributionRule { + id: string; + name: string; + status: number; + deviceCount: number; + totalTraffic: number; + distributedTraffic: number; + lastDistributionTime: string; + createTime: string; + creator: string; + distributionInterval: number; + maxDistributionPerDay: number; + timeRange: { start: string; end: string }; + targetChannels?: string[]; + distributionRatio?: Record; + priority?: 'high' | 'medium' | 'low'; + filterConditions?: string[]; +} + +// 流量分发列表响应类型 +export interface TrafficDistributionListResponse { + list: DistributionRule[]; + total: number; + page: number; + limit: number; +} + +/** + * 获取流量分发规则列表 + * @param params 查询参数 + * @returns 流量分发规则列表 + */ +export const fetchDistributionRules = async (params: { + page?: number; + limit?: number; + keyword?: string; +} = {}): Promise> => { + const { page = 1, limit = 10, keyword = "" } = params; + + const queryParams = new URLSearchParams(); + queryParams.append('type', WorkbenchTaskType.TRAFFIC_DISTRIBUTION.toString()); + queryParams.append('page', page.toString()); + queryParams.append('limit', limit.toString()); + + if (keyword) { + queryParams.append('keyword', keyword); + } + + return get>(`/v1/workbench/list?${queryParams.toString()}`); +}; + +/** + * 获取流量分发规则详情 + * @param id 规则ID + * @returns 流量分发规则详情 + */ +export const fetchDistributionRuleDetail = async (id: string): Promise> => { + return get>(`/v1/workbench/detail/${id}`); +}; + +/** + * 创建流量分发规则 + * @param params 创建参数 + * @returns 创建结果 + */ +export const createDistributionRule = async (params: any): Promise> => { + return post>('/v1/workbench/create', { + ...params, + type: WorkbenchTaskType.TRAFFIC_DISTRIBUTION + }); +}; + +/** + * 更新流量分发规则 + * @param id 规则ID + * @param params 更新参数 + * @returns 更新结果 + */ +export const updateDistributionRule = async (id: string, params: any): Promise> => { + return put>(`/v1/workbench/update/${id}`, { + ...params, + type: WorkbenchTaskType.TRAFFIC_DISTRIBUTION + }); +}; + +/** + * 删除流量分发规则 + * @param id 规则ID + * @returns 删除结果 + */ +export const deleteDistributionRule = async (id: string): Promise> => { + return del>(`/v1/workbench/delete/${id}`); +}; + +/** + * 启动/暂停流量分发规则 + * @param id 规则ID + * @param status 状态:1-启动,2-暂停 + * @returns 操作结果 + */ +export const toggleDistributionRuleStatus = async (id: string, status: WorkbenchTaskStatus.RUNNING | WorkbenchTaskStatus.PAUSED): Promise> => { + return put>(`/v1/workbench/status/${id}`, { status }); +}; \ No newline at end of file diff --git a/nkebao/src/components/BottomNav.tsx b/nkebao/src/components/BottomNav.tsx index 046bf0b8..f8e42433 100644 --- a/nkebao/src/components/BottomNav.tsx +++ b/nkebao/src/components/BottomNav.tsx @@ -3,13 +3,17 @@ import { Link, useLocation } from 'react-router-dom'; import { Home, Users, User, Briefcase } from 'lucide-react'; const navItems = [ - { href: "/", icon: Home, label: "首页" }, - { href: "/scenarios", icon: Users, label: "场景获客" }, - { href: "/workspace", icon: Briefcase, label: "工作台" }, - { href: "/profile", icon: User, label: "我的" }, + { href: "/", icon: Home, label: "首页", id: "home" }, + { href: "/scenarios", icon: Users, label: "场景获客", id: "scenarios" }, + { href: "/workspace", icon: Briefcase, label: "工作台", id: "workspace" }, + { href: "/profile", icon: User, label: "我的", id: "profile" }, ]; -export default function BottomNav() { +interface BottomNavProps { + activeTab?: string; +} + +export default function BottomNav({ activeTab }: BottomNavProps) { const location = useLocation(); return ( @@ -17,7 +21,7 @@ export default function BottomNav() {
{navItems.map((item) => { const IconComponent = item.icon; - const isActive = location.pathname === item.href; + const isActive = activeTab ? activeTab === item.id : location.pathname === item.href; return ( - + 选择设备 @@ -102,7 +104,7 @@ export function DeviceSelectionDialog({
setSearchQuery(e.target.value)} className="pl-9" @@ -153,7 +155,8 @@ export function DeviceSelectionDialog({
IMEI: {device.imei}
-
微信号: {device.wxid}
+
微信号: {device.wxid||'-'}
+
昵称: {device.nickname||'-'}
{device.usedInPlans > 0 && (
已用于 {device.usedInPlans} 个计划
diff --git a/nkebao/src/components/ui/dialog.tsx b/nkebao/src/components/ui/dialog.tsx index c90f07ab..bebe71a4 100644 --- a/nkebao/src/components/ui/dialog.tsx +++ b/nkebao/src/components/ui/dialog.tsx @@ -41,7 +41,7 @@ interface DialogContentProps { export function DialogContent({ children, className = '' }: DialogContentProps) { return ( -
+
{children}
); diff --git a/nkebao/src/pages/workspace/moments-sync/new.tsx b/nkebao/src/pages/workspace/moments-sync/new.tsx index ba65e2f2..75fcc9c3 100644 --- a/nkebao/src/pages/workspace/moments-sync/new.tsx +++ b/nkebao/src/pages/workspace/moments-sync/new.tsx @@ -5,8 +5,7 @@ import { Input } from '@/components/ui/input'; import { Switch } from '@/components/ui/switch'; import { useToast } from '@/components/ui/toast'; import { createMomentsSyncTask, updateMomentsSyncTask, fetchMomentsSyncTaskDetail } from '@/api/momentsSync'; -import { ChevronLeft, Clock, Search } from 'lucide-react'; -import Layout from '@/components/Layout'; +import { ChevronLeft } from 'lucide-react'; import { DeviceSelectionDialog } from '@/components/DeviceSelectionDialog'; import { ContentLibrarySelectionDialog } from '@/components/ContentLibrarySelectionDialog'; import { ContentType } from '@/types/moments-sync'; @@ -18,230 +17,33 @@ interface StepIndicatorProps { function StepIndicator({ currentStep }: StepIndicatorProps) { const steps = [ - { id: 1, title: "步骤 1", subtitle: "基础设置" }, - { id: 2, title: "步骤 2", subtitle: "设备选择" }, - { id: 3, title: "步骤 3", subtitle: "选择内容库" }, + { id: 1, title: "基础设置" }, + { id: 2, title: "设备选择" }, + { id: 3, title: "选择内容库" }, ]; return ( -
- {/* 背景连线 */} -
-
1 ? `${((currentStep - 1) / (steps.length - 1)) * 100}%` : '0%' - }} - /> -
- - {/* 步骤圆圈 */} - {steps.map((step) => ( -
= step.id ? "text-blue-600" : "text-gray-400" - }`} - > -
= step.id - ? "bg-blue-600 text-white shadow-sm" - : "bg-white border-2 border-gray-200 text-gray-400" +
+ {steps.map((step, index) => ( +
+
- {step.id} + {index + 1} +
+
+ {step.title}
-
{step.subtitle}
))}
); } -// 基础设置组件 -interface BasicSettingsProps { - formData: { - taskName: string; - startTime: string; - endTime: string; - syncCount: number; - interval: number; - accountType: "business" | "personal"; - enabled: boolean; - contentTypes: ContentType[]; - targetTags: string[]; - filterKeywords: string[]; - }; - onChange: (data: Partial) => void; - onNext: () => void; -} - -function BasicSettings({ formData, onChange, onNext }: BasicSettingsProps) { - return ( -
-
-
-
任务名称
- onChange({ taskName: e.target.value })} - placeholder="请输入任务名称" - className="h-12 border-0 border-b border-gray-200 rounded-none focus-visible:ring-0 focus-visible:border-blue-600 px-0 text-base" - /> -
- -
-
允许发布时间段
-
-
- onChange({ startTime: e.target.value })} - className="h-12 pl-10 rounded-xl border-gray-200 text-base" - /> - -
- -
- onChange({ endTime: e.target.value })} - className="h-12 pl-10 rounded-xl border-gray-200 text-base" - /> - -
-
-
- -
-
每日同步数量
-
- - {formData.syncCount} - - 条朋友圈 -
-
- -
-
同步间隔
-
- - {formData.interval} - - 分钟 -
-
- -
-
账号类型
-
-
- -
-
- -
-
-
- -
-
内容类型
-
- {(['text', 'image', 'video'] as const).map((type) => ( -
- -
- ))} -
-
- -
- 是否启用 - onChange({ enabled: checked })} - className="data-[state=checked]:bg-blue-600 h-7 w-12" - /> -
-
- - -
- ); -} - export default function NewMomentsSyncTask() { const navigate = useNavigate(); const { id } = useParams<{ id: string }>(); @@ -364,118 +166,236 @@ export default function NewMomentsSyncTask() { } }; - return ( - -
- -

{isEditMode ? '编辑朋友圈同步' : '新建朋友圈同步'}

+ // 基础设置步骤内容 + const renderBasicSettings = () => { + return ( +
+
+
任务名称
+ handleUpdateFormData({ taskName: e.target.value })} + placeholder="请输入任务名称" + className="h-12 rounded-lg border border-gray-200 focus-visible:ring-0 focus-visible:border-blue-600" + /> +
+ +
+
每日同步数量
+
+ +
+ {formData.syncCount} +
+ + 条/天
- } - > -
-
-
- -
- {currentStep === 1 && ( - - )} - {currentStep === 2 && ( -
-
- - setDeviceDialogOpen(true)} - readOnly - /> -
- {formData.selectedDevices.length > 0 && ( -
- 已选设备:{formData.selectedDevices.length} 个 -
- {formData.selectedDevices.map(id => { - // 这里可以根据实际API获取设备名称 - return `设备 ${id}`; - }).join(', ')} -
-
- )} -
- - -
- { - handleUpdateFormData({ selectedDevices: devices }); - }} - /> -
- )} - {currentStep === 3 && ( -
-
- - setContentLibraryDialogOpen(true)} - readOnly - /> -
- {formData.selectedLibraries.length > 0 && ( -
- 已选内容库:{formData.selectedLibraries.length} 个 -
- {formData.selectedLibraries.map(id => { - // 这里可以根据实际API获取内容库名称 - return `朋友圈内容库${id}`; - }).join(', ')} -
-
- )} -
- - -
- { - handleUpdateFormData({ selectedLibraries: libraries }); - }} - /> -
- )} + +
+
同步间隔
+
+ +
+ {formData.interval} +
+ + 分钟 +
+
设置每次发朋友圈的时间间隔
+
+ +
+
同步时间
+
+
+ handleUpdateFormData({ startTime: e.target.value })} + className="h-12 rounded-lg border-gray-200 text-base" + /> +
+ +
+ handleUpdateFormData({ endTime: e.target.value })} + className="h-12 rounded-lg border-gray-200 text-base" + />
+ +
+
账号类型
+
+ + +
+
+ +
+ 是否启用 + handleUpdateFormData({ enabled: checked })} + className="data-[state=checked]:bg-blue-600 h-7 w-12" + /> +
+ +
- + ); + }; + + return ( +
+
+
+ +

新建朋友圈同步

+
+
+ +
+ + + {currentStep === 1 && renderBasicSettings()} + + {currentStep === 2 && ( +
+ setDeviceDialogOpen(true)} + readOnly + /> + + {formData.selectedDevices.length > 0 && ( +
+ 已选设备:{formData.selectedDevices.length} 个 +
+ )} + +
+ + +
+ + { + handleUpdateFormData({ selectedDevices: devices }); + }} + /> +
+ )} + + {currentStep === 3 && ( +
+ setContentLibraryDialogOpen(true)} + readOnly + /> + + {formData.selectedLibraries.length > 0 && ( +
+ 已选内容库:{formData.selectedLibraries.length} 个 +
+ )} + +
+ + +
+ + { + handleUpdateFormData({ selectedLibraries: libraries }); + }} + /> +
+ )} +
+ +
+
+ +
+
+
); } \ No newline at end of file diff --git a/nkebao/src/pages/workspace/traffic-distribution/TrafficDistribution.tsx b/nkebao/src/pages/workspace/traffic-distribution/TrafficDistribution.tsx index 6750c7b1..2c521090 100644 --- a/nkebao/src/pages/workspace/traffic-distribution/TrafficDistribution.tsx +++ b/nkebao/src/pages/workspace/traffic-distribution/TrafficDistribution.tsx @@ -10,6 +10,7 @@ import { Edit, Trash2, Pause, + Play, Users, Share2, } from 'lucide-react'; @@ -23,70 +24,57 @@ import PageHeader from '@/components/PageHeader'; import BottomNav from '@/components/BottomNav'; import { useToast } from '@/components/ui/toast'; import '@/components/Layout.css'; - -interface DistributionRule { - id: string; - name: string; - status: 'running' | 'paused' | 'completed'; - deviceCount: number; - totalTraffic: number; - distributedTraffic: number; - lastDistributionTime: string; - createTime: string; - creator: string; - distributionInterval: number; - maxDistributionPerDay: number; - timeRange: { start: string; end: string }; - targetChannels: string[]; - distributionRatio: Record; - priority: 'high' | 'medium' | 'low'; - filterConditions: string[]; -} +import { + fetchDistributionRules, + deleteDistributionRule, + toggleDistributionRuleStatus, + DistributionRule, + WorkbenchTaskStatus +} from '@/api/trafficDistribution'; export default function TrafficDistribution() { const navigate = useNavigate(); const { toast } = useToast(); - // 移除expandedRuleId状态 const [searchTerm, setSearchTerm] = useState(''); const [isLoading, setIsLoading] = useState(false); - const [tasks, setTasks] = useState([ - { - id: '1', - name: '流量分发', - deviceCount: 2, - totalTraffic: 2, - distributedTraffic: 125, - lastDistributionTime: '2025-07-02 09:00', - createTime: '2024-11-20 19:04:14', - creator: '售前', - status: 'running', - distributionInterval: 300, - maxDistributionPerDay: 2000, - timeRange: { start: '08:00', end: '22:00' }, - targetChannels: ['抖音', '小红书', '公众号'], - distributionRatio: { - '抖音': 40, - '小红书': 35, - '公众号': 25, - }, - priority: 'high', - filterConditions: ['VIP客户', '高价值'], - }, - ]); + const [currentPage, setCurrentPage] = useState(1); + const [totalItems, setTotalItems] = useState(0); + const [tasks, setTasks] = useState([]); - // 移除展开功能 - - const handleDelete = (ruleId: string) => { + // 处理删除 + const handleDelete = async (ruleId: string) => { const ruleToDelete = tasks.find((rule) => rule.id === ruleId); if (!ruleToDelete) return; if (!window.confirm(`确定要删除"${ruleToDelete.name}"吗?`)) return; - setTasks(tasks.filter((rule) => rule.id !== ruleId)); - toast({ - title: '删除成功', - description: '已成功删除分发规则', - }); + try { + setIsLoading(true); + const response = await deleteDistributionRule(ruleId); + + if (response.code === 200) { + setTasks(tasks.filter((rule) => rule.id !== ruleId)); + toast({ + title: '删除成功', + description: '已成功删除分发规则', + }); + } else { + toast({ + title: '删除失败', + description: response.msg || '操作失败,请稍后重试', + variant: 'destructive', + }); + } + } catch (error) { + console.error('删除流量分发规则失败:', error); + toast({ + title: '删除失败', + description: '操作失败,请稍后重试', + variant: 'destructive', + }); + } finally { + setIsLoading(false); + } }; const handleEdit = (ruleId: string) => { @@ -97,57 +85,86 @@ export default function TrafficDistribution() { navigate(`/workspace/traffic-distribution/${ruleId}`); }; - const handleCopy = (ruleId: string) => { + const handleCopy = async (ruleId: string) => { const ruleToCopy = tasks.find((rule) => rule.id === ruleId); if (ruleToCopy) { - const newRule = { - ...ruleToCopy, - id: `${Date.now()}`, - name: `${ruleToCopy.name} (复制)`, - createTime: new Date().toISOString().replace('T', ' ').substring(0, 19), - }; - setTasks([...tasks, newRule]); - toast({ - title: '复制成功', - description: '已成功复制分发规则', - }); + try { + // 这里可以添加复制API调用 + toast({ + title: '复制成功', + description: '已成功复制分发规则', + }); + // 重新加载列表 + fetchData(); + } catch (error) { + console.error('复制流量分发规则失败:', error); + toast({ + title: '复制失败', + description: '操作失败,请稍后重试', + variant: 'destructive', + }); + } } }; - const toggleRuleStatus = (ruleId: string) => { + const toggleRuleStatus = async (ruleId: string) => { const rule = tasks.find((r) => r.id === ruleId); if (!rule) return; - setTasks( - tasks.map((rule) => - rule.id === ruleId ? { ...rule, status: rule.status === 'running' ? 'paused' : 'running' } : rule, - ), - ); + try { + setIsLoading(true); + const newStatus = rule.status === WorkbenchTaskStatus.RUNNING + ? WorkbenchTaskStatus.PAUSED + : WorkbenchTaskStatus.RUNNING; + + const response = await toggleDistributionRuleStatus(ruleId, newStatus); + + if (response.code === 200) { + setTasks( + tasks.map((task) => + task.id === ruleId ? { ...task, status: newStatus } : task + ) + ); - toast({ - title: rule.status === 'running' ? '已暂停' : '已启动', - description: `${rule.name}规则${rule.status === 'running' ? '已暂停' : '已启动'}`, - }); + toast({ + title: newStatus === WorkbenchTaskStatus.RUNNING ? '已启动' : '已暂停', + description: `${rule.name}规则${newStatus === WorkbenchTaskStatus.RUNNING ? '已启动' : '已暂停'}`, + }); + } else { + toast({ + title: '操作失败', + description: response.msg || '操作失败,请稍后重试', + variant: 'destructive', + }); + } + } catch (error) { + console.error('切换流量分发规则状态失败:', error); + toast({ + title: '操作失败', + description: '操作失败,请稍后重试', + variant: 'destructive', + }); + } finally { + setIsLoading(false); + } }; const handleCreateNew = () => { navigate('/workspace/traffic-distribution/new'); - toast({ - title: '创建新分发', - description: '正在前往创建页面', - }); }; // 添加卡片菜单组件 type CardMenuProps = { + rule: DistributionRule; onEdit: () => void; - onPause: () => void; + onToggleStatus: () => void; onDelete: () => void; }; - function CardMenu({ onEdit, onPause, onDelete }: CardMenuProps) { + function CardMenu({ rule, onEdit, onToggleStatus, onDelete }: CardMenuProps) { const [open, setOpen] = useState(false); const menuRef = useRef(null); + const isRunning = rule.status === WorkbenchTaskStatus.RUNNING; useEffect(() => { function handleClickOutside(event: MouseEvent) { @@ -182,8 +199,9 @@ export default function TrafficDistribution() {
{ onEdit(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}> 编辑计划
-
{ onPause(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}> - 暂停计划 +
{ onToggleStatus(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}> + {isRunning ? : } + {isRunning ? '暂停计划' : '启动计划'}
{ onDelete(); setOpen(false); }} style={{ padding: 8, cursor: "pointer", display: "flex", alignItems: "center", borderRadius: 6, fontSize: 14, gap: 6, color: "#e53e3e", transition: "background .2s" }} onMouseOver={e => (e.currentTarget as HTMLDivElement).style.background="#f5f5f5"} onMouseOut={e => (e.currentTarget as HTMLDivElement).style.background=""}> 删除计划 @@ -198,180 +216,216 @@ export default function TrafficDistribution() { rule.name.toLowerCase().includes(searchTerm.toLowerCase()), ); - const getStatusColor = (status: string) => { + const getStatusColor = (status: number) => { switch (status) { - case 'running': + case WorkbenchTaskStatus.RUNNING: return 'bg-green-100 text-green-800'; - case 'paused': + case WorkbenchTaskStatus.PAUSED: return 'bg-gray-100 text-gray-800'; - case 'completed': + case WorkbenchTaskStatus.COMPLETED: return 'bg-blue-100 text-blue-800'; + case WorkbenchTaskStatus.FAILED: + return 'bg-red-100 text-red-800'; default: return 'bg-gray-100 text-gray-800'; } }; - const getStatusText = (status: string) => { + const getStatusText = (status: number) => { switch (status) { - case 'running': + case WorkbenchTaskStatus.RUNNING: return '进行中'; - case 'paused': + case WorkbenchTaskStatus.PAUSED: return '已暂停'; - case 'completed': + case WorkbenchTaskStatus.COMPLETED: return '已完成'; + case WorkbenchTaskStatus.FAILED: + return '已失败'; + case WorkbenchTaskStatus.PENDING: + return '待处理'; default: return '未知'; } }; - // 模拟加载数据 - useEffect(() => { - const fetchData = async () => { - setIsLoading(true); - try { - // 这里可以添加实际的API调用 - // const response = await fetch('/api/traffic-distribution'); - // const data = await response.json(); - // setTasks(data); - - // 模拟加载延迟 - await new Promise(resolve => setTimeout(resolve, 500)); - } catch (error) { - console.error('获取流量分发数据失败:', error); + // 加载数据 + const fetchData = async (page = currentPage, keyword = searchTerm) => { + setIsLoading(true); + try { + const response = await fetchDistributionRules({ + page, + limit: 10, + keyword + }); + + if (response.code === 200 && response.data) { + setTasks(response.data.list); + setTotalItems(response.data.total); + setCurrentPage(response.data.page); + } else { toast({ title: '获取数据失败', - description: '无法获取流量分发数据,请稍后重试', + description: response.msg || '无法获取流量分发数据,请稍后重试', variant: 'destructive', }); - } finally { - setIsLoading(false); } - }; + } catch (error) { + console.error('获取流量分发数据失败:', error); + toast({ + title: '获取数据失败', + description: '无法获取流量分发数据,请稍后重试', + variant: 'destructive', + }); + } finally { + setIsLoading(false); + } + }; + // 初始加载和搜索 + useEffect(() => { + fetchData(1, searchTerm); + }, []); + + // 处理搜索 + const handleSearch = () => { + fetchData(1, searchTerm); + }; + + // 处理刷新 + const handleRefresh = () => { fetchData(); - }, [toast]); + }; return ( - + header={} + footer={} + > +
+
+
+
+ + setSearchTerm(e.target.value)} + className="pl-9 h-10 w-48" + /> +
+ +
+
+ + - } - /> - } - footer={} - > -
-
- {/* 搜索和筛选 */} - -
-
- - setSearchTerm(e.target.value)} - /> -
- - -
-
- - {/* 规则列表 */} -
- {isLoading ? ( - // 加载状态 -
-
-
- ) : filteredRules.length === 0 ? ( - - -

暂无分发计划

-

创建您的第一个流量分发计划

- -
- ) : ( - filteredRules.map((rule) => ( - -
-
-

{rule.name}

-
- - 进行中 - - toggleRuleStatus(rule.id)} - disabled={rule.status === 'completed'} - /> - handleEdit(rule.id)} - onPause={() => toggleRuleStatus(rule.id)} - onDelete={() => handleDelete(rule.id)} - /> -
-
-
- - {/* 统计数据 - 第一行 */} -
-
-
2
-
分发账号
-
-
-
7
-
分发设备
-
-
-
ALL
-
流量池
-
-
- - {/* 统计数据 - 第二行 */} -
-
-
125
-
日均分发量
-
-
-
2
-
总流量池数量
-
-
- - {/* 底部信息 */} -
-
- - 上次执行: {rule.lastDistributionTime} -
-
创建人: {rule.creator}
-
-
- )) - )}
+ + {isLoading ? ( +
+ {[1, 2, 3].map((i) => ( + +
+
+
+
+
+
+
+ ))} +
+ ) : filteredRules.length > 0 ? ( +
+ {filteredRules.map((rule) => ( + +
+
+

handleView(rule.id)} + > + {rule.name} +

+ handleEdit(rule.id)} + onToggleStatus={() => toggleRuleStatus(rule.id)} + onDelete={() => handleDelete(rule.id)} + /> +
+
+ + 创建于 {rule.createTime?.substring(0, 16) || '未知时间'} +
+
+ + {getStatusText(rule.status)} + +
+ {rule.distributedTraffic || 0} + / + {rule.totalTraffic || 0} +
+
+
+
+
+ + {rule.deviceCount || 0} 个设备 +
+
+ toggleRuleStatus(rule.id)} + className="data-[state=checked]:bg-blue-600" + /> +
+
+
+ ))} +
+ ) : ( +
+
暂无流量分发规则
+ +
+ )} + + {totalItems > 10 && ( +
+
+ +
+ 第 {currentPage} 页,共 {Math.ceil(totalItems / 10)} 页 +
+ +
+
+ )}
); diff --git a/nkebao/src/types/device.ts b/nkebao/src/types/device.ts index 968acefc..b6c5f9fe 100644 --- a/nkebao/src/types/device.ts +++ b/nkebao/src/types/device.ts @@ -20,6 +20,7 @@ export interface ServerDevice { wechatId: string; alive: number; totalFriend: number; + nickname: string; } // 服务端API返回的设备列表响应