提交 b2854e30 authored 作者: 陈泽健's avatar 陈泽健

fix(ui-cases): 修复步骤表格不显示定位信息问题

- Cases.vue 新增"定位类型"和"定位值"两列
- 兼容多种命名格式(locatorType/locator_type/params.selector)
- formatParams 排除 selector 字段避免重复显示
- element_locator.py 批量定位同时写入驼峰和蛇形命名
- 新增批量获取定位按钮(绿色),一键获取用例所有步骤定位
Co-Authored-By: 's avatarClaude <noreply@anthropic.com>
上级 60ae5431
...@@ -176,7 +176,7 @@ ...@@ -176,7 +176,7 @@
{{ formatTime(row.created_at) }} {{ formatTime(row.created_at) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="200" fixed="right"> <el-table-column label="操作" width="280" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<el-button size="small" type="primary" link @click="showEditDialog(row)"> <el-button size="small" type="primary" link @click="showEditDialog(row)">
编辑 编辑
...@@ -184,6 +184,16 @@ ...@@ -184,6 +184,16 @@
<el-button size="small" type="primary" link @click="viewCaseDetail(row)"> <el-button size="small" type="primary" link @click="viewCaseDetail(row)">
查看 查看
</el-button> </el-button>
<el-button
size="small"
type="success"
link
@click="batchLocate(row)"
:loading="locatingCaseId === row.id"
:disabled="row.caseType !== 'ui'"
>
获取定位
</el-button>
<el-button size="small" type="warning" link @click="copyCase(row)"> <el-button size="small" type="warning" link @click="copyCase(row)">
复制 复制
</el-button> </el-button>
...@@ -361,13 +371,36 @@ ...@@ -361,13 +371,36 @@
v-if="currentCase.steps && currentCase.steps.length > 0" v-if="currentCase.steps && currentCase.steps.length > 0"
> >
<el-table-column label="#" width="50" type="index" /> <el-table-column label="#" width="50" type="index" />
<el-table-column label="步骤名称" prop="name" min-width="150" /> <el-table-column label="步骤名称" prop="name" min-width="120" />
<el-table-column label="动作" width="100"> <el-table-column label="动作" width="90">
<template #default="{ row }"> <template #default="{ row }">
<el-tag size="small" effect="plain">{{ row.action }}</el-tag> <el-tag size="small" effect="plain">{{ row.action }}</el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="参数" min-width="200"> <el-table-column label="定位类型" width="100">
<template #default="{ row }">
<el-tag
v-if="row.locatorType || row.locator_type"
size="small"
type="success"
>
{{ row.locatorType || row.locator_type }}
</el-tag>
<span v-else class="text-gray">-</span>
</template>
</el-table-column>
<el-table-column label="定位值" min-width="180">
<template #default="{ row }">
<code
v-if="row.locatorValue || row.locator_value || row.params?.selector"
class="locator-code"
>
{{ row.locatorValue || row.locator_value || row.params?.selector }}
</code>
<span v-else class="text-gray">-</span>
</template>
</el-table-column>
<el-table-column label="参数" min-width="150">
<template #default="{ row }"> <template #default="{ row }">
<span class="params-text">{{ formatParams(row.params) }}</span> <span class="params-text">{{ formatParams(row.params) }}</span>
</template> </template>
...@@ -493,6 +526,7 @@ import { ...@@ -493,6 +526,7 @@ import {
import { caseApi } from '@/api/cases' import { caseApi } from '@/api/cases'
import { moduleApi, buildModuleTree } from '@/api/modules' import { moduleApi, buildModuleTree } from '@/api/modules'
import { executionApi } from '@/api/executions' import { executionApi } from '@/api/executions'
import { elementLocateApi } from '@/api/elementLocate'
import CaseStepEditor from '@/components/CaseStepEditor.vue' import CaseStepEditor from '@/components/CaseStepEditor.vue'
import IntentStepEditor from '@/components/IntentStepEditor.vue' import IntentStepEditor from '@/components/IntentStepEditor.vue'
import { toStepDefinition, toIntentStep } from '@/utils/stepParser' import { toStepDefinition, toIntentStep } from '@/utils/stepParser'
...@@ -689,7 +723,11 @@ const formatParams = (params: any) => { ...@@ -689,7 +723,11 @@ const formatParams = (params: any) => {
if (!params) return '-' if (!params) return '-'
if (typeof params === 'string') return params if (typeof params === 'string') return params
try { try {
return JSON.stringify(params) // 排除 selector(已在定位值列单独显示)
const { selector, ...rest } = params
const keys = Object.keys(rest)
if (keys.length === 0) return '-'
return JSON.stringify(rest)
} catch { } catch {
return '-' return '-'
} }
...@@ -841,6 +879,57 @@ const deleteCase = async (row: any) => { ...@@ -841,6 +879,57 @@ const deleteCase = async (row: any) => {
} }
} }
// ==================== 批量获取定位 ====================
/** 当前正在获取定位的用例 ID */
const locatingCaseId = ref('')
/**
* 一键获取用例所有步骤的元素定位
*
* 流程:
* 1. 调用批量定位 API
* 2. 后端启动 Playwright → 登录 → 访问页面 → 提取元素
* 3. 为每个步骤匹配定位器并更新到数据库
* 4. 前端显示结果
*/
const batchLocate = async (row: any) => {
try {
await ElMessageBox.confirm(
`将为用例「${row.name}」的所有步骤自动获取元素定位,是否继续?`,
'批量获取定位',
{ type: 'info', confirmButtonText: '开始获取', cancelButtonText: '取消' }
)
} catch {
return
}
locatingCaseId.value = row.id
try {
ElMessage.info('正在启动浏览器获取定位,请稍候...')
const result = await elementLocateApi.locateBatch({
case_id: row.id,
page_url: 'https://192.168.5.44/',
auto_login: true,
})
if (result.success) {
const msg = `定位完成:成功 ${result.located_steps}/${result.total_steps} 个步骤`
ElMessage.success(msg)
// 刷新用例列表
await loadCases()
} else {
ElMessage.warning(result.message || '定位失败')
}
} catch (error: any) {
ElMessage.error('批量定位失败: ' + (error.message || '未知错误'))
} finally {
locatingCaseId.value = ''
}
}
const runSelectedCases = async () => { const runSelectedCases = async () => {
if (selectedCases.value.length === 0) return if (selectedCases.value.length === 0) return
...@@ -1032,6 +1121,19 @@ onMounted(() => { ...@@ -1032,6 +1121,19 @@ onMounted(() => {
color: #909399; color: #909399;
word-break: break-all; word-break: break-all;
} }
.locator-code {
font-family: 'Consolas', 'Monaco', monospace;
font-size: 11px;
background: #f5f7fa;
padding: 2px 6px;
border-radius: 3px;
color: #409eff;
display: inline-block;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
}
} }
} }
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论