feat(测试计划): 测试计划详情-功能用例详情基本信息tab和详情tab
This commit is contained in:
parent
f1fe90aeb1
commit
2e08868d98
|
@ -25,6 +25,7 @@ import {
|
|||
MoveTestPlanModuleUrl,
|
||||
planDetailBugPageUrl,
|
||||
planPassRateUrl,
|
||||
RunFeatureCaseUrl,
|
||||
updateTestPlanModuleUrl,
|
||||
UpdateTestPlanUrl,
|
||||
} from '@/api/requrls/test-plan/testPlan';
|
||||
|
@ -42,6 +43,7 @@ import type {
|
|||
PlanDetailBugItem,
|
||||
PlanDetailFeatureCaseItem,
|
||||
PlanDetailFeatureCaseListQueryParams,
|
||||
RunFeatureCaseParams,
|
||||
TestPlanDetail,
|
||||
TestPlanItem,
|
||||
UseCountType,
|
||||
|
@ -164,3 +166,7 @@ export function disassociateCase(data: DisassociateCaseParams) {
|
|||
export function batchDisassociateCase(data: BatchFeatureCaseParams) {
|
||||
return MSR.post({ url: BatchDisassociateCaseUrl, data });
|
||||
}
|
||||
// 计划详情-功能用例-执行
|
||||
export function runFeatureCase(data: RunFeatureCaseParams) {
|
||||
return MSR.post({ url: RunFeatureCaseUrl, data });
|
||||
}
|
||||
|
|
|
@ -52,3 +52,5 @@ export const GetFeatureCaseModuleUrl = '/test-plan/functional/case/tree';
|
|||
export const DisassociateCaseUrl = '/test-plan/functional/case/disassociate';
|
||||
// 计划详情-功能用例-批量取消关联用例
|
||||
export const BatchDisassociateCaseUrl = '/test-plan/functional/case/batch/disassociate';
|
||||
// 计划详情-功能用例-执行
|
||||
export const RunFeatureCaseUrl = '/test-plan/functional/case/run';
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
position="tr"
|
||||
trigger="click"
|
||||
>
|
||||
<a-button :disabled="props.disabled" type="outline">
|
||||
<a-button :disabled="props.disabled" type="outline" class="arco-btn-outline--secondary">
|
||||
<template #icon> <icon-plus class="text-[14px]" /> </template>
|
||||
{{ t('system.orgTemplate.addAttachment') }}
|
||||
</a-button>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import type { PassRateCountDetail, planStatusType, TestPlanDetail } from '@/models/testPlan/testPlan';
|
||||
import { LastExecuteResults } from '@/enums/caseEnum';
|
||||
|
||||
// TODO: 对照后端字段
|
||||
// 测试计划详情
|
||||
|
@ -39,4 +40,11 @@ export const defaultDetailCount: PassRateCountDetail = {
|
|||
apiScenarioCount: 0,
|
||||
};
|
||||
|
||||
export const defaultExecuteForm = {
|
||||
lastExecResult: 'PASSED' as LastExecuteResults,
|
||||
content: '',
|
||||
planCommentFileIds: [],
|
||||
notifier: [] as string[],
|
||||
};
|
||||
|
||||
export default {};
|
||||
|
|
|
@ -175,4 +175,9 @@ export default {
|
|||
'common.moreSetting': 'More settings',
|
||||
'common.remark': 'Remark',
|
||||
'common.case': 'Case',
|
||||
'common.caseLevel': 'Case level',
|
||||
'common.caseStatus': 'Case status',
|
||||
'common.responsiblePerson': 'Responsible person',
|
||||
'common.updateUserName': 'Update user name',
|
||||
'common.updateTime': 'Update time',
|
||||
};
|
||||
|
|
|
@ -178,4 +178,9 @@ export default {
|
|||
'common.baseInfo': '基本信息',
|
||||
'common.remark': '备注',
|
||||
'common.case': '用例',
|
||||
'common.caseLevel': '用例等级',
|
||||
'common.caseStatus': '用例状态',
|
||||
'common.responsiblePerson': '责任人',
|
||||
'common.updateUserName': '更新人',
|
||||
'common.updateTime': '更新时间',
|
||||
};
|
||||
|
|
|
@ -180,4 +180,19 @@ export interface PassRateCountDetail {
|
|||
apiScenarioCount: number;
|
||||
}
|
||||
|
||||
export interface ExecuteFeatureCaseFormParams {
|
||||
lastExecResult: LastExecuteResults;
|
||||
content?: string;
|
||||
commentIds?: string[];
|
||||
planCommentFileIds?: string[];
|
||||
}
|
||||
|
||||
export interface RunFeatureCaseParams extends ExecuteFeatureCaseFormParams {
|
||||
projectId: string;
|
||||
id: string;
|
||||
testPlanId: string;
|
||||
caseId: string;
|
||||
notifier?: string;
|
||||
}
|
||||
|
||||
export default {};
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
<template #index="{ rowIndex }">
|
||||
<div class="circle text-[12px] font-medium"> {{ rowIndex + 1 }}</div>
|
||||
</template>
|
||||
<template #caseStep="{ record }">
|
||||
<template v-if="!props.isTestPlan" #caseStep="{ record }">
|
||||
<!-- v-if="record.showStep" -->
|
||||
<a-textarea
|
||||
:ref="(el: refItem) => setStepRefMap(el, record)"
|
||||
|
@ -16,7 +16,7 @@
|
|||
@blur="blurHandler(record, 'step')"
|
||||
/>
|
||||
</template>
|
||||
<template #expectedResult="{ record }">
|
||||
<template v-if="!props.isTestPlan" #expectedResult="{ record }">
|
||||
<a-textarea
|
||||
:ref="(el: refItem) => setExpectedRefMap(el, record)"
|
||||
v-model="record.expected"
|
||||
|
@ -28,6 +28,9 @@
|
|||
@blur="blurHandler(record, 'expected')"
|
||||
/>
|
||||
</template>
|
||||
<template #lastExecResult="{ record }">
|
||||
<ExecuteResult :execute-result="record.executeResult" />
|
||||
</template>
|
||||
<template #operation="{ record }">
|
||||
<MsTableMoreAction
|
||||
v-if="!record.internal"
|
||||
|
@ -53,6 +56,7 @@
|
|||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { getGenerateId } from '@/utils';
|
||||
|
@ -68,6 +72,7 @@
|
|||
stepList: any;
|
||||
isDisabled?: boolean;
|
||||
isScrollY?: boolean;
|
||||
isTestPlan?: boolean;
|
||||
}>(),
|
||||
{
|
||||
isDisabled: false,
|
||||
|
@ -100,17 +105,35 @@
|
|||
{
|
||||
title: 'system.orgTemplate.useCaseStep',
|
||||
slotName: 'caseStep',
|
||||
dataIndex: 'caseStep',
|
||||
dataIndex: 'step',
|
||||
showDrag: true,
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.orgTemplate.expectedResult',
|
||||
dataIndex: 'expectedResult',
|
||||
dataIndex: 'expected',
|
||||
slotName: 'expectedResult',
|
||||
showDrag: true,
|
||||
showInTable: true,
|
||||
},
|
||||
...(!props.isTestPlan
|
||||
? []
|
||||
: [
|
||||
{
|
||||
title: 'system.orgTemplate.actualResult',
|
||||
dataIndex: 'actualResult',
|
||||
slotName: 'actualResult',
|
||||
showDrag: true,
|
||||
showInTable: true,
|
||||
},
|
||||
{
|
||||
title: 'system.orgTemplate.stepExecutionResult',
|
||||
dataIndex: 'executeResult',
|
||||
slotName: 'lastExecResult',
|
||||
showDrag: true,
|
||||
showInTable: true,
|
||||
},
|
||||
]),
|
||||
{
|
||||
title: 'system.orgTemplate.operation',
|
||||
slotName: 'operation',
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
>
|
||||
<span class="absolute right-[6px] top-0">
|
||||
<a-button
|
||||
v-if="props.allowEdit"
|
||||
v-if="props.allowEdit && !props.isTestPlan"
|
||||
v-permission="['FUNCTIONAL_CASE:READ+UPDATE']"
|
||||
type="text"
|
||||
class="px-0"
|
||||
|
@ -45,7 +45,7 @@
|
|||
"
|
||||
class="relative"
|
||||
>
|
||||
<div class="absolute left-16 top-0 font-normal">
|
||||
<div v-if="!props.isTestPlan" class="absolute left-16 top-0 font-normal">
|
||||
<a-divider direction="vertical" />
|
||||
<a-dropdown :popup-max-height="false" @select="handleSelectType">
|
||||
<span class="changeType cursor-pointer text-[var(--color-text-3)]"
|
||||
|
@ -63,7 +63,12 @@
|
|||
</div>
|
||||
<!-- 步骤描述 -->
|
||||
<div v-if="detailForm.caseEditType === 'STEP'" class="w-full">
|
||||
<AddStep v-model:step-list="stepData" :is-scroll-y="false" :is-disabled="!isEditPreposition" />
|
||||
<AddStep
|
||||
v-model:step-list="stepData"
|
||||
:is-scroll-y="false"
|
||||
:is-test-plan="props.isTestPlan"
|
||||
:is-disabled="!isEditPreposition"
|
||||
/>
|
||||
</div>
|
||||
<!-- 文本描述 -->
|
||||
<MsRichText
|
||||
|
@ -113,13 +118,13 @@
|
|||
{{ t('common.save') }}
|
||||
</a-button></div
|
||||
>
|
||||
<div v-permission="['FUNCTIONAL_CASE:READ+UPDATE']">
|
||||
<div v-if="!props.isTestPlan" v-permission="['FUNCTIONAL_CASE:READ+UPDATE']">
|
||||
<AddAttachment v-model:file-list="fileList" multiple @change="handleChange" @link-file="associatedFile" />
|
||||
</div>
|
||||
</a-form>
|
||||
<!-- 文件列表开始 -->
|
||||
<div class="w-[90%]">
|
||||
<div v-if="!props.allowEdit" class="mb-[16px] font-medium text-[var(--color-text-1)]">
|
||||
<div v-if="!props.allowEdit || props.isTestPlan" class="mb-[16px] font-medium text-[var(--color-text-1)]">
|
||||
{{ t('caseManagement.featureCase.attachment') }}
|
||||
</div>
|
||||
<MsFileList
|
||||
|
@ -132,7 +137,7 @@
|
|||
}"
|
||||
:upload-func="uploadOrAssociationFile"
|
||||
:handle-delete="deleteFileHandler"
|
||||
:show-delete="props.allowEdit"
|
||||
:show-delete="props.allowEdit && !props.isTestPlan"
|
||||
@finish="uploadFileOver"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
|
@ -149,6 +154,7 @@
|
|||
{{ t('ms.upload.preview') }}
|
||||
</MsButton>
|
||||
<SaveAsFilePopover
|
||||
v-if="!props.isTestPlan"
|
||||
v-model:visible="transferVisible"
|
||||
:saving-file="activeTransferFileParams"
|
||||
:file-save-as-source-id="(form.id as string)"
|
||||
|
@ -291,6 +297,7 @@
|
|||
allowEdit?: boolean; // 是否允许编辑
|
||||
formRules?: FormRuleItem[]; // 编辑表单
|
||||
formApi?: any;
|
||||
isTestPlan?: boolean; // 测试计划页面的
|
||||
}>(),
|
||||
{
|
||||
allowEdit: true, // 是否允许编辑
|
||||
|
@ -582,6 +589,8 @@
|
|||
return {
|
||||
step: item.desc,
|
||||
expected: item.result,
|
||||
actualResult: item.actualResult ?? '',
|
||||
executeResult: item.executeResult ?? 'PASSED',
|
||||
};
|
||||
});
|
||||
}
|
||||
|
|
|
@ -145,7 +145,37 @@ export function getModules(moduleIds: string, treeData: ModuleTreeNode[]) {
|
|||
}
|
||||
}
|
||||
|
||||
// 处理自定义字段
|
||||
// 自定义字段
|
||||
export function getCustomField(customFields: any) {
|
||||
const multipleExcludes = ['MULTIPLE_SELECT', 'CHECKBOX', 'MULTIPLE_MEMBER'];
|
||||
const selectExcludes = ['MEMBER', 'RADIO', 'SELECT'];
|
||||
let selectValue: Record<string, any>;
|
||||
// 处理多选项
|
||||
if (multipleExcludes.includes(customFields.type) && customFields.defaultValue) {
|
||||
selectValue = JSON.parse(customFields.defaultValue);
|
||||
return (
|
||||
(customFields.options || [])
|
||||
.filter((item: any) => selectValue.includes(item.value))
|
||||
.map((it: any) => it.text)
|
||||
.join(',') || '-'
|
||||
);
|
||||
}
|
||||
if (customFields.type === 'MULTIPLE_INPUT') {
|
||||
// 处理标签形式
|
||||
return JSON.parse(customFields.defaultValue).join(',') || '-';
|
||||
}
|
||||
if (selectExcludes.includes(customFields.type)) {
|
||||
return (
|
||||
(customFields.options || [])
|
||||
.filter((item: any) => customFields.defaultValue === item.value)
|
||||
.map((it: any) => it.text)
|
||||
.join() || '-'
|
||||
);
|
||||
}
|
||||
return customFields.defaultValue || '-';
|
||||
}
|
||||
|
||||
// 处理表格自定义字段
|
||||
export function getTableFields(customFields: CustomAttributes[], itemDataIndex: MsTableColumnData, userId: string) {
|
||||
const multipleExcludes = ['MULTIPLE_SELECT', 'CHECKBOX', 'MULTIPLE_MEMBER'];
|
||||
const selectExcludes = ['MEMBER', 'RADIO', 'SELECT'];
|
||||
|
|
|
@ -261,16 +261,7 @@
|
|||
</a-spin>
|
||||
</div>
|
||||
</MsCard>
|
||||
<MsDrawer
|
||||
v-model:visible="editCaseVisible"
|
||||
:title="t('caseManagement.caseReview.updateCase')"
|
||||
:width="1200"
|
||||
:ok-text="t('common.update')"
|
||||
:ok-loading="updateCaseLoading"
|
||||
@confirm="updateCase"
|
||||
>
|
||||
<caseTemplateDetail v-if="editCaseVisible" v-model:form-mode-value="editCaseForm" :case-id="activeCaseId" />
|
||||
</MsDrawer>
|
||||
<EditCaseDetailDrawer v-model:visible="editCaseVisible" :case-id="activeCaseId" @load-case="loadCase" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -278,21 +269,19 @@
|
|||
* @description 功能测试-用例评审-用例详情
|
||||
*/
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MSAvatar from '@/components/pure/ms-avatar/index.vue';
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsDescription, { Description } from '@/components/pure/ms-description/index.vue';
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsEmpty from '@/components/pure/ms-empty/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsPagination from '@/components/pure/ms-pagination/index';
|
||||
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
||||
import caseTemplateDetail from '../caseManagementFeature/components/caseTemplateDetail.vue';
|
||||
import caseTabDemand from '../caseManagementFeature/components/tabContent/tabDemand/associatedDemandTable.vue';
|
||||
import caseTabDetail from '../caseManagementFeature/components/tabContent/tabDetail.vue';
|
||||
import EditCaseDetailDrawer from './components/editCaseDetailDrawer.vue';
|
||||
import reviewForm from './components/reviewForm.vue';
|
||||
|
||||
import {
|
||||
|
@ -300,7 +289,7 @@
|
|||
getReviewDetail,
|
||||
getReviewDetailCasePage,
|
||||
} from '@/api/modules/case-management/caseReview';
|
||||
import { getCaseDetail, updateCaseRequest } from '@/api/modules/case-management/featureCase';
|
||||
import { getCaseDetail } from '@/api/modules/case-management/featureCase';
|
||||
import { reviewDefaultDetail, reviewResultMap } from '@/config/caseManagement';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
@ -309,6 +298,8 @@
|
|||
import type { DetailCase } from '@/models/caseManagement/featureCase';
|
||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
import { getCustomField } from '@/views/case-management/caseManagementFeature/components/utils';
|
||||
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
|
@ -430,34 +421,6 @@
|
|||
}
|
||||
const caseDetailLoading = ref(false);
|
||||
|
||||
function getCustomField(customFields: any) {
|
||||
const multipleExcludes = ['MULTIPLE_SELECT', 'CHECKBOX', 'MULTIPLE_MEMBER'];
|
||||
const selectExcludes = ['MEMBER', 'RADIO', 'SELECT'];
|
||||
let selectValue: Record<string, any>;
|
||||
// 处理多选项
|
||||
if (multipleExcludes.includes(customFields.type) && customFields.defaultValue) {
|
||||
selectValue = JSON.parse(customFields.defaultValue);
|
||||
return (
|
||||
(customFields.options || [])
|
||||
.filter((item: any) => selectValue.includes(item.value))
|
||||
.map((it: any) => it.text)
|
||||
.join(',') || '-'
|
||||
);
|
||||
}
|
||||
if (customFields.type === 'MULTIPLE_INPUT') {
|
||||
// 处理标签形式
|
||||
return JSON.parse(customFields.defaultValue).join(',') || '-';
|
||||
}
|
||||
if (selectExcludes.includes(customFields.type)) {
|
||||
return (
|
||||
(customFields.options || [])
|
||||
.filter((item: any) => customFields.defaultValue === item.value)
|
||||
.map((it: any) => it.text)
|
||||
.join() || '-'
|
||||
);
|
||||
}
|
||||
return customFields.defaultValue || '-';
|
||||
}
|
||||
// 加载用例详情
|
||||
async function loadCaseDetail() {
|
||||
try {
|
||||
|
@ -588,23 +551,10 @@
|
|||
}
|
||||
|
||||
const editCaseVisible = ref(false);
|
||||
const editCaseForm = ref<Record<string, any>>({});
|
||||
const updateCaseLoading = ref(false);
|
||||
|
||||
async function updateCase() {
|
||||
try {
|
||||
updateCaseLoading.value = true;
|
||||
await updateCaseRequest(editCaseForm.value);
|
||||
editCaseVisible.value = false;
|
||||
Message.success(t('caseManagement.featureCase.editSuccess'));
|
||||
loadCaseList();
|
||||
loadCaseDetail();
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
updateCaseLoading.value = false;
|
||||
}
|
||||
async function loadCase() {
|
||||
await loadCaseList();
|
||||
loadCaseDetail();
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
|
@ -638,8 +588,7 @@
|
|||
keyword.value = route.query.reviewId as string;
|
||||
}
|
||||
initDetail();
|
||||
loadCaseList();
|
||||
loadCaseDetail();
|
||||
loadCase();
|
||||
if (showTab.value === 'detail') {
|
||||
initReviewHistoryList();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
v-model:visible="editCaseVisible"
|
||||
:title="t('caseManagement.caseReview.updateCase')"
|
||||
:width="1200"
|
||||
:ok-text="t('common.update')"
|
||||
:ok-loading="updateCaseLoading"
|
||||
@confirm="updateCase"
|
||||
>
|
||||
<CaseTemplateDetail v-if="editCaseVisible" v-model:form-mode-value="editCaseForm" :case-id="props.caseId" />
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import CaseTemplateDetail from '@/views/case-management/caseManagementFeature/components/caseTemplateDetail.vue';
|
||||
|
||||
import { updateCaseRequest } from '@/api/modules/case-management/featureCase';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const props = defineProps<{
|
||||
caseId: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'loadCase'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const editCaseVisible = defineModel<boolean>('visible', {
|
||||
required: true,
|
||||
});
|
||||
const editCaseForm = ref<Record<string, any>>({});
|
||||
const updateCaseLoading = ref(false);
|
||||
|
||||
async function updateCase() {
|
||||
try {
|
||||
updateCaseLoading.value = true;
|
||||
await updateCaseRequest(editCaseForm.value);
|
||||
editCaseVisible.value = false;
|
||||
Message.success(t('caseManagement.featureCase.editSuccess'));
|
||||
emit('loadCase');
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
updateCaseLoading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -89,6 +89,8 @@ export default {
|
|||
'system.orgTemplate.exitPreview': 'Exit Preview',
|
||||
'system.orgTemplate.useCaseStep': 'useCase Step',
|
||||
'system.orgTemplate.expectedResult': 'Expected Result',
|
||||
'system.orgTemplate.actualResult': 'Actual result',
|
||||
'system.orgTemplate.stepExecutionResult': 'Step execution result',
|
||||
'system.orgTemplate.numberIndex': 'Index',
|
||||
'system.orgTemplate.addStep': 'Add Step',
|
||||
'system.orgTemplate.caseName': 'Use case name',
|
||||
|
|
|
@ -88,6 +88,8 @@ export default {
|
|||
'system.orgTemplate.exitPreview': '退出预览',
|
||||
'system.orgTemplate.useCaseStep': '用例步骤',
|
||||
'system.orgTemplate.expectedResult': '预期结果',
|
||||
'system.orgTemplate.actualResult': '实际结果',
|
||||
'system.orgTemplate.stepExecutionResult': '步骤执行结果',
|
||||
'system.orgTemplate.numberIndex': '序号',
|
||||
'system.orgTemplate.addStep': '添加步骤',
|
||||
'system.orgTemplate.caseName': '用例名称',
|
||||
|
|
|
@ -399,7 +399,8 @@
|
|||
name: TestPlanRouteEnum.TEST_PLAN_INDEX_DETAIL_FEATURE_CASE_DETAIL,
|
||||
query: {
|
||||
...route.query,
|
||||
caseId: record.id,
|
||||
caseId: record.caseId,
|
||||
testPlanCaseId: record.id,
|
||||
},
|
||||
state: {
|
||||
params: JSON.stringify(getTableQueryParams()),
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
<template>
|
||||
<a-form ref="formRef" :model="form">
|
||||
<a-form-item field="lastExecResult" class="mb-[8px]">
|
||||
<a-radio-group v-model:model-value="form.lastExecResult" @change="clearContent">
|
||||
<a-radio v-for="item in executionResultList" :key="item.key" :value="item.key">
|
||||
<ExecuteResult :execute-result="item.key" />
|
||||
</a-radio>
|
||||
</a-radio-group>
|
||||
</a-form-item>
|
||||
<a-form-item field="content" asterisk-position="end" class="mb-0">
|
||||
<div class="flex w-full items-center">
|
||||
<MsRichText
|
||||
v-model:raw="form.content"
|
||||
v-model:commentIds="form.commentIds"
|
||||
:upload-image="handleUploadImage"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
class="w-full"
|
||||
/>
|
||||
</div>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { FormInstance } from '@arco-design/web-vue';
|
||||
|
||||
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
|
||||
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
||||
|
||||
import { editorUploadFile } from '@/api/modules/case-management/featureCase';
|
||||
import { PreviewEditorImageUrl } from '@/api/requrls/case-management/featureCase';
|
||||
import { defaultExecuteForm } from '@/config/testPlan';
|
||||
|
||||
import type { ExecuteFeatureCaseFormParams } from '@/models/testPlan/testPlan';
|
||||
|
||||
import { executionResultMap } from '@/views/case-management/caseManagementFeature/components/utils';
|
||||
|
||||
const form = defineModel<ExecuteFeatureCaseFormParams>('form', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
|
||||
const executionResultList = computed(() =>
|
||||
Object.values(executionResultMap).filter((item) => item.key !== 'UN_EXECUTED')
|
||||
);
|
||||
|
||||
async function handleUploadImage(file: File) {
|
||||
const { data } = await editorUploadFile({
|
||||
fileList: [file],
|
||||
});
|
||||
return data;
|
||||
}
|
||||
|
||||
function clearContent() {
|
||||
form.value = {
|
||||
...defaultExecuteForm,
|
||||
lastExecResult: form.value.lastExecResult,
|
||||
};
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
clearContent,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-form-item-label-col) {
|
||||
display: none;
|
||||
}
|
||||
:deep(.arco-form-item-wrapper-col) {
|
||||
flex: 1;
|
||||
}
|
||||
</style>
|
|
@ -0,0 +1,91 @@
|
|||
<template>
|
||||
<ExecuteForm v-model:form="form" />
|
||||
<!-- TODO: 双击富文本内容打开弹窗 -->
|
||||
<a-button
|
||||
type="primary"
|
||||
class="mt-[12px]"
|
||||
:disabled="submitDisabled"
|
||||
:loading="submitLoading"
|
||||
@click="() => submit()"
|
||||
>
|
||||
{{ t('caseManagement.caseReview.commitResult') }}
|
||||
</a-button>
|
||||
<a-modal
|
||||
v-model:visible="modalVisible"
|
||||
:title="t('testPlan.featureCase.startExecution')"
|
||||
class="p-[4px]"
|
||||
title-align="start"
|
||||
body-class="p-0"
|
||||
:width="800"
|
||||
:cancel-button-props="{ disabled: submitLoading }"
|
||||
:ok-button-props="{ disabled: submitDisabled }"
|
||||
:ok-loading="submitLoading"
|
||||
:ok-text="t('caseManagement.caseReview.commitResult')"
|
||||
@before-ok="submit"
|
||||
>
|
||||
<ExecuteForm v-model:form="form" />
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
import ExecuteForm from '@/views/test-plan/testPlan/detail/featureCase/components/executeForm.vue';
|
||||
|
||||
import { runFeatureCase } from '@/api/modules/test-plan/testPlan';
|
||||
import { defaultExecuteForm } from '@/config/testPlan';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import type { ExecuteFeatureCaseFormParams } from '@/models/testPlan/testPlan';
|
||||
|
||||
const props = defineProps<{
|
||||
caseId: string;
|
||||
testPlanId: string;
|
||||
id: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const form = ref<ExecuteFeatureCaseFormParams>({ ...defaultExecuteForm });
|
||||
|
||||
const modalVisible = ref(false);
|
||||
const submitLoading = ref(false);
|
||||
const submitDisabled = computed(
|
||||
() =>
|
||||
form.value.lastExecResult !== 'PASSED' &&
|
||||
(form.value.content === '' || form.value.content?.trim() === '<p style=""></p>')
|
||||
);
|
||||
|
||||
// 提交执行
|
||||
async function submit() {
|
||||
try {
|
||||
submitLoading.value = true;
|
||||
const params = {
|
||||
projectId: appStore.currentProjectId,
|
||||
caseId: props.caseId,
|
||||
testPlanId: props.testPlanId,
|
||||
id: props.id,
|
||||
lastExecResult: form.value.lastExecResult,
|
||||
content: form.value.content,
|
||||
notifier: form.value?.commentIds?.join(';'),
|
||||
};
|
||||
await runFeatureCase(params);
|
||||
modalVisible.value = false;
|
||||
Message.success(t('common.updateSuccess'));
|
||||
form.value = { ...defaultExecuteForm };
|
||||
emit('done');
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
submitLoading.value = false;
|
||||
}
|
||||
}
|
||||
</script>
|
|
@ -32,7 +32,8 @@
|
|||
<div
|
||||
v-for="item of caseList"
|
||||
:key="item.id"
|
||||
:class="['case-item', caseDetail.id === item.id ? 'case-item--active' : '']"
|
||||
:class="['case-item', caseDetail.id === item.caseId ? 'case-item--active' : '']"
|
||||
@click="changeActiveCase(item)"
|
||||
>
|
||||
<div class="mb-[8px] flex items-center justify-between">
|
||||
<div class="text-[var(--color-text-4)]">{{ item.num }}</div>
|
||||
|
@ -57,11 +58,13 @@
|
|||
</a-spin>
|
||||
</div>
|
||||
<!-- 右侧 -->
|
||||
<a-spin :loading="caseDetailLoading" class="relative flex flex-1 flex-col p-[16px]">
|
||||
<div class="flex">
|
||||
<a-spin :loading="caseDetailLoading" class="relative flex h-full flex-1 flex-col">
|
||||
<div class="flex px-[16px] pt-[16px]">
|
||||
<div class="mr-[24px] flex flex-1 items-center">
|
||||
<MsStatusTag :status="caseDetail.status || 'PREPARED'" />
|
||||
<div class="ml-[8px] mr-[2px] font-medium text-[rgb(var(--primary-5))]">[{{ caseDetail.num }}]</div>
|
||||
<div class="ml-[8px] mr-[2px] cursor-pointer font-medium text-[rgb(var(--primary-5))]" @click="goCaseDetail"
|
||||
>[{{ caseDetail.num }}]</div
|
||||
>
|
||||
<div class="flex-1 overflow-hidden">
|
||||
<a-tooltip :content="caseDetail.name">
|
||||
<div class="one-line-text max-w-[100%] font-medium">
|
||||
|
@ -70,16 +73,52 @@
|
|||
</a-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<a-button type="outline">{{ t('common.edit') }}</a-button>
|
||||
<a-button v-permission="['FUNCTIONAL_CASE:READ+UPDATE']" type="outline" @click="editCaseVisible = true">{{
|
||||
t('common.edit')
|
||||
}}</a-button>
|
||||
</div>
|
||||
<MsTab
|
||||
v-model:active-key="activeTab"
|
||||
:show-badge="false"
|
||||
:content-tab-list="contentTabList"
|
||||
no-content
|
||||
class="relative border-b"
|
||||
class="relative mx-[16px] border-b"
|
||||
/>
|
||||
<div class="tab-content">
|
||||
<div :class="[' flex-1', activeTab !== 'detail' ? 'tab-content' : 'overflow-hidden']">
|
||||
<!-- TODO: 属性的样式 -->
|
||||
<MsDescription v-if="activeTab === 'baseInfo'" :descriptions="descriptions" :column="2" />
|
||||
<div v-else-if="activeTab === 'detail'" class="align-content-start flex h-full flex-col">
|
||||
<CaseTabDetail is-test-plan :form="caseDetail" />
|
||||
<!-- 开始执行 -->
|
||||
<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="font-medium text-[var(--color-text-1)]">
|
||||
{{ t('testPlan.featureCase.startExecution') }}
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<a-switch v-model:model-value="autoNext" size="small" />
|
||||
<div class="mx-[8px]">{{ t('caseManagement.caseReview.autoNext') }}</div>
|
||||
<a-tooltip position="right">
|
||||
<template #content>
|
||||
<div>{{ t('caseManagement.caseReview.autoNextTip1') }}</div>
|
||||
<div>{{ t('caseManagement.caseReview.autoNextTip2') }}</div>
|
||||
</template>
|
||||
<icon-question-circle
|
||||
class="text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-4))]"
|
||||
size="16"
|
||||
/>
|
||||
</a-tooltip>
|
||||
<!-- TODO: 缺陷 -->
|
||||
</div>
|
||||
</div>
|
||||
<ExecuteSubmit
|
||||
:id="activeId"
|
||||
:case-id="activeCaseId"
|
||||
:test-plan-id="route.query.id as string"
|
||||
@done="executeDone"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<BugList v-if="activeTab === 'defectList'" :case-id="caseDetail.id" />
|
||||
<!-- TODO 待写页面 还未提交 -->
|
||||
<!-- <ExecutionHistory v-if="activeTab === 'executionHistory'" :case-id="caseDetail.id" /> -->
|
||||
|
@ -87,35 +126,56 @@
|
|||
</a-spin>
|
||||
</div>
|
||||
</MsCard>
|
||||
<EditCaseDetailDrawer v-model:visible="editCaseVisible" :case-id="activeCaseId" @load-case="loadCase" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useRoute } from 'vue-router';
|
||||
import { useRoute, useRouter } from 'vue-router';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsDescription, { Description } from '@/components/pure/ms-description/index.vue';
|
||||
import MsEmpty from '@/components/pure/ms-empty/index.vue';
|
||||
import MsPagination from '@/components/pure/ms-pagination/index';
|
||||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
||||
import MsStatusTag from '@/components/business/ms-status-tag/index.vue';
|
||||
import BugList from './bug/index.vue';
|
||||
import ExecuteSubmit from './executeSubmit.vue';
|
||||
import CaseTabDetail from '@/views/case-management/caseManagementFeature/components/tabContent/tabDetail.vue';
|
||||
import EditCaseDetailDrawer from '@/views/case-management/caseReview/components/editCaseDetailDrawer.vue';
|
||||
|
||||
import { getCaseDetail } from '@/api/modules/case-management/featureCase';
|
||||
// import ExecutionHistory from '@/views/test-plan/testPlan/detail/featureCase/detail/executionHistory/index.vue';
|
||||
import { getPlanDetailFeatureCaseList } from '@/api/modules/test-plan/testPlan';
|
||||
import { getPlanDetailFeatureCaseList, getTestPlanDetail } from '@/api/modules/test-plan/testPlan';
|
||||
import { testPlanDefaultDetail } from '@/config/testPlan';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import type { PlanDetailFeatureCaseItem } from '@/models/testPlan/testPlan';
|
||||
import type { PlanDetailFeatureCaseItem, TestPlanDetail } from '@/models/testPlan/testPlan';
|
||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
import { executionResultMap } from '@/views/case-management/caseManagementFeature/components/utils';
|
||||
import { executionResultMap, getCustomField } from '@/views/case-management/caseManagementFeature/components/utils';
|
||||
|
||||
const { t } = useI18n();
|
||||
const route = useRoute();
|
||||
const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
|
||||
// TODO
|
||||
const planDetail = ref({ num: '111', name: '222lalallalallalalalal222lalallalallalalalal222lalallalallalalalal' });
|
||||
const planDetail = ref<TestPlanDetail>({
|
||||
...testPlanDefaultDetail,
|
||||
});
|
||||
async function getPlanDetail() {
|
||||
try {
|
||||
planDetail.value = await getTestPlanDetail(route.query.id as string);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const activeCaseId = ref(route.query.caseId as string);
|
||||
const activeId = ref(route.query.testPlanCaseId as string);
|
||||
const keyword = ref('');
|
||||
const lastExecResult = ref('');
|
||||
const executeResultOptions = computed(() => {
|
||||
|
@ -164,9 +224,18 @@
|
|||
}
|
||||
}
|
||||
|
||||
function goCaseDetail() {
|
||||
window.open(
|
||||
`${window.location.origin}#${
|
||||
router.resolve({ name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE }).fullPath
|
||||
}?id=${activeCaseId.value}&orgId=${appStore.currentOrgId}&pId=${appStore.currentProjectId}`
|
||||
);
|
||||
}
|
||||
|
||||
const caseDetail = ref<any>({});
|
||||
const caseDetailLoading = ref(false);
|
||||
const activeTab = ref('detail');
|
||||
const editCaseVisible = ref(false);
|
||||
const contentTabList = ref([
|
||||
{
|
||||
value: 'baseInfo',
|
||||
|
@ -185,6 +254,102 @@
|
|||
label: t('testPlan.featureCase.executionHistory'),
|
||||
},
|
||||
]);
|
||||
const descriptions = ref<Description[]>([]);
|
||||
|
||||
// 获取用例详情
|
||||
async function loadCaseDetail() {
|
||||
try {
|
||||
caseDetailLoading.value = true;
|
||||
const res = await getCaseDetail(activeCaseId.value);
|
||||
caseDetail.value = res;
|
||||
descriptions.value = [
|
||||
{
|
||||
label: t('common.belongModule'),
|
||||
value: res.moduleName || t('common.root'),
|
||||
},
|
||||
{
|
||||
label: t('common.tag'),
|
||||
value: res.tags,
|
||||
isTag: true,
|
||||
},
|
||||
{
|
||||
label: t('caseManagement.featureCase.reviewResult'),
|
||||
value: res.reviewStatus,
|
||||
},
|
||||
// 解析用例模板的自定义字段
|
||||
...res.customFields.map((e: Record<string, any>) => {
|
||||
try {
|
||||
return {
|
||||
label: e.fieldName,
|
||||
value: getCustomField(e),
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
label: e.fieldName,
|
||||
value: e.defaultValue,
|
||||
};
|
||||
}
|
||||
}),
|
||||
{
|
||||
label: t('common.creator'),
|
||||
value: res.createUserName,
|
||||
},
|
||||
{
|
||||
label: t('common.createTime'),
|
||||
value: dayjs(res.createTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
];
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
caseDetailLoading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
function changeActiveCase(item: PlanDetailFeatureCaseItem) {
|
||||
if (activeCaseId.value !== item.caseId) {
|
||||
activeCaseId.value = item.caseId;
|
||||
activeId.value = item.id;
|
||||
}
|
||||
}
|
||||
watch(
|
||||
() => activeCaseId.value,
|
||||
() => {
|
||||
loadCaseDetail();
|
||||
}
|
||||
);
|
||||
|
||||
async function loadCase() {
|
||||
await loadCaseList();
|
||||
await loadCaseDetail();
|
||||
}
|
||||
|
||||
const autoNext = ref(true);
|
||||
async function executeDone() {
|
||||
if (autoNext.value) {
|
||||
// 自动下一个,更改激活的 id会刷新详情
|
||||
const index = caseList.value.findIndex((e) => e.caseId === activeCaseId.value);
|
||||
if (index < caseList.value.length - 1) {
|
||||
await loadCaseList();
|
||||
activeCaseId.value = caseList.value[index + 1].caseId;
|
||||
activeId.value = caseList.value[index + 1].id;
|
||||
} else if (pageNation.value.current * pageNation.value.pageSize < pageNation.value.total) {
|
||||
// 当前页不是最后一页,则加载下一页并激活第一个用例
|
||||
pageNation.value.current += 1;
|
||||
await loadCaseList();
|
||||
activeCaseId.value = caseList.value[0].caseId;
|
||||
activeId.value = caseList.value[0].id;
|
||||
} else {
|
||||
// 当前是最后一个,刷新数据
|
||||
loadCaseDetail();
|
||||
loadCaseList();
|
||||
}
|
||||
} else {
|
||||
// 不自动下一个才请求详情
|
||||
loadCase();
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(async () => {
|
||||
const lastPageParams = window.history.state.params ? JSON.parse(window.history.state.params) : null; // 获取上个页面带过来的表格查询参数
|
||||
|
@ -201,9 +366,8 @@
|
|||
moduleIds,
|
||||
};
|
||||
}
|
||||
await loadCaseList();
|
||||
// TODO 获取用例详情 暂时
|
||||
caseDetail.value = caseList.value[0] ?? {};
|
||||
getPlanDetail();
|
||||
await loadCase();
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -231,7 +395,15 @@
|
|||
}
|
||||
}
|
||||
.tab-content {
|
||||
@apply overflow-y-auto;
|
||||
|
||||
padding: 16px;
|
||||
.ms-scroll-bar();
|
||||
}
|
||||
:deep(.caseDetailWrapper) {
|
||||
@apply flex-1 overflow-y-auto;
|
||||
|
||||
padding: 16px;
|
||||
.ms-scroll-bar();
|
||||
@apply py-4;
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -95,4 +95,5 @@ export default {
|
|||
'testPlan.featureCase.disassociateTip': '确认取消关联 { name } 吗?',
|
||||
'testPlan.featureCase.disassociateTipContent': '取消后,影响测试计划相关统计',
|
||||
'testPlan.featureCase.batchDisassociateTipContent': '取消后,再次关联,执行结果为:未执行',
|
||||
'testPlan.featureCase.startExecution': '开始执行',
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue