feat(测试计划): 测试计划详情-修改功能用例步骤执行结果

This commit is contained in:
teukkk 2024-05-16 13:03:35 +08:00 committed by Craftsman
parent f24b54c9e9
commit 7db01bf024
9 changed files with 84 additions and 8 deletions

View File

@ -86,8 +86,12 @@ export interface StepList {
expected: string; // 预期 expected: string; // 预期
showStep: boolean; // 编辑步骤模式 showStep: boolean; // 编辑步骤模式
showExpected: boolean; // 编辑预期模式 showExpected: boolean; // 编辑预期模式
actualResult?: string; // 实际
executeResult?: string; // 步骤执行结果
} }
export type StepExecutionResult = Pick<StepList, 'actualResult' | 'executeResult'>;
// 关联文件列表 // 关联文件列表
export interface AssociatedList { export interface AssociatedList {
id: string; id: string;

View File

@ -28,8 +28,31 @@
@blur="blurHandler(record, 'expected')" @blur="blurHandler(record, 'expected')"
/> />
</template> </template>
<template #actualResult="{ record }">
<a-textarea
v-model="record.actualResult"
:max-length="1000"
size="mini"
:auto-size="true"
class="w-max-[267px] param-input"
:placeholder="t('system.orgTemplate.actualResultTip')"
/>
</template>
<template #lastExecResult="{ record }"> <template #lastExecResult="{ record }">
<ExecuteResult :execute-result="record.executeResult" /> <a-select
v-if="hasAnyPermission(['PROJECT_TEST_PLAN:READ+EXECUTE'])"
v-model:model-value="record.executeResult"
:placeholder="t('common.pleaseSelect')"
class="param-input w-full"
>
<template #label>
<span class="text-[var(--color-text-2)]"><ExecuteResult :execute-result="record.executeResult" /></span>
</template>
<a-option v-for="item in executionResultList" :key="item.key" :value="item.key">
<ExecuteResult :execute-result="item.key" />
</a-option>
</a-select>
<span v-else class="text-[var(--color-text-2)]"><ExecuteResult :execute-result="record.executeResult" /></span>
</template> </template>
<template #operation="{ record }"> <template #operation="{ record }">
<MsTableMoreAction <MsTableMoreAction
@ -60,10 +83,14 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { getGenerateId } from '@/utils'; import { getGenerateId } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import type { StepList } from '@/models/caseManagement/featureCase'; import type { StepList } from '@/models/caseManagement/featureCase';
import { LastExecuteResults } from '@/enums/caseEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
import { executionResultMap } from '@/views/case-management/caseManagementFeature/components/utils';
type refItem = Element | ComponentPublicInstance | null; type refItem = Element | ComponentPublicInstance | null;
const { t } = useI18n(); const { t } = useI18n();
@ -82,6 +109,10 @@
const emit = defineEmits(['update:stepList']); const emit = defineEmits(['update:stepList']);
const executionResultList = computed(() =>
Object.values(executionResultMap).filter((item) => item.key !== LastExecuteResults.UN_EXECUTED)
);
// //
const stepData = ref<StepList[]>([ const stepData = ref<StepList[]>([
{ {

View File

@ -745,6 +745,7 @@
defineExpose({ defineExpose({
handleOK, handleOK,
getParams, getParams,
stepData,
}); });
</script> </script>

View File

@ -103,6 +103,7 @@ export default {
'system.orgTemplate.textDescription': 'Text description', 'system.orgTemplate.textDescription': 'Text description',
'system.orgTemplate.stepTip': 'Please enter steps', 'system.orgTemplate.stepTip': 'Please enter steps',
'system.orgTemplate.expectationTip': 'Please enter expectations', 'system.orgTemplate.expectationTip': 'Please enter expectations',
'system.orgTemplate.actualResultTip': 'Please enter actuality',
'system.orgTemplate.addAttachment': 'Add attachment', 'system.orgTemplate.addAttachment': 'Add attachment',
'system.orgTemplate.addAttachmentTip': 'Support any type of file, the file size does not exceed 50MB', 'system.orgTemplate.addAttachmentTip': 'Support any type of file, the file size does not exceed 50MB',
'system.orgTemplate.enabledSuccessfully': 'Enabled successfully', 'system.orgTemplate.enabledSuccessfully': 'Enabled successfully',

View File

@ -102,6 +102,7 @@ export default {
'system.orgTemplate.textDescription': '文本描述', 'system.orgTemplate.textDescription': '文本描述',
'system.orgTemplate.stepTip': '请输入步骤', 'system.orgTemplate.stepTip': '请输入步骤',
'system.orgTemplate.expectationTip': '请输入预期', 'system.orgTemplate.expectationTip': '请输入预期',
'system.orgTemplate.actualResultTip': '请输入实际',
'system.orgTemplate.addAttachment': '添加附件', 'system.orgTemplate.addAttachment': '添加附件',
'system.orgTemplate.addAttachmentTip': '支持任意类型文件,文件大小不超过 50MB', 'system.orgTemplate.addAttachmentTip': '支持任意类型文件,文件大小不超过 50MB',
'system.orgTemplate.enabledSuccessfully': '启用成功', 'system.orgTemplate.enabledSuccessfully': '启用成功',

View File

@ -103,6 +103,7 @@
import { ModuleTreeNode } from '@/models/common'; import { ModuleTreeNode } from '@/models/common';
import type { PlanDetailFeatureCaseItem, PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan'; import type { PlanDetailFeatureCaseItem, PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
import { LastExecuteResults } from '@/enums/caseEnum';
import { TestPlanRouteEnum } from '@/enums/routeEnum'; import { TestPlanRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum'; import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum'; import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
@ -217,6 +218,7 @@
{ {
title: 'testPlan.featureCase.executor', title: 'testPlan.featureCase.executor',
dataIndex: 'executeUserName', dataIndex: 'executeUserName',
showTooltip: true,
width: 150, width: 150,
showDrag: true, showDrag: true,
}, },
@ -243,6 +245,7 @@
(record) => { (record) => {
return { return {
...record, ...record,
lastExecResult: record.lastExecResult ?? LastExecuteResults.UN_EXECUTED,
caseLevel: getCaseLevels(record.customFields), caseLevel: getCaseLevels(record.customFields),
moduleId: getModules(record.moduleId, props.moduleTree), moduleId: getModules(record.moduleId, props.moduleTree),
}; };

View File

@ -33,17 +33,18 @@
import { defaultExecuteForm } from '@/config/testPlan'; import { defaultExecuteForm } from '@/config/testPlan';
import type { ExecuteFeatureCaseFormParams } from '@/models/testPlan/testPlan'; import type { ExecuteFeatureCaseFormParams } from '@/models/testPlan/testPlan';
import { LastExecuteResults } from '@/enums/caseEnum';
import { executionResultMap } from '@/views/case-management/caseManagementFeature/components/utils'; import { executionResultMap } from '@/views/case-management/caseManagementFeature/components/utils';
const form = defineModel<ExecuteFeatureCaseFormParams>('form', { const form = defineModel<ExecuteFeatureCaseFormParams>('form', {
required: true, default: () => ({ ...defaultExecuteForm }),
}); });
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const executionResultList = computed(() => const executionResultList = computed(() =>
Object.values(executionResultMap).filter((item) => item.key !== 'UN_EXECUTED') Object.values(executionResultMap).filter((item) => item.key !== LastExecuteResults.UN_EXECUTED)
); );
async function handleUploadImage(file: File) { async function handleUploadImage(file: File) {

View File

@ -38,12 +38,15 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import type { StepExecutionResult } from '@/models/caseManagement/featureCase';
import type { ExecuteFeatureCaseFormParams } from '@/models/testPlan/testPlan'; import type { ExecuteFeatureCaseFormParams } from '@/models/testPlan/testPlan';
import { LastExecuteResults } from '@/enums/caseEnum';
const props = defineProps<{ const props = defineProps<{
caseId: string; caseId: string;
testPlanId: string; testPlanId: string;
id: string; id: string;
stepExecutionResult?: StepExecutionResult[];
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
@ -63,6 +66,21 @@
(form.value.content === '' || form.value.content?.trim() === '<p style=""></p>') (form.value.content === '' || form.value.content?.trim() === '<p style=""></p>')
); );
watch(
() => props.stepExecutionResult,
() => {
const executionResultList = props.stepExecutionResult?.map((item) => item.executeResult);
if (executionResultList?.includes(LastExecuteResults.FAILED)) {
form.value.lastExecResult = LastExecuteResults.FAILED;
} else if (executionResultList?.includes(LastExecuteResults.BLOCKED)) {
form.value.lastExecResult = LastExecuteResults.BLOCKED;
} else {
form.value.lastExecResult = LastExecuteResults.PASSED;
}
},
{ deep: true }
);
// //
async function submit() { async function submit() {
try { try {
@ -73,6 +91,7 @@
testPlanId: props.testPlanId, testPlanId: props.testPlanId,
id: props.id, id: props.id,
lastExecResult: form.value.lastExecResult, lastExecResult: form.value.lastExecResult,
stepsExecResult: JSON.stringify(props.stepExecutionResult) ?? '',
content: form.value.content, content: form.value.content,
notifier: form.value?.commentIds?.join(';'), notifier: form.value?.commentIds?.join(';'),
}; };

View File

@ -32,12 +32,12 @@
<div <div
v-for="item of caseList" v-for="item of caseList"
:key="item.id" :key="item.id"
:class="['case-item', caseDetail.id === item.caseId ? 'case-item--active' : '']" :class="['case-item', activeId === item.id ? 'case-item--active' : '']"
@click="changeActiveCase(item)" @click="changeActiveCase(item)"
> >
<div class="mb-[8px] flex items-center justify-between"> <div class="mb-[8px] flex items-center justify-between">
<div class="text-[var(--color-text-4)]">{{ item.num }}</div> <div class="text-[var(--color-text-4)]">{{ item.num }}</div>
<ExecuteResult :execute-result="item.lastExecResult" /> <ExecuteResult :execute-result="item.lastExecResult ?? LastExecuteResults.UN_EXECUTED" />
</div> </div>
<a-tooltip :content="item.name"> <a-tooltip :content="item.name">
<div class="one-line-text">{{ item.name }}</div> <div class="one-line-text">{{ item.name }}</div>
@ -88,7 +88,7 @@
<!-- TODO: 属性的样式 --> <!-- TODO: 属性的样式 -->
<MsDescription v-if="activeTab === 'baseInfo'" :descriptions="descriptions" :column="2" /> <MsDescription v-if="activeTab === 'baseInfo'" :descriptions="descriptions" :column="2" />
<div v-else-if="activeTab === 'detail'" class="align-content-start flex h-full flex-col"> <div v-else-if="activeTab === 'detail'" class="align-content-start flex h-full flex-col">
<CaseTabDetail is-test-plan :form="caseDetail" /> <CaseTabDetail ref="caseTabDetailRef" is-test-plan :form="caseDetail" />
<!-- 开始执行 --> <!-- 开始执行 -->
<div class="px-[16px] py-[8px] shadow-[0_-1px_4px_rgba(2,2,2,0.1)]"> <div class="px-[16px] py-[8px] shadow-[0_-1px_4px_rgba(2,2,2,0.1)]">
<div class="mb-[12px] flex items-center justify-between"> <div class="mb-[12px] flex items-center justify-between">
@ -115,6 +115,7 @@
:id="activeId" :id="activeId"
:case-id="activeCaseId" :case-id="activeCaseId"
:test-plan-id="route.query.id as string" :test-plan-id="route.query.id as string"
:step-execution-result="stepExecutionResult"
@done="executeDone" @done="executeDone"
/> />
</div> </div>
@ -153,6 +154,7 @@
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import type { PlanDetailFeatureCaseItem, TestPlanDetail } from '@/models/testPlan/testPlan'; import type { PlanDetailFeatureCaseItem, TestPlanDetail } from '@/models/testPlan/testPlan';
import { LastExecuteResults } from '@/enums/caseEnum';
import { CaseManagementRouteEnum } from '@/enums/routeEnum'; import { CaseManagementRouteEnum } from '@/enums/routeEnum';
import { executionResultMap, getCustomField } from '@/views/case-management/caseManagementFeature/components/utils'; import { executionResultMap, getCustomField } from '@/views/case-management/caseManagementFeature/components/utils';
@ -308,7 +310,7 @@
} }
function changeActiveCase(item: PlanDetailFeatureCaseItem) { function changeActiveCase(item: PlanDetailFeatureCaseItem) {
if (activeCaseId.value !== item.caseId) { if (activeId.value !== item.id) {
activeCaseId.value = item.caseId; activeCaseId.value = item.caseId;
activeId.value = item.id; activeId.value = item.id;
} }
@ -317,6 +319,7 @@
() => activeCaseId.value, () => activeCaseId.value,
() => { () => {
loadCaseDetail(); loadCaseDetail();
// TODO
} }
); );
@ -325,11 +328,21 @@
await loadCaseDetail(); await loadCaseDetail();
} }
const caseTabDetailRef = ref<InstanceType<typeof CaseTabDetail>>();
const stepExecutionResult = computed(() => {
const stepData = caseTabDetailRef.value?.stepData;
return stepData?.map((item) => {
return {
actualResult: item.actualResult,
executeResult: item.executeResult,
};
});
});
const autoNext = ref(true); const autoNext = ref(true);
async function executeDone() { async function executeDone() {
if (autoNext.value) { if (autoNext.value) {
// id // id
const index = caseList.value.findIndex((e) => e.caseId === activeCaseId.value); const index = caseList.value.findIndex((e) => e.id === activeId.value);
if (index < caseList.value.length - 1) { if (index < caseList.value.length - 1) {
await loadCaseList(); await loadCaseList();
activeCaseId.value = caseList.value[index + 1].caseId; activeCaseId.value = caseList.value[index + 1].caseId;
@ -344,10 +357,12 @@
// //
loadCaseDetail(); loadCaseDetail();
loadCaseList(); loadCaseList();
// TODO
} }
} else { } else {
// //
loadCase(); loadCase();
// TODO
} }
} }