feat(测试计划): 关联功能用例

This commit is contained in:
teukkk 2024-05-09 11:14:48 +08:00 committed by 刘瑞斌
parent 26ac5c4636
commit 91615d9e45
9 changed files with 202 additions and 17 deletions

View File

@ -7,11 +7,13 @@ import {
deletePlanUrl,
DeleteTestPlanModuleUrl,
getStatisticalCountUrl,
GetTestPlanDetailUrl,
GetTestPlanListUrl,
GetTestPlanModuleCountUrl,
GetTestPlanModuleUrl,
MoveTestPlanModuleUrl,
updateTestPlanModuleUrl,
UpdateTestPlanUrl,
} from '@/api/requrls/test-plan/testPlan';
import type { CreateOrUpdateModule, UpdateModule } from '@/models/caseManagement/featureCase';
@ -58,6 +60,16 @@ export function getTestPlanList(data: TableQueryParams) {
export function addTestPlan(data: AddTestPlanParams) {
return MSR.post({ url: AddTestPlanUrl, data });
}
// 获取测试计划详情
export function getTestPlanDetail(id: string) {
return MSR.get<AddTestPlanParams>({ url: `${GetTestPlanDetailUrl}/${id}` });
}
// 更新测试计划
export function updateTestPlan(data: AddTestPlanParams) {
return MSR.post({ url: UpdateTestPlanUrl, data });
}
// 批量删除测试计划
export function batchDeletePlan(data: TableQueryParams) {
return MSR.post({ url: batchDeletePlanUrl, data });

View File

@ -14,6 +14,10 @@ export const GetTestPlanModuleCountUrl = '/test-plan/module/count';
export const GetTestPlanListUrl = '/test-plan/page';
// 创建测试计划
export const AddTestPlanUrl = '/test-plan/add';
// 获取测试计划详情
export const GetTestPlanDetailUrl = '/test-plan';
// 更新测试计划
export const UpdateTestPlanUrl = '/test-plan/update';
// 批量删除测试计划
export const batchDeletePlanUrl = '/test-plan/batch-delete';
// 删除测试计划

View File

@ -33,6 +33,7 @@ export interface AssociateCaseRequest extends BatchApiParams {
apiSelectIds?: string[];
apiCaseSelectIds?: string[];
apiScenarioSelectIds?: string[];
totalCount?: number;
}
export interface AddTestPlanParams {

View File

@ -0,0 +1,58 @@
<template>
<MsCaseAssociate
v-model:visible="innerVisible"
v-model:currentSelectCase="currentSelectCase"
:get-modules-func="getCaseModuleTree"
:get-table-func="getCaseList"
:confirm-loading="confirmLoading"
:associated-ids="[]"
:project-id="currentProjectId"
:type="RequestModuleEnum.CASE_MANAGEMENT"
hide-project-select
is-hidden-case-level
:has-not-associated-ids="props.hasNotAssociatedIds"
@save="saveHandler"
>
</MsCaseAssociate>
</template>
<script setup lang="ts">
import MsCaseAssociate from '@/components/business/ms-case-associate/index.vue';
import { RequestModuleEnum } from '@/components/business/ms-case-associate/utils';
import { getCaseList, getCaseModuleTree } from '@/api/modules/case-management/featureCase';
import useAppStore from '@/store/modules/app';
import type { AssociateCaseRequest } from '@/models/testPlan/testPlan';
import { CaseLinkEnum } from '@/enums/caseEnum';
const props = defineProps<{
hasNotAssociatedIds?: string[];
}>();
const innerVisible = defineModel<boolean>('visible', {
required: true,
});
const emit = defineEmits<{
(e: 'success', val: AssociateCaseRequest): void;
}>();
const appStore = useAppStore();
const currentSelectCase = ref<keyof typeof CaseLinkEnum>('FUNCTIONAL');
const currentProjectId = ref(appStore.currentProjectId);
const confirmLoading = ref<boolean>(false);
function saveHandler(params: AssociateCaseRequest) {
try {
confirmLoading.value = true;
emit('success', { ...params });
innerVisible.value = false;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
confirmLoading.value = false;
}
}
</script>

View File

@ -198,7 +198,9 @@
<MsButton class="!mx-0">{{ t('testPlan.testPlanIndex.execution') }}</MsButton>
<a-divider direction="vertical" :margin="8"></a-divider>
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+UPDATE']" class="!mx-0">{{ t('common.edit') }}</MsButton>
<MsButton v-permission="['PROJECT_TEST_PLAN:READ+UPDATE']" class="!mx-0" @click="emit('edit', record.id)">{{
t('common.edit')
}}</MsButton>
<a-divider direction="vertical" :margin="8"></a-divider>
<MsTableMoreAction :list="moreActions" @select="handleMoreActionSelect($event, record)" />
@ -301,6 +303,7 @@
const emit = defineEmits<{
(e: 'init', params: any): void;
(e: 'edit', id: string): void;
}>();
const columns: MsTableColumn = [
@ -592,10 +595,14 @@
};
}
async function fetchData() {
resetSelector();
async function loadPlanList() {
setLoadListParams(await initTableParams());
loadList();
}
async function fetchData() {
resetSelector();
await loadPlanList();
const tableParams = await initTableParams();
emit('init', {
...tableParams,
@ -905,6 +912,10 @@
fetchData();
});
defineExpose({
loadPlanList,
});
await tableStore.initColumn(TableKeyEnum.TEST_PLAN_ALL_TABLE, columns, 'drawer');
</script>

View File

@ -1,12 +1,12 @@
<template>
<MsDrawer
v-model:visible="innerVisible"
:title="form.id ? t('case.updateCase') : t('testPlan.testPlanIndex.createTestPlan')"
:title="props.planId?.length ? t('case.updateCase') : t('testPlan.testPlanIndex.createTestPlan')"
:width="800"
unmount-on-close
:ok-text="form.id ? 'common.update' : 'common.create'"
:ok-text="props.planId?.length ? 'common.update' : 'common.create'"
:save-continue-text="t('case.saveContinueText')"
:show-continue="!form.id"
:show-continue="!props.planId?.length"
:ok-loading="drawerLoading"
@confirm="handleDrawerConfirm(false)"
@continue="handleDrawerConfirm(true)"
@ -68,6 +68,36 @@
<a-form-item field="tags" :label="t('common.tag')" class="w-[436px]">
<MsTagsInput v-model:model-value="form.tags" :max-tag-count="10" :max-length="50" />
</a-form-item>
<a-form-item v-if="!props.planId?.length">
<template #label>
<div class="flex items-center">
{{ t('testPlan.planForm.pickCases') }}
<a-divider margin="4px" direction="vertical" />
<MsButton
type="text"
:disabled="form.baseAssociateCaseRequest.selectIds.length === 0"
@click="clearSelectedCases"
>
{{ t('caseManagement.caseReview.clearSelectedCases') }}
</MsButton>
</div>
</template>
<div class="flex w-[436px] items-center rounded bg-[var(--color-text-n9)] p-[12px]">
<div class="text-[var(--color-text-2)]">
{{
t('caseManagement.caseReview.selectedCases', {
count: form.baseAssociateCaseRequest.selectAll
? form.baseAssociateCaseRequest.totalCount
: form.baseAssociateCaseRequest.selectIds.length,
})
}}
</div>
<a-divider margin="8px" direction="vertical" />
<MsButton type="text" class="font-medium" @click="caseAssociateVisible = true">
{{ t('ms.case.associate.title') }}
</MsButton>
</div>
</a-form-item>
<MsMoreSettingCollapse>
<template #content>
<div v-for="item in switchList" :key="item.key" class="mb-[24px] flex items-center gap-[8px]">
@ -95,35 +125,48 @@
</MsMoreSettingCollapse>
</a-form>
</MsDrawer>
<AssociateDrawer
v-model:visible="caseAssociateVisible"
:has-not-associated-ids="form.baseAssociateCaseRequest.selectIds"
@success="writeAssociateCases"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { FormInstance, Message, TreeNodeData } from '@arco-design/web-vue';
import { FormInstance, Message, TreeNodeData, ValidatedError } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsMoreSettingCollapse from '@/components/pure/ms-more-setting-collapse/index.vue';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import AssociateDrawer from './components/associateDrawer.vue';
import { addTestPlan } from '@/api/modules/test-plan/testPlan';
import { addTestPlan, getTestPlanDetail, updateTestPlan } from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { ModuleTreeNode } from '@/models/common';
import type { AddTestPlanParams, SwitchListModel } from '@/models/testPlan/testPlan';
import type { AddTestPlanParams, AssociateCaseRequest, SwitchListModel } from '@/models/testPlan/testPlan';
import { testPlanTypeEnum } from '@/enums/testPlanEnum';
const { t } = useI18n();
const appStore = useAppStore();
const props = defineProps<{
planId?: string;
moduleTree?: ModuleTreeNode[];
}>();
const innerVisible = defineModel<boolean>('visible', {
required: true,
});
const emit = defineEmits<{
(e: 'close'): void;
(e: 'loadPlanList'): void;
}>();
const { t } = useI18n();
const appStore = useAppStore();
const drawerLoading = ref(false);
const formRef = ref<FormInstance>();
const initForm: AddTestPlanParams = {
@ -155,29 +198,40 @@
},
];
const caseAssociateVisible = ref(false);
function clearSelectedCases() {
form.value.baseAssociateCaseRequest = cloneDeep(initForm.baseAssociateCaseRequest);
}
function writeAssociateCases(param: AssociateCaseRequest) {
form.value.baseAssociateCaseRequest = { ...param };
}
function handleCancel() {
innerVisible.value = false;
formRef.value?.resetFields();
form.value = cloneDeep(initForm);
emit('close');
}
function handleDrawerConfirm(isContinue: boolean) {
formRef.value?.validate(async (errors) => {
formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
if (!errors) {
drawerLoading.value = true;
try {
// TODO
const params: AddTestPlanParams = {
...cloneDeep(form.value),
plannedStartTime: form.value.cycle ? form.value.cycle[0] : undefined,
plannedEndTime: form.value.cycle ? form.value.cycle[1] : undefined,
projectId: appStore.currentProjectId,
};
if (!form.value?.id) {
if (!props.planId?.length) {
await addTestPlan(params);
Message.success(t('common.createSuccess'));
} else {
await updateTestPlan(params);
Message.success(t('common.updateSuccess'));
}
// TODO
emit('loadPlanList');
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
@ -191,4 +245,26 @@
}
});
}
async function getDetail() {
try {
if (props.planId?.length) {
const result = await getTestPlanDetail(props.planId);
form.value = cloneDeep(result);
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
watch(
() => innerVisible.value,
(val) => {
if (val) {
form.value = cloneDeep(initForm);
getDetail();
}
}
);
</script>

View File

@ -78,17 +78,25 @@
<template #second>
<div class="p-[16px]">
<PlanTable
ref="planTableRef"
:active-folder="activeFolder"
:offspring-ids="offspringIds"
:active-folder-type="activeCaseType"
:modules-count="modulesCount"
:node-name="nodeName"
@init="initModulesCount"
@edit="handleEdit"
/>
</div>
</template>
</MsSplitBox>
<CreateAndEditPlanDrawer v-model:visible="showPlanDrawer" :module-tree="folderTree" />
<CreateAndEditPlanDrawer
v-model:visible="showPlanDrawer"
:plan-id="planId"
:module-tree="folderTree"
@close="resetPlanId"
@load-plan-list="loadPlanList"
/>
</MsCard>
</template>
@ -220,6 +228,19 @@
break;
}
}
const planTableRef = ref<InstanceType<typeof PlanTable>>();
const planId = ref('');
function handleEdit(id: string) {
planId.value = id;
showPlanDrawer.value = true;
}
function resetPlanId() {
planId.value = '';
}
function loadPlanList() {
planTableRef.value?.loadPlanList();
}
</script>
<style scoped lang="less">

View File

@ -79,4 +79,5 @@ export default {
'testPlan.planForm.passThreshold': 'Pass threshold',
'testPlan.planForm.repeatCaseTip1': 'Enable: Repeatedly associate the same case',
'testPlan.planForm.repeatCaseTip2': 'Close: Cannot be associated with the same case repeatedly',
'testPlan.planForm.pickCases': 'Select cases',
};

View File

@ -77,4 +77,5 @@ export default {
'testPlan.planForm.passThreshold': '通过阀值',
'testPlan.planForm.repeatCaseTip1': '开启:可重复关联同一个用例',
'testPlan.planForm.repeatCaseTip2': '关闭:不可重复关联同一用例',
'testPlan.planForm.pickCases': '选择用例',
};