feat(测试计划): 关联用例抽屉页面调整&联调&独立报告&聚合报告页面&缺陷管理&用例管理评论参数调整

This commit is contained in:
xinxin.wu 2024-06-12 11:08:00 +08:00 committed by 刘瑞斌
parent a3dc954774
commit 92f09ba18c
32 changed files with 595 additions and 243 deletions

View File

@ -62,6 +62,7 @@ import {
TestPlanCaseAssociatedPageUrl,
TestPlanCaseDetailUrl,
TestPlanGroupOptionsUrl,
TestPlanScenarioAssociatedPageUrl,
updateTestPlanModuleUrl,
UpdateTestPlanUrl,
} from '@/api/requrls/test-plan/testPlan';
@ -344,6 +345,10 @@ export function getTestPlanAssociationApiList(data: TableQueryParams) {
export function getTestPlanAssociationCaseList(data: TableQueryParams) {
return MSR.post<CommonList<ApiCaseDetail>>({ url: TestPlanCaseAssociatedPageUrl, data });
}
// 功能用例-关联用例-接口用例-CASE
export function getPlanScenarioAssociatedList(data: TableQueryParams) {
return MSR.post<CommonList<ApiCaseDetail>>({ url: TestPlanScenarioAssociatedPageUrl, data });
}
// 测试计划-复制测试计划&测试计划组
export function testPlanAndGroupCopy(id: string) {
return MSR.get({ url: `${TestPlanAndGroupCopyUrl}/${id}` });

View File

@ -84,6 +84,8 @@ export const PlanDetailExecuteHistoryUrl = '/api/scenario/execute/page';
export const TestPlanApiAssociatedPageUrl = '/test-plan/association/api/page';
// 功能用例-关联用例-接口用例-CASE
export const TestPlanCaseAssociatedPageUrl = '/test-plan/association/api/case/page';
// 功能用例-关联用例-接口用例-CASE
export const TestPlanScenarioAssociatedPageUrl = '/test-plan/association/api/scenario/page';
// 测试计划-复制
export const TestPlanAndGroupCopyUrl = '/test-plan/copy';
// 测试计划-计划组下拉

View File

@ -210,7 +210,6 @@
return {
keyword: props.keyword,
projectId: props.currentProject,
protocol: 'HTTP',
moduleIds: props.activeModule === 'all' || !props.activeModule ? [] : [props.activeModule, ...props.offspringIds],
excludeIds: [...(props.associatedIds || [])], // id
condition: {
@ -243,28 +242,18 @@
const tableRef = ref<InstanceType<typeof MsBaseTable>>();
watch(
() => props.activeSourceType,
(val) => {
if (val) {
tableRef.value?.initColumn(columns);
resetSelector();
resetFilterParams();
setPagination({
current: 1,
});
}
() => () => props.currentProject,
() => {
loadCaseList();
}
);
watch(
() => props.currentProject,
() => props.activeModule,
(val) => {
if (val) {
loadCaseList();
}
},
{
immediate: true,
}
);

View File

@ -65,6 +65,7 @@
showType: string;
getPageApiType: keyof typeof CasePageApiTypeEnum; // Api
extraTableParams?: TableQueryParams; //
protocols: string[];
}>();
const emit = defineEmits<{
@ -174,7 +175,7 @@
return {
keyword: props.keyword,
projectId: props.currentProject,
protocol: 'HTTP',
protocols: props.protocols,
moduleIds: props.activeModule === 'all' || !props.activeModule ? [] : [props.activeModule, ...props.offspringIds],
excludeIds: [...(props.associatedIds || [])], // id
condition: {
@ -205,29 +206,12 @@
}
watch(
() => props.activeSourceType,
(val) => {
if (val) {
resetSelector();
resetFilterParams();
setPagination({
current: 1,
});
}
() => [() => props.currentProject, () => props.protocols],
() => {
loadApiList();
}
);
watch(
() => props.currentProject,
(val) => {
if (val) {
loadApiList();
}
},
{
immediate: true,
}
);
watch(
() => props.showType,
(val) => {
@ -239,6 +223,15 @@
}
);
watch(
() => props.activeModule,
(val) => {
if (val) {
loadApiList();
}
}
);
function getApiSaveParams() {
const { excludeKeys, selectedKeys, selectorStatus } = propsRes.value;
const tableParams = getTableParams();

View File

@ -110,7 +110,7 @@
sorter: true,
},
fixed: 'left',
width: 100,
width: 150,
showTooltip: true,
columnSelectorDisabled: true,
},
@ -291,6 +291,15 @@
}
);
watch(
() => props.activeModule,
(val) => {
if (val) {
loadCaseList();
}
}
);
defineExpose({
getFunctionalSaveParams,
loadCaseList,

View File

@ -1,12 +1,23 @@
<template>
<MsFolderAll
<TreeFolderAll
v-if="props.activeTab === CaseLinkEnum.API"
v-model:selectedProtocols="selectedProtocols"
:active-folder="activeFolder"
:folder-name="t('caseManagement.caseReview.allCases')"
:folder-name="props.folderName"
:all-count="allCount"
:show-expand-api="false"
@set-active-folder="setActiveFolder"
@selected-protocols-change="selectedProtocolsChange"
/>
<MsFolderAll
v-else
:active-folder="activeFolder"
:folder-name="props.folderName"
:all-count="allCount"
@set-active-folder="setActiveFolder"
>
</MsFolderAll>
<a-divider class="my-[8px]" />
<a-divider class="my-[8px] mt-0" />
<div class="mb-[8px] flex items-center gap-[8px]">
<a-input
v-model:model-value="moduleKeyword"
@ -60,6 +71,7 @@
import MsFolderAll from '@/components/business/ms-folder-all/index.vue';
import MsTree from '@/components/business/ms-tree/index.vue';
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import TreeFolderAll from '@/views/api-test/components/treeFolderAll.vue';
import { useI18n } from '@/hooks/useI18n';
import { mapTree } from '@/utils';
@ -79,15 +91,18 @@
getModulesApiType: CaseModulesApiTypeEnum[keyof CaseModulesApiTypeEnum];
activeTab: keyof typeof CaseLinkEnum;
extraModulesParams?: Record<string, any>; //
showType?: string;
folderName: string;
}>();
const emit = defineEmits<{
(e: 'folderNodeSelect', ids: string[], _offspringIds: string[], nodeName?: string): void;
(e: 'init', params: ModuleTreeNode[]): void;
(e: 'init', params: ModuleTreeNode[], selectedProtocols?: string[]): void;
(e: 'changeProtocol', selectedProtocols: string[]): void;
(e: 'update:selectedKeys', selectedKeys: string[]): void;
}>();
const selectedKeys = useVModel(props, 'selectedKeys', emit);
const moduleKeyword = ref('');
const activeFolder = ref<string>('all');
const allCount = ref(0);
@ -106,7 +121,7 @@
function setActiveFolder(id: string) {
activeFolder.value = id;
emit('folderNodeSelect', [id], [], t('caseManagement.featureCase.allCase'));
emit('folderNodeSelect', [id], [], props.folderName);
}
/**
@ -122,23 +137,28 @@
emit('folderNodeSelect', _selectedKeys as string[], offspringIds, node.name);
}
const selectedProtocols = ref<string[]>([]);
/**
* 初始化模块树
*/
async function initModules() {
try {
moduleLoading.value = true;
const res = await getModuleTreeFunc(props.getModulesApiType, props.activeTab, {
const getModuleParams = {
projectId: props.currentProject,
...props.extraModulesParams,
});
protocols:
props.activeTab === CaseLinkEnum.API && props.showType === 'API' ? selectedProtocols.value : undefined,
};
const res = await getModuleTreeFunc(props.getModulesApiType, props.activeTab, getModuleParams);
caseTree.value = mapTree<ModuleTreeNode>(res, (node) => {
return {
...node,
count: props.modulesCount?.[node.id] || 0,
};
});
emit('init', caseTree.value);
emit('init', caseTree.value, selectedProtocols.value);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
@ -147,6 +167,11 @@
}
}
function selectedProtocolsChange() {
emit('changeProtocol', selectedProtocols.value);
initModules();
}
/**
* 初始化模块文件数量
*/

View File

@ -8,43 +8,125 @@
unmount-on-close
>
<template #headerLeft>
<div class="float-left">
<a-select
v-model="innerProject"
class="ml-2 w-[240px]"
:default-value="innerProject"
allow-search
:placeholder="t('common.pleaseSelect')"
>
<template #arrow-icon>
<icon-caret-down />
</template>
<a-tooltip v-for="item of projectList" :key="item.id" :mouse-enter-delay="500" :content="item.name">
<a-option :value="item.id" :class="item.id === innerProject ? 'arco-select-option-selected' : ''">
{{ item.name }}
</a-option>
</a-tooltip>
</a-select>
<a-popconfirm
v-if="!getIsVisited()"
class="ms-pop-confirm--hidden-cancel change-project-pop"
position="br"
:popup-visible="selectPopVisible"
popup-container="#typeSelectGroupRef"
:ok-text="t('ms.case.associate.gotIt')"
@ok="okHandler"
@popup-visible-change="handlePopChange"
>
<div class="float-left">
<a-input-group>
<a-select
v-model="functionalType"
class="ml-2 w-[100px]"
:default-value="innerProject"
:placeholder="t('common.pleaseSelect')"
>
<template #arrow-icon>
<icon-caret-down />
</template>
<a-tooltip v-for="item of functionalList" :key="item.id" :mouse-enter-delay="500" :content="item.name">
<a-option :value="item.id" :class="item.id === innerProject ? 'arco-select-option-selected' : ''">
{{ item.name }}
</a-option>
</a-tooltip>
</a-select>
<a-select
v-model="innerProject"
:popup-visible="selectVisible"
class="w-[240px]"
:default-value="innerProject"
allow-search
:placeholder="t('common.pleaseSelect')"
@popup-visible-change="changeProjectHandler"
>
<template #arrow-icon>
<icon-caret-down />
</template>
<a-tooltip v-for="item of projectList" :key="item.id" :mouse-enter-delay="500" :content="item.name">
<a-option :value="item.id" :class="item.id === innerProject ? 'arco-select-option-selected' : ''">
{{ item.name }}
</a-option>
</a-tooltip>
</a-select>
</a-input-group>
</div>
<template #cancel-text>
<div> </div>
</template>
<template #icon>
<MsIcon class="text-[rgb(var(--primary-5))]" type="icon-icon_warning_filled" size="16" />
</template>
<template #content>
<div class="font-semibold text-[var(--color-text-1)]">
{{ t('ms.case.associate.switchProject') }}
</div>
<div class="mt-[8px] w-[215px] text-[12px] leading-[16px] text-[var(--color-text-2)]">
{{ t('ms.case.associate.switchProjectPopTip') }}
</div>
</template>
</a-popconfirm>
<div v-else class="float-left">
<a-input-group>
<a-select
v-model="functionalType"
class="ml-2 w-[100px]"
:default-value="innerProject"
allow-search
:placeholder="t('common.pleaseSelect')"
>
<template #arrow-icon>
<icon-caret-down />
</template>
<a-tooltip v-for="item of functionalList" :key="item.id" :mouse-enter-delay="500" :content="item.name">
<a-option :value="item.id" :class="item.id === innerProject ? 'arco-select-option-selected' : ''">
{{ item.name }}
</a-option>
</a-tooltip>
</a-select>
<a-select
id="typeRadioGroupRef"
v-model:model-value="innerProject"
:popup-visible="selectVisible"
class="w-[240px]"
:default-value="innerProject"
allow-search
:placeholder="t('common.pleaseSelect')"
@popup-visible-change="changeProjectHandler"
>
<template #arrow-icon>
<icon-caret-down />
</template>
<a-tooltip v-for="item of projectList" :key="item.id" :mouse-enter-delay="500" :content="item.name">
<a-option :value="item.id" :class="item.id === innerProject ? 'arco-select-option-selected' : ''">
{{ item.name }}
</a-option>
</a-tooltip>
</a-select>
</a-input-group>
</div>
</template>
<MsTab
v-model:active-key="activeTab"
:show-badge="false"
:content-tab-list="contentTabList"
class="no-content relative border-b"
/>
<div class="flex h-[calc(100vh-104px)]">
<div class="flex h-[calc(100vh-58px)]">
<div class="w-[292px] border-r border-[var(--color-text-n8)] p-[16px]">
<CaseTree
ref="caseTreeRef"
v-model:selected-keys="selectedKeys"
:modules-count="modulesCount"
:selected-keys="selectedKeys"
:get-modules-api-type="props.getModulesApiType"
:current-project="innerProject"
:active-tab="activeTab"
:active-tab="associationType"
:extra-modules-params="props.extraModulesParams"
:show-type="showType"
:folder-name="folderName"
@folder-node-select="handleFolderNodeSelect"
@init="initModuleTree"
@change-protocol="handleProtocolChange"
/>
</div>
<div class="flex w-[calc(100%-293px)] flex-col p-[16px]">
@ -60,25 +142,31 @@
>
<template #left>
<div class="flex w-full items-center justify-between">
<a-radio-group v-if="activeTab === 'API'" v-model="showType" type="button" class="file-show-type mr-2">
<a-radio-group
v-if="associationType === 'API'"
v-model="showType"
type="button"
class="file-show-type mr-2"
>
<a-radio value="API" class="show-type-icon p-[2px]">API</a-radio>
<a-radio value="CASE" class="show-type-icon p-[2px]">CASE</a-radio>
</a-radio-group>
<a-popover v-else title="" position="bottom">
<div class="flex">
<div class="one-line-text mr-1 max-h-[32px] max-w-[300px] text-[var(--color-text-1)]">
{{ activeFolderName }}
{{ activeFolderName || folderName }}
</div>
<span class="text-[var(--color-text-4)]"> ({{ modulesCount[activeFolder] || 0 }})</span>
</div>
<template #content>
<div class="max-w-[400px] text-[14px] font-medium text-[var(--color-text-1)]">
{{ activeFolderName }}
{{ activeFolderName || folderName }}
<span class="text-[var(--color-text-4)]">({{ modulesCount[activeFolder] || 0 }})</span>
</div>
</template>
</a-popover>
<a-checkbox v-if="activeTab === 'FUNCTIONAL'" v-model="isAddAssociatedCase">
<!-- TODO 正式版暂时不上了 -->
<!-- <a-checkbox v-if="associationType === 'FUNCTIONAL'" v-model="isAddAssociatedCase">
<div class="flex items-center">
{{ t('ms.case.associate.addAssociatedCase') }}
<a-tooltip position="top" :content="t('ms.case.associate.automaticallyAddApiCase')">
@ -88,13 +176,13 @@
/>
</a-tooltip>
</div>
</a-checkbox>
</a-checkbox> -->
</div>
</template>
</MsAdvanceFilter>
<!-- 功能用例 -->
<CaseTable
v-if="activeTab === CaseLinkEnum.FUNCTIONAL"
v-if="associationType === CaseLinkEnum.FUNCTIONAL"
ref="functionalTableRef"
:association-type="associateType"
:get-page-api-type="getPageApiType"
@ -102,14 +190,14 @@
:offspring-ids="offspringIds"
:current-project="innerProject"
:associated-ids="props.associatedIds"
:active-source-type="activeTab"
:active-source-type="associationType"
:extra-table-params="props.extraTableParams"
:keyword="keyword"
@get-module-count="initModulesCount"
/>
<!-- 接口用例 API -->
<ApiTable
v-if="activeTab === CaseLinkEnum.API && showType === 'API'"
v-if="associationType === CaseLinkEnum.API && showType === 'API'"
ref="apiTableRef"
:get-page-api-type="getPageApiType"
:extra-table-params="props.extraTableParams"
@ -118,14 +206,15 @@
:offspring-ids="offspringIds"
:current-project="innerProject"
:associated-ids="props.associatedIds"
:active-source-type="activeTab"
:active-source-type="associationType"
:keyword="keyword"
:show-type="showType"
:protocols="selectedProtocols"
@get-module-count="initModulesCount"
/>
<!-- 接口用例 CASE -->
<ApiCaseTable
v-if="activeTab === CaseLinkEnum.API && showType === 'CASE'"
v-if="associationType === CaseLinkEnum.API && showType === 'CASE'"
ref="caseTableRef"
:get-page-api-type="getPageApiType"
:extra-table-params="props.extraTableParams"
@ -134,14 +223,15 @@
:offspring-ids="offspringIds"
:current-project="innerProject"
:associated-ids="props.associatedIds"
:active-source-type="activeTab"
:active-source-type="associationType"
:keyword="keyword"
:show-type="showType"
:protocols="selectedProtocols"
@get-module-count="initModulesCount"
/>
<!-- 接口场景用例 -->
<ScenarioCaseTable
v-if="activeTab === CaseLinkEnum.SCENARIO"
v-if="associationType === CaseLinkEnum.SCENARIO"
ref="scenarioTableRef"
:association-type="associateType"
:modules-count="modulesCount"
@ -149,52 +239,16 @@
:offspring-ids="offspringIds"
:current-project="innerProject"
:associated-ids="props.associatedIds"
:active-source-type="activeTab"
:active-source-type="associationType"
:get-page-api-type="getPageApiType"
:extra-table-params="props.extraTableParams"
:keyword="keyword"
@get-module-count="initModulesCount"
/>
<div class="footer">
<div class="flex flex-1 items-center">
<slot name="footerLeft">
<a-form ref="formRef" :model="form" layout="vertical" class="mb-0 max-w-[260px]">
<a-form-item
field="name"
hide-label
class="test-set-form-item"
:rules="[{ required: true, message: t('project.commonScript.publicScriptNameNotEmpty') }]"
>
<a-input-group class="w-full">
<div class="test-set h-[32px] w-[80px]">{{ t('ms.case.associate.testSet') }}</div>
<a-select
v-model="form.testMap"
class="max-w-[260px]"
:default-value="innerProject"
allow-search
:placeholder="t('common.pleaseSelect')"
>
<template #arrow-icon>
<icon-caret-down />
</template>
<a-tooltip
v-for="item of testList"
:key="item.value"
:mouse-enter-delay="500"
:content="item.name"
>
<a-option
:value="item.value"
:class="item.value === form.testMap ? 'arco-select-option-selected' : ''"
>
{{ item.label }}
</a-option>
</a-tooltip>
</a-select>
</a-input-group>
</a-form-item>
</a-form>
</slot>
<slot name="footerLeft"></slot>
</div>
<div class="flex items-center">
<slot name="footerRight">
@ -215,11 +269,9 @@
<script setup lang="ts">
import { ref } from 'vue';
import { useVModel } from '@vueuse/core';
import { FormInstance, SelectOptionData, ValidatedError } from '@arco-design/web-vue';
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsTab from '@/components/pure/ms-tab/index.vue';
import ApiCaseTable from './apiCaseTable.vue';
import ApiTable from './apiTable.vue';
import CaseTable from './caseTable.vue';
@ -228,6 +280,7 @@
import { getAssociatedProjectOptions } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n';
import useVisit from '@/hooks/useVisit';
import useAppStore from '@/store/modules/app';
import type { ModuleTreeNode, TableQueryParams } from '@/models/common';
@ -237,6 +290,10 @@
import { initGetModuleCountFunc } from './utils/moduleCount';
const visitedKey = 'changeLinkProject';
const { addVisited, getIsVisited } = useVisit(visitedKey);
const appStore = useAppStore();
const { t } = useI18n();
@ -254,7 +311,9 @@
confirmLoading?: boolean;
associatedIds?: string[]; // id
hideProjectSelect?: boolean; //
associatedType: keyof typeof CaseLinkEnum; //
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
(e: 'update:projectId', val: string): void;
@ -264,50 +323,44 @@
(e: 'save', params: any): void; // table
}>();
const projectList = ref<ProjectListItem[]>([]);
const keyword = ref<string>('');
const projectList = ref<ProjectListItem[]>([]);
const innerProject = useVModel(props, 'projectId', emit);
const showType = ref('API');
const innerVisible = useVModel(props, 'visible', emit);
const associateType = ref<string>('project');
const modulesCount = ref<Record<string, any>>({});
const associationType = ref<keyof typeof CaseLinkEnum>('FUNCTIONAL');
const activeTab = ref<keyof typeof CaseLinkEnum>(CaseLinkEnum.FUNCTIONAL);
const form = ref({
type: t('ms.case.associate.testSet'),
testMap: '',
});
const testList = ref<SelectOptionData>([]);
const contentTabList = [
{
value: CaseLinkEnum.FUNCTIONAL,
label: t('ms.case.associate.functionalCase'),
},
{
value: CaseLinkEnum.API,
label: t('ms.case.associate.apiCase'),
},
{
value: CaseLinkEnum.SCENARIO,
label: t('ms.case.associate.apiScenarioCase'),
},
];
const activeFolder = ref('all');
const activeFolderName = ref(t('ms.case.associate.allCase'));
const selectedKeys = computed({
get: () => [activeFolder.value],
set: (val) => val,
});
const folderName = computed(() => {
switch (associationType.value) {
case CaseLinkEnum.FUNCTIONAL:
return t('caseManagement.caseReview.allCases');
case CaseLinkEnum.API:
return t('apiTestManagement.allApi');
case CaseLinkEnum.SCENARIO:
return t('apiScenario.allScenario');
default:
return '';
}
});
/**
* 处理模块树节点选中事件
*/
const offspringIds = ref<string[]>([]);
const activeFolderName = ref('');
function handleFolderNodeSelect(ids: string[], _offspringIds: string[], name?: string) {
[activeFolder.value] = ids;
@ -315,21 +368,14 @@
activeFolderName.value = name ?? '';
}
const moduleTree = ref<ModuleTreeNode[]>([]);
function initModuleTree(tree: ModuleTreeNode[]) {
moduleTree.value = unref(tree);
}
const isAddAssociatedCase = ref<boolean>(false);
const formRef = ref<FormInstance | null>(null);
const functionalTableRef = ref<InstanceType<typeof CaseTable>>();
const apiTableRef = ref<InstanceType<typeof ApiTable>>();
const caseTableRef = ref<InstanceType<typeof ApiCaseTable>>();
const scenarioTableRef = ref<InstanceType<typeof ScenarioCaseTable>>();
function makeParams() {
switch (activeTab.value) {
switch (props.associatedType) {
case CaseLinkEnum.FUNCTIONAL:
return functionalTableRef.value?.getFunctionalSaveParams();
case CaseLinkEnum.API:
@ -348,12 +394,6 @@
if (!params?.selectIds.length) {
return;
}
formRef.value?.validate(async (errors: undefined | Record<string, ValidatedError>) => {
if (!errors) {
// emit('save', params);
}
});
// TODO:
emit('save', params);
}
@ -362,13 +402,12 @@
keyword.value = '';
activeFolder.value = 'all';
activeFolderName.value = t('ms.case.associate.allCase');
formRef.value?.resetFields();
emit('close');
}
async function initProjectList(setDefault: boolean) {
try {
projectList.value = await getAssociatedProjectOptions(appStore.currentOrgId, activeTab.value);
projectList.value = await getAssociatedProjectOptions(appStore.currentOrgId, associationType.value);
if (setDefault) {
innerProject.value = projectList.value[0].id;
}
@ -377,12 +416,14 @@
console.log(error);
}
}
const selectedProtocols = ref<string[]>([]);
async function initModulesCount(params: TableQueryParams) {
try {
modulesCount.value = await initGetModuleCountFunc(props.getModuleCountApiType, activeTab.value, {
modulesCount.value = await initGetModuleCountFunc(props.getModuleCountApiType, associationType.value, {
...params,
...props.extraModuleCountParams,
protocols:
associationType.value === CaseLinkEnum.API && showType.value === 'API' ? selectedProtocols.value : undefined,
});
} catch (error) {
// eslint-disable-next-line no-console
@ -390,25 +431,72 @@
}
}
watch(
() => activeTab.value,
(val) => {
if (val) {
showType.value = 'API';
activeFolder.value = 'all';
initProjectList(true);
}
const selectVisible = ref<boolean>(false);
const selectPopVisible = ref<boolean>(false);
function loadCaseList() {
switch (associationType.value) {
case CaseLinkEnum.FUNCTIONAL:
return functionalTableRef.value?.loadCaseList();
case CaseLinkEnum.API:
return showType.value === 'API' ? apiTableRef.value?.loadApiList() : caseTableRef.value?.loadCaseList();
case CaseLinkEnum.SCENARIO:
return scenarioTableRef.value?.loadScenarioList();
default:
break;
}
);
}
const moduleTree = ref<ModuleTreeNode[]>([]);
function initModuleTree(tree: ModuleTreeNode[], _protocols?: string[]) {
moduleTree.value = unref(tree);
selectedProtocols.value = _protocols || [];
loadCaseList();
}
const functionalType = ref('project');
const functionalList = ref([
{
id: 'project',
name: t('ms.case.associate.project'),
},
]);
function changeProjectHandler(visible: boolean) {
if (visible && !getIsVisited()) {
selectPopVisible.value = true;
} else {
selectPopVisible.value = false;
selectVisible.value = visible;
}
}
function handlePopChange(visible: boolean) {
if (visible) {
selectPopVisible.value = false;
}
}
function okHandler() {
addVisited();
selectPopVisible.value = false;
selectVisible.value = true;
}
function handleProtocolChange(val: string[]) {
selectedProtocols.value = val;
}
watch(
() => props.visible,
(val) => {
if (val) {
associationType.value = props.associatedType;
initProjectList(false);
innerProject.value = appStore.currentProjectId;
}
activeTab.value = CaseLinkEnum.FUNCTIONAL;
selectPopVisible.value = false;
keyword.value = '';
}
);
@ -420,19 +508,6 @@
}
}
);
function loadCaseList() {
switch (activeTab.value) {
case CaseLinkEnum.FUNCTIONAL:
return functionalTableRef.value?.loadCaseList();
case CaseLinkEnum.API:
return showType.value === 'API' ? apiTableRef.value?.loadApiList() : caseTableRef.value?.loadCaseList();
case CaseLinkEnum.SCENARIO:
return scenarioTableRef.value?.loadScenarioList();
default:
break;
}
}
</script>
<style scoped lang="less">
@ -495,3 +570,14 @@
}
}
</style>
<style lang="less">
.change-project-pop {
.arco-trigger-popup-wrapper {
.arco-popconfirm-popup-content {
width: 215px !important;
border: none;
}
}
}
</style>

View File

@ -17,4 +17,8 @@ export default {
'ms.case.associate.testSet': 'Set of tests',
'ms.case.associate.addAssociatedCase': 'Add associated use case',
'ms.case.associate.automaticallyAddApiCase': 'Automatically adds associated interface use cases',
'ms.case.associate.project': 'project',
'ms.case.associate.gotIt': 'I got it',
'ms.case.associate.switchProject': 'Switch project?',
'ms.case.associate.switchProjectPopTip': 'After switching, the selected data will be cleared',
};

View File

@ -17,4 +17,8 @@ export default {
'ms.case.associate.testSet': '测试集',
'ms.case.associate.addAssociatedCase': '添加已关联用例',
'ms.case.associate.automaticallyAddApiCase': '自动添加已关联的接口用例',
'ms.case.associate.project': '项目',
'ms.case.associate.gotIt': '知道了',
'ms.case.associate.switchProject': '切换项目?',
'ms.case.associate.switchProjectPopTip': '切换后,已选数据将清空',
};

View File

@ -29,6 +29,11 @@
:script-identifier="record.scriptIdentifier"
/>
</template>
<template #createUserName="{ record }">
<a-tooltip :content="`${record.createUserName}`" position="tl">
<div class="one-line-text">{{ record.createUserName }}</div>
</a-tooltip>
</template>
</MsBaseTable>
</template>
@ -46,6 +51,7 @@
import useAppStore from '@/store/modules/app';
import type { TableQueryParams } from '@/models/common';
import { CasePageApiTypeEnum } from '@/enums/associateCaseEnum';
import { CaseLinkEnum } from '@/enums/caseEnum';
import { ReportEnum, ReportStatus } from '@/enums/reportEnum';
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
@ -57,13 +63,14 @@
const props = defineProps<{
associationType: string; // | |
modulesCount: Record<string, number>; //
activeModule: string;
offspringIds: string[];
currentProject: string;
associatedIds?: string[]; // ids
activeSourceType: keyof typeof CaseLinkEnum;
keyword: string;
getPageApiType: keyof typeof CasePageApiTypeEnum; // Api
extraTableParams?: TableQueryParams; //
}>();
const emit = defineEmits<{
@ -169,9 +176,11 @@
showDrag: true,
},
];
const getPageList = computed(() => {
return getPublicLinkCaseListMap[props.getPageApiType][props.activeSourceType];
});
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, setPagination, resetFilterParams } =
useTable(undefined, {
useTable(getPageList.value, {
columns,
showSetting: false,
selectable: true,
@ -188,7 +197,9 @@
excludeIds: [...(props.associatedIds || [])], // id
condition: {
keyword: props.keyword,
filter: propsRes.value.filter,
},
...props.extraTableParams,
};
}
@ -240,6 +251,15 @@
}
);
watch(
() => props.activeModule,
(val) => {
if (val) {
loadScenarioList();
}
}
);
function getScenarioSaveParams() {
const { excludeKeys, selectedKeys, selectorStatus } = propsRes.value;
const tableParams = getTableParams();

View File

@ -1,12 +1,16 @@
import { getModuleCount } from '@/api/modules/api-test/management';
import { getModuleCount as getScenarioModuleCount } from '@/api/modules/api-test/scenario';
import { getCaseModulesCounts } from '@/api/modules/case-management/featureCase';
import { getCaseModulesCounts, getPublicLinkCaseModulesCounts } from '@/api/modules/case-management/featureCase';
import { CaseCountApiTypeEnum } from '@/enums/associateCaseEnum';
import { CaseLinkEnum } from '@/enums/caseEnum';
// 获取模块数量Map
export const getModuleTreeCountApiMap: Record<string, any> = {
[CaseCountApiTypeEnum.FUNCTIONAL_CASE_COUNT]: {
[CaseLinkEnum.FUNCTIONAL]: getPublicLinkCaseModulesCounts,
[CaseLinkEnum.API]: getPublicLinkCaseModulesCounts,
},
[CaseCountApiTypeEnum.TEST_PLAN_CASE_COUNT]: {
[CaseLinkEnum.FUNCTIONAL]: getCaseModulesCounts,
[CaseLinkEnum.API]: getModuleCount,
@ -20,12 +24,7 @@ export function initGetModuleCountFunc(
activeTab: keyof typeof CaseLinkEnum,
params: Record<string, any>
) {
switch (type) {
case CaseCountApiTypeEnum.TEST_PLAN_CASE_COUNT:
return getModuleTreeCountApiMap[type][activeTab](params);
default:
break;
}
return getModuleTreeCountApiMap[type as keyof typeof CaseCountApiTypeEnum][activeTab](params);
}
export default {};

View File

@ -1,12 +1,16 @@
import { getModuleTreeOnlyModules } from '@/api/modules/api-test/management';
import { getModuleTree as getScenarioModuleTree } from '@/api/modules/api-test/scenario';
import { getCaseModuleTree } from '@/api/modules/case-management/featureCase';
import { getCaseModuleTree, getPublicLinkModuleTree } from '@/api/modules/case-management/featureCase';
import { CaseModulesApiTypeEnum } from '@/enums/associateCaseEnum';
import { CaseLinkEnum } from '@/enums/caseEnum';
// 模块树接口
export const getModuleTreeApiMap: Record<string, any> = {
[CaseModulesApiTypeEnum.FUNCTIONAL_CASE_MODULE]: {
[CaseLinkEnum.FUNCTIONAL]: getPublicLinkModuleTree,
[CaseLinkEnum.API]: getPublicLinkModuleTree,
},
[CaseModulesApiTypeEnum.TEST_PLAN_LINK_CASE_MODULE]: {
[CaseLinkEnum.FUNCTIONAL]: getCaseModuleTree,
[CaseLinkEnum.API]: getModuleTreeOnlyModules,
@ -20,12 +24,7 @@ export function getModuleTreeFunc(
activeTab: keyof typeof CaseLinkEnum,
params: Record<string, any>
) {
switch (getModulesApiType) {
case CaseModulesApiTypeEnum.TEST_PLAN_LINK_CASE_MODULE:
return getModuleTreeApiMap[getModulesApiType][activeTab](params);
default:
break;
}
return getModuleTreeApiMap[getModulesApiType as keyof typeof CaseModulesApiTypeEnum][activeTab](params);
}
export default {};

View File

@ -1,6 +1,7 @@
import { getUnAssociatedList } from '@/api/modules/bug-management';
import { getCaseList, getPublicLinkCaseList } from '@/api/modules/case-management/featureCase';
import {
getPlanScenarioAssociatedList,
getTestPlanAssociationApiList,
getTestPlanAssociationCaseList,
getTestPlanCaseList,
@ -13,8 +14,11 @@ import { CaseLinkEnum } from '@/enums/caseEnum';
export const getPublicLinkCaseListMap: Record<string, any> = {
// 功能用例 目前只有接口用例、场景用例
[CasePageApiTypeEnum.FUNCTIONAL_CASE_PAGE]: {
[CaseLinkEnum.API]: getPublicLinkCaseList,
[CaseLinkEnum.SCENARIO]: getPublicLinkCaseList,
[CaseLinkEnum.FUNCTIONAL]: getPublicLinkCaseList,
[CaseLinkEnum.API]: {
API: getPublicLinkCaseList,
CASE: getPublicLinkCaseList,
},
},
// 用例评审 目前只有功能用例
[CasePageApiTypeEnum.CASE_REVIEW_CASE_PAGE]: {
@ -31,6 +35,7 @@ export const getPublicLinkCaseListMap: Record<string, any> = {
API: getTestPlanAssociationApiList,
CASE: getTestPlanAssociationCaseList,
},
[CaseLinkEnum.SCENARIO]: getPlanScenarioAssociatedList,
},
};

View File

@ -11,10 +11,9 @@
<div class="float-left">
<a-select
v-if="props?.moduleOptions"
v-model:model-value="caseType"
v-model="caseType"
class="ml-2 max-w-[100px]"
:placeholder="t('caseManagement.featureCase.PleaseSelect')"
@change="changeCaseTypeHandler"
>
<a-option v-for="item of props?.moduleOptions" :key="item.value" :value="item.value">
{{ t(item.label) }}
@ -788,18 +787,20 @@
}
);
function changeCaseTypeHandler(
value: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
) {
caseType.value = value as keyof typeof CaseLinkEnum;
if (!props.hideProjectSelect) {
initProjectList(true);
watch(
() => caseType.value,
(val) => {
if (val) {
if (!props.hideProjectSelect) {
initProjectList(true);
}
resetFilterParams();
initModules(true);
searchCase();
initFilter();
}
}
resetFilterParams();
initModules(true);
searchCase();
initFilter();
}
);
watch(
() => innerProject.value,

View File

@ -47,6 +47,7 @@ export default defineComponent({
const expendedIds = ref<string[]>([]); // 展开的评论id
// 被@的用户id
const noticeUserIds = ref<string[]>([]);
const uploadFileIds = ref<string[]>([]);
const { t } = useI18n();
const resetCurrentItem = () => {
@ -79,6 +80,7 @@ export default defineComponent({
notifier: noticeUserIds.value.join(';'),
replyUser: currentItem.replyId || item.createUser,
parentId,
uploadFileIds: uploadFileIds.value,
};
if (currentItem.commentType === 'EDIT') {
params.id = item.id;
@ -136,6 +138,10 @@ export default defineComponent({
onUpdate:noticeUserIds={(ids: string[]) => {
noticeUserIds.value = ids;
}}
filedIds={uploadFileIds.value}
onUpdate:filedIds={(ids: string[]) => {
uploadFileIds.value = ids;
}}
uploadImage={uploadImage.value}
previewUrl={previewUrl.value}
onCancel={() => resetCurrentItem()}

View File

@ -19,6 +19,7 @@
v-if="props.mode === 'rich'"
v-model:raw="currentContent"
v-model:commentIds="commentIds"
v-model:filed-ids="uploadFileIds"
:upload-image="props.uploadImage"
:preview-url="props.previewUrl"
class="w-full"
@ -66,6 +67,7 @@
const currentContent = defineModel<string>('defaultValue', { default: '' });
const commentIds = defineModel<string[]>('noticeUserIds', { default: [] });
const uploadFileIds = defineModel<string[]>('filedIds', { default: [] });
const userStore = useUserStore();
const emit = defineEmits<{
(event: 'publish', value: string): void;

View File

@ -38,4 +38,5 @@ export interface CommentParams extends WriteCommentProps {
notifier?: string; // 通知人
fetchType?: FetchType; // 发送后端请求类型 编辑还是新增
commentType?: CommentType; // 评论类型
uploadFileIds: string[]; // 评论上传文件
}

View File

@ -11,7 +11,13 @@
v-if="typeof isExpandAll === 'boolean'"
:content="isExpandAll ? t('common.collapseAll') : t('common.expandAll')"
>
<MsButton type="icon" status="secondary" class="!mr-0 p-[4px]" @click="changeExpand">
<MsButton
v-if="typeof isExpandAll === 'boolean'"
type="icon"
status="secondary"
class="!mr-0 p-[4px]"
@click="changeExpand"
>
<MsIcon :type="isExpandAll ? 'icon-icon_folder_collapse1' : 'icon-icon_folder_expansion1'" />
</MsButton>
</a-tooltip>

View File

@ -36,6 +36,7 @@
ref="commentInputRef"
v-model:content="content"
v-model:notice-user-ids="noticeUserIds"
v-model:filed-ids="uploadFileIds"
v-permission="['FUNCTIONAL_CASE:READ+COMMENT']"
:preview-url="PreviewEditorImageUrl"
:is-active="isActive"
@ -203,7 +204,7 @@
const commentInputRef = ref<InstanceType<typeof inputComment>>();
const content = ref('');
const isActive = ref<boolean>(false);
const uploadFileIds = ref<string[]>([]);
const noticeUserIds = ref<string[]>([]);
/**
@ -219,6 +220,7 @@
parentId: '',
content: currentContent,
event: noticeUserIds.value.join(';') ? 'AT' : 'COMMENT', // (: COMMENT; @: AT; /@: REPLAY;)
uploadFileIds: uploadFileIds.value,
};
await createCommentList(params);
getAllCommentList();

View File

@ -9,7 +9,11 @@
:extra-table-params="{
testPlanId: props?.testPlanId,
}"
:extra-modules-params="{
testPlanId: props?.testPlanId,
}"
:associated-ids="props.hasNotAssociatedIds || []"
:associated-type="associationType"
@save="saveHandler"
>
</MsCaseAssociate>
@ -26,6 +30,7 @@
import type { AssociateCaseRequest, AssociateCaseRequestType } from '@/models/testPlan/testPlan';
import { CaseCountApiTypeEnum, CaseModulesApiTypeEnum, CasePageApiTypeEnum } from '@/enums/associateCaseEnum';
import { CaseLinkEnum } from '@/enums/caseEnum';
const { t } = useI18n();
const props = defineProps<{
@ -68,4 +73,7 @@
}
innerVisible.value = false;
}
// TODO
const associationType = ref<keyof typeof CaseLinkEnum>('FUNCTIONAL');
</script>

View File

@ -218,6 +218,7 @@
v-if="activeTab === 'comment' && hasAnyPermission(['PROJECT_BUG:READ+COMMENT'])"
ref="commentInputRef"
v-model:notice-user-ids="noticeUserIds"
v-model:filed-ids="uploadFileIds"
:content="commentContent"
is-show-avatar
:upload-image="handleUploadImage"
@ -624,7 +625,7 @@
'validate-trigger': ['change'],
},
};
const uploadFileIds = ref<string[]>([]);
async function publishHandler(currentContent: string) {
try {
const params = {
@ -634,6 +635,7 @@
parentId: '',
content: currentContent,
event: noticeUserIds.value.join(';') ? 'AT' : 'COMMENT', // (: COMMENT; @: AT; /@: REPLY;)
uploadFileIds: uploadFileIds.value,
};
await createOrUpdateComment(params as CommentParams);
Message.success(t('common.publishSuccessfully'));

View File

@ -230,6 +230,7 @@
ref="commentInputRef"
v-model:content="content"
v-model:notice-user-ids="noticeUserIds"
v-model:filed-ids="uploadFileIds"
v-permission="['FUNCTIONAL_CASE:READ+COMMENT']"
:preview-url="PreviewEditorImageUrl"
:is-active="isActive"
@ -593,6 +594,7 @@
const isActive = ref<boolean>(false);
const noticeUserIds = ref<string[]>([]);
const uploadFileIds = ref<string[]>([]);
async function publishHandler(currentContent: string) {
try {
const params: CommentParams = {
@ -602,6 +604,7 @@
parentId: '',
content: currentContent,
event: noticeUserIds.value.join(';') ? 'AT' : 'COMMENT', // (: COMMENT; @: AT; /@: REPLAY;)
uploadFileIds: uploadFileIds.value,
};
await createCommentList(params);
if (activeTab.value === 'comments') {

View File

@ -50,6 +50,7 @@
{{ caseTypeOptions.find((e) => e.value === record.sourceType)?.label }}
</template>
</ms-base-table>
<!-- TODO: 涉及接口调整放到下一个版本再替换暂时还是使用原来的 -->
<MsCaseAssociate
v-model:visible="innerVisible"
v-model:currentSelectCase="currentSelectCase"

View File

@ -57,7 +57,6 @@
const featureCaseStore = useFeatureCaseStore();
const router = useRouter();
const route = useRoute();
// const activeTab = computed(() => featureCaseStore.activeTab);
const { openModal } = useModal();
const { t } = useI18n();

View File

@ -204,6 +204,7 @@
font-size: 14px;
border-radius: 4px;
cursor: pointer;
line-height: 38px;
&.active {
color: rgb(var(--primary-5));
background: rgb(var(--primary-1));

View File

@ -27,8 +27,8 @@
@cancel="handleCancel"
@handle-summary="handleSummary"
/>
<MsCard>
<div class="flex items-center justify-between">
<MsCard simple auto-height auto-width>
<div class="mb-[16px] flex items-center justify-between">
<div class="block-title">{{ t('report.detail.api.reportDetail') }}</div>
<a-radio-group class="mb-2" :model-value="currentMode" type="button" @change="handleModeChange">
<a-radio value="drawer">
@ -45,33 +45,31 @@
</a-radio>
</a-radio-group>
</div>
<ReportDetailTable :current-mode="currentMode" :report-id="detail.id" :share-id="shareId" />
</MsCard>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute } from 'vue-router';
import { useEventListener } from '@vueuse/core';
import { Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import MsCard from '@/components/pure/ms-card/index.vue';
import SetReportChart from '@/views/api-test/report/component/case/setReportChart.vue';
import ReportDetailTable from '@/views/test-plan/report/detail/component/reportDetailTable.vue';
import ReportHeader from '@/views/test-plan/report/detail/component/reportHeader.vue';
import ReportMetricsItem from '@/views/test-plan/report/detail/component/ReportMetricsItem.vue';
import Summary from '@/views/test-plan/report/detail/component/summary.vue';
import { updateReportDetail } from '@/api/modules/test-plan/report';
import { defaultReportDetail, statusConfig } from '@/config/testPlan';
import { defaultReportDetail } from '@/config/testPlan';
import { useI18n } from '@/hooks/useI18n';
import { addCommasToNumber } from '@/utils';
import type { LegendData } from '@/models/apiTest/report';
import type {
countDetail,
PlanReportDetail,
ReportMetricsItemModel,
StatusListType,
} from '@/models/testPlan/testPlanReport';
import type { PlanReportDetail, ReportMetricsItemModel } from '@/models/testPlan/testPlanReport';
import { getIndicators } from '@/views/api-test/report/utils';
@ -216,10 +214,19 @@
richText.value.summary = summaryContent.value;
}
const currentMode = ref('');
const currentMode = ref<string>('drawer');
const handleModeChange = (value: string | number | boolean) => {
currentMode.value = value as string;
};
onMounted(async () => {
nextTick(() => {
const editorContent = document.querySelector('.editor-content');
useEventListener(editorContent, 'click', () => {
showButton.value = true;
});
});
});
</script>
<style scoped lang="less">

View File

@ -0,0 +1,119 @@
<template>
<MsBaseTable v-bind="propsRes" no-disable filter-icon-align-left v-on="propsEvent">
<template #passRateTitle="{ columnConfig }">
<div class="flex items-center text-[var(--color-text-3)]">
{{ t(columnConfig.title as string) }}
<a-tooltip position="right" :content="t('testPlan.testPlanIndex.passRateTitleTip')">
<icon-question-circle
class="ml-[4px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
size="16"
/>
</a-tooltip>
</div>
</template>
<template #operation="{ record }">
<MsButton class="!mx-0" @click="openReport(record)">{{ t('report.detail.testPlanGroup.viewReport') }}</MsButton>
</template>
</MsBaseTable>
<ReportDrawer v-model:visible="reportVisible" :report-id="reportId" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import ReportDrawer from '@/views/test-plan/testPlan/detail/reportDrawer.vue';
import { getReportBugList, getReportShareBugList } from '@/api/modules/test-plan/report';
import { useI18n } from '@/hooks/useI18n';
import useOpenNewPage from '@/hooks/useOpenNewPage';
import { RouteEnum } from '@/enums/routeEnum';
const { openNewPage } = useOpenNewPage();
const { t } = useI18n();
const props = defineProps<{
reportId: string;
shareId?: string;
currentMode: string;
}>();
const columns: MsTableColumn = [
{
title: 'testPlan.testPlanIndex.operation',
slotName: 'operation',
dataIndex: 'operation',
fixed: 'left',
showInTable: true,
showDrag: false,
width: 80,
},
{
title: 'report.plan.name',
dataIndex: 'name',
showTooltip: true,
width: 180,
},
{
title: 'report.detail.testPlanGroup.result',
dataIndex: 'result',
showTooltip: true,
width: 200,
},
{
title: 'report.detail.threshold',
dataIndex: 'threshold',
slotName: 'threshold',
width: 150,
},
{
title: 'report.passRate',
dataIndex: 'executeUser',
titleSlotName: 'passRateTitle',
showTooltip: true,
width: 150,
},
{
title: 'report.detail.testPlanGroup.useCasesCount',
dataIndex: 'bugCount',
width: 100,
},
];
const reportBugList = () => {
return !props.shareId ? getReportBugList : getReportShareBugList;
};
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(reportBugList(), {
columns,
heightUsed: 20,
showSelectorAll: false,
});
function loadReportDetailList() {
setLoadListParams({ reportId: props.reportId, shareId: props.shareId ?? undefined });
loadList();
}
watchEffect(() => {
if (props.reportId) {
loadReportDetailList();
}
});
const reportVisible = ref(false);
function openReport(record: any) {
if (props.currentMode === 'drawer') {
reportVisible.value = true;
} else {
openNewPage(RouteEnum.TEST_PLAN_REPORT_DETAIL, {
reportId: record.id,
});
}
}
</script>
<style scoped></style>

View File

@ -0,0 +1,47 @@
<template>
<PlanDetail :detail-info="detail" />
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { cloneDeep } from 'lodash-es';
import PlanDetail from '@/views/test-plan/report/detail/component/planDetail.vue';
import { getReportDetail, planGetShareHref } from '@/api/modules/test-plan/report';
import { defaultReportDetail } from '@/config/testPlan';
import { NOT_FOUND_RESOURCE } from '@/router/constants';
import type { PlanReportDetail } from '@/models/testPlan/testPlanReport';
const route = useRoute();
const router = useRouter();
const reportId = ref<string>(route.query.id as string);
const detail = ref<PlanReportDetail>(cloneDeep(defaultReportDetail));
async function getShareDetail() {
try {
const hrefShareDetail = await planGetShareHref(route.query.shareId as string);
reportId.value = hrefShareDetail.reportId;
if (hrefShareDetail.deleted) {
router.push({
name: NOT_FOUND_RESOURCE,
});
return;
}
detail.value = await getReportDetail(reportId.value, route.query.shareId as string);
} catch (error) {
console.log(error);
}
}
watchEffect(() => {
if (route.query.shareId) {
getShareDetail();
}
});
</script>
<style scoped></style>

View File

@ -1,5 +1,7 @@
<template>
<PlanDetail :detail-info="detail" @update-success="getDetail()" />
<!-- TODO 待联调计划组报告 -->
<!-- <PlanGroupDetail :detail-info="detail" @update-success="getDetail()" /> -->
</template>
<script setup lang="ts">
@ -8,6 +10,7 @@
import { cloneDeep } from 'lodash-es';
import PlanDetail from '@/views/test-plan/report/detail/component/planDetail.vue';
import PlanGroupDetail from '@/views/test-plan/report/detail/component/planGroupDetail.vue';
import { getReportDetail } from '@/api/modules/test-plan/report';
import { defaultReportDetail } from '@/config/testPlan';

View File

@ -42,4 +42,7 @@ export default {
'report.detail.oneClickSummary': 'One click report summary',
'report.detail.testPlanTotal': 'Total plan',
'report.detail.testPlanCaseTotal': 'Total use cases',
'report.detail.testPlanGroup.result': 'Result',
'report.detail.testPlanGroup.useCasesCount': 'Use cases',
'report.detail.testPlanGroup.viewReport': 'View Report',
};

View File

@ -44,4 +44,7 @@ export default {
'report.detail.testPlanGroupReport': '测试组报告',
'report.detail.testPlanTotal': '计划总数',
'report.detail.testPlanCaseTotal': '用例总数',
'report.detail.testPlanGroup.result': '结果',
'report.detail.testPlanGroup.useCasesCount': '用例数',
'report.detail.testPlanGroup.viewReport': '查看报告',
};

View File

@ -108,13 +108,11 @@
/>
<span :class="getIconClass(record)">{{ record.childrenCount || 0 }}</span>
</div>
<div
:class="`${
record.type === testPlanTypeEnum.TEST_PLAN ? 'text-[rgb(var(--primary-5))]' : ''
} one-line-text ${hasIndent(record)}`"
@click="openDetail(record.id)"
>{{ record.num }}</div
<div v-if="record.type === testPlanTypeEnum.TEST_PLAN" :class="`one-line-text ${hasIndent(record)}`"
><MsButton type="text" @click="openDetail(record.id)">{{ record.num }}</MsButton></div
>
<div v-else :class="`one-line-text ${hasIndent(record)}`">{{ record.num }}</div>
<a-tooltip position="right" :disabled="!getSchedule(record.id)" :mouse-enter-delay="300">
<MsTag
v-if="getSchedule(record.id)"