diff --git a/src/X1.WebUI/modify.md b/src/X1.WebUI/modify.md index 8ed0118..f414eb6 100644 --- a/src/X1.WebUI/modify.md +++ b/src/X1.WebUI/modify.md @@ -626,3 +626,30 @@ if "%PACKAGE_MANAGER%"=="npm" ( - ✅ **所有表单类型正常工作** - 设备注册、网络连通性、网络性能、语音通话表单 - ✅ **代码类型安全性显著提升** - 完整的类型定义和检查 - ✅ **构建成功** - 项目可以正常构建和部署 + +## 2024-12-19 任务执行表格按钮图标化 + +### 修改描述: +将TaskExecutionTable组件中的"开始"和"停止"文字按钮改为图标按钮,提升用户体验和界面美观度。 + +### 修改文件: +- `X1.WebUI/src/pages/taskExecution/TaskExecutionTable.tsx` + +### 具体修改: +1. **导入图标组件** - 添加 `import { Play, Square } from 'lucide-react';` +2. **替换开始按钮** - 将"开始"文字改为 `Play` 图标,使用绿色主题 +3. **替换停止按钮** - 将"停止"文字改为 `Square` 图标,使用红色主题 +4. **优化按钮样式** - 添加悬停效果、背景色变化和圆角设计 +5. **添加工具提示** - 为图标按钮添加 `title` 属性,显示操作说明 + +### 技术细节: +- 使用 `Play` 图标表示开始操作(绿色主题) +- 使用 `Square` 图标表示停止操作(红色主题) +- 按钮样式包含悬停状态和过渡动画 +- 保持原有的点击事件处理逻辑不变 + +### 用户体验改进: +- ✅ **视觉识别性提升** - 图标比文字更直观易懂 +- ✅ **界面美观度提升** - 图标按钮比文字按钮更现代化 +- ✅ **操作反馈增强** - 悬停效果和背景色变化提供更好的交互反馈 +- ✅ **空间利用优化** - 图标按钮占用空间更小,表格更紧凑 diff --git a/src/X1.WebUI/src/constants/navigationMenuPresets.ts b/src/X1.WebUI/src/constants/navigationMenuPresets.ts index c045689..f0be360 100644 --- a/src/X1.WebUI/src/constants/navigationMenuPresets.ts +++ b/src/X1.WebUI/src/constants/navigationMenuPresets.ts @@ -162,12 +162,21 @@ export const subMenuPresets: NavigationMenuPreset[] = [ description: '查看和管理所有测试任务', parentTitle: '任务管理' }, + { + title: '任务执行明细', + path: '/dashboard/tasks/execution', + icon: 'ClipboardList', + permissionCode: 'taskexecution.view', + sortOrder: 2, + description: '查看任务执行明细,进行开始和停止操作', + parentTitle: '任务管理' + }, { title: '创建任务', path: '/dashboard/tasks/create', icon: 'ClipboardList', permissionCode: 'tasks.create', - sortOrder: 2, + sortOrder: 3, description: '创建新的测试任务', parentTitle: '任务管理' }, @@ -176,7 +185,7 @@ export const subMenuPresets: NavigationMenuPreset[] = [ path: '/dashboard/tasks/reviews', icon: 'ClipboardList', permissionCode: 'taskreviews.view', - sortOrder: 3, + sortOrder: 4, description: '审核和批准测试任务', parentTitle: '任务管理' }, @@ -185,7 +194,7 @@ export const subMenuPresets: NavigationMenuPreset[] = [ path: '/dashboard/tasks/executions', icon: 'ClipboardList', permissionCode: 'taskexecutions.view', - sortOrder: 4, + sortOrder: 5, description: '执行和管理测试任务', parentTitle: '任务管理' }, diff --git a/src/X1.WebUI/src/pages/navigation-menus/NavigationMenuForm_Examples.md b/src/X1.WebUI/src/pages/navigation-menus/NavigationMenuForm_Examples.md index 8e0d51d..04af5e0 100644 --- a/src/X1.WebUI/src/pages/navigation-menus/NavigationMenuForm_Examples.md +++ b/src/X1.WebUI/src/pages/navigation-menus/NavigationMenuForm_Examples.md @@ -303,6 +303,21 @@ 自动识别结果: SubMenuItem(子菜单项) ``` +**任务执行明细** +``` +菜单标题: 任务执行明细 +菜单路径: /dashboard/tasks/execution +父级菜单: [选择"任务管理"菜单的ID] +图标: Play +权限代码: taskexecution.view +排序: 2 +启用状态: ✅ +系统菜单: ✅ +描述: 查看任务执行明细,进行开始和停止操作 + +自动识别结果: SubMenuItem(子菜单项) +``` + **创建任务** ``` 菜单标题: 创建任务 @@ -310,7 +325,7 @@ 父级菜单: [选择"任务管理"菜单的ID] 图标: ClipboardList 权限代码: tasks.create -排序: 2 +排序: 3 启用状态: ✅ 系统菜单: ✅ 描述: 创建新的测试任务 @@ -710,6 +725,7 @@ - `testcases`: 用例管理 - `teststeps`: 测试步骤 - `tasks`: 任务管理 +- `taskexecution`: 任务执行明细 - `taskreviews`: 任务审核 - `taskexecutions`: 任务执行 - `functionalanalysis`: 功能分析 @@ -752,6 +768,7 @@ TestTube: 测试/实验 ClipboardList: 任务/列表 FolderOpen: 文件夹/场景管理 Activity: 活动/信令分析 +Play: 播放/执行 ``` ## 📋 创建步骤建议 diff --git a/src/X1.WebUI/src/pages/taskExecution/TaskExecutionTable.tsx b/src/X1.WebUI/src/pages/taskExecution/TaskExecutionTable.tsx new file mode 100644 index 0000000..9a4a556 --- /dev/null +++ b/src/X1.WebUI/src/pages/taskExecution/TaskExecutionTable.tsx @@ -0,0 +1,220 @@ +import React from 'react'; +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { TestScenarioTask } from '@/services/testScenarioTaskService'; +import { Badge } from '@/components/ui/badge'; +import { Play, Square } from 'lucide-react'; + +interface TaskExecutionTableProps { + tasks: TestScenarioTask[]; + loading: boolean; + onStart: (task: TestScenarioTask) => void; + onStop: (task: TestScenarioTask) => void; + page: number; + pageSize: number; + total: number; + onPageChange: (page: number) => void; + hideCard?: boolean; + density?: 'relaxed' | 'default' | 'compact'; + columns?: { key: string; title: string; visible: boolean }[]; +} + +// 优先级徽章组件 +const PriorityBadge: React.FC<{ priority: number }> = ({ priority }) => { + const getPriorityConfig = (priority: number) => { + switch (priority) { + case 0: + return { label: '普通', className: 'bg-blue-100 text-blue-800' }; + case 1: + return { label: '高', className: 'bg-orange-100 text-orange-800' }; + default: + return { label: '未知', className: 'bg-gray-100 text-gray-800' }; + } + }; + + const config = getPriorityConfig(priority); + return ( + + {config.label} + + ); +}; + +// 生命周期徽章组件 +const LifecycleBadge: React.FC<{ lifecycle: string }> = ({ lifecycle }) => { + const getLifecycleConfig = (lifecycle: string) => { + switch (lifecycle) { + case 'Created': + return { label: '已创建', className: 'bg-blue-100 text-blue-800' }; + case 'Scheduled': + return { label: '已调度', className: 'bg-green-100 text-green-800' }; + case 'Archived': + return { label: '已归档', className: 'bg-gray-100 text-gray-800' }; + case 'Cancelled': + return { label: '已取消', className: 'bg-red-100 text-red-800' }; + default: + return { label: '未知', className: 'bg-gray-100 text-gray-800' }; + } + }; + + const config = getLifecycleConfig(lifecycle); + return ( + + {config.label} + + ); +}; + +// 状态徽章组件 +const StatusBadge: React.FC<{ isDisabled: boolean }> = ({ isDisabled }) => { + return ( + + {isDisabled ? '禁用' : '启用'} + + ); +}; + +// 日期格式化组件 +const DateDisplay: React.FC<{ date?: string }> = ({ date }) => { + if (!date) return -; + + const formatDate = (dateString: string) => { + const date = new Date(dateString); + return date.toLocaleDateString('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit' + }); + }; + + return {formatDate(date)}; +}; + +export default function TaskExecutionTable({ + tasks, + loading, + onStart, + onStop, + hideCard = false, + density = 'default', + columns = [] +}: TaskExecutionTableProps) { + + const visibleColumns = columns.filter(col => col.visible); + + const renderCell = (task: TestScenarioTask, columnKey: string) => { + switch (columnKey) { + case 'taskCode': + return ( +
+ {task.taskCode} +
+ ); + case 'taskName': + return ( +
+ {task.taskName} +
+ ); + case 'scenarioCode': + return ( +
+ {task.scenarioCode} +
+ ); + case 'deviceCode': + return ( +
+ {task.deviceCode} +
+ ); + case 'priority': + return ; + case 'lifecycle': + return ; + case 'isDisabled': + return ; + case 'createdAt': + return ; + case 'flowCount': + return ( +
+ + {task.flowCount} 个流程 + +
+ ); + case 'actions': + return ( +
+ + +
+ ); + default: + return null; + } + }; + + const Wrapper = hideCard ? React.Fragment : 'div'; + const wrapperProps = hideCard ? {} : { className: 'rounded-md border bg-background' }; + const rowClass = density === 'relaxed' ? 'h-20' : density === 'compact' ? 'h-8' : 'h-12'; + const cellPadding = density === 'relaxed' ? 'py-5' : density === 'compact' ? 'py-1' : 'py-3'; + + return ( + + + + + {visibleColumns.map(col => ( + + {col.title} + + ))} + + + + {loading ? ( + + + 加载中... + + + ) : tasks.length === 0 ? ( + + + 暂无数据 + + + ) : ( + tasks.map((task) => ( + + {visibleColumns.map((column) => ( + + {renderCell(task, column.key)} + + ))} + + )) + )} + +
+
+ ); +} diff --git a/src/X1.WebUI/src/pages/taskExecution/TaskExecutionView.tsx b/src/X1.WebUI/src/pages/taskExecution/TaskExecutionView.tsx new file mode 100644 index 0000000..1023258 --- /dev/null +++ b/src/X1.WebUI/src/pages/taskExecution/TaskExecutionView.tsx @@ -0,0 +1,219 @@ +import React, { useEffect, useState } from 'react'; +import { testScenarioTaskService, TestScenarioTask, GetTestScenarioTasksRequest } from '@/services/testScenarioTaskService'; +import TaskExecutionTable from './TaskExecutionTable'; +import { Input } from '@/components/ui/input'; +import PaginationBar from '@/components/ui/PaginationBar'; +import TableToolbar, { DensityType } from '@/components/ui/TableToolbar'; +import { useToast } from '@/components/ui/use-toast'; +import { Button } from '@/components/ui/button'; + +const defaultColumns = [ + { key: 'taskCode', title: '任务编码', visible: true }, + { key: 'taskName', title: '任务名称', visible: true }, + { key: 'scenarioCode', title: '场景编码', visible: true }, + { key: 'deviceCode', title: '设备编码', visible: true }, + { key: 'priority', title: '优先级', visible: true }, + { key: 'lifecycle', title: '生命周期', visible: true }, + { key: 'isDisabled', title: '状态', visible: true }, + { key: 'flowCount', title: '流程数量', visible: true }, + { key: 'createdAt', title: '创建时间', visible: true }, + { key: 'actions', title: '操作', visible: true } +]; + +export default function TaskExecutionView() { + const [tasks, setTasks] = useState([]); + const [loading, setLoading] = useState(false); + const [total, setTotal] = useState(0); + const [pageNumber, setPageNumber] = useState(1); + const [pageSize, setPageSize] = useState(10); + const [density, setDensity] = useState('default'); + const [columns, setColumns] = useState(defaultColumns); + + // 搜索参数 + const [searchTerm, setSearchTerm] = useState(''); + const [isDisabled, setIsDisabled] = useState(undefined); + + // Toast 提示 + const { toast } = useToast(); + + const fetchTasks = async (params: Partial = {}) => { + setLoading(true); + const queryParams: GetTestScenarioTasksRequest = { + pageNumber, + pageSize, + searchTerm, + isDisabled, + ...params + }; + + const result = await testScenarioTaskService.getTestScenarioTasks(queryParams); + if (result.isSuccess && result.data) { + setTasks(result.data.tasks || []); + setTotal(result.data.totalCount || 0); + } + setLoading(false); + }; + + useEffect(() => { + fetchTasks(); + // eslint-disable-next-line + }, [pageNumber, pageSize]); + + const handleStart = async (task: TestScenarioTask) => { + try { + // TODO: 实现开始任务的逻辑 + toast({ + title: "开始任务", + description: `任务 "${task.taskName}" 已开始执行`, + }); + // 这里可以调用相应的API来开始任务 + // await taskExecutionService.startTask(task.taskId); + } catch (error) { + console.error('开始任务失败:', error); + toast({ + title: "开始失败", + description: "网络错误,请稍后重试", + variant: "destructive", + }); + } + }; + + const handleStop = async (task: TestScenarioTask) => { + try { + // TODO: 实现停止任务的逻辑 + toast({ + title: "停止任务", + description: `任务 "${task.taskName}" 已停止执行`, + }); + // 这里可以调用相应的API来停止任务 + // await taskExecutionService.stopTask(task.taskId); + } catch (error) { + console.error('停止任务失败:', error); + toast({ + title: "停止失败", + description: "网络错误,请稍后重试", + variant: "destructive", + }); + } + }; + + // 查询按钮 + const handleQuery = () => { + setPageNumber(1); + fetchTasks({ pageNumber: 1 }); + }; + + // 重置按钮 + const handleReset = () => { + setSearchTerm(''); + setIsDisabled(undefined); + setPageNumber(1); + fetchTasks({ + searchTerm: '', + isDisabled: undefined, + pageNumber: 1 + }); + }; + + // 每页条数选择 + const handlePageSizeChange = (size: number) => { + setPageSize(size); + setPageNumber(1); + }; + + return ( +
+
+ {/* 搜索工具栏 */} +
+
{ + e.preventDefault(); + handleQuery(); + }} + > +
+ + ) => setSearchTerm(e.target.value)} + /> +
+
+ + +
+ {/* 按钮组 */} +
+ + +
+
+
+ + {/* 表格整体卡片区域,包括工具栏、表格、分页 */} +
+ {/* 顶部操作栏:工具栏 */} +
+ fetchTasks()} + onDensityChange={setDensity} + onColumnsChange={setColumns} + onColumnsReset={() => setColumns(defaultColumns)} + columns={columns} + density={density} + /> +
+ + {/* 表格区域 */} + + + {/* 分页 */} + +
+
+
+ ); +} diff --git a/src/X1.WebUI/src/routes/AppRouter.tsx b/src/X1.WebUI/src/routes/AppRouter.tsx index 8d99ead..b239d52 100644 --- a/src/X1.WebUI/src/routes/AppRouter.tsx +++ b/src/X1.WebUI/src/routes/AppRouter.tsx @@ -22,6 +22,7 @@ const TestStepsView = lazy(() => import('@/pages/teststeps/TestStepsView')); // 任务管理页面 const TasksView = lazy(() => import('@/pages/tasks/TasksView')); +const TaskExecutionView = lazy(() => import('@/pages/taskExecution/TaskExecutionView')); // 结果分析页面 const FunctionalAnalysisView = lazy(() => import('@/pages/analysis/FunctionalAnalysisView')); @@ -176,6 +177,13 @@ export function AppRouter() { } /> + + + + + + } /> {/* 结果分析路由 */}