| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230 |
- // src/app/admin/tasks/page.tsx
- 'use client';
- import { useState, useEffect } from 'react';
- import api from '@/lib/api';
- import { RefreshCw, Search } from 'lucide-react';
- // 组件导入
- import TaskTable, { VasTask } from '@/components/admin/tasks/TaskTable';
- import TaskDetailModal from '@/components/admin/tasks/TaskDetailModal';
- import TaskEditModal from '@/components/admin/tasks/TaskEditModal';
- import Pagination from '@/components/common/Pagination';
- import ExpiringTaskAlert from '@/components/admin/tasks/ExpiringTaskAlert'; // [新增] 紧急任务组件
- export default function AdminTasksPage() {
- // === 状态定义 ===
- const [tasks, setTasks] = useState<VasTask[]>([]);
- const [loading, setLoading] = useState(true);
-
- // 分页、搜索、筛选状态
- const [page, setPage] = useState(1);
- const [pageSize] = useState(10);
- const [total, setTotal] = useState(0);
- const [keyword, setKeyword] = useState('');
- const [statusFilter, setStatusFilter] = useState('all');
- // 弹窗状态
- const [selectedTask, setSelectedTask] = useState<VasTask | null>(null);
- const [isDetailOpen, setIsDetailOpen] = useState(false);
- const [isEditOpen, setIsEditOpen] = useState(false);
- // === 数据获取逻辑 ===
- const fetchTasks = async (targetPage: number = page) => {
- setLoading(true);
- try {
- const params: any = {
- keyword,
- page: targetPage,
- size: pageSize,
- };
-
- if (statusFilter !== 'all') {
- params.status = statusFilter;
- }
- const res = await api.get('/api/vas/task/list', { params });
-
- const data = res.data.data || {};
-
- // 兼容两种常见的分页返回格式
- if (Array.isArray(data)) {
- setTasks(data);
- setTotal(data.length);
- } else {
- setTasks(data.items || []);
- setTotal(data.total || 0);
- }
-
- setPage(targetPage);
- } catch (e) {
- console.warn("API Error, maybe using mock data or network failed");
- setTasks([]);
- setTotal(0);
- } finally {
- setLoading(false);
- }
- };
- // 监听筛选条件变化,自动刷新
- useEffect(() => {
- fetchTasks(1);
- }, [statusFilter]);
- // === 事件处理 ===
- const handleSearch = () => {
- fetchTasks(1);
- };
- const handleKeyDown = (e: React.KeyboardEvent) => {
- if (e.key === 'Enter') handleSearch();
- };
- // 重置任务回队列
- const handleRetry = async (taskId: number) => {
- if(!confirm("确定要重置该任务回队列吗?状态将变为 pending。")) return;
- try {
- await api.post('/api/vas/task/return_to_queue', null, { params: { task_id: taskId } });
- alert("操作成功");
- fetchTasks(page);
- } catch (e) {
- alert("操作失败");
- }
- };
- // 强制标记完成
- const handleConfirm = async (taskId: number) => {
- if(!confirm("确定要强制标记完成吗?这将跳过后续脚本执行。")) return;
- try {
- await api.post('/api/vas/task/manual_confirm', null, { params: { task_id: taskId } });
- alert("操作成功");
- fetchTasks(page);
- } catch (e) {
- alert("操作失败");
- }
- };
- // 打开详情弹窗
- const handleViewDetail = (task: VasTask) => {
- setSelectedTask(task);
- setIsDetailOpen(true);
- };
- // 打开编辑弹窗
- const handleEdit = (task: VasTask) => {
- setSelectedTask(task);
- setIsEditOpen(true);
- };
- // 提交编辑
- const handleSubmitEdit = async (taskId: number, data: any) => {
- try {
- await api.post('/api/vas/task/update', data, {params: {"id": taskId}});
- alert("任务更新成功");
- setIsEditOpen(false);
- fetchTasks(page); // 刷新列表
- } catch (e: any) {
- alert("更新失败: " + (e.response?.data?.message || "未知错误"));
- }
- };
- return (
- <div className="p-4 md:p-6">
-
- {/* === 1. 头部区域 === */}
- <div className="flex flex-col md:flex-row md:items-center justify-between gap-4 mb-6">
-
- {/* 标题 */}
- <div>
- <h1 className="text-2xl font-bold text-slate-800">系统任务队列</h1>
- <p className="text-sm text-slate-500 mt-1">监控机器人执行状态及调试日志</p>
- </div>
-
- {/* 操作区:筛选、搜索、刷新 */}
- <div className="flex flex-col sm:flex-row gap-3 w-full md:w-auto">
-
- {/* 状态筛选下拉框 */}
- <select
- className="w-full sm:w-auto border border-slate-300 rounded-lg text-sm px-3 py-2 outline-none focus:ring-2 focus:ring-blue-500 bg-white appearance-none cursor-pointer"
- value={statusFilter}
- onChange={(e) => setStatusFilter(e.target.value)}
- >
- <option value="all">所有状态 (All)</option>
- <option value="pending">等待中 (Pending)</option>
- <option value="running">运行中 (Running)</option>
- <option value="grabbed">待确认 (Grabbed)</option>
- <option value="completed">已完成 (Completed)</option>
- <option value="cancelled">已取消 (Cancelled)</option>
- </select>
- {/* 搜索框组 */}
- <div className="flex gap-2 w-full sm:w-auto">
- <div className="relative flex-1 sm:w-64">
- <input
- type="text"
- placeholder="Order / Route / User Inputs"
- className="w-full pl-9 pr-4 py-2 border border-slate-300 rounded-lg text-sm focus:ring-2 focus:ring-blue-500 outline-none transition"
- value={keyword}
- onChange={e => setKeyword(e.target.value)}
- onKeyDown={handleKeyDown}
- />
- <Search size={16} className="absolute left-3 top-2.5 text-gray-400" />
- </div>
- <button
- onClick={handleSearch}
- className="flex-shrink-0 p-2 bg-white border border-slate-200 rounded-lg hover:bg-slate-50 text-slate-600 transition"
- title="刷新列表"
- >
- <RefreshCw size={18} />
- </button>
- </div>
-
- </div>
- </div>
- {/* === [新增] 2. 紧急任务预警区域 === */}
- {/* 如果有即将过期的任务,这个组件会自动显示;否则不渲染 */}
- <ExpiringTaskAlert
- onViewDetail={handleViewDetail}
- onEdit={handleEdit}
- />
- {/* === 3. 主表格区域 === */}
- <TaskTable
- tasks={tasks}
- loading={loading}
- onRetry={handleRetry}
- onManualConfirm={handleConfirm}
- onViewDetail={handleViewDetail}
- onEdit={handleEdit}
- />
- {/* === 4. 分页区域 === */}
- <div className="mt-4">
- <Pagination
- currentPage={page}
- total={total}
- pageSize={pageSize}
- onPageChange={(p) => fetchTasks(p)}
- />
- </div>
- {/* === 5. 弹窗组件 === */}
- <TaskDetailModal
- isOpen={isDetailOpen}
- onClose={() => setIsDetailOpen(false)}
- task={selectedTask}
- />
- <TaskEditModal
- isOpen={isEditOpen}
- onClose={() => setIsEditOpen(false)}
- task={selectedTask}
- onSubmit={handleSubmitEdit}
- />
- </div>
- );
- }
|