feat(测试计划): 测试计划表补充遗漏&细节

This commit is contained in:
xinxin.wu 2024-05-08 14:28:16 +08:00 committed by Craftsman
parent a872b43c9d
commit 4358a82355
13 changed files with 632 additions and 196 deletions

View File

@ -28,6 +28,8 @@ export default {
'common.updateSuccess': 'Update success',
'common.updateFailed': 'Update failed',
'common.deleteConfirm': 'Delete confirm',
'common.deleteConfirmTitle': 'Delete confirm {name} 吗',
'common.archiveConfirmTitle': 'Archive confirm {name} 吗',
'common.deleteSuccess': 'Delete success',
'common.deleteFailed': 'Delete failed',
'common.addSuccess': 'Added successfully',
@ -121,6 +123,7 @@ export default {
'common.move': 'Move',
'common.moveSuccess': 'Move successful',
'common.batchMove': 'Batch move',
'common.batchArchiveSuccess': 'Archive successful',
'common.batchCopy': 'Batch copy',
'common.batchCopySuccess': 'Batch copy successful',
'common.batchMoveSuccess': 'Batch move successful',

View File

@ -30,6 +30,7 @@ export default {
'common.updateFailed': '更新失败',
'common.deleteConfirm': '确认删除?',
'common.deleteConfirmTitle': '确认删除 {name} 吗',
'common.archiveConfirmTitle': '确认归档 {name} 吗',
'common.deleteSuccess': '删除成功',
'common.deleteFailed': '删除失败',
'common.addSuccess': '添加成功',
@ -122,6 +123,7 @@ export default {
'common.move': '移动',
'common.moveSuccess': '移动成功',
'common.batchMove': '批量移动',
'common.batchArchiveSuccess': '归档成功!',
'common.batchCopy': '批量复制',
'common.batchCopySuccess': '批量复制成功',
'common.batchMoveSuccess': '批量移动成功',

View File

@ -1,12 +1,14 @@
import { BatchApiParams } from '../common';
export type planStatusType = 'PREPARED' | 'UNDERWAY' | 'COMPLETED' | 'ARCHIVED';
// 计划分页
export interface TestPlanItem {
id?: string;
projectId: string;
num: number;
name: string;
status: string;
status: planStatusType;
type: string;
tags: string[];
schedule: string; // 是否定时

View File

@ -0,0 +1,101 @@
<template>
<a-modal
v-model:visible="showModalVisible"
class="ms-modal-form ms-modal-no-padding ms-modal-small"
unmount-on-close
title-align="start"
:mask="true"
:mask-closable="false"
@close="cancelHandler"
>
<template #title>
<div class="flex items-center justify-start">
<MsIcon type="icon-icon_close_colorful" class="mr-[8px] text-[rgb(var(--danger-6))]" size="16" />
<div class="text-[var(--color-text-1)]">
{{ t('common.deleteConfirmTitle', { name: characterLimit(record?.name) }) }}
</div>
</div>
</template>
{{ contentTip }}
<template #footer>
<div class="flex justify-end">
<a-button type="secondary" :disabled="confirmLoading" @click="cancelHandler">
{{ t('common.cancel') }}
</a-button>
<a-button class="ml-3" type="primary" status="danger" :loading="confirmLoading" @click="confirmHandler(true)">
{{ t('common.confirmDelete') }}
</a-button>
<a-button
v-if="props.record?.status === 'COMPLETED'"
:loading="confirmLoading"
class="ml-3"
type="primary"
@click="confirmHandler(false)"
>
{{ t('common.archive') }}
</a-button>
</div>
</template>
</a-modal>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useVModel } from '@vueuse/core';
import { Message } from '@arco-design/web-vue';
import { useI18n } from '@/hooks/useI18n';
import { characterLimit } from '@/utils';
import type { TestPlanItem } from '@/models/testPlan/testPlan';
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
// isScheduled: boolean; // TODO
record: TestPlanItem | undefined; // record
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
}>();
const showModalVisible = useVModel(props, 'visible', emit);
function cancelHandler() {
showModalVisible.value = false;
}
const confirmLoading = ref<boolean>(false);
function confirmHandler(isDelete: boolean) {
try {
Message.success(isDelete ? t('common.deleteSuccess') : t('common.batchArchiveSuccess'));
} catch (error) {
console.log(error);
}
}
function getDeleteTip() {
switch (props.record && props.record.status) {
case 'ARCHIVED':
return t('testPlan.testPlanIndex.deleteArchivedPlan');
case 'UNDERWAY':
return t('testPlan.testPlanIndex.deleteRunningPlan');
case 'COMPLETED':
return t('testPlan.testPlanIndex.deleteCompletedPlan');
default:
return t('testPlan.testPlanIndex.deletePendingPlan');
}
}
const contentTip = computed(() => {
return getDeleteTip();
});
</script>
<style scoped lang="less">
:deep(.ms-modal-form .arco-modal-body) {
padding: 0 !important;
}
</style>

View File

@ -0,0 +1,180 @@
<template>
<MsDialog
v-model:visible="isVisible"
dialog-size="small"
:title="t('testPlan.testPlanIndex.batchEdit', { number: props.batchParams.currentSelectCount })"
ok-text="common.update"
:confirm="confirmHandler"
:close="closeHandler"
unmount-on-close
:switch-props="{
switchName: t('caseManagement.featureCase.appendTag'),
switchTooltip: t('caseManagement.featureCase.enableTags'),
showSwitch: form.selectedAttrsId === 'tags' ? true : false,
enable: form.append,
}"
>
<div class="form">
<a-form ref="formRef" class="rounded-[4px]" :model="form" layout="vertical">
<a-form-item
field="selectedAttrsId"
:label="t('apiTestManagement.chooseAttr')"
:rules="[{ required: true, message: t('apiTestManagement.attrRequired') }]"
asterisk-position="end"
>
<a-select v-model="form.selectedAttrsId" :placeholder="t('common.pleaseSelect')">
<a-option v-for="item of attrOptions" :key="item.value" :value="item.value">
{{ t(item.name) }}
</a-option>
</a-select>
</a-form-item>
<a-form-item
v-if="form.selectedAttrsId === 'tags'"
field="values"
:label="t('apiTestManagement.batchUpdate')"
:validate-trigger="['blur', 'input']"
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
asterisk-position="end"
class="mb-0"
required
>
<MsTagsInput
v-model:model-value="form.tags"
placeholder="common.tagsInputPlaceholder"
allow-clear
unique-value
retain-input-value
/>
</a-form-item>
<a-form-item
v-else
field="value"
:label="t('apiTestManagement.batchUpdate')"
:rules="[{ required: true, message: t('apiTestManagement.valueRequired') }]"
asterisk-position="end"
class="mb-0"
>
<a-select
v-model="form.value"
:placeholder="t('common.pleaseSelect')"
:disabled="form.selectedAttrsId === ''"
>
<a-option v-for="item of valueOptions" :key="item.value" :value="item.value">
{{ t(item.label) }}
</a-option>
</a-select>
</a-form-item>
</a-form>
</div>
</MsDialog>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { FormInstance } from '@arco-design/web-vue';
import MsDialog from '@/components/pure/ms-dialog/index.vue';
import type { BatchActionQueryParams } from '@/components/pure/ms-table/type';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { TableQueryParams } from '@/models/common';
import Message from '@arco-design/web-vue/es/message';
const isVisible = ref<boolean>(false);
const appStore = useAppStore();
const { t } = useI18n();
const props = defineProps<{
visible: boolean;
batchParams: BatchActionQueryParams;
activeFolder: string;
offspringIds: string[];
condition?: TableQueryParams;
}>();
const emits = defineEmits<{
(e: 'update:visible', visible: boolean): void;
(e: 'success'): void;
}>();
const currentProjectId = computed(() => appStore.currentProjectId);
const initForm = {
selectedAttrsId: '',
append: false,
tags: [],
value: '',
};
const form = ref({ ...initForm });
const attrOptions = [
{
name: 'common.tag',
value: 'tags',
},
];
const formRef = ref<FormInstance | null>(null);
function closeHandler() {
isVisible.value = false;
formRef.value?.resetFields();
form.value = { ...initForm };
form.value.tags = [];
}
async function confirmHandler(enable: boolean | undefined) {
await formRef.value?.validate().then(async (error) => {
if (!error) {
try {
const customField = {
fieldId: '',
value: '',
};
const { selectedIds, selectAll, excludeIds } = props.batchParams;
const params: TableQueryParams = {
selectIds: selectedIds || [],
selectAll: !!selectAll,
excludeIds: excludeIds || [],
projectId: currentProjectId.value,
append: enable as boolean,
tags: form.value.tags,
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
customField: form.value.selectedAttrsId === 'systemTags' ? {} : customField,
condition: {
...props.condition,
},
};
Message.success(t('caseManagement.featureCase.editSuccess'));
closeHandler();
emits('success');
} catch (e) {
console.log(e);
}
} else {
return false;
}
});
}
const valueOptions = ref<{ value: string; label: string }[]>([]);
watch(
() => isVisible.value,
(val) => {
emits('update:visible', val);
}
);
watch(
() => props.visible,
(val) => {
isVisible.value = val;
}
);
</script>
<style scoped></style>

View File

@ -10,31 +10,47 @@
@refresh="fetchData"
>
<template #left>
<a-radio-group v-model="showType" type="button" class="file-show-type mr-2">
<!-- TODO 这个版本不上 -->
<!-- <a-radio-group v-model="showType" type="button" class="file-show-type mr-2">
<a-radio :value="testPlanTypeEnum.ALL" class="show-type-icon p-[2px]">{{
t('testPlan.testPlanIndex.all')
}}</a-radio>
<a-radio :value="testPlanTypeEnum.TEST_PLAN" class="show-type-icon p-[2px]">{{
t('testPlan.testPlanIndex.testPlan')
}}</a-radio>
<!-- <a-radio value="testPlanGroup" class="show-type-icon p-[2px]">{{
t('testPlan.testPlanIndex.testPlanGroup')
}}</a-radio> -->
</a-radio-group>
<a-radio value="testPlanGroup" class="show-type-icon p-[2px]">{{
t('testPlan.testPlanIndex.testPlanGroup')
}}</a-radio>
</a-radio-group> -->
<a-popover title="" position="bottom">
<div class="flex">
<div class="one-line-text mr-1 max-h-[32px] max-w-[116px] text-[var(--color-text-1)]">
{{ props.activeFolder === 'all' ? t('testPlan.testPlanIndex.allTestPlan') : props.nodeName }}
</div>
<span class="text-[var(--color-text-4)]"> ({{ props.modulesCount[props.activeFolder] || 0 }})</span>
</div>
<template #content>
<div class="max-w-[400px] text-[14px] font-medium text-[var(--color-text-1)]">
{{ props.nodeName }}
<span class="text-[var(--color-text-4)]">({{ props.modulesCount[props.activeFolder] || 0 }})</span>
</div>
</template>
</a-popover>
</template>
</MsAdvanceFilter>
<MsBaseTable
v-bind="propsRes"
ref="tableRef"
class="mt-4"
:action-config="tableBatchActions"
:expanded-keys="expandedKeys"
:action-config="testPlanBatchActions"
filter-icon-align-left
v-on="propsEvent"
@batch-action="handleTableBatch"
>
<!-- :expanded-keys="expandedKeys" -->
<template #num="{ record }">
<div class="flex items-center">
<!-- TODO 这个版本不做 -->
<!-- <div class="flex items-center">
<div v-if="record.childrenCount" class="mr-2 flex items-center" @click="expandHandler(record)">
<MsIcon
type="icon-icon_split-turn-down-left"
@ -56,17 +72,30 @@
<div>
<div>{{ t('testPlan.testPlanIndex.scheduledTaskOpened') }}</div>
<div>{{ t('testPlan.testPlanIndex.nextExecutionTime') }}</div>
<!-- TODO 缺少字段 -->
<div>---</div>
</div>
<!-- TODO 缺少字段 -->
<!-- <div> {{ t('testPlan.testPlanIndex.scheduledTaskUnEnable') }} </div> -->
<div> {{ t('testPlan.testPlanIndex.scheduledTaskUnEnable') }} </div>
</template>
</a-tooltip>
</div>
</div> -->
<div class="flex items-center">
<div class="one-line-text cursor-pointer text-[rgb(var(--primary-5))]">{{ record.num }}</div>
<a-tooltip position="right" :disabled="!record.schedule" :mouse-enter-delay="300">
<MsTag v-if="record.schedule" size="small" type="link" theme="outline" class="ml-2">{{
t('testPlan.testPlanIndex.timing')
}}</MsTag>
<template #content>
<div>
<div>{{ t('testPlan.testPlanIndex.scheduledTaskOpened') }}</div>
<div>{{ t('testPlan.testPlanIndex.nextExecutionTime') }}</div>
</div>
<div> {{ t('testPlan.testPlanIndex.scheduledTaskUnEnable') }} </div>
</template>
</a-tooltip></div
>
</template>
<template #statusFilter="{ columnConfig }">
<a-trigger v-model:popup-visible="statusFilterVisible" trigger="click" @popup-visible-change="handleFilterHidden">
<a-trigger v-model:popup-visible="statusFilterVisible" @popup-visible-change="handleFilterHidden">
<a-button type="text" class="arco-btn-text--secondary" @click="statusFilterVisible = true">
{{ t(columnConfig.title as string) }}
<icon-down :class="statusFilterVisible ? 'text-[rgb(var(--primary-5))]' : ''" />
@ -75,13 +104,13 @@
<div class="arco-table-filters-content">
<div class="flex items-center justify-center px-[6px] py-[2px]">
<a-checkbox-group v-model:model-value="statusFilters" direction="vertical" size="small">
<a-checkbox v-for="key of Object.keys(reviewStatusMap)" :key="key" :value="key">
<a-checkbox v-for="key of Object.keys(planStatusMap)" :key="key" :value="key">
<a-tag
:color="reviewStatusMap[key as ReviewStatus].color"
:class="[reviewStatusMap[key as ReviewStatus].class, 'px-[4px]']"
:color="planStatusMap[key as planStatusType].color"
:class="[planStatusMap[key as planStatusType].class, 'px-[4px]']"
size="small"
>
{{ t(reviewStatusMap[key as ReviewStatus].label) }}
{{ t(planStatusMap[key as planStatusType].label) }}
</a-tag>
</a-checkbox>
</a-checkbox-group>
@ -103,38 +132,33 @@
<statusTag :status="record.status" />
</template>
<template #passRate="{ record }">
<!-- <template #passRate="{ record }">
<div class="mr-[8px] w-[100px]">
<StatusProgress :status-detail="record.statusDetail" height="5px" />
</div>
<div class="text-[var(--color-text-1)]">
{{ `${record.passRate || 0}%` }}
</div>
</template>
</template> -->
<template #passRateTitleSlot="{ columnConfig }">
<div class="flex items-center text-[var(--color-text-3)]">
{{ t(columnConfig.title as string) }}
<a-tooltip position="right">
<a-tooltip position="right" :content="t('testPlan.testPlanIndex.passRateTitleTip')">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
<template #content>
<!-- TODO 需要提供文案 -->
<!-- <div>{{ t('apiTestDebug.encodeTip1') }}</div>
<div>{{ t('apiTestDebug.encodeTip2') }}</div> -->
</template>
</a-tooltip>
</div>
</template>
<template #useCount="{ record }">
<!-- <template #useCount="{ record }">
<a-popover position="bottom" content-class="p-[16px]" trigger="click">
<div>{{ record.useCaseCount.caseCount }}</div>
<template #content>
<table class="min-w-[144px]">
<tr>
<td class="popover-label-td">
<div>{{ t('project.testPlanIndex.TotalCases') }}</div>
<div>{{ t('testPlan.testPlanIndex.TotalCases') }}</div>
</td>
<td class="popover-value-td">
{{ record.useCaseCount.caseCount }}
@ -142,7 +166,7 @@
</tr>
<tr>
<td class="popover-label-td">
<div class="text-[var(--color-text-1)]">{{ t('project.testPlanIndex.functionalUseCase') }}</div>
<div class="text-[var(--color-text-1)]">{{ t('testPlan.testPlanIndex.functionalUseCase') }}</div>
</td>
<td class="popover-value-td">
{{ record.useCaseCount.caseCount }}
@ -150,7 +174,7 @@
</tr>
<tr>
<td class="popover-label-td">
<div class="text-[var(--color-text-1)]">{{ t('project.testPlanIndex.apiCase') }}</div>
<div class="text-[var(--color-text-1)]">{{ t('testPlan.testPlanIndex.apiCase') }}</div>
</td>
<td class="popover-value-td">
{{ record.useCaseCount.caseCount }}
@ -158,7 +182,7 @@
</tr>
<tr>
<td class="popover-label-td">
<div class="text-[var(--color-text-1)]">{{ t('project.testPlanIndex.apiScenarioCase') }}</div>
<div class="text-[var(--color-text-1)]">{{ t('testPlan.testPlanIndex.apiScenarioCase') }}</div>
</td>
<td class="popover-value-td">
{{ record.useCaseCount.caseCount }}
@ -167,7 +191,7 @@
</table>
</template>
</a-popover>
</template>
</template> -->
<template #operation="{ record }">
<div class="flex items-center">
@ -218,37 +242,51 @@
@save="handleMoveOrCopy"
/>
<ScheduledModal v-model:visible="showScheduledTaskModal" />
<ActionModal v-model:visible="showStatusDeleteModal" :record="activeRecord" />
<BatchEditModal
v-model:visible="showEditModel"
:batch-params="batchParams"
:active-folder="props.activeFolder"
:offspring-ids="props.offspringIds"
:condition="conditionParams"
@success="successHandler"
/>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
import { FilterFormItem } from '@/components/pure/ms-advance-filter/type';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
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 MsTag from '@/components/pure/ms-tag/ms-tag.vue';
import ActionModal from './actionModal.vue';
import BatchEditModal from './batchEditModal.vue';
import BatchMoveOrCopy from './batchMoveOrCopy.vue';
import ScheduledModal from './scheduledModal.vue';
import StatusProgress from './statusProgress.vue';
import statusTag from '@/views/case-management/caseReview/components/statusTag.vue';
import { getTestPlanList, getTestPlanModule } from '@/api/modules/test-plan/testPlan';
import { reviewStatusMap } from '@/config/caseManagement';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import { useAppStore, useTableStore } from '@/store';
import { characterLimit } from '@/utils';
import { ReviewStatus } from '@/models/caseManagement/caseReview';
import type { planStatusType, TestPlanItem } from '@/models/testPlan/testPlan';
import { ColumnEditTypeEnum, TableKeyEnum } from '@/enums/tableEnum';
import { testPlanTypeEnum } from '@/enums/testPlanEnum';
import { planStatusMap } from '../config';
const tableStore = useTableStore();
const appStore = useAppStore();
const { t } = useI18n();
@ -259,6 +297,7 @@
activeFolderType: 'folder' | 'module';
offspringIds: string[]; // id
modulesCount: Record<string, number>; //
nodeName: string; //
}>();
const emit = defineEmits<{
@ -413,13 +452,20 @@
const keyword = ref<string>('');
const statusFilterVisible = ref(false);
const statusFilters = ref<string[]>([]);
const showType = ref<keyof typeof testPlanTypeEnum>(testPlanTypeEnum.TEST_PLAN);
const tableBatchActions = {
const testPlanBatchActions = {
baseAction: [
// TODO
// {
// label: 'testPlan.testPlanIndex.execute',
// eventTag: 'execute',
// permission: ['PROJECT_TEST_PLAN:READ+EXECUTE'],
// },
{
label: 'testPlan.testPlanIndex.execute',
eventTag: 'execute',
permission: ['PROJECT_TEST_PLAN:READ+EXECUTE'],
label: 'common.edit',
eventTag: 'edit',
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
},
{
label: 'common.copy',
@ -430,23 +476,23 @@
// label: 'common.export',
// eventTag: 'export',
// },
],
moreAction: [
// {
// label: 'testPlan.testPlanIndex.openTimingTask',
// eventTag: 'openTimingTask',
// permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
// },
// {
// label: 'testPlan.testPlanIndex.closeTimingTask',
// eventTag: 'closeTimingTask',
// permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
// },
{
label: 'common.move',
eventTag: 'move',
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
},
],
moreAction: [
{
label: 'testPlan.testPlanIndex.openTimingTask',
eventTag: 'openTimingTask',
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
},
{
label: 'testPlan.testPlanIndex.closeTimingTask',
eventTag: 'closeTimingTask',
permission: ['PROJECT_TEST_PLAN:READ+UPDATE'],
},
{
label: 'common.archive',
eventTag: 'archive',
@ -469,14 +515,15 @@
label: 'common.copy',
eventTag: 'copy',
},
{
label: 'testPlan.testPlanIndex.createScheduledTask',
eventTag: 'createScheduledTask',
},
{
label: 'testPlan.testPlanIndex.configuration',
eventTag: 'config',
},
// TODO
// {
// label: 'testPlan.testPlanIndex.createScheduledTask',
// eventTag: 'createScheduledTask',
// },
// {
// label: 'testPlan.testPlanIndex.configuration',
// eventTag: 'config',
// },
{
label: 'common.archive',
eventTag: 'archive',
@ -497,7 +544,7 @@
projectId: 'string',
num: '100944',
name: '系统示例',
status: 'PREPARED',
status: 'COMPLETED',
tags: ['string'],
schedule: 'string',
createUser: 'string',
@ -522,74 +569,74 @@
apiCount: 3,
scenarioCount: 3,
},
children: [
{
id: '100945',
projectId: 'string',
num: '100945',
name: '系统示例',
status: 'COMPLETED',
tags: ['string'],
schedule: 'string',
createUser: 'string',
createTime: 'string',
moduleName: 'string',
moduleId: 'string',
testPlanItem: [],
testPlanGroupId: 'string',
passCount: 0,
unPassCount: 0,
reviewedCount: 0,
underReviewedCount: 0,
childrenCount: 0,
useCaseCount: {
caseCount: 3,
apiCount: 3,
scenarioCount: 3,
},
statusDetail: {
tolerance: 100,
UNPENDING: 100,
RUNNING: 30,
SUCCESS: 30,
ERROR: 30,
executionProgress: '100%',
},
},
{
id: '100955',
projectId: 'string',
num: '100955',
name: '系统示例',
status: 'COMPLETED',
tags: ['string'],
schedule: 'string',
createUser: 'string',
createTime: 'string',
moduleName: 'string',
moduleId: 'string',
testPlanItem: [],
testPlanGroupId: 'string',
passCount: 0,
unPassCount: 0,
reviewedCount: 0,
underReviewedCount: 0,
childrenCount: 0,
useCaseCount: {
caseCount: 3,
apiCount: 3,
scenarioCount: 3,
},
statusDetail: {
tolerance: 100,
UNPENDING: 100,
RUNNING: 30,
SUCCESS: 30,
ERROR: 30,
executionProgress: '100%',
},
},
],
// children: [
// {
// id: '100945',
// projectId: 'string',
// num: '100945',
// name: '',
// status: 'COMPLETED',
// tags: ['string'],
// schedule: 'string',
// createUser: 'string',
// createTime: 'string',
// moduleName: 'string',
// moduleId: 'string',
// testPlanItem: [],
// testPlanGroupId: 'string',
// passCount: 0,
// unPassCount: 0,
// reviewedCount: 0,
// underReviewedCount: 0,
// childrenCount: 0,
// useCaseCount: {
// caseCount: 3,
// apiCount: 3,
// scenarioCount: 3,
// },
// statusDetail: {
// tolerance: 100,
// UNPENDING: 100,
// RUNNING: 30,
// SUCCESS: 30,
// ERROR: 30,
// executionProgress: '100%',
// },
// },
// {
// id: '100955',
// projectId: 'string',
// num: '100955',
// name: '',
// status: 'COMPLETED',
// tags: ['string'],
// schedule: 'string',
// createUser: 'string',
// createTime: 'string',
// moduleName: 'string',
// moduleId: 'string',
// testPlanItem: [],
// testPlanGroupId: 'string',
// passCount: 0,
// unPassCount: 0,
// reviewedCount: 0,
// underReviewedCount: 0,
// childrenCount: 0,
// useCaseCount: {
// caseCount: 3,
// apiCount: 3,
// scenarioCount: 3,
// },
// statusDetail: {
// tolerance: 100,
// UNPENDING: 100,
// RUNNING: 30,
// SUCCESS: 30,
// ERROR: 30,
// executionProgress: '100%',
// },
// },
// ],
},
];
@ -623,8 +670,6 @@
combine: {},
});
const showType = ref<keyof typeof testPlanTypeEnum>('ALL');
async function initTableParams() {
conditionParams.value = {
keyword: keyword.value,
@ -740,7 +785,7 @@
title: t('testPlan.testPlanIndex.confirmBatchArchivePlan', {
count: batchParams.value.currentSelectCount,
}),
content: t('testPlan.testPlanIndex.confirmBatchDeletePlanContentTip'),
content: t('testPlan.testPlanIndex.confirmBatchArchivePlanContent'),
okText: t('common.archive'),
cancelText: t('common.cancel'),
okButtonProps: {
@ -749,7 +794,7 @@
onBeforeOk: async () => {
try {
const { selectedIds, selectAll, excludeIds } = batchParams.value;
Message.success(t('common.deleteSuccess'));
Message.success(t('common.batchArchiveSuccess'));
fetchData();
} catch (error) {
console.log(error);
@ -759,7 +804,7 @@
});
}
/**
* 归档
* 删除
*/
function handleDelete() {
openModal({
@ -767,7 +812,7 @@
title: t('testPlan.testPlanIndex.confirmBatchDeletePlan', {
count: batchParams.value.currentSelectCount,
}),
content: t('testPlan.testPlanIndex.confirmBatchDeletePlanContentTip'),
content: t('testPlan.testPlanIndex.confirmBatchDeletePlanContent'),
okText: t('common.confirmDelete'),
cancelText: t('common.cancel'),
okButtonProps: {
@ -786,6 +831,19 @@
});
}
/**
* 批量编辑
*/
const showEditModel = ref<boolean>(false);
function handleEdit() {
showEditModel.value = true;
}
function successHandler() {
fetchData();
}
/**
* 批量操作
*/
@ -813,6 +871,9 @@
case 'delete':
handleDelete();
break;
case 'edit':
handleEdit();
break;
default:
break;
@ -828,7 +889,36 @@
showScheduledTaskModal.value = true;
}
function handleMoreActionSelect(item: ActionsItem, record: any) {
const showStatusDeleteModal = ref<boolean>(false);
const activeRecord = ref<TestPlanItem>();
function deleteStatusHandler(record: TestPlanItem) {
activeRecord.value = cloneDeep(record);
showStatusDeleteModal.value = true;
}
function archiveHandle(record: TestPlanItem) {
openModal({
type: 'warning',
title: t('common.archiveConfirmTitle', { name: characterLimit(record.name) }),
content: t('testPlan.testPlanIndex.confirmArchivePlan'),
okText: t('common.archive'),
cancelText: t('common.cancel'),
okButtonProps: {
status: 'normal',
},
onBeforeOk: async () => {
try {
Message.success(t('common.batchArchiveSuccess'));
fetchData();
} catch (error) {
console.log(error);
}
},
hideCancel: false,
});
}
function handleMoreActionSelect(item: ActionsItem, record: TestPlanItem) {
switch (item.eventTag) {
case 'copy':
copyHandler();
@ -836,6 +926,12 @@
case 'createScheduledTask':
handleScheduledTask();
break;
case 'delete':
deleteStatusHandler(record);
break;
case 'archive':
archiveHandle(record);
break;
default:
break;
}
@ -843,17 +939,18 @@
const expandedKeys = ref<string[]>([]);
function expandHandler(record: any) {
if (expandedKeys.value.includes(record.id)) {
expandedKeys.value = expandedKeys.value.filter((key) => key !== record.id);
} else {
expandedKeys.value = [...expandedKeys.value, record.id];
}
}
function getIconClass(record: any) {
return expandedKeys.value.includes(record.id) ? 'text-[rgb(var(--primary-5))]' : 'text-[var(--color-text-4)]';
}
// TODO
// function expandHandler(record: any) {
// if (expandedKeys.value.includes(record.id)) {
// expandedKeys.value = expandedKeys.value.filter((key) => key !== record.id);
// } else {
// expandedKeys.value = [...expandedKeys.value, record.id];
// }
// }
// TODO
// function getIconClass(record: any) {
// return expandedKeys.value.includes(record.id) ? 'text-[rgb(var(--primary-5))]' : 'text-[var(--color-text-4)]';
// }
function handleFilterHidden(val: boolean) {
if (!val) {
@ -893,11 +990,6 @@
}
);
// TODO
// onMounted(() => {
// setProps({ data });
// });
onBeforeMount(() => {
fetchData();
});
@ -906,18 +998,16 @@
</script>
<style scoped lang="less">
:deep(.arco-table-cell-expand-icon .arco-table-cell-inline-icon) {
display: none;
}
:deep(.arco-table-cell-align-left) > span:first-child {
padding-left: 0 !important;
}
.arrowIcon {
transform: scaleX(-1);
}
:deep(.ms-modal-form .arco-modal-body) {
padding: 0 !important;
}
// TODO
// :deep(.arco-table-cell-expand-icon .arco-table-cell-inline-icon) {
// display: none;
// }
// :deep(.arco-table-cell-align-left) > span:first-child {
// padding-left: 0 !important;
// }
// .arrowIcon {
// transform: scaleX(-1);
// }
.popover-label-td {
@apply flex items-center;

View File

@ -20,7 +20,7 @@
<template #footer>
<div class="mb-[6px] mt-[4px] p-[3px_8px]">
<MsButton type="text" class="text-[rgb(var(--primary-5))]" @click="createCustomFrequency">
{{ t('project.testPlanIndex.customFrequency') }}
{{ t('testPlan.testPlanIndex.customFrequency') }}
</MsButton>
</div>
</template>
@ -28,14 +28,14 @@
</a-form-item>
<a-radio-group v-model="form.env" class="mb-4">
<a-radio value="">
{{ t('project.testPlanIndex.defaultEnv') }}
{{ t('testPlan.testPlanIndex.defaultEnv') }}
<span class="float-right mx-1 mt-[1px]">
<a-tooltip :content="t('testPlan.testPlanIndex.envTip')" position="top">
<IconQuestionCircle class="h-[16px] w-[16px] text-[--color-text-4] hover:text-[rgb(var(--primary-5))]"
/></a-tooltip>
</span>
</a-radio>
<a-radio value="new"> {{ t('project.testPlanIndex.newEnv') }}</a-radio>
<a-radio value="new"> {{ t('testPlan.testPlanIndex.newEnv') }}</a-radio>
</a-radio-group>
<a-radio-group v-model="form.methods">
<a-radio value="serial">{{ t('testPlan.testPlanIndex.serial') }}</a-radio>
@ -59,7 +59,7 @@
<span class="text-[var(--color-text-4)]">CPU</span>
<span class="mx-2"> {{ item.cpuRate }}</span>
<MsTag theme="outline" :type="item.status ? 'link' : 'success'" size="small">
{{ item.status ? t('project.testPlanIndex.doing') : t('project.testPlanIndex.inFreeTime') }}
{{ item.status ? t('testPlan.testPlanIndex.doing') : t('testPlan.testPlanIndex.inFreeTime') }}
</MsTag>
</div>
</div>

View File

@ -4,7 +4,7 @@
<table class="min-w-[144px]">
<tr>
<td class="popover-label-td">
<div>{{ t('project.testPlanIndex.tolerance') }}</div>
<div>{{ t('testPlan.testPlanIndex.tolerance') }}</div>
</td>
<td class="popover-value-td">
{{ props.statusDetail.tolerance }}
@ -12,7 +12,7 @@
</tr>
<tr>
<td class="popover-label-td">
<div>{{ t('project.testPlanIndex.executionProgress') }}</div>
<div>{{ t('testPlan.testPlanIndex.executionProgress') }}</div>
</td>
<td class="popover-value-td">
{{ props.statusDetail.executionProgress }}

View File

@ -105,7 +105,7 @@
modulesCount?: Record<string, number>; //
}>();
const emits = defineEmits(['update:selectedKeys', 'planTreeNodeSelect', 'init', 'dragUpdate']);
const emits = defineEmits(['update:selectedKeys', 'planTreeNodeSelect', 'init', 'dragUpdate', 'getNodeName']);
const currentProjectId = computed(() => appStore.currentProjectId);
@ -213,7 +213,7 @@
offspringIds.push(e.id);
return e;
});
emits('planTreeNodeSelect', selectedKeys, offspringIds);
emits('planTreeNodeSelect', selectedKeys, offspringIds, node.name);
};
//

View File

@ -0,0 +1,32 @@
import type { planStatusType } from '@/models/testPlan/testPlan';
export type PlanStatusMap = Record<
planStatusType,
{
label: string;
color: string;
class: string;
}
>;
export const planStatusMap: PlanStatusMap = {
PREPARED: {
label: 'caseManagement.caseReview.unStart',
color: 'var(--color-text-n8)',
class: '!text-[var(--color-text-1)]',
},
UNDERWAY: {
label: 'caseManagement.caseReview.going',
color: 'rgb(var(--link-2))',
class: '!text-[rgb(var(--link-6))]',
},
COMPLETED: {
label: 'caseManagement.caseReview.finished',
color: 'rgb(var(--success-2))',
class: '!text-[rgb(var(--success-6))]',
},
ARCHIVED: {
label: 'caseManagement.caseReview.archived',
color: 'var(--color-text-n8)',
class: '!text-[var(--color-text-4)]',
},
};

View File

@ -82,6 +82,7 @@
:offspring-ids="offspringIds"
:active-folder-type="activeCaseType"
:modules-count="modulesCount"
:node-name="nodeName"
@init="initModulesCount"
/>
</div>
@ -177,11 +178,14 @@
const activeCaseType = ref<'folder' | 'module'>('folder'); //
const offspringIds = ref<string[]>([]);
const nodeName = ref<string>('');
//
function planNodeSelect(keys: string[], _offspringIds: string[]) {
function planNodeSelect(keys: string[], _offspringIds: string[], moduleName: string) {
[activeFolder.value] = keys;
activeCaseType.value = 'module';
offspringIds.value = [..._offspringIds];
nodeName.value = moduleName;
}
/**
@ -196,7 +200,7 @@
}
/**
* 右侧表格数据刷新后若当前展示的是模块刷新模块树的统计数量
* 刷新模块树的统计数量
*/
function initModulesCount(params: any) {}

View File

@ -36,6 +36,7 @@ export default {
'testPlan.testPlanIndex.confirmBatchDeletePlan': 'Are you sure to delete {count} test plans?',
'testPlan.testPlanIndex.confirmBatchDeletePlanContentTip':
'Only sub-plans within the test plan and plan group are deleted',
'testPlan.testPlanIndex.confirmBatchDeletePlanContent': 'After delete, data unrecoverable, please careful operation.',
'testPlan.testPlanIndex.confirmBatchArchivePlan': 'Are you sure to archive {count} test plans?',
'testPlan.testPlanIndex.confirmBatchArchivePlanContent':
'Only completed test plans can be archived! \n after filing, implement information no longer update and editing, data unrecoverable, please careful operation.',
@ -49,17 +50,28 @@ export default {
'testPlan.testPlanIndex.timingState': 'Timing state',
'testPlan.testPlanIndex.timingStateEnable': 'Enable: Executes scheduled tasks',
'testPlan.testPlanIndex.timingStateClose': 'Close: Stops a scheduled task',
'project.testPlanIndex.customFrequency': 'Custom frequency',
'project.testPlanIndex.doing': 'Doing',
'project.testPlanIndex.inFreeTime': 'In free time',
'project.testPlanIndex.defaultEnv': 'Default Environment',
'project.testPlanIndex.newEnv': 'New Environment',
'project.testPlanIndex.executionProgress': 'Execution progress',
'project.testPlanIndex.tolerance': 'tolerance',
'project.testPlanIndex.TotalCases': 'Total use cases',
'project.testPlanIndex.functionalUseCase': 'case',
'project.testPlanIndex.apiCase': 'Api use case',
'project.testPlanIndex.apiScenarioCase': 'Api scenario use cases',
'testPlan.testPlanIndex.customFrequency': 'Custom frequency',
'testPlan.testPlanIndex.doing': 'Doing',
'testPlan.testPlanIndex.inFreeTime': 'In free time',
'testPlan.testPlanIndex.defaultEnv': 'Default Environment',
'testPlan.testPlanIndex.newEnv': 'New Environment',
'testPlan.testPlanIndex.executionProgress': 'Execution progress',
'testPlan.testPlanIndex.tolerance': 'tolerance',
'testPlan.testPlanIndex.TotalCases': 'Total use cases',
'testPlan.testPlanIndex.functionalUseCase': 'case',
'testPlan.testPlanIndex.apiCase': 'Api use case',
'testPlan.testPlanIndex.apiScenarioCase': 'Api scenario use cases',
'testPlan.testPlanIndex.deleteArchivedPlan':
'After the program has been archived, delete data unrecoverable, please careful operation.',
'testPlan.testPlanIndex.deletePendingPlan':
'The plan is not executed, the data cannot be recovered after deletion, please operate carefully!',
'testPlan.testPlanIndex.deleteRunningPlan':
'Scheduled tasks are stopped and deleted. Exercise caution when performing this operation',
'testPlan.testPlanIndex.deleteCompletedPlan':
'The proposed plan is completed, the option is archived, and the use case information and execution results are retained;If you continue to delete, the data will not be restored, please be careful!',
'testPlan.testPlanIndex.confirmArchivePlan':
'After filing, implement information no longer update and editing, data unrecoverable, please careful operation',
'testPlan.testPlanIndex.passRateTitleTip': 'Passed use cases/all use cases *100%',
'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',

View File

@ -36,9 +36,10 @@ export default {
'testPlan.testPlanIndex.parallel': '并行',
'testPlan.testPlanIndex.confirmBatchDeletePlan': '确认删除 {count} 个测试计划吗?',
'testPlan.testPlanIndex.confirmBatchDeletePlanContentTip': '仅删除测试计划和计划组内的子计划',
'testPlan.testPlanIndex.confirmBatchDeletePlanContent': '删除后,数据不可恢复,请谨慎操作!',
'testPlan.testPlanIndex.confirmBatchArchivePlan': '确认归档 {count} 个测试计划吗?',
'testPlan.testPlanIndex.confirmBatchArchivePlanContent':
'仅 已完成 测试计划可归档!\n 归档后,执行信息不再更新且不可编辑,数据不可恢复,请谨慎操作!',
'仅 已完成 测试计划可归档!\r\n 归档后,执行信息不再更新且不可编辑,数据不可恢复,请谨慎操作!',
'testPlan.testPlanIndex.selectedCount': '(已选 {count} 项数据)',
'testPlan.testPlanIndex.createScheduledTask': '创建定时任务',
'testPlan.testPlanIndex.updateScheduledTask': '更新定时任务',
@ -49,17 +50,26 @@ export default {
'testPlan.testPlanIndex.timingState': '定时状态',
'testPlan.testPlanIndex.timingStateEnable': '开启:执行定时任务',
'testPlan.testPlanIndex.timingStateClose': '关闭:停止定时任务',
'project.testPlanIndex.customFrequency': '自定义频率',
'project.testPlanIndex.doing': '进行中',
'project.testPlanIndex.inFreeTime': '空闲中',
'project.testPlanIndex.defaultEnv': '默认环境',
'project.testPlanIndex.newEnv': '新环境',
'project.testPlanIndex.executionProgress': '执行进度',
'project.testPlanIndex.tolerance': '容错率',
'project.testPlanIndex.TotalCases': '用例总数',
'project.testPlanIndex.functionalUseCase': '功能用例',
'project.testPlanIndex.apiCase': '接口用例',
'project.testPlanIndex.apiScenarioCase': '接口场景用例',
'testPlan.testPlanIndex.customFrequency': '自定义频率',
'testPlan.testPlanIndex.doing': '进行中',
'testPlan.testPlanIndex.inFreeTime': '空闲中',
'testPlan.testPlanIndex.defaultEnv': '默认环境',
'testPlan.testPlanIndex.newEnv': '新环境',
'testPlan.testPlanIndex.executionProgress': '执行进度',
'testPlan.testPlanIndex.tolerance': '容错率',
'testPlan.testPlanIndex.TotalCases': '用例总数',
'testPlan.testPlanIndex.functionalUseCase': '功能用例',
'testPlan.testPlanIndex.apiCase': '接口用例',
'testPlan.testPlanIndex.apiScenarioCase': '接口场景用例',
'testPlan.testPlanIndex.deleteArchivedPlan': '计划 已归档,删除后数据不可恢复,请谨慎操作!',
'testPlan.testPlanIndex.deletePendingPlan': '计划 未执行,删除后数据不可恢复,请谨慎操作!',
'testPlan.testPlanIndex.deleteRunningPlan':
'计划 进行中,删除后,终止执行且不可恢复,定时任务停止并删除,请谨慎操作!',
'testPlan.testPlanIndex.deleteCompletedPlan':
'建议计划 已完成 ,选择归档,用例信息及执行结果都将被保留;若继续删除,数据将不会恢复,请谨慎操作!',
'testPlan.testPlanIndex.confirmArchivePlan': '归档后,执行信息不再更新且不可编辑,数据不可恢复,请谨慎操作!',
'testPlan.testPlanIndex.passRateTitleTip': '已通过用例/全部用例*100%',
'testPlan.testPlanIndex.batchEdit': '批量编辑 (已选 { number } 项数据)',
'testPlan.planForm.namePlaceholder': '请输入测试计划名称',
'testPlan.planForm.nameRequired': '测试计划名称不能为空',
'testPlan.planForm.planStartAndEndTime': '计划起止时间',