feat(测试计划): 关联用例抽屉页面调整&联调&独立报告&聚合报告页面&缺陷管理&用例管理评论参数调整
This commit is contained in:
parent
a3dc954774
commit
92f09ba18c
|
@ -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}` });
|
||||
|
|
|
@ -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';
|
||||
// 测试计划-计划组下拉
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化模块文件数量
|
||||
*/
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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': '切换后,已选数据将清空',
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 {};
|
||||
|
|
|
@ -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 {};
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
};
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -38,4 +38,5 @@ export interface CommentParams extends WriteCommentProps {
|
|||
notifier?: string; // 通知人
|
||||
fetchType?: FetchType; // 发送后端请求类型 编辑还是新增
|
||||
commentType?: CommentType; // 评论类型
|
||||
uploadFileIds: string[]; // 评论上传文件
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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'));
|
||||
|
|
|
@ -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') {
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -57,7 +57,6 @@
|
|||
const featureCaseStore = useFeatureCaseStore();
|
||||
const router = useRouter();
|
||||
const route = useRoute();
|
||||
// const activeTab = computed(() => featureCaseStore.activeTab);
|
||||
const { openModal } = useModal();
|
||||
const { t } = useI18n();
|
||||
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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';
|
||||
|
|
|
@ -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',
|
||||
};
|
||||
|
|
|
@ -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': '查看报告',
|
||||
};
|
||||
|
|
|
@ -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)"
|
||||
|
|
Loading…
Reference in New Issue