feat(测试计划): 创建测试计划抽屉
This commit is contained in:
parent
9b0f27215a
commit
1fba073345
|
@ -1,6 +1,7 @@
|
|||
import MSR from '@/api/http/index';
|
||||
import {
|
||||
addTestPlanModuleUrl,
|
||||
AddTestPlanUrl,
|
||||
DeleteTestPlanModuleUrl,
|
||||
GetTestPlanListUrl,
|
||||
GetTestPlanModuleCountUrl,
|
||||
|
@ -12,7 +13,7 @@ import {
|
|||
import type { CreateOrUpdateModule, UpdateModule } from '@/models/caseManagement/featureCase';
|
||||
import type { CommonList, MoveModules, TableQueryParams } from '@/models/common';
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import type { TestPlanItem } from '@/models/testPlan/testPlan';
|
||||
import type { AddTestPlanParams, TestPlanItem } from '@/models/testPlan/testPlan';
|
||||
|
||||
// 获取模块树
|
||||
export function getTestPlanModule(params: TableQueryParams) {
|
||||
|
@ -48,3 +49,8 @@ export function getPlanModulesCounts(data: TableQueryParams) {
|
|||
export function getTestPlanList(data: TableQueryParams) {
|
||||
return MSR.post<CommonList<TestPlanItem>>({ url: GetTestPlanListUrl, data });
|
||||
}
|
||||
|
||||
// 创建测试计划
|
||||
export function addTestPlan(data: AddTestPlanParams) {
|
||||
return MSR.post({ url: AddTestPlanUrl, data });
|
||||
}
|
||||
|
|
|
@ -12,3 +12,5 @@ export const DeleteTestPlanModuleUrl = '/test-plan/module/delete';
|
|||
export const GetTestPlanModuleCountUrl = '/test-plan/module/count';
|
||||
// 测试计划列表
|
||||
export const GetTestPlanListUrl = '/test-plan/page';
|
||||
// 创建测试计划
|
||||
export const AddTestPlanUrl = '/test-plan/add';
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
<template>
|
||||
<a-collapse v-model:active-key="moreSettingActive" :bordered="false" :show-expand-icon="false">
|
||||
<a-collapse-item :key="1">
|
||||
<template #header>
|
||||
<MsButton
|
||||
type="text"
|
||||
@click="() => (moreSettingActive.length > 0 ? (moreSettingActive = []) : (moreSettingActive = [1]))"
|
||||
>
|
||||
{{ t('common.moreSetting') }}
|
||||
<icon-down v-if="moreSettingActive.length > 0" class="text-rgb(var(--primary-5))" />
|
||||
<icon-right v-else class="text-rgb(var(--primary-5))" />
|
||||
</MsButton>
|
||||
</template>
|
||||
<div class="mt-[24px]">
|
||||
<slot name="content"></slot>
|
||||
</div>
|
||||
</a-collapse-item>
|
||||
</a-collapse>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const moreSettingActive = ref<number[]>([]);
|
||||
function clearMoreSettingActive() {
|
||||
moreSettingActive.value = [];
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
clearMoreSettingActive,
|
||||
});
|
||||
</script>
|
|
@ -164,4 +164,6 @@ export default {
|
|||
'common.unExecute': 'Not executed',
|
||||
'common.pass': 'Pass',
|
||||
'common.unPass': 'Fail pass',
|
||||
'common.belongModule': 'Belong module',
|
||||
'common.moreSetting': 'More settings',
|
||||
};
|
||||
|
|
|
@ -164,4 +164,6 @@ export default {
|
|||
'common.unExecute': '未执行',
|
||||
'common.pass': '通过',
|
||||
'common.unPass': '不通过',
|
||||
'common.belongModule': '所属模块',
|
||||
'common.moreSetting': '更多设置',
|
||||
};
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { BatchApiParams } from '../common';
|
||||
|
||||
// 计划分页
|
||||
export interface TestPlanItem {
|
||||
id?: string;
|
||||
|
@ -24,4 +26,31 @@ export interface ResourcesItem {
|
|||
status: boolean;
|
||||
}
|
||||
|
||||
export interface AssociateCaseRequest extends BatchApiParams {
|
||||
functionalSelectIds?: string[];
|
||||
apiSelectIds?: string[];
|
||||
apiCaseSelectIds?: string[];
|
||||
apiScenarioSelectIds?: string[];
|
||||
}
|
||||
|
||||
export interface AddTestPlanParams {
|
||||
id?: string;
|
||||
name: string;
|
||||
projectId: string;
|
||||
groupId?: string;
|
||||
moduleId: string;
|
||||
cycle?: number[];
|
||||
plannedStartTime?: number;
|
||||
plannedEndTime?: number;
|
||||
tags: string[];
|
||||
description?: string;
|
||||
testPlanning: boolean; // 是否开启测试规划
|
||||
automaticStatusUpdate: boolean; // 是否自定更新功能用例状态
|
||||
repeatCase: boolean; // 是否允许重复添加用例
|
||||
passThreshold: number;
|
||||
type: string;
|
||||
baseAssociateCaseRequest: AssociateCaseRequest;
|
||||
groupOption?: boolean;
|
||||
}
|
||||
|
||||
export default {};
|
||||
|
|
|
@ -164,10 +164,7 @@
|
|||
if (isSetDefaultKey) {
|
||||
selectedNodeKeys.value = [caseTree.value[0].id];
|
||||
}
|
||||
emits(
|
||||
'init',
|
||||
caseTree.value.map((e) => e.name)
|
||||
);
|
||||
emits('init', caseTree.value);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
<template>
|
||||
<MsDrawer
|
||||
v-model:visible="innerVisible"
|
||||
:title="form.id ? t('case.updateCase') : t('testPlan.testPlanIndex.createTestPlan')"
|
||||
:width="800"
|
||||
unmount-on-close
|
||||
:ok-text="form.id ? 'common.update' : 'common.create'"
|
||||
:save-continue-text="t('case.saveContinueText')"
|
||||
:show-continue="!form.id"
|
||||
:ok-loading="drawerLoading"
|
||||
@confirm="handleDrawerConfirm(false)"
|
||||
@continue="handleDrawerConfirm(true)"
|
||||
@cancel="handleCancel"
|
||||
>
|
||||
<a-form ref="formRef" :model="form" layout="vertical">
|
||||
<a-form-item
|
||||
field="name"
|
||||
:label="t('caseManagement.featureCase.planName')"
|
||||
:rules="[{ required: true, message: t('testPlan.planForm.nameRequired') }]"
|
||||
class="w-[732px]"
|
||||
>
|
||||
<a-input v-model="form.name" :max-length="255" :placeholder="t('testPlan.planForm.namePlaceholder')" />
|
||||
</a-form-item>
|
||||
<a-form-item field="description" :label="t('common.desc')" class="w-[732px]">
|
||||
<a-textarea v-model:model-value="form.description" :placeholder="t('common.pleaseInput')" :max-length="1000" />
|
||||
</a-form-item>
|
||||
<a-form-item :label="t('common.belongModule')" class="w-[436px]">
|
||||
<a-tree-select
|
||||
v-model:modelValue="form.moduleId"
|
||||
:data="props.moduleTree"
|
||||
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||
:tree-props="{
|
||||
virtualListProps: {
|
||||
height: 200,
|
||||
threshold: 200,
|
||||
},
|
||||
}"
|
||||
allow-search
|
||||
:filter-tree-node="filterTreeNode"
|
||||
>
|
||||
<template #tree-slot-title="node">
|
||||
<a-tooltip :content="`${node.name}`" position="tl">
|
||||
<div class="inline-flex w-full">
|
||||
<div class="one-line-text w-[240px] text-[var(--color-text-1)]">
|
||||
{{ node.name }}
|
||||
</div>
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-tree-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="cycle"
|
||||
:label="t('testPlan.planForm.planStartAndEndTime')"
|
||||
asterisk-position="end"
|
||||
class="w-[436px]"
|
||||
>
|
||||
<a-range-picker
|
||||
v-model:model-value="form.cycle"
|
||||
show-time
|
||||
value-format="timestamp"
|
||||
:separator="t('common.to')"
|
||||
:time-picker-props="{
|
||||
defaultValue: ['00:00:00', '00:00:00'],
|
||||
}"
|
||||
/>
|
||||
</a-form-item>
|
||||
<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>
|
||||
<MsMoreSettingCollapse>
|
||||
<template #content>
|
||||
<div v-for="item in switchList" :key="item.key" class="mb-[24px] flex items-center gap-[8px]">
|
||||
<a-switch v-model="form[item.key as keyof AddTestPlanParams] as boolean" size="small" />
|
||||
{{ t(item.label) }}
|
||||
<a-tooltip :position="item.tooltipPosition">
|
||||
<template #content>
|
||||
<div v-for="descItem in item.desc" :key="descItem">{{ t(descItem) }}</div>
|
||||
</template>
|
||||
<IconQuestionCircle class="h-[16px] w-[16px] text-[--color-text-4] hover:text-[rgb(var(--primary-5))]" />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<a-form-item field="passThreshold" :label="t('testPlan.planForm.passThreshold')">
|
||||
<a-input-number
|
||||
v-model:model-value="form.passThreshold"
|
||||
size="small"
|
||||
mode="button"
|
||||
class="w-[120px]"
|
||||
:min="1"
|
||||
:max="100"
|
||||
:default-value="100"
|
||||
/>
|
||||
</a-form-item>
|
||||
</template>
|
||||
</MsMoreSettingCollapse>
|
||||
</a-form>
|
||||
</MsDrawer>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue';
|
||||
import { FormInstance, Message, TreeNodeData } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
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 { addTestPlan } 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 } from '@/models/testPlan/testPlan';
|
||||
import { testPlanTypeEnum } from '@/enums/testPlanEnum';
|
||||
|
||||
interface SwitchListModel {
|
||||
key: string;
|
||||
label: string;
|
||||
desc: string[];
|
||||
tooltipPosition: 'top' | 'tl' | 'tr' | 'bottom' | 'bl' | 'br' | 'left' | 'lt' | 'lb' | 'right' | 'rt' | 'rb';
|
||||
}
|
||||
|
||||
const { t } = useI18n();
|
||||
const appStore = useAppStore();
|
||||
|
||||
const props = defineProps<{
|
||||
moduleTree?: ModuleTreeNode[];
|
||||
}>();
|
||||
const innerVisible = defineModel<boolean>('visible', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const drawerLoading = ref(false);
|
||||
const formRef = ref<FormInstance>();
|
||||
const initForm: AddTestPlanParams = {
|
||||
name: '',
|
||||
projectId: '',
|
||||
moduleId: 'root',
|
||||
cycle: [],
|
||||
tags: [],
|
||||
description: '',
|
||||
testPlanning: false,
|
||||
automaticStatusUpdate: true,
|
||||
repeatCase: false,
|
||||
passThreshold: 100,
|
||||
type: testPlanTypeEnum.TEST_PLAN,
|
||||
baseAssociateCaseRequest: { selectIds: [], selectAll: false, condition: {} },
|
||||
};
|
||||
const form = ref<AddTestPlanParams>(cloneDeep(initForm));
|
||||
|
||||
function filterTreeNode(searchValue: string, nodeData: TreeNodeData) {
|
||||
return (nodeData as ModuleTreeNode).name.toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
|
||||
}
|
||||
|
||||
const switchList: SwitchListModel[] = [
|
||||
{
|
||||
key: 'repeatCase',
|
||||
label: 'testPlan.planForm.associateRepeatCase',
|
||||
tooltipPosition: 'bl',
|
||||
desc: ['testPlan.planForm.repeatCaseTip1', 'testPlan.planForm.repeatCaseTip2'],
|
||||
},
|
||||
];
|
||||
|
||||
function handleCancel() {
|
||||
innerVisible.value = false;
|
||||
formRef.value?.resetFields();
|
||||
form.value = cloneDeep(initForm);
|
||||
}
|
||||
|
||||
function handleDrawerConfirm(isContinue: boolean) {
|
||||
formRef.value?.validate(async (errors) => {
|
||||
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) {
|
||||
await addTestPlan(params);
|
||||
Message.success(t('common.createSuccess'));
|
||||
}
|
||||
// TODO 刷新外层数据
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
drawerLoading.value = false;
|
||||
}
|
||||
if (!isContinue) {
|
||||
handleCancel();
|
||||
}
|
||||
form.value.name = '';
|
||||
}
|
||||
});
|
||||
}
|
||||
</script>
|
|
@ -9,7 +9,7 @@
|
|||
:placeholder="t('caseManagement.featureCase.searchTip')"
|
||||
allow-clear
|
||||
/>
|
||||
<a-dropdown-button class="ml-2" type="primary" @click="handleSelect">
|
||||
<a-dropdown-button class="ml-2" type="primary" @click="handleSelect('createPlan')">
|
||||
{{ t('common.newCreate') }}
|
||||
<template #icon>
|
||||
<icon-down />
|
||||
|
@ -87,6 +87,7 @@
|
|||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
<CreateAndEditPlanDrawer v-model:visible="showPlanDrawer" :module-tree="folderTree" />
|
||||
</MsCard>
|
||||
</template>
|
||||
|
||||
|
@ -101,12 +102,14 @@
|
|||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
import PlanTable from './components/planTable.vue';
|
||||
import TestPlanTree from './components/testPlanTree.vue';
|
||||
import CreateAndEditPlanDrawer from './createAndEditPlanDrawer.vue';
|
||||
|
||||
import { createPlanModuleTree } from '@/api/modules/test-plan/testPlan';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
import type { CaseModuleQueryParams, CreateOrUpdateModule, ValidateInfo } from '@/models/caseManagement/featureCase';
|
||||
import type { ModuleTreeNode } from '@/models/common';
|
||||
|
||||
import Message from '@arco-design/web-vue/es/message';
|
||||
|
||||
|
@ -141,7 +144,6 @@
|
|||
};
|
||||
|
||||
const addSubVisible = ref(false);
|
||||
const rootModulesName = ref<string[]>([]);
|
||||
const planTreeRef = ref();
|
||||
const confirmLoading = ref(false);
|
||||
const confirmRef = ref();
|
||||
|
@ -186,8 +188,11 @@
|
|||
* 设置根模块名称列表
|
||||
* @param names 根模块名称列表
|
||||
*/
|
||||
function setRootModules(names: string[]) {
|
||||
rootModulesName.value = names;
|
||||
const rootModulesName = ref<string[]>([]);
|
||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||
function setRootModules(treeNode: ModuleTreeNode[]) {
|
||||
folderTree.value = treeNode;
|
||||
rootModulesName.value = treeNode.map((e) => e.name);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -195,7 +200,16 @@
|
|||
*/
|
||||
function initModulesCount(params: any) {}
|
||||
|
||||
function handleSelect() {}
|
||||
const showPlanDrawer = ref(false);
|
||||
function handleSelect(value: string | number | Record<string, any> | undefined) {
|
||||
switch (value) {
|
||||
case 'createPlan':
|
||||
showPlanDrawer.value = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
|
@ -60,4 +60,11 @@ export default {
|
|||
'project.testPlanIndex.functionalUseCase': 'case',
|
||||
'project.testPlanIndex.apiCase': 'Api use case',
|
||||
'project.testPlanIndex.apiScenarioCase': 'Api scenario use cases',
|
||||
'testPlan.planForm.namePlaceholder': 'Please enter the name of the test plan',
|
||||
'testPlan.planForm.nameRequired': 'Test plan name cannot be empty',
|
||||
'testPlan.planForm.planStartAndEndTime': 'Planned start and end time',
|
||||
'testPlan.planForm.associateRepeatCase': 'Allow associated duplicate cases',
|
||||
'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',
|
||||
};
|
||||
|
|
|
@ -60,4 +60,11 @@ export default {
|
|||
'project.testPlanIndex.functionalUseCase': '功能用例',
|
||||
'project.testPlanIndex.apiCase': '接口用例',
|
||||
'project.testPlanIndex.apiScenarioCase': '接口场景用例',
|
||||
'testPlan.planForm.namePlaceholder': '请输入测试计划名称',
|
||||
'testPlan.planForm.nameRequired': '测试计划名称不能为空',
|
||||
'testPlan.planForm.planStartAndEndTime': '计划起止时间',
|
||||
'testPlan.planForm.associateRepeatCase': '允许关联重复用例',
|
||||
'testPlan.planForm.passThreshold': '通过阀值',
|
||||
'testPlan.planForm.repeatCaseTip1': '开启:可重复关联同一个用例',
|
||||
'testPlan.planForm.repeatCaseTip2': '关闭:不可重复关联同一用例',
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue