feat(ti): 完善触控交互执行与报告页面

增加项目执行、重新执行和执行过程入口。

按执行实例展示检测统计、结果、异常信息及录像。
This commit is contained in:
lixiaolong 2026-09-16 17:26:05 +08:00
parent 1b744ff0c5
commit 622c22ff89
4 changed files with 204 additions and 123 deletions

View File

@ -55,11 +55,10 @@ export const projectExecute = (data) => {
})
}
export const executeProject = (data) => {
export const queryExecuteInstId = (projectId) => {
return request({
url: '/ti/project/evaluation',
method: 'post',
data
url: `/ti/project/queryExecuteInstId/${projectId}`,
method: 'get'
})
}
@ -68,9 +67,10 @@ export const executeProject = (data) => {
* @param {*} projectId
* @returns
*/
export function getValuationProject(projectId) {
export function getValuationProject(projectId, instId) {
return request({
url: '/ti/project/evaluation/' + projectId,
method: 'get'
method: 'get',
params: instId ? { instId } : undefined
})
}
}

View File

@ -77,8 +77,7 @@ import {
const props = defineProps({
drawer: Boolean,
projectId: String,
functionIds: Array
projectId: String
});
const emits = defineEmits(["close"]);
@ -137,10 +136,10 @@ const confirm = () => {
})
if (res.code === 200) {
ruleFormRef.value.resetFields();
ElMessage.success('执行成功')
ElMessage.success(`任务已启动,执行实例:${res.data}`)
emits("close");
} else {
ElMessage.success(res.msg)
ElMessage.error(res.msg)
}
})
.catch((err) => {

View File

@ -1,75 +1,122 @@
<template>
<div class="container">
<Info :infoData="projectData" />
<div class="test-container">
<div class="title">测试结果</div>
<div class="report-container">
<div class="report-item">成功率 :{{ reportData?.successRate || '' }}</div>
<div class="report-item">成功率得分 :{{ reportData?.successRateScore || '' }}</div>
<!-- <el-button @click="handler('重庆')">生成报告</el-button>
<el-button>导出结果</el-button> -->
<div class="report-item">总数:{{ reportData?.totalCount ?? '--' }}</div>
<div class="report-item report-item--success">通过:{{ reportData?.successCount ?? '--' }}</div>
<div class="report-item report-item--failed">失败:{{ reportData?.failureCount ?? '--' }}</div>
<div class="report-item">
成功率:{{ reportData?.successRate == null ? '--' : `${reportData.successRate}%` }}
</div>
</div>
</div>
<el-table :data="tableData" style="width: 100%">
<el-table-column prop="aeId" width="150" show-overflow-tooltip label="结果ID" />
<el-table-column prop="content" label="功能" width="260" show-overflow-tooltip />
<el-table-column prop="status" label="状态" show-overflow-tooltip>
<el-descriptions v-if="reportData" :column="2" border class="execution-summary">
<el-descriptions-item label="执行实例">{{ reportData.instId }}</el-descriptions-item>
<el-descriptions-item label="执行状态">
{{ executionStatusText[reportData.executionStatus] || reportData.executionStatus }}
</el-descriptions-item>
<el-descriptions-item label="开始时间">{{ formatTime(reportData.startTime) }}</el-descriptions-item>
<el-descriptions-item label="结束时间">{{ formatTime(reportData.endTime) }}</el-descriptions-item>
</el-descriptions>
<el-table v-loading="loading" :data="tableData" style="width: 100%">
<el-table-column prop="aeId" width="150" show-overflow-tooltip label="结果ID" />
<el-table-column prop="content" label="功能" width="220" show-overflow-tooltip />
<el-table-column prop="status" label="状态" width="110">
<template #default="{ row }">
<el-tag :type="statusType[row.status] || 'info'">{{ statusTypeContext[row.status] }}</el-tag>
<el-tag :type="statusType[row.status] || 'info'">
{{ statusTypeContext[row.status] || '未知' }}
</el-tag>
</template>
</el-table-column>
<el-table-column prop="videoPath" label="视频路径" show-overflow-tooltip>
<el-table-column prop="videoPath" label="录像" width="110">
<template #default="{ row }">
<el-text class="mx-1" type="primary" @click="openVideoDialog(row)">播放视频</el-text>
<el-text
v-if="row.videoPath"
class="video-link"
type="primary"
@click="openVideoDialog(row)"
>
播放视频
</el-text>
<span v-else>--</span>
</template>
</el-table-column>
<el-table-column prop="itemResponseTime" label="响应时间" />
<el-table-column prop="failureReason" label="失败原因"></el-table-column>
<el-table-column prop="executionDetail" label="执行详情" />
<el-table-column prop="itemResponseTime" label="响应时间(ms)" width="130">
<template #default="{ row }">{{ row.itemResponseTime ?? '--' }}</template>
</el-table-column>
<el-table-column prop="failureReason" label="失败原因" min-width="180" show-overflow-tooltip>
<template #default="{ row }">{{ row.failureReason || '--' }}</template>
</el-table-column>
<el-table-column prop="executionDetail" label="执行详情" min-width="260" show-overflow-tooltip />
</el-table>
<el-dialog v-model="centerDialogVisible" title="播放视频" width="800" center>
<el-dialog v-model="centerDialogVisible" title="播放视频" width="800" center destroy-on-close>
<Video :src="videoSrc" />
</el-dialog>
</div>
</template>
<script setup>
import Info from './info.vue';
import { ElMessage } from 'element-plus'
import Info from './info.vue'
import { getValuationProject } from '@/api/is/project'
import Video from '@/components/Video/index.vue'
const route = useRoute();
const route = useRoute()
const projectData = route.query.data
? JSON.parse(route.query.data)
: {};
const tableData = ref([]);
const reportData = ref(null)
const getListScheme = async () => {
const res = await getValuationProject(projectData.projectId)
if (res.code === 200) {
reportData.value = {
successRate: res.data.successRate,
successRateScore: res.data.successRateScore
}
tableData.value = res.data.itemDetails
}
let projectData = {}
try {
projectData = route.query.data ? JSON.parse(route.query.data) : {}
} catch {
projectData = {}
}
const tableData = ref([])
const reportData = ref(null)
const loading = ref(false)
const statusType = {
0: 'info',
1: 'conductedState',
2: 'successfulState',
3: 'danger'
1: 'warning',
2: 'success',
3: 'danger',
4: 'danger'
}
const statusTypeContext = {
0: '待评估',
1: '评估中',
2: '评估成功',
3: '评估失败'
0: '待执行',
1: '执行中',
2: '通过',
3: '失败',
4: '失败'
}
const executionStatusText = {
0: '已完成',
1: '执行中',
2: '执行失败',
3: '已暂停',
4: '已终止'
}
const getReport = async () => {
loading.value = true
try {
const res = await getValuationProject(projectData.projectId, route.query.instId)
if (res.code === 200) {
reportData.value = res.data
tableData.value = res.data?.itemDetails || []
}
} catch (error) {
ElMessage.error(error.message || '加载测试结果失败')
} finally {
loading.value = false
}
}
const centerDialogVisible = ref(false)
@ -79,9 +126,13 @@ const openVideoDialog = (row) => {
centerDialogVisible.value = true
}
onMounted(() => {
getListScheme()
})
const formatTime = (value) => {
if (!value) return '--'
const date = new Date(value)
return Number.isNaN(date.getTime()) ? value : date.toLocaleString('zh-CN', { hour12: false })
}
onMounted(getReport)
</script>
<style lang="scss" scoped>
@ -89,17 +140,12 @@ onMounted(() => {
width: 100%;
height: 100%;
.btn-container {
background: #fff;
margin: 4px 0;
padding: 4px 0;
}
.test-container {
height: 40px;
min-height: 48px;
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
.title {
font-size: 20px;
@ -108,54 +154,15 @@ onMounted(() => {
.report-container {
display: flex;
gap: 12px;
flex-wrap: wrap;
gap: 16px;
}
.report-item--success { color: #67c23a; }
.report-item--failed { color: #f56c6c; }
}
.context-container {
height: calc(100% - 200px);
display: flex;
gap: 10px;
.left {
width: 300px;
.title {
height: 40px;
display: flex;
align-items: center;
background: #fff;
padding-left: 12px;
border: 1px solid #e4e7ed;
margin-bottom: 2px;
}
.schemaList {
height: calc(100% - 40px);
overflow-y: auto;
background: #fff;
border: 1px solid #e4e7ed;
.schema {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px;
&:hover {
background: #f0ecec;
}
}
.activeItem {
background: #f0ecec;
}
}
}
.right {
flex: 1;
}
}
.execution-summary { margin-bottom: 16px; }
.video-link { cursor: pointer; }
}
</style>
</style>

View File

@ -113,7 +113,7 @@
<el-table-column
label="操作"
align="center"
width="320px"
width="380px"
class-name="small-padding fixed-width"
fixed="right"
>
@ -121,16 +121,23 @@
<el-button
size="mini"
type="text"
v-if="row.status == '0'"
v-if="row.status != '1'"
@click="handleExecute(row)"
>执行
>{{ row.status == '0' ? '执行' : '重新执行' }}
</el-button>
<el-button
v-if="row.status != '0'"
size="mini"
type="text"
@click="handleOpenExecution(row)"
>执行过程
</el-button>
<el-button
v-if="row.status == '2'"
size="mini"
type="text"
@click="handleEvaluate(row)"
>评价
>查看报告
</el-button>
<el-button
size="mini"
@ -329,11 +336,40 @@
</template>
</el-dialog>
<el-drawer
v-model="executionOpen"
:destroy-on-close="true"
:size="580"
:title="`${currentProject.projectName || ''}的执行过程`"
append-to-body
>
<el-alert
v-if="latestInstId"
:title="`执行实例:${latestInstId}`"
type="info"
:closable="false"
style="margin-bottom: 12px"
/>
<el-card
v-for="item in executionFlows"
:key="item.id"
shadow="hover"
style="width: 100%; margin-bottom: 10px"
>
<div class="flex align-center" style="cursor: pointer" @click="handleFlowView(item)">
<div>{{ item.name }}</div>
<div class="flex align-center" style="margin-left: 10px">
<el-icon><View /></el-icon>
</div>
</div>
</el-card>
<el-empty v-if="!executionFlows.length" description="暂无执行流程" />
</el-drawer>
<Run
:drawer="executeOpen"
@close="closeRun"
:projectId="executeProjectId"
:functionIds="functionIds"
/>
</div>
</template>
@ -345,8 +381,16 @@ import { ElMessage, ElMessageBox } from "element-plus";
import { getTreeDataApi } from '@/api/is/func/index'
import { getChildrenByParentIdWithLevelLimitApi } from "@/api/evaluate/indicator.js";
import { getFunction } from "@/api/is/func/index.js";
import { addProject, getProjectList, updateProject, deleteProject } from "@/api/is/project/index.js";
import {
addProject,
getProjectList,
updateProject,
deleteProject,
getExecuteRunParams,
queryExecuteInstId,
} from "@/api/is/project/index.js";
import Run from "@/views/is/project/evaluate/Run.vue";
import { appendParamsToPath } from "@/utils/fn.js";
const topContainerRef = ref();
const containerHeight = useContainerHeight(topContainerRef);
@ -618,19 +662,9 @@ const getFuncList = (value) => {
const executeOpen = ref(false);
const executeProjectId = ref(null);
const functionIds = ref([])
const handleExecute = (row) => {
executeOpen.value = true;
executeProjectId.value = row.projectId;
// 后端临时应付检查添加,后续可删除
let arr = []
try {
arr = JSON.parse(row.testFuncIds);
} catch {
arr = []
} finally {
functionIds.value = arr
}
}
const router = useRouter()
@ -644,6 +678,47 @@ const handleEvaluate = (row) => {
})
}
const executionOpen = ref(false)
const currentProject = ref({})
const executionFlows = ref([])
const latestInstId = ref('')
const handleOpenExecution = async (row) => {
currentProject.value = row
executionOpen.value = true
latestInstId.value = ''
executionFlows.value = []
try {
const [flowRes, instRes] = await Promise.all([
getExecuteRunParams({ projectId: row.projectId }),
queryExecuteInstId(row.projectId),
])
latestInstId.value = instRes.data || ''
executionFlows.value = (flowRes.data || []).map((item) => ({
id: item.itemId,
name: item.itemName || '触控交互流程',
}))
} catch (error) {
ElMessage.error(error.message || '加载执行过程失败')
}
}
const handleFlowView = (item) => {
if (!latestInstId.value) {
ElMessage.warning('未找到执行实例')
return
}
const params = {
instId: latestInstId.value,
taskId: currentProject.value.projectId,
itemId: item.id,
name: item.name,
state: '0',
isLog: true,
}
window.open(appendParamsToPath('/flow', params), '_blank')
}
const closeRun = () => {
executeOpen.value = false
getList()