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 (
+
+
+ {/* 搜索工具栏 */}
+
+
+ {/* 表格整体卡片区域,包括工具栏、表格、分页 */}
+
+ {/* 顶部操作栏:工具栏 */}
+
+
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() {
} />
+
+
+
+
+
+ } />
{/* 结果分析路由 */}