feat(测试计划): 测试计划详情-功能用例详情基本信息tab和详情tab
This commit is contained in:
parent
f1fe90aeb1
commit
2e08868d98
|
@ -25,6 +25,7 @@ import {
|
||||||
MoveTestPlanModuleUrl,
|
MoveTestPlanModuleUrl,
|
||||||
planDetailBugPageUrl,
|
planDetailBugPageUrl,
|
||||||
planPassRateUrl,
|
planPassRateUrl,
|
||||||
|
RunFeatureCaseUrl,
|
||||||
updateTestPlanModuleUrl,
|
updateTestPlanModuleUrl,
|
||||||
UpdateTestPlanUrl,
|
UpdateTestPlanUrl,
|
||||||
} from '@/api/requrls/test-plan/testPlan';
|
} from '@/api/requrls/test-plan/testPlan';
|
||||||
|
@ -42,6 +43,7 @@ import type {
|
||||||
PlanDetailBugItem,
|
PlanDetailBugItem,
|
||||||
PlanDetailFeatureCaseItem,
|
PlanDetailFeatureCaseItem,
|
||||||
PlanDetailFeatureCaseListQueryParams,
|
PlanDetailFeatureCaseListQueryParams,
|
||||||
|
RunFeatureCaseParams,
|
||||||
TestPlanDetail,
|
TestPlanDetail,
|
||||||
TestPlanItem,
|
TestPlanItem,
|
||||||
UseCountType,
|
UseCountType,
|
||||||
|
@ -164,3 +166,7 @@ export function disassociateCase(data: DisassociateCaseParams) {
|
||||||
export function batchDisassociateCase(data: BatchFeatureCaseParams) {
|
export function batchDisassociateCase(data: BatchFeatureCaseParams) {
|
||||||
return MSR.post({ url: BatchDisassociateCaseUrl, data });
|
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 DisassociateCaseUrl = '/test-plan/functional/case/disassociate';
|
||||||
// 计划详情-功能用例-批量取消关联用例
|
// 计划详情-功能用例-批量取消关联用例
|
||||||
export const BatchDisassociateCaseUrl = '/test-plan/functional/case/batch/disassociate';
|
export const BatchDisassociateCaseUrl = '/test-plan/functional/case/batch/disassociate';
|
||||||
|
// 计划详情-功能用例-执行
|
||||||
|
export const RunFeatureCaseUrl = '/test-plan/functional/case/run';
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
position="tr"
|
position="tr"
|
||||||
trigger="click"
|
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>
|
<template #icon> <icon-plus class="text-[14px]" /> </template>
|
||||||
{{ t('system.orgTemplate.addAttachment') }}
|
{{ t('system.orgTemplate.addAttachment') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
import type { PassRateCountDetail, planStatusType, TestPlanDetail } from '@/models/testPlan/testPlan';
|
import type { PassRateCountDetail, planStatusType, TestPlanDetail } from '@/models/testPlan/testPlan';
|
||||||
|
import { LastExecuteResults } from '@/enums/caseEnum';
|
||||||
|
|
||||||
// TODO: 对照后端字段
|
// TODO: 对照后端字段
|
||||||
// 测试计划详情
|
// 测试计划详情
|
||||||
|
@ -39,4 +40,11 @@ export const defaultDetailCount: PassRateCountDetail = {
|
||||||
apiScenarioCount: 0,
|
apiScenarioCount: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const defaultExecuteForm = {
|
||||||
|
lastExecResult: 'PASSED' as LastExecuteResults,
|
||||||
|
content: '',
|
||||||
|
planCommentFileIds: [],
|
||||||
|
notifier: [] as string[],
|
||||||
|
};
|
||||||
|
|
||||||
export default {};
|
export default {};
|
||||||
|
|
|
@ -175,4 +175,9 @@ export default {
|
||||||
'common.moreSetting': 'More settings',
|
'common.moreSetting': 'More settings',
|
||||||
'common.remark': 'Remark',
|
'common.remark': 'Remark',
|
||||||
'common.case': 'Case',
|
'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.baseInfo': '基本信息',
|
||||||
'common.remark': '备注',
|
'common.remark': '备注',
|
||||||
'common.case': '用例',
|
'common.case': '用例',
|
||||||
|
'common.caseLevel': '用例等级',
|
||||||
|
'common.caseStatus': '用例状态',
|
||||||
|
'common.responsiblePerson': '责任人',
|
||||||
|
'common.updateUserName': '更新人',
|
||||||
|
'common.updateTime': '更新时间',
|
||||||
};
|
};
|
||||||
|
|
|
@ -180,4 +180,19 @@ export interface PassRateCountDetail {
|
||||||
apiScenarioCount: number;
|
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 {};
|
export default {};
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<template #index="{ rowIndex }">
|
<template #index="{ rowIndex }">
|
||||||
<div class="circle text-[12px] font-medium"> {{ rowIndex + 1 }}</div>
|
<div class="circle text-[12px] font-medium"> {{ rowIndex + 1 }}</div>
|
||||||
</template>
|
</template>
|
||||||
<template #caseStep="{ record }">
|
<template v-if="!props.isTestPlan" #caseStep="{ record }">
|
||||||
<!-- v-if="record.showStep" -->
|
<!-- v-if="record.showStep" -->
|
||||||
<a-textarea
|
<a-textarea
|
||||||
:ref="(el: refItem) => setStepRefMap(el, record)"
|
:ref="(el: refItem) => setStepRefMap(el, record)"
|
||||||
|
@ -16,7 +16,7 @@
|
||||||
@blur="blurHandler(record, 'step')"
|
@blur="blurHandler(record, 'step')"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #expectedResult="{ record }">
|
<template v-if="!props.isTestPlan" #expectedResult="{ record }">
|
||||||
<a-textarea
|
<a-textarea
|
||||||
:ref="(el: refItem) => setExpectedRefMap(el, record)"
|
:ref="(el: refItem) => setExpectedRefMap(el, record)"
|
||||||
v-model="record.expected"
|
v-model="record.expected"
|
||||||
|
@ -28,6 +28,9 @@
|
||||||
@blur="blurHandler(record, 'expected')"
|
@blur="blurHandler(record, 'expected')"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
<template #lastExecResult="{ record }">
|
||||||
|
<ExecuteResult :execute-result="record.executeResult" />
|
||||||
|
</template>
|
||||||
<template #operation="{ record }">
|
<template #operation="{ record }">
|
||||||
<MsTableMoreAction
|
<MsTableMoreAction
|
||||||
v-if="!record.internal"
|
v-if="!record.internal"
|
||||||
|
@ -53,6 +56,7 @@
|
||||||
import useTable from '@/components/pure/ms-table/useTable';
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
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 { useI18n } from '@/hooks/useI18n';
|
||||||
import { getGenerateId } from '@/utils';
|
import { getGenerateId } from '@/utils';
|
||||||
|
@ -68,6 +72,7 @@
|
||||||
stepList: any;
|
stepList: any;
|
||||||
isDisabled?: boolean;
|
isDisabled?: boolean;
|
||||||
isScrollY?: boolean;
|
isScrollY?: boolean;
|
||||||
|
isTestPlan?: boolean;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
isDisabled: false,
|
isDisabled: false,
|
||||||
|
@ -100,17 +105,35 @@
|
||||||
{
|
{
|
||||||
title: 'system.orgTemplate.useCaseStep',
|
title: 'system.orgTemplate.useCaseStep',
|
||||||
slotName: 'caseStep',
|
slotName: 'caseStep',
|
||||||
dataIndex: 'caseStep',
|
dataIndex: 'step',
|
||||||
showDrag: true,
|
showDrag: true,
|
||||||
showInTable: true,
|
showInTable: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'system.orgTemplate.expectedResult',
|
title: 'system.orgTemplate.expectedResult',
|
||||||
dataIndex: 'expectedResult',
|
dataIndex: 'expected',
|
||||||
slotName: 'expectedResult',
|
slotName: 'expectedResult',
|
||||||
showDrag: true,
|
showDrag: true,
|
||||||
showInTable: 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',
|
title: 'system.orgTemplate.operation',
|
||||||
slotName: 'operation',
|
slotName: 'operation',
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
>
|
>
|
||||||
<span class="absolute right-[6px] top-0">
|
<span class="absolute right-[6px] top-0">
|
||||||
<a-button
|
<a-button
|
||||||
v-if="props.allowEdit"
|
v-if="props.allowEdit && !props.isTestPlan"
|
||||||
v-permission="['FUNCTIONAL_CASE:READ+UPDATE']"
|
v-permission="['FUNCTIONAL_CASE:READ+UPDATE']"
|
||||||
type="text"
|
type="text"
|
||||||
class="px-0"
|
class="px-0"
|
||||||
|
@ -45,7 +45,7 @@
|
||||||
"
|
"
|
||||||
class="relative"
|
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-divider direction="vertical" />
|
||||||
<a-dropdown :popup-max-height="false" @select="handleSelectType">
|
<a-dropdown :popup-max-height="false" @select="handleSelectType">
|
||||||
<span class="changeType cursor-pointer text-[var(--color-text-3)]"
|
<span class="changeType cursor-pointer text-[var(--color-text-3)]"
|
||||||
|
@ -63,7 +63,12 @@
|
||||||
</div>
|
</div>
|
||||||
<!-- 步骤描述 -->
|
<!-- 步骤描述 -->
|
||||||
<div v-if="detailForm.caseEditType === 'STEP'" class="w-full">
|
<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>
|
</div>
|
||||||
<!-- 文本描述 -->
|
<!-- 文本描述 -->
|
||||||
<MsRichText
|
<MsRichText
|
||||||
|
@ -113,13 +118,13 @@
|
||||||
{{ t('common.save') }}
|
{{ t('common.save') }}
|
||||||
</a-button></div
|
</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" />
|
<AddAttachment v-model:file-list="fileList" multiple @change="handleChange" @link-file="associatedFile" />
|
||||||
</div>
|
</div>
|
||||||
</a-form>
|
</a-form>
|
||||||
<!-- 文件列表开始 -->
|
<!-- 文件列表开始 -->
|
||||||
<div class="w-[90%]">
|
<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') }}
|
{{ t('caseManagement.featureCase.attachment') }}
|
||||||
</div>
|
</div>
|
||||||
<MsFileList
|
<MsFileList
|
||||||
|
@ -132,7 +137,7 @@
|
||||||
}"
|
}"
|
||||||
:upload-func="uploadOrAssociationFile"
|
:upload-func="uploadOrAssociationFile"
|
||||||
:handle-delete="deleteFileHandler"
|
:handle-delete="deleteFileHandler"
|
||||||
:show-delete="props.allowEdit"
|
:show-delete="props.allowEdit && !props.isTestPlan"
|
||||||
@finish="uploadFileOver"
|
@finish="uploadFileOver"
|
||||||
>
|
>
|
||||||
<template #actions="{ item }">
|
<template #actions="{ item }">
|
||||||
|
@ -149,6 +154,7 @@
|
||||||
{{ t('ms.upload.preview') }}
|
{{ t('ms.upload.preview') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
<SaveAsFilePopover
|
<SaveAsFilePopover
|
||||||
|
v-if="!props.isTestPlan"
|
||||||
v-model:visible="transferVisible"
|
v-model:visible="transferVisible"
|
||||||
:saving-file="activeTransferFileParams"
|
:saving-file="activeTransferFileParams"
|
||||||
:file-save-as-source-id="(form.id as string)"
|
:file-save-as-source-id="(form.id as string)"
|
||||||
|
@ -291,6 +297,7 @@
|
||||||
allowEdit?: boolean; // 是否允许编辑
|
allowEdit?: boolean; // 是否允许编辑
|
||||||
formRules?: FormRuleItem[]; // 编辑表单
|
formRules?: FormRuleItem[]; // 编辑表单
|
||||||
formApi?: any;
|
formApi?: any;
|
||||||
|
isTestPlan?: boolean; // 测试计划页面的
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
allowEdit: true, // 是否允许编辑
|
allowEdit: true, // 是否允许编辑
|
||||||
|
@ -582,6 +589,8 @@
|
||||||
return {
|
return {
|
||||||
step: item.desc,
|
step: item.desc,
|
||||||
expected: item.result,
|
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) {
|
export function getTableFields(customFields: CustomAttributes[], itemDataIndex: MsTableColumnData, userId: string) {
|
||||||
const multipleExcludes = ['MULTIPLE_SELECT', 'CHECKBOX', 'MULTIPLE_MEMBER'];
|
const multipleExcludes = ['MULTIPLE_SELECT', 'CHECKBOX', 'MULTIPLE_MEMBER'];
|
||||||
const selectExcludes = ['MEMBER', 'RADIO', 'SELECT'];
|
const selectExcludes = ['MEMBER', 'RADIO', 'SELECT'];
|
||||||
|
|
|
@ -261,16 +261,7 @@
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</div>
|
</div>
|
||||||
</MsCard>
|
</MsCard>
|
||||||
<MsDrawer
|
<EditCaseDetailDrawer v-model:visible="editCaseVisible" :case-id="activeCaseId" @load-case="loadCase" />
|
||||||
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>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
@ -278,21 +269,19 @@
|
||||||
* @description 功能测试-用例评审-用例详情
|
* @description 功能测试-用例评审-用例详情
|
||||||
*/
|
*/
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
import { useRoute, useRouter } from 'vue-router';
|
||||||
import { Message } from '@arco-design/web-vue';
|
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import MSAvatar from '@/components/pure/ms-avatar/index.vue';
|
import MSAvatar from '@/components/pure/ms-avatar/index.vue';
|
||||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||||
import MsDescription, { Description } from '@/components/pure/ms-description/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 MsEmpty from '@/components/pure/ms-empty/index.vue';
|
||||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
import MsPagination from '@/components/pure/ms-pagination/index';
|
import MsPagination from '@/components/pure/ms-pagination/index';
|
||||||
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||||
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
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 caseTabDemand from '../caseManagementFeature/components/tabContent/tabDemand/associatedDemandTable.vue';
|
||||||
import caseTabDetail from '../caseManagementFeature/components/tabContent/tabDetail.vue';
|
import caseTabDetail from '../caseManagementFeature/components/tabContent/tabDetail.vue';
|
||||||
|
import EditCaseDetailDrawer from './components/editCaseDetailDrawer.vue';
|
||||||
import reviewForm from './components/reviewForm.vue';
|
import reviewForm from './components/reviewForm.vue';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
|
@ -300,7 +289,7 @@
|
||||||
getReviewDetail,
|
getReviewDetail,
|
||||||
getReviewDetailCasePage,
|
getReviewDetailCasePage,
|
||||||
} from '@/api/modules/case-management/caseReview';
|
} 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 { reviewDefaultDetail, reviewResultMap } from '@/config/caseManagement';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
@ -309,6 +298,8 @@
|
||||||
import type { DetailCase } from '@/models/caseManagement/featureCase';
|
import type { DetailCase } from '@/models/caseManagement/featureCase';
|
||||||
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
|
||||||
|
|
||||||
|
import { getCustomField } from '@/views/case-management/caseManagementFeature/components/utils';
|
||||||
|
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
@ -430,34 +421,6 @@
|
||||||
}
|
}
|
||||||
const caseDetailLoading = ref(false);
|
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() {
|
async function loadCaseDetail() {
|
||||||
try {
|
try {
|
||||||
|
@ -588,23 +551,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const editCaseVisible = ref(false);
|
const editCaseVisible = ref(false);
|
||||||
const editCaseForm = ref<Record<string, any>>({});
|
|
||||||
const updateCaseLoading = ref(false);
|
|
||||||
|
|
||||||
async function updateCase() {
|
async function loadCase() {
|
||||||
try {
|
await loadCaseList();
|
||||||
updateCaseLoading.value = true;
|
loadCaseDetail();
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
|
@ -638,8 +588,7 @@
|
||||||
keyword.value = route.query.reviewId as string;
|
keyword.value = route.query.reviewId as string;
|
||||||
}
|
}
|
||||||
initDetail();
|
initDetail();
|
||||||
loadCaseList();
|
loadCase();
|
||||||
loadCaseDetail();
|
|
||||||
if (showTab.value === 'detail') {
|
if (showTab.value === 'detail') {
|
||||||
initReviewHistoryList();
|
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.exitPreview': 'Exit Preview',
|
||||||
'system.orgTemplate.useCaseStep': 'useCase Step',
|
'system.orgTemplate.useCaseStep': 'useCase Step',
|
||||||
'system.orgTemplate.expectedResult': 'Expected Result',
|
'system.orgTemplate.expectedResult': 'Expected Result',
|
||||||
|
'system.orgTemplate.actualResult': 'Actual result',
|
||||||
|
'system.orgTemplate.stepExecutionResult': 'Step execution result',
|
||||||
'system.orgTemplate.numberIndex': 'Index',
|
'system.orgTemplate.numberIndex': 'Index',
|
||||||
'system.orgTemplate.addStep': 'Add Step',
|
'system.orgTemplate.addStep': 'Add Step',
|
||||||
'system.orgTemplate.caseName': 'Use case name',
|
'system.orgTemplate.caseName': 'Use case name',
|
||||||
|
|
|
@ -88,6 +88,8 @@ export default {
|
||||||
'system.orgTemplate.exitPreview': '退出预览',
|
'system.orgTemplate.exitPreview': '退出预览',
|
||||||
'system.orgTemplate.useCaseStep': '用例步骤',
|
'system.orgTemplate.useCaseStep': '用例步骤',
|
||||||
'system.orgTemplate.expectedResult': '预期结果',
|
'system.orgTemplate.expectedResult': '预期结果',
|
||||||
|
'system.orgTemplate.actualResult': '实际结果',
|
||||||
|
'system.orgTemplate.stepExecutionResult': '步骤执行结果',
|
||||||
'system.orgTemplate.numberIndex': '序号',
|
'system.orgTemplate.numberIndex': '序号',
|
||||||
'system.orgTemplate.addStep': '添加步骤',
|
'system.orgTemplate.addStep': '添加步骤',
|
||||||
'system.orgTemplate.caseName': '用例名称',
|
'system.orgTemplate.caseName': '用例名称',
|
||||||
|
|
|
@ -399,7 +399,8 @@
|
||||||
name: TestPlanRouteEnum.TEST_PLAN_INDEX_DETAIL_FEATURE_CASE_DETAIL,
|
name: TestPlanRouteEnum.TEST_PLAN_INDEX_DETAIL_FEATURE_CASE_DETAIL,
|
||||||
query: {
|
query: {
|
||||||
...route.query,
|
...route.query,
|
||||||
caseId: record.id,
|
caseId: record.caseId,
|
||||||
|
testPlanCaseId: record.id,
|
||||||
},
|
},
|
||||||
state: {
|
state: {
|
||||||
params: JSON.stringify(getTableQueryParams()),
|
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
|
<div
|
||||||
v-for="item of caseList"
|
v-for="item of caseList"
|
||||||
:key="item.id"
|
: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="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>
|
||||||
|
@ -57,11 +58,13 @@
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</div>
|
</div>
|
||||||
<!-- 右侧 -->
|
<!-- 右侧 -->
|
||||||
<a-spin :loading="caseDetailLoading" class="relative flex flex-1 flex-col p-[16px]">
|
<a-spin :loading="caseDetailLoading" class="relative flex h-full flex-1 flex-col">
|
||||||
<div class="flex">
|
<div class="flex px-[16px] pt-[16px]">
|
||||||
<div class="mr-[24px] flex flex-1 items-center">
|
<div class="mr-[24px] flex flex-1 items-center">
|
||||||
<MsStatusTag :status="caseDetail.status || 'PREPARED'" />
|
<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">
|
<div class="flex-1 overflow-hidden">
|
||||||
<a-tooltip :content="caseDetail.name">
|
<a-tooltip :content="caseDetail.name">
|
||||||
<div class="one-line-text max-w-[100%] font-medium">
|
<div class="one-line-text max-w-[100%] font-medium">
|
||||||
|
@ -70,16 +73,52 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
<MsTab
|
<MsTab
|
||||||
v-model:active-key="activeTab"
|
v-model:active-key="activeTab"
|
||||||
:show-badge="false"
|
:show-badge="false"
|
||||||
:content-tab-list="contentTabList"
|
:content-tab-list="contentTabList"
|
||||||
no-content
|
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" />
|
<BugList v-if="activeTab === 'defectList'" :case-id="caseDetail.id" />
|
||||||
<!-- TODO 待写页面 还未提交 -->
|
<!-- TODO 待写页面 还未提交 -->
|
||||||
<!-- <ExecutionHistory v-if="activeTab === 'executionHistory'" :case-id="caseDetail.id" /> -->
|
<!-- <ExecutionHistory v-if="activeTab === 'executionHistory'" :case-id="caseDetail.id" /> -->
|
||||||
|
@ -87,35 +126,56 @@
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</div>
|
</div>
|
||||||
</MsCard>
|
</MsCard>
|
||||||
|
<EditCaseDetailDrawer v-model:visible="editCaseVisible" :case-id="activeCaseId" @load-case="loadCase" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<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 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 MsEmpty from '@/components/pure/ms-empty/index.vue';
|
||||||
import MsPagination from '@/components/pure/ms-pagination/index';
|
import MsPagination from '@/components/pure/ms-pagination/index';
|
||||||
import MsTab from '@/components/pure/ms-tab/index.vue';
|
import MsTab from '@/components/pure/ms-tab/index.vue';
|
||||||
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
||||||
import MsStatusTag from '@/components/business/ms-status-tag/index.vue';
|
import MsStatusTag from '@/components/business/ms-status-tag/index.vue';
|
||||||
import BugList from './bug/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 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 { useI18n } from '@/hooks/useI18n';
|
||||||
import useAppStore from '@/store/modules/app';
|
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 { t } = useI18n();
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
const router = useRouter();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
|
||||||
// TODO
|
const planDetail = ref<TestPlanDetail>({
|
||||||
const planDetail = ref({ num: '111', name: '222lalallalallalalalal222lalallalallalalalal222lalallalallalalalal' });
|
...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 keyword = ref('');
|
||||||
const lastExecResult = ref('');
|
const lastExecResult = ref('');
|
||||||
const executeResultOptions = computed(() => {
|
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 caseDetail = ref<any>({});
|
||||||
const caseDetailLoading = ref(false);
|
const caseDetailLoading = ref(false);
|
||||||
const activeTab = ref('detail');
|
const activeTab = ref('detail');
|
||||||
|
const editCaseVisible = ref(false);
|
||||||
const contentTabList = ref([
|
const contentTabList = ref([
|
||||||
{
|
{
|
||||||
value: 'baseInfo',
|
value: 'baseInfo',
|
||||||
|
@ -185,6 +254,102 @@
|
||||||
label: t('testPlan.featureCase.executionHistory'),
|
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 () => {
|
onBeforeMount(async () => {
|
||||||
const lastPageParams = window.history.state.params ? JSON.parse(window.history.state.params) : null; // 获取上个页面带过来的表格查询参数
|
const lastPageParams = window.history.state.params ? JSON.parse(window.history.state.params) : null; // 获取上个页面带过来的表格查询参数
|
||||||
|
@ -201,9 +366,8 @@
|
||||||
moduleIds,
|
moduleIds,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
await loadCaseList();
|
getPlanDetail();
|
||||||
// TODO 获取用例详情 暂时
|
await loadCase();
|
||||||
caseDetail.value = caseList.value[0] ?? {};
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -231,7 +395,15 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.tab-content {
|
.tab-content {
|
||||||
|
@apply overflow-y-auto;
|
||||||
|
|
||||||
|
padding: 16px;
|
||||||
|
.ms-scroll-bar();
|
||||||
|
}
|
||||||
|
:deep(.caseDetailWrapper) {
|
||||||
|
@apply flex-1 overflow-y-auto;
|
||||||
|
|
||||||
|
padding: 16px;
|
||||||
.ms-scroll-bar();
|
.ms-scroll-bar();
|
||||||
@apply py-4;
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -95,4 +95,5 @@ export default {
|
||||||
'testPlan.featureCase.disassociateTip': '确认取消关联 { name } 吗?',
|
'testPlan.featureCase.disassociateTip': '确认取消关联 { name } 吗?',
|
||||||
'testPlan.featureCase.disassociateTipContent': '取消后,影响测试计划相关统计',
|
'testPlan.featureCase.disassociateTipContent': '取消后,影响测试计划相关统计',
|
||||||
'testPlan.featureCase.batchDisassociateTipContent': '取消后,再次关联,执行结果为:未执行',
|
'testPlan.featureCase.batchDisassociateTipContent': '取消后,再次关联,执行结果为:未执行',
|
||||||
|
'testPlan.featureCase.startExecution': '开始执行',
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in New Issue