feat(测试计划): 关联功能用例
This commit is contained in:
parent
26ac5c4636
commit
91615d9e45
|
@ -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 });
|
||||
|
|
|
@ -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';
|
||||
// 删除测试计划
|
||||
|
|
|
@ -33,6 +33,7 @@ export interface AssociateCaseRequest extends BatchApiParams {
|
|||
apiSelectIds?: string[];
|
||||
apiCaseSelectIds?: string[];
|
||||
apiScenarioSelectIds?: string[];
|
||||
totalCount?: number;
|
||||
}
|
||||
|
||||
export interface AddTestPlanParams {
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -77,4 +77,5 @@ export default {
|
|||
'testPlan.planForm.passThreshold': '通过阀值',
|
||||
'testPlan.planForm.repeatCaseTip1': '开启:可重复关联同一个用例',
|
||||
'testPlan.planForm.repeatCaseTip2': '关闭:不可重复关联同一用例',
|
||||
'testPlan.planForm.pickCases': '选择用例',
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue