feat(测试计划): 测试计划关联用例选择交互&关联用例联调_中
This commit is contained in:
parent
ecb6200759
commit
59da4f4ebc
|
@ -1,8 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<a-config-provider :locale="locale">
|
<a-config-provider :locale="locale">
|
||||||
<a-spin :loading="loading">
|
<router-view />
|
||||||
<router-view />
|
|
||||||
</a-spin>
|
|
||||||
<!-- <global-setting /> -->
|
<!-- <global-setting /> -->
|
||||||
</a-config-provider>
|
</a-config-provider>
|
||||||
</template>
|
</template>
|
||||||
|
@ -46,8 +44,6 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const loading = ref(false);
|
|
||||||
|
|
||||||
// 初始化平台风格和主题色
|
// 初始化平台风格和主题色
|
||||||
watchStyle(appStore.pageConfig.style, appStore.pageConfig);
|
watchStyle(appStore.pageConfig.style, appStore.pageConfig);
|
||||||
watchTheme(appStore.pageConfig.theme, appStore.pageConfig);
|
watchTheme(appStore.pageConfig.theme, appStore.pageConfig);
|
||||||
|
@ -94,24 +90,26 @@
|
||||||
state.value = getQueryVariable('state') || '';
|
state.value = getQueryVariable('state') || '';
|
||||||
if (state.value.split('#')[0] === 'fit2cloud-lark-qr') {
|
if (state.value.split('#')[0] === 'fit2cloud-lark-qr') {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
appStore.showLoading();
|
||||||
const larkCallback = await getLarkCallback(code || '');
|
const larkCallback = await getLarkCallback(code || '');
|
||||||
userStore.qrCodeLogin(larkCallback);
|
userStore.qrCodeLogin(larkCallback);
|
||||||
setLoginExpires();
|
setLoginExpires();
|
||||||
loading.value = false;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
} finally {
|
||||||
|
appStore.hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (state.value.split('#')[0] === 'fit2cloud-lark-suite-qr') {
|
if (state.value.split('#')[0] === 'fit2cloud-lark-suite-qr') {
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
appStore.showLoading();
|
||||||
const larkCallback = await getLarkSuiteCallback(code || '');
|
const larkCallback = await getLarkSuiteCallback(code || '');
|
||||||
userStore.qrCodeLogin(larkCallback);
|
userStore.qrCodeLogin(larkCallback);
|
||||||
setLoginExpires();
|
setLoginExpires();
|
||||||
loading.value = false;
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(err);
|
console.log(err);
|
||||||
|
} finally {
|
||||||
|
appStore.hideLoading();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
await userStore.checkIsLogin();
|
await userStore.checkIsLogin();
|
||||||
|
|
|
@ -8,6 +8,9 @@
|
||||||
moreAction: [],
|
moreAction: [],
|
||||||
}"
|
}"
|
||||||
v-on="propsEvent"
|
v-on="propsEvent"
|
||||||
|
@row-select-change="rowSelectChange"
|
||||||
|
@select-all-change="selectAllChange"
|
||||||
|
@clear-selector="clearSelector"
|
||||||
>
|
>
|
||||||
<template #num="{ record }">
|
<template #num="{ record }">
|
||||||
<MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton>
|
<MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton>
|
||||||
|
@ -41,6 +44,9 @@
|
||||||
<template #[FilterSlotNameEnum.API_TEST_CASE_API_LAST_EXECUTE_STATUS]="{ filterContent }">
|
<template #[FilterSlotNameEnum.API_TEST_CASE_API_LAST_EXECUTE_STATUS]="{ filterContent }">
|
||||||
<ExecutionStatus :module-type="ReportEnum.API_REPORT" :status="filterContent.value" />
|
<ExecutionStatus :module-type="ReportEnum.API_REPORT" :status="filterContent.value" />
|
||||||
</template>
|
</template>
|
||||||
|
<template #count>
|
||||||
|
<slot></slot>
|
||||||
|
</template>
|
||||||
</MsBaseTable>
|
</MsBaseTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -54,6 +60,7 @@
|
||||||
import useTable from '@/components/pure/ms-table/useTable';
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||||
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
||||||
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
|
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
|
||||||
|
|
||||||
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
||||||
|
@ -69,6 +76,8 @@
|
||||||
import { SpecialColumnEnum, TableKeyEnum } from '@/enums/tableEnum';
|
import { SpecialColumnEnum, TableKeyEnum } from '@/enums/tableEnum';
|
||||||
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||||
|
|
||||||
|
import type { moduleKeysType } from './types';
|
||||||
|
import useModuleSelection from './useModuleSelection';
|
||||||
import { getPublicLinkCaseListMap } from './utils/page';
|
import { getPublicLinkCaseListMap } from './utils/page';
|
||||||
import { casePriorityOptions } from '@/views/api-test/components/config';
|
import { casePriorityOptions } from '@/views/api-test/components/config';
|
||||||
|
|
||||||
|
@ -87,6 +96,7 @@
|
||||||
getPageApiType: keyof typeof CasePageApiTypeEnum; // 获取未关联分页Api
|
getPageApiType: keyof typeof CasePageApiTypeEnum; // 获取未关联分页Api
|
||||||
extraTableParams?: TableQueryParams; // 查询表格的额外参数
|
extraTableParams?: TableQueryParams; // 查询表格的额外参数
|
||||||
protocols: string[];
|
protocols: string[];
|
||||||
|
moduleTree: MsTreeNodeData[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -96,6 +106,10 @@
|
||||||
(e: 'update:selectedIds'): void;
|
(e: 'update:selectedIds'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const innerSelectedModulesMaps = defineModel<Record<string, moduleKeysType>>('selectedModulesMaps', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
|
||||||
const tableStore = useTableStore();
|
const tableStore = useTableStore();
|
||||||
|
|
||||||
const lastReportStatusListOptions = computed(() => {
|
const lastReportStatusListOptions = computed(() => {
|
||||||
|
@ -332,6 +346,22 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { rowSelectChange, selectAllChange, clearSelector, setModuleTree } = useModuleSelection(
|
||||||
|
innerSelectedModulesMaps.value,
|
||||||
|
propsRes.value,
|
||||||
|
props.moduleTree
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.moduleTree,
|
||||||
|
(val) => {
|
||||||
|
setModuleTree(val);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
getApiCaseSaveParams,
|
getApiCaseSaveParams,
|
||||||
loadCaseList,
|
loadCaseList,
|
||||||
|
|
|
@ -10,6 +10,9 @@
|
||||||
}"
|
}"
|
||||||
v-on="propsEvent"
|
v-on="propsEvent"
|
||||||
@filter-change="getModuleCount"
|
@filter-change="getModuleCount"
|
||||||
|
@row-select-change="rowSelectChange"
|
||||||
|
@select-all-change="selectAllChange"
|
||||||
|
@clear-selector="clearSelector"
|
||||||
>
|
>
|
||||||
<template #num="{ record }">
|
<template #num="{ record }">
|
||||||
<MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton>
|
<MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton>
|
||||||
|
@ -28,6 +31,9 @@
|
||||||
<div class="one-line-text">{{ record.createUserName }}</div>
|
<div class="one-line-text">{{ record.createUserName }}</div>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
<template #count>
|
||||||
|
<slot></slot>
|
||||||
|
</template>
|
||||||
</MsBaseTable>
|
</MsBaseTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -36,6 +42,7 @@
|
||||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||||
import useTable from '@/components/pure/ms-table/useTable';
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
@ -52,6 +59,8 @@
|
||||||
import { SpecialColumnEnum, TableKeyEnum } from '@/enums/tableEnum';
|
import { SpecialColumnEnum, TableKeyEnum } from '@/enums/tableEnum';
|
||||||
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||||
|
|
||||||
|
import type { moduleKeysType } from './types';
|
||||||
|
import useModuleSelection from './useModuleSelection';
|
||||||
import { getPublicLinkCaseListMap } from './utils/page';
|
import { getPublicLinkCaseListMap } from './utils/page';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
@ -71,6 +80,7 @@
|
||||||
getPageApiType: keyof typeof CasePageApiTypeEnum; // 获取未关联分页Api
|
getPageApiType: keyof typeof CasePageApiTypeEnum; // 获取未关联分页Api
|
||||||
extraTableParams?: TableQueryParams; // 查询表格的额外参数
|
extraTableParams?: TableQueryParams; // 查询表格的额外参数
|
||||||
protocols: string[];
|
protocols: string[];
|
||||||
|
moduleTree: MsTreeNodeData[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -81,6 +91,9 @@
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tableStore = useTableStore();
|
const tableStore = useTableStore();
|
||||||
|
const innerSelectedModulesMaps = defineModel<Record<string, moduleKeysType>>('selectedModulesMaps', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
|
||||||
const requestMethodsOptions = computed(() => {
|
const requestMethodsOptions = computed(() => {
|
||||||
return Object.values(RequestMethods).map((e) => {
|
return Object.values(RequestMethods).map((e) => {
|
||||||
|
@ -180,10 +193,10 @@
|
||||||
propsEvent,
|
propsEvent,
|
||||||
loadList,
|
loadList,
|
||||||
setLoadListParams,
|
setLoadListParams,
|
||||||
resetSelector,
|
|
||||||
setPagination,
|
setPagination,
|
||||||
resetFilterParams,
|
resetFilterParams,
|
||||||
setTableSelected,
|
setTableSelected,
|
||||||
|
resetSelector,
|
||||||
} = useTable(getPublicLinkCaseListMap[props.getPageApiType][props.activeSourceType].API, {
|
} = useTable(getPublicLinkCaseListMap[props.getPageApiType][props.activeSourceType].API, {
|
||||||
tableKey: TableKeyEnum.ASSOCIATE_CASE_API,
|
tableKey: TableKeyEnum.ASSOCIATE_CASE_API,
|
||||||
showSetting: true,
|
showSetting: true,
|
||||||
|
@ -239,7 +252,6 @@
|
||||||
setPagination({
|
setPagination({
|
||||||
current: 1,
|
current: 1,
|
||||||
});
|
});
|
||||||
resetSelector();
|
|
||||||
resetFilterParams();
|
resetFilterParams();
|
||||||
loadApiList();
|
loadApiList();
|
||||||
}
|
}
|
||||||
|
@ -299,6 +311,22 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { rowSelectChange, selectAllChange, clearSelector, setModuleTree } = useModuleSelection(
|
||||||
|
innerSelectedModulesMaps.value,
|
||||||
|
propsRes.value,
|
||||||
|
props.moduleTree
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.moduleTree,
|
||||||
|
(val) => {
|
||||||
|
setModuleTree(val);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
getApiSaveParams,
|
getApiSaveParams,
|
||||||
loadApiList,
|
loadApiList,
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
}"
|
}"
|
||||||
v-on="propsEvent"
|
v-on="propsEvent"
|
||||||
@filter-change="getModuleCount"
|
@filter-change="getModuleCount"
|
||||||
|
@row-select-change="rowSelectChange"
|
||||||
|
@select-all-change="selectAllChange"
|
||||||
|
@clear-selector="clearSelector"
|
||||||
>
|
>
|
||||||
<template #num="{ record }">
|
<template #num="{ record }">
|
||||||
<MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton>
|
<MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton>
|
||||||
|
@ -38,6 +41,9 @@
|
||||||
<template #lastExecResult="{ record }">
|
<template #lastExecResult="{ record }">
|
||||||
<ExecuteResult :execute-result="record.lastExecResult" />
|
<ExecuteResult :execute-result="record.lastExecResult" />
|
||||||
</template>
|
</template>
|
||||||
|
<template #count>
|
||||||
|
<slot></slot>
|
||||||
|
</template>
|
||||||
</MsBaseTable>
|
</MsBaseTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -51,6 +57,7 @@
|
||||||
import useTable from '@/components/pure/ms-table/useTable';
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||||
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
|
||||||
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
|
|
||||||
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
import useOpenNewPage from '@/hooks/useOpenNewPage';
|
||||||
import useTableStore from '@/hooks/useTableStore';
|
import useTableStore from '@/hooks/useTableStore';
|
||||||
|
@ -63,6 +70,8 @@
|
||||||
import { SpecialColumnEnum, TableKeyEnum } from '@/enums/tableEnum';
|
import { SpecialColumnEnum, TableKeyEnum } from '@/enums/tableEnum';
|
||||||
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||||
|
|
||||||
|
import type { moduleKeysType } from './types';
|
||||||
|
import useModuleSelection from './useModuleSelection';
|
||||||
import { getPublicLinkCaseListMap } from './utils/page';
|
import { getPublicLinkCaseListMap } from './utils/page';
|
||||||
import { casePriorityOptions } from '@/views/api-test/components/config';
|
import { casePriorityOptions } from '@/views/api-test/components/config';
|
||||||
import { executionResultMap, statusIconMap } from '@/views/case-management/caseManagementFeature/components/utils';
|
import { executionResultMap, statusIconMap } from '@/views/case-management/caseManagementFeature/components/utils';
|
||||||
|
@ -77,6 +86,7 @@
|
||||||
associatedIds?: string[]; // 已关联ids
|
associatedIds?: string[]; // 已关联ids
|
||||||
activeSourceType: keyof typeof CaseLinkEnum;
|
activeSourceType: keyof typeof CaseLinkEnum;
|
||||||
keyword: string;
|
keyword: string;
|
||||||
|
moduleTree: MsTreeNodeData[];
|
||||||
getPageApiType: keyof typeof CasePageApiTypeEnum; // 获取未关联分页Api
|
getPageApiType: keyof typeof CasePageApiTypeEnum; // 获取未关联分页Api
|
||||||
extraTableParams?: TableQueryParams; // 查询表格的额外参数
|
extraTableParams?: TableQueryParams; // 查询表格的额外参数
|
||||||
}>();
|
}>();
|
||||||
|
@ -86,12 +96,17 @@
|
||||||
(e: 'refresh'): void;
|
(e: 'refresh'): void;
|
||||||
(e: 'initModules'): void;
|
(e: 'initModules'): void;
|
||||||
(e: 'update:selectedIds'): void;
|
(e: 'update:selectedIds'): void;
|
||||||
|
(e: 'clearSelect'): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const tableStore = useTableStore();
|
const tableStore = useTableStore();
|
||||||
|
|
||||||
const innerSelectedIds = defineModel<string[]>('selectedIds', { required: true });
|
const innerSelectedIds = defineModel<string[]>('selectedIds', { required: true });
|
||||||
|
|
||||||
|
const innerSelectedModulesMaps = defineModel<Record<string, moduleKeysType>>('selectedModulesMaps', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
|
||||||
const reviewResultOptions = computed(() => {
|
const reviewResultOptions = computed(() => {
|
||||||
return Object.keys(statusIconMap).map((key) => {
|
return Object.keys(statusIconMap).map((key) => {
|
||||||
return {
|
return {
|
||||||
|
@ -218,26 +233,25 @@
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, resetFilterParams, setTableSelected } =
|
const { propsRes, propsEvent, loadList, setLoadListParams, resetFilterParams, setTableSelected } = useTable(
|
||||||
useTable(
|
getPageList.value,
|
||||||
getPageList.value,
|
{
|
||||||
{
|
tableKey: TableKeyEnum.ASSOCIATE_CASE,
|
||||||
tableKey: TableKeyEnum.ASSOCIATE_CASE,
|
showSetting: true,
|
||||||
showSetting: true,
|
isSimpleSetting: true,
|
||||||
isSimpleSetting: true,
|
onlyPageSize: true,
|
||||||
onlyPageSize: true,
|
selectable: true,
|
||||||
selectable: true,
|
showSelectAll: true,
|
||||||
showSelectAll: true,
|
heightUsed: 310,
|
||||||
heightUsed: 310,
|
showSelectorAll: false,
|
||||||
showSelectorAll: false,
|
},
|
||||||
},
|
(record) => {
|
||||||
(record) => {
|
return {
|
||||||
return {
|
...record,
|
||||||
...record,
|
caseLevel: getCaseLevel(record),
|
||||||
caseLevel: getCaseLevel(record),
|
};
|
||||||
};
|
}
|
||||||
}
|
);
|
||||||
);
|
|
||||||
|
|
||||||
async function getTableParams() {
|
async function getTableParams() {
|
||||||
const { excludeKeys } = propsRes.value;
|
const { excludeKeys } = propsRes.value;
|
||||||
|
@ -253,6 +267,7 @@
|
||||||
|
|
||||||
async function getModuleCount() {
|
async function getModuleCount() {
|
||||||
const tableParams = await getTableParams();
|
const tableParams = await getTableParams();
|
||||||
|
// 这里的count始终都是全量的
|
||||||
emit('getModuleCount', {
|
emit('getModuleCount', {
|
||||||
...tableParams,
|
...tableParams,
|
||||||
current: propsRes.value.msPagination?.current,
|
current: propsRes.value.msPagination?.current,
|
||||||
|
@ -301,6 +316,22 @@
|
||||||
return [...propsRes.value.selectedKeys];
|
return [...propsRes.value.selectedKeys];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const { rowSelectChange, selectAllChange, clearSelector, setModuleTree } = useModuleSelection(
|
||||||
|
innerSelectedModulesMaps.value,
|
||||||
|
propsRes.value,
|
||||||
|
props.moduleTree
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.moduleTree,
|
||||||
|
(val) => {
|
||||||
|
setModuleTree(val);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => selectIds.value,
|
() => selectIds.value,
|
||||||
(val) => {
|
(val) => {
|
||||||
|
@ -309,7 +340,6 @@
|
||||||
);
|
);
|
||||||
|
|
||||||
watch([() => props.currentProject, () => props.activeModule], () => {
|
watch([() => props.currentProject, () => props.activeModule], () => {
|
||||||
resetSelector();
|
|
||||||
resetFilterParams();
|
resetFilterParams();
|
||||||
loadCaseList();
|
loadCaseList();
|
||||||
});
|
});
|
||||||
|
|
|
@ -28,10 +28,21 @@
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</div>
|
</div>
|
||||||
<a-spin class="w-full" :loading="moduleLoading">
|
<div class="flex items-center justify-between">
|
||||||
|
<a-checkbox v-model:model-value="isCheckAll" :indeterminate="indeterminate" @change="handleChangeAll">{{
|
||||||
|
t('ms.case.associate.allData')
|
||||||
|
}}</a-checkbox>
|
||||||
|
<span class="pr-[8px] text-[var(--color-text-brand)]">
|
||||||
|
{{ allCount }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<a-spin class="w-full pl-[8px]" :loading="moduleLoading">
|
||||||
<MsTree
|
<MsTree
|
||||||
v-model:selected-keys="selectedKeys"
|
v-model:selected-keys="selectedKeys"
|
||||||
:data="caseTree"
|
v-model:checked-keys="checkedKeys"
|
||||||
|
v-model:halfCheckedKeys="halfCheckedKeys"
|
||||||
|
v-model:isCheckAll="isCheckAll"
|
||||||
|
v-model:data="caseTree"
|
||||||
:keyword="moduleKeyword"
|
:keyword="moduleKeyword"
|
||||||
:empty-text="t('common.noData')"
|
:empty-text="t('common.noData')"
|
||||||
:virtual-list-props="virtualListProps"
|
:virtual-list-props="virtualListProps"
|
||||||
|
@ -44,14 +55,30 @@
|
||||||
:expand-all="isExpandAll"
|
:expand-all="isExpandAll"
|
||||||
block-node
|
block-node
|
||||||
title-tooltip-position="top"
|
title-tooltip-position="top"
|
||||||
|
checkable
|
||||||
|
check-strictly
|
||||||
@select="folderNodeSelect"
|
@select="folderNodeSelect"
|
||||||
|
@check="checkNode"
|
||||||
>
|
>
|
||||||
<template #title="nodeData">
|
<template #title="nodeData">
|
||||||
<div class="inline-flex w-full gap-[8px]">
|
<div class="inline-flex w-full gap-[8px]">
|
||||||
<div class="one-line-text w-full text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
<div class="one-line-text w-full text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
||||||
<div class="ms-tree-node-count ml-[4px] text-[var(--color-text-brand)]">{{ nodeData.count || 0 }}</div>
|
<div class="ms-tree-node-count ml-[4px] flex items-center text-[var(--color-text-brand)]">
|
||||||
|
{{ nodeData.count || 0 }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
<template #extra="nodeData">
|
||||||
|
<MsButton
|
||||||
|
v-if="nodeData.children && nodeData.children.length"
|
||||||
|
@click="selectCurrent(nodeData, !!checkedKeys.includes(nodeData.id))"
|
||||||
|
>{{
|
||||||
|
checkedKeys.includes(nodeData.id)
|
||||||
|
? t('ms.case.associate.cancelCurrent')
|
||||||
|
: t('ms.case.associate.selectCurrent')
|
||||||
|
}}</MsButton
|
||||||
|
>
|
||||||
|
</template>
|
||||||
</MsTree>
|
</MsTree>
|
||||||
</a-spin>
|
</a-spin>
|
||||||
</template>
|
</template>
|
||||||
|
@ -60,6 +87,7 @@
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
import { useVModel } from '@vueuse/core';
|
import { useVModel } from '@vueuse/core';
|
||||||
|
|
||||||
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import MsTree from '@/components/business/ms-tree/index.vue';
|
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
import TreeFolderAll from '@/views/api-test/components/treeFolderAll.vue';
|
import TreeFolderAll from '@/views/api-test/components/treeFolderAll.vue';
|
||||||
|
@ -76,7 +104,7 @@
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
modulesCount?: Record<string, number>; // 模块数量统计对象
|
modulesCount: Record<string, number>; // 模块数量统计对象
|
||||||
selectedKeys: string[]; // 选中的节点 key
|
selectedKeys: string[]; // 选中的节点 key
|
||||||
currentProject: string;
|
currentProject: string;
|
||||||
getModulesApiType: CaseModulesApiTypeEnum[keyof CaseModulesApiTypeEnum];
|
getModulesApiType: CaseModulesApiTypeEnum[keyof CaseModulesApiTypeEnum];
|
||||||
|
@ -91,9 +119,25 @@
|
||||||
(e: 'init', params: ModuleTreeNode[], selectedProtocols?: string[]): void;
|
(e: 'init', params: ModuleTreeNode[], selectedProtocols?: string[]): void;
|
||||||
(e: 'changeProtocol', selectedProtocols: string[]): void;
|
(e: 'changeProtocol', selectedProtocols: string[]): void;
|
||||||
(e: 'update:selectedKeys', selectedKeys: string[]): void;
|
(e: 'update:selectedKeys', selectedKeys: string[]): void;
|
||||||
|
(e: 'update:halfCheckedKeys', halfCheckedKeys: string[]): void;
|
||||||
|
(e: 'check', _checkedKeys: Array<string | number>, checkedNodes: MsTreeNodeData): void;
|
||||||
|
(e: 'selectParent', node: MsTreeNodeData, isSelected: boolean): void;
|
||||||
|
(e: 'checkAllModule', isCheckedAll: boolean): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const selectedKeys = useVModel(props, 'selectedKeys', emit);
|
const selectedKeys = useVModel(props, 'selectedKeys', emit);
|
||||||
|
const checkedKeys = defineModel<(string | number)[]>('checkedKeys', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
const halfCheckedKeys = defineModel<(string | number)[]>('halfCheckedKeys', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
const isCheckAll = defineModel<boolean>('isCheckedAll', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
const indeterminate = defineModel<boolean>('indeterminate', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
const moduleKeyword = ref('');
|
const moduleKeyword = ref('');
|
||||||
const activeFolder = ref<string>('all');
|
const activeFolder = ref<string>('all');
|
||||||
const allCount = ref(0);
|
const allCount = ref(0);
|
||||||
|
@ -103,7 +147,7 @@
|
||||||
|
|
||||||
const virtualListProps = computed(() => {
|
const virtualListProps = computed(() => {
|
||||||
return {
|
return {
|
||||||
height: 'calc(100vh - 180px)',
|
height: 'calc(100vh - 236px)',
|
||||||
threshold: 200,
|
threshold: 200,
|
||||||
fixedSize: true,
|
fixedSize: true,
|
||||||
buffer: 15, // 缓冲区默认 10 的时候,虚拟滚动的底部 padding 计算有问题
|
buffer: 15, // 缓冲区默认 10 的时候,虚拟滚动的底部 padding 计算有问题
|
||||||
|
@ -129,6 +173,42 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedProtocols = ref<string[]>([]);
|
const selectedProtocols = ref<string[]>([]);
|
||||||
|
// 递归计算并更新节点的 count
|
||||||
|
function processTreeData(nodes: MsTreeNodeData[]): MsTreeNodeData[] {
|
||||||
|
const traverse = (node: MsTreeNodeData): number => {
|
||||||
|
let totalChildrenCount = 0;
|
||||||
|
|
||||||
|
if (node.children && node.children.length > 0) {
|
||||||
|
totalChildrenCount = node.children.reduce((sum, child) => {
|
||||||
|
return sum + traverse(child);
|
||||||
|
}, 0);
|
||||||
|
node.count -= totalChildrenCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
return node.count + totalChildrenCount;
|
||||||
|
};
|
||||||
|
|
||||||
|
nodes.forEach((node: MsTreeNodeData) => traverse(node));
|
||||||
|
return nodes;
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateTreeCount(treeData: MsTreeNodeData[]) {
|
||||||
|
caseTree.value = mapTree<ModuleTreeNode>(treeData, (node) => {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
count: props.modulesCount[node.id],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
const updatedModuleTreeCount = processTreeData(caseTree.value) as MsTreeNodeData[];
|
||||||
|
caseTree.value = mapTree<ModuleTreeNode>(updatedModuleTreeCount, (node) => {
|
||||||
|
return {
|
||||||
|
...node,
|
||||||
|
count: node.count,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
allCount.value = props.modulesCount.all || 0;
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* 初始化模块树
|
* 初始化模块树
|
||||||
*/
|
*/
|
||||||
|
@ -143,15 +223,13 @@
|
||||||
};
|
};
|
||||||
|
|
||||||
const res = await getModuleTreeFunc(props.getModulesApiType, props.activeTab, getModuleParams);
|
const res = await getModuleTreeFunc(props.getModulesApiType, props.activeTab, getModuleParams);
|
||||||
caseTree.value = mapTree<ModuleTreeNode>(res, (node) => {
|
|
||||||
return {
|
calculateTreeCount(res);
|
||||||
...node,
|
|
||||||
count: props.modulesCount?.[node.id] || 0,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
if (setDefault) {
|
if (setDefault) {
|
||||||
setActiveFolder('all');
|
setActiveFolder('all');
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('init', caseTree.value, selectedProtocols.value);
|
emit('init', caseTree.value, selectedProtocols.value);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@ -166,22 +244,33 @@
|
||||||
initModules();
|
initModules();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function checkNode(_checkedKeys: Array<string | number>, checkedNodes: MsTreeNodeData) {
|
||||||
|
emit('check', _checkedKeys, checkedNodes);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectCurrent(node: MsTreeNodeData, isSelected: boolean) {
|
||||||
|
emit('selectParent', node, isSelected);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化模块文件数量
|
* 初始化模块文件数量
|
||||||
*/
|
*/
|
||||||
watch(
|
watch(
|
||||||
() => props.modulesCount,
|
() => props.modulesCount,
|
||||||
(obj) => {
|
() => {
|
||||||
caseTree.value = mapTree<ModuleTreeNode>(caseTree.value, (node) => {
|
calculateTreeCount(caseTree.value);
|
||||||
return {
|
},
|
||||||
...node,
|
{
|
||||||
count: obj?.[node.id] || 0,
|
deep: true,
|
||||||
};
|
immediate: true,
|
||||||
});
|
|
||||||
allCount.value = obj?.all || 0;
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function handleChangeAll(value: boolean | (string | number | boolean)[], ev: Event) {
|
||||||
|
isCheckAll.value = value as boolean;
|
||||||
|
emit('checkAllModule', isCheckAll.value);
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.showType,
|
() => props.showType,
|
||||||
(val) => {
|
(val) => {
|
||||||
|
|
|
@ -113,11 +113,15 @@
|
||||||
</a-input-group>
|
</a-input-group>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="flex h-[calc(100vh-58px)]">
|
<div class="flex h-[calc(100vh-118px)]">
|
||||||
<div class="w-[292px] border-r border-[var(--color-text-n8)] p-[16px]">
|
<div class="w-[292px] border-r border-[var(--color-text-n8)] p-[16px]">
|
||||||
<CaseTree
|
<CaseTree
|
||||||
ref="caseTreeRef"
|
ref="caseTreeRef"
|
||||||
|
v-model:checkedKeys="checkedKeys"
|
||||||
v-model:selected-keys="selectedKeys"
|
v-model:selected-keys="selectedKeys"
|
||||||
|
v-model:halfCheckedKeys="halfCheckedKeys"
|
||||||
|
v-model:isCheckedAll="isCheckedAll"
|
||||||
|
v-model:indeterminate="indeterminate"
|
||||||
:modules-count="modulesCount"
|
:modules-count="modulesCount"
|
||||||
:get-modules-api-type="props.getModulesApiType"
|
:get-modules-api-type="props.getModulesApiType"
|
||||||
:current-project="innerProject"
|
:current-project="innerProject"
|
||||||
|
@ -128,9 +132,12 @@
|
||||||
@folder-node-select="handleFolderNodeSelect"
|
@folder-node-select="handleFolderNodeSelect"
|
||||||
@init="initModuleTree"
|
@init="initModuleTree"
|
||||||
@change-protocol="handleProtocolChange"
|
@change-protocol="handleProtocolChange"
|
||||||
|
@select-parent="selectParent"
|
||||||
|
@check="checkNode"
|
||||||
|
@check-all-module="checkAllModule"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex w-[calc(100%-293px)] flex-col p-[16px]">
|
<div class="relative flex w-[calc(100%-293px)] flex-col p-[16px]">
|
||||||
<MsAdvanceFilter
|
<MsAdvanceFilter
|
||||||
v-model:keyword="keyword"
|
v-model:keyword="keyword"
|
||||||
:filter-config-list="[]"
|
:filter-config-list="[]"
|
||||||
|
@ -166,18 +173,6 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
<!-- 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')">
|
|
||||||
<icon-question-circle
|
|
||||||
class="ml-[4px] mr-[12px] text-[var(--color-text-4)] hover:text-[rgb(var(--primary-5))]"
|
|
||||||
size="16"
|
|
||||||
/>
|
|
||||||
</a-tooltip>
|
|
||||||
</div>
|
|
||||||
</a-checkbox> -->
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</MsAdvanceFilter>
|
</MsAdvanceFilter>
|
||||||
|
@ -186,6 +181,7 @@
|
||||||
v-if="associationType === CaseLinkEnum.FUNCTIONAL"
|
v-if="associationType === CaseLinkEnum.FUNCTIONAL"
|
||||||
ref="functionalTableRef"
|
ref="functionalTableRef"
|
||||||
v-model:selectedIds="selectedIds"
|
v-model:selectedIds="selectedIds"
|
||||||
|
v-model:selectedModulesMaps="selectedModulesMaps"
|
||||||
:association-type="associateType"
|
:association-type="associateType"
|
||||||
:get-page-api-type="getPageApiType"
|
:get-page-api-type="getPageApiType"
|
||||||
:active-module="activeFolder"
|
:active-module="activeFolder"
|
||||||
|
@ -195,14 +191,18 @@
|
||||||
:active-source-type="associationType"
|
:active-source-type="associationType"
|
||||||
:extra-table-params="props.extraTableParams"
|
:extra-table-params="props.extraTableParams"
|
||||||
:keyword="keyword"
|
:keyword="keyword"
|
||||||
|
:module-tree="moduleTree"
|
||||||
@get-module-count="initModulesCount"
|
@get-module-count="initModulesCount"
|
||||||
@refresh="loadCaseList"
|
@refresh="loadCaseList"
|
||||||
/>
|
>
|
||||||
|
<TotalCount :total-count="totalCount" />
|
||||||
|
</CaseTable>
|
||||||
<!-- 接口用例 API -->
|
<!-- 接口用例 API -->
|
||||||
<ApiTable
|
<ApiTable
|
||||||
v-if="associationType === CaseLinkEnum.API && showType === 'API'"
|
v-if="associationType === CaseLinkEnum.API && showType === 'API'"
|
||||||
ref="apiTableRef"
|
ref="apiTableRef"
|
||||||
v-model:selectedIds="selectedIds"
|
v-model:selectedIds="selectedIds"
|
||||||
|
v-model:selectedModulesMaps="selectedModulesMaps"
|
||||||
:get-page-api-type="getPageApiType"
|
:get-page-api-type="getPageApiType"
|
||||||
:extra-table-params="props.extraTableParams"
|
:extra-table-params="props.extraTableParams"
|
||||||
:association-type="associateType"
|
:association-type="associateType"
|
||||||
|
@ -214,13 +214,17 @@
|
||||||
:keyword="keyword"
|
:keyword="keyword"
|
||||||
:show-type="showType"
|
:show-type="showType"
|
||||||
:protocols="selectedProtocols"
|
:protocols="selectedProtocols"
|
||||||
|
:module-tree="moduleTree"
|
||||||
@get-module-count="initModulesCount"
|
@get-module-count="initModulesCount"
|
||||||
/>
|
>
|
||||||
|
<TotalCount :total-count="totalCount" />
|
||||||
|
</ApiTable>
|
||||||
<!-- 接口用例 CASE -->
|
<!-- 接口用例 CASE -->
|
||||||
<ApiCaseTable
|
<ApiCaseTable
|
||||||
v-if="associationType === CaseLinkEnum.API && showType === 'CASE'"
|
v-if="associationType === CaseLinkEnum.API && showType === 'CASE'"
|
||||||
ref="caseTableRef"
|
ref="caseTableRef"
|
||||||
v-model:selectedIds="selectedIds"
|
v-model:selectedIds="selectedIds"
|
||||||
|
v-model:selectedModulesMaps="selectedModulesMaps"
|
||||||
:get-page-api-type="getPageApiType"
|
:get-page-api-type="getPageApiType"
|
||||||
:extra-table-params="props.extraTableParams"
|
:extra-table-params="props.extraTableParams"
|
||||||
:association-type="associateType"
|
:association-type="associateType"
|
||||||
|
@ -232,12 +236,16 @@
|
||||||
:keyword="keyword"
|
:keyword="keyword"
|
||||||
:show-type="showType"
|
:show-type="showType"
|
||||||
:protocols="selectedProtocols"
|
:protocols="selectedProtocols"
|
||||||
|
:module-tree="moduleTree"
|
||||||
@get-module-count="initModulesCount"
|
@get-module-count="initModulesCount"
|
||||||
/>
|
>
|
||||||
|
<TotalCount :total-count="totalCount" />
|
||||||
|
</ApiCaseTable>
|
||||||
<!-- 接口场景用例 -->
|
<!-- 接口场景用例 -->
|
||||||
<ScenarioCaseTable
|
<ScenarioCaseTable
|
||||||
v-if="associationType === CaseLinkEnum.SCENARIO"
|
v-if="associationType === CaseLinkEnum.SCENARIO"
|
||||||
ref="scenarioTableRef"
|
ref="scenarioTableRef"
|
||||||
|
v-model:selectedModulesMaps="selectedModulesMaps"
|
||||||
v-model:selectedIds="selectedIds"
|
v-model:selectedIds="selectedIds"
|
||||||
:association-type="associateType"
|
:association-type="associateType"
|
||||||
:modules-count="modulesCount"
|
:modules-count="modulesCount"
|
||||||
|
@ -249,30 +257,85 @@
|
||||||
:get-page-api-type="getPageApiType"
|
:get-page-api-type="getPageApiType"
|
||||||
:extra-table-params="props.extraTableParams"
|
:extra-table-params="props.extraTableParams"
|
||||||
:keyword="keyword"
|
:keyword="keyword"
|
||||||
|
:module-tree="moduleTree"
|
||||||
|
:total-count="totalCount"
|
||||||
@get-module-count="initModulesCount"
|
@get-module-count="initModulesCount"
|
||||||
@refresh="loadCaseList"
|
@refresh="loadCaseList"
|
||||||
/>
|
>
|
||||||
|
<TotalCount :total-count="totalCount" />
|
||||||
|
</ScenarioCaseTable>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="footer !ml-[10px] w-[calc(100%-10px)]">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<slot name="footerLeft">
|
||||||
|
<div v-if="props.associatedType === CaseLinkEnum.FUNCTIONAL" class="flex items-center">
|
||||||
|
<a-switch v-model:model-value="syncCase" size="small" type="line" />
|
||||||
|
<div class="ml-[8px]">{{ t('ms.case.associate.syncFunctionalCase') }}</div>
|
||||||
|
<a-tooltip :content="t('ms.case.associate.addAutomaticallyCase')" position="top">
|
||||||
|
<icon-question-circle
|
||||||
|
class="ml-[4px] text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||||
|
size="16"
|
||||||
|
/>
|
||||||
|
</a-tooltip>
|
||||||
|
</div>
|
||||||
|
<div v-if="props.associatedType === CaseLinkEnum.FUNCTIONAL" class="ml-[16px] flex items-center">
|
||||||
|
<a-tree-select
|
||||||
|
v-model="apiCaseCollectionId"
|
||||||
|
:field-names="{
|
||||||
|
title: 'name',
|
||||||
|
key: 'id',
|
||||||
|
children: 'children',
|
||||||
|
}"
|
||||||
|
class="w-[200px]"
|
||||||
|
:data="apiSetTree"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<div class="text-[var(--color-text-brand)]">{{ t('ms.case.associate.api') }}</div>
|
||||||
|
</template>
|
||||||
|
<template #tree-slot-title="node">
|
||||||
|
<a-tooltip :content="`${node.name}`" position="tl">
|
||||||
|
<div class="one-line-text w-[180px]">{{ node.name }}</div>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</a-tree-select>
|
||||||
|
|
||||||
<div class="footer">
|
<a-tree-select
|
||||||
<div class="flex flex-1 items-center">
|
v-model="apiScenarioCollectionId"
|
||||||
<slot name="footerLeft"></slot>
|
:field-names="{
|
||||||
|
title: 'name',
|
||||||
|
key: 'id',
|
||||||
|
children: 'children',
|
||||||
|
}"
|
||||||
|
class="ml-[12px] w-[200px]"
|
||||||
|
:data="scenarioSetTree"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<div class="text-[var(--color-text-brand)]">{{ t('ms.case.associate.scenario') }}</div>
|
||||||
|
</template>
|
||||||
|
<template #tree-slot-title="node">
|
||||||
|
<a-tooltip :content="`${node.name}`" position="tl">
|
||||||
|
<div class="one-line-text w-[180px]">{{ node.name }}</div>
|
||||||
|
</a-tooltip>
|
||||||
|
</template>
|
||||||
|
</a-tree-select>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
</slot>
|
||||||
<slot name="footerRight">
|
</div>
|
||||||
<a-button type="secondary" :disabled="props.confirmLoading" class="mr-[12px]" @click="cancel">
|
<div class="flex items-center">
|
||||||
{{ t('common.cancel') }}
|
<slot name="footerRight">
|
||||||
</a-button>
|
<a-button type="secondary" :disabled="props.confirmLoading" class="mr-[12px]" @click="cancel">
|
||||||
<a-button
|
{{ t('common.cancel') }}
|
||||||
:loading="props.confirmLoading"
|
</a-button>
|
||||||
type="primary"
|
<a-button
|
||||||
:disabled="!selectedIds.length"
|
:loading="props.confirmLoading"
|
||||||
@click="handleConfirm"
|
type="primary"
|
||||||
>
|
:disabled="!isDisabledSaveButton"
|
||||||
{{ t('ms.case.associate.associate') }}
|
@click="handleConfirm"
|
||||||
</a-button>
|
>
|
||||||
</slot>
|
{{ t('ms.case.associate.associate') }}
|
||||||
</div>
|
</a-button>
|
||||||
</div>
|
</slot>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</MsDrawer>
|
</MsDrawer>
|
||||||
|
@ -284,13 +347,16 @@
|
||||||
|
|
||||||
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
|
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
|
||||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||||
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
import ApiCaseTable from './apiCaseTable.vue';
|
import ApiCaseTable from './apiCaseTable.vue';
|
||||||
import ApiTable from './apiTable.vue';
|
import ApiTable from './apiTable.vue';
|
||||||
import CaseTable from './caseTable.vue';
|
import CaseTable from './caseTable.vue';
|
||||||
import CaseTree from './caseTree.vue';
|
import CaseTree from './caseTree.vue';
|
||||||
import ScenarioCaseTable from './scenarioCaseTable.vue';
|
import ScenarioCaseTable from './scenarioCaseTable.vue';
|
||||||
|
import TotalCount from './totalCount.vue';
|
||||||
|
|
||||||
import { getAssociatedProjectOptions } from '@/api/modules/case-management/featureCase';
|
import { getAssociatedProjectOptions } from '@/api/modules/case-management/featureCase';
|
||||||
|
import { getApiCaseModule, getApiScenarioModule } from '@/api/modules/test-plan/testPlan';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useVisit from '@/hooks/useVisit';
|
import useVisit from '@/hooks/useVisit';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
@ -300,10 +366,10 @@
|
||||||
import { CaseModulesApiTypeEnum, CasePageApiTypeEnum } from '@/enums/associateCaseEnum';
|
import { CaseModulesApiTypeEnum, CasePageApiTypeEnum } from '@/enums/associateCaseEnum';
|
||||||
import { CaseLinkEnum } from '@/enums/caseEnum';
|
import { CaseLinkEnum } from '@/enums/caseEnum';
|
||||||
|
|
||||||
|
import type { moduleKeysType, saveParams } from './types';
|
||||||
import { initGetModuleCountFunc } from './utils/moduleCount';
|
import { initGetModuleCountFunc } from './utils/moduleCount';
|
||||||
|
|
||||||
const visitedKey = 'changeLinkProject';
|
const visitedKey = 'changeLinkProject';
|
||||||
|
|
||||||
const { addVisited, getIsVisited } = useVisit(visitedKey);
|
const { addVisited, getIsVisited } = useVisit(visitedKey);
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
@ -321,6 +387,7 @@
|
||||||
extraModuleCountParams?: TableQueryParams; // 查询模块数量额外参数
|
extraModuleCountParams?: TableQueryParams; // 查询模块数量额外参数
|
||||||
okButtonDisabled?: boolean; // 确认按钮是否禁用
|
okButtonDisabled?: boolean; // 确认按钮是否禁用
|
||||||
confirmLoading?: boolean;
|
confirmLoading?: boolean;
|
||||||
|
modulesMaps?: Record<string, saveParams>;
|
||||||
associatedIds?: string[]; // 已关联用例id集合用于去重已关联
|
associatedIds?: string[]; // 已关联用例id集合用于去重已关联
|
||||||
hideProjectSelect?: boolean; // 是否隐藏项目选择
|
hideProjectSelect?: boolean; // 是否隐藏项目选择
|
||||||
associatedType: keyof typeof CaseLinkEnum; // 关联类型
|
associatedType: keyof typeof CaseLinkEnum; // 关联类型
|
||||||
|
@ -351,11 +418,24 @@
|
||||||
const activeFolder = ref('all');
|
const activeFolder = ref('all');
|
||||||
const selectedIds = ref<string[]>([]);
|
const selectedIds = ref<string[]>([]);
|
||||||
|
|
||||||
|
const checkedKeys = ref<Array<string | number>>([]);
|
||||||
|
const halfCheckedKeys = ref<Array<string | number>>([]);
|
||||||
|
|
||||||
|
// 数据集合
|
||||||
|
const selectedModulesMaps = ref<Record<string, moduleKeysType>>({});
|
||||||
|
|
||||||
|
// 计算用例是否选择禁用关联按钮
|
||||||
|
const isDisabledSaveButton = computed(() => {
|
||||||
|
return Object.values(selectedModulesMaps.value).some((module) => module.selectAll || module.selectIds.size > 0);
|
||||||
|
});
|
||||||
|
|
||||||
const selectedKeys = computed({
|
const selectedKeys = computed({
|
||||||
get: () => [activeFolder.value],
|
get: () => [activeFolder.value],
|
||||||
set: (val) => val,
|
set: (val) => val,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const syncCase = ref<boolean>(true);
|
||||||
|
|
||||||
const folderName = computed(() => {
|
const folderName = computed(() => {
|
||||||
switch (associationType.value) {
|
switch (associationType.value) {
|
||||||
case CaseLinkEnum.FUNCTIONAL:
|
case CaseLinkEnum.FUNCTIONAL:
|
||||||
|
@ -399,26 +479,54 @@
|
||||||
const caseTableRef = ref<InstanceType<typeof ApiCaseTable>>();
|
const caseTableRef = ref<InstanceType<typeof ApiCaseTable>>();
|
||||||
const scenarioTableRef = ref<InstanceType<typeof ScenarioCaseTable>>();
|
const scenarioTableRef = ref<InstanceType<typeof ScenarioCaseTable>>();
|
||||||
|
|
||||||
function makeParams() {
|
function getMapParams() {
|
||||||
switch (props.associatedType) {
|
const selectedParams: Record<string, saveParams> = {};
|
||||||
case CaseLinkEnum.FUNCTIONAL:
|
Object.entries(selectedModulesMaps.value).forEach(([moduleId, selectedProps]) => {
|
||||||
return functionalTableRef.value?.getFunctionalSaveParams();
|
const { selectAll, selectIds, excludeIds, count } = selectedProps;
|
||||||
case CaseLinkEnum.API:
|
selectedParams[moduleId] = {
|
||||||
return showType.value === 'API'
|
count,
|
||||||
? apiTableRef.value?.getApiSaveParams()
|
selectAll,
|
||||||
: caseTableRef.value?.getApiCaseSaveParams();
|
selectIds: [...selectIds],
|
||||||
case CaseLinkEnum.SCENARIO:
|
excludeIds: [...excludeIds],
|
||||||
return scenarioTableRef.value?.getScenarioSaveParams();
|
};
|
||||||
default:
|
});
|
||||||
break;
|
return selectedParams;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const isCheckedAll = ref<boolean>(false);
|
||||||
|
const indeterminate = ref<boolean>(false);
|
||||||
|
|
||||||
|
const totalCount = computed(() => {
|
||||||
|
if (isCheckedAll.value) {
|
||||||
|
return modulesCount.value.all;
|
||||||
|
}
|
||||||
|
return Object.values(selectedModulesMaps.value).reduce((total, module) => {
|
||||||
|
return total + (module.selectAll ? module.count : module.selectIds.size);
|
||||||
|
}, 0);
|
||||||
|
});
|
||||||
|
|
||||||
|
const apiCaseCollectionId = ref<string>('');
|
||||||
|
const apiScenarioCollectionId = ref<string>('');
|
||||||
|
|
||||||
// 保存
|
// 保存
|
||||||
function handleConfirm() {
|
function handleConfirm() {
|
||||||
const params = makeParams();
|
const params = {
|
||||||
if (!params?.selectIds.length) {
|
moduleMaps: getMapParams(),
|
||||||
return;
|
syncCase: syncCase.value,
|
||||||
|
apiCaseCollectionId: apiCaseCollectionId.value,
|
||||||
|
apiScenarioCollectionId: apiScenarioCollectionId.value,
|
||||||
|
selectAllModule: isCheckedAll.value,
|
||||||
|
projectId: innerProject.value,
|
||||||
|
associateType: 'FUNCTIONAL',
|
||||||
|
totalCount: totalCount.value,
|
||||||
|
};
|
||||||
|
|
||||||
|
if (params.associateType === CaseLinkEnum.API) {
|
||||||
|
params.associateType = showType.value;
|
||||||
|
} else {
|
||||||
|
params.associateType = props.associatedType;
|
||||||
}
|
}
|
||||||
|
|
||||||
emit('save', params);
|
emit('save', params);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,11 +553,15 @@
|
||||||
console.log(error);
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const selectedProtocols = ref<string[]>([]);
|
const selectedProtocols = ref<string[]>([]);
|
||||||
async function initModulesCount(params: TableQueryParams) {
|
async function initModulesCount(params: TableQueryParams) {
|
||||||
try {
|
try {
|
||||||
modulesCount.value = await initGetModuleCountFunc(props.getModuleCountApiType, associationType.value, {
|
modulesCount.value = await initGetModuleCountFunc(props.getModuleCountApiType, associationType.value, {
|
||||||
...params,
|
...params,
|
||||||
|
moduleIds: [],
|
||||||
|
filter: {},
|
||||||
|
keyword: '',
|
||||||
...props.extraModuleCountParams,
|
...props.extraModuleCountParams,
|
||||||
protocols: associationType.value === CaseLinkEnum.API ? selectedProtocols.value : undefined,
|
protocols: associationType.value === CaseLinkEnum.API ? selectedProtocols.value : undefined,
|
||||||
});
|
});
|
||||||
|
@ -480,20 +592,32 @@
|
||||||
|
|
||||||
const moduleTree = ref<ModuleTreeNode[]>([]);
|
const moduleTree = ref<ModuleTreeNode[]>([]);
|
||||||
function initModuleTree(tree: ModuleTreeNode[], _protocols?: string[]) {
|
function initModuleTree(tree: ModuleTreeNode[], _protocols?: string[]) {
|
||||||
moduleTree.value = unref(tree);
|
moduleTree.value = tree;
|
||||||
selectedProtocols.value = _protocols || [];
|
selectedProtocols.value = _protocols || [];
|
||||||
if (props.associatedType === CaseLinkEnum.API) {
|
if (props.associatedType === CaseLinkEnum.API) {
|
||||||
loadCaseList();
|
loadCaseList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const functionalType = ref('project');
|
const apiSetTree = ref<ModuleTreeNode[]>();
|
||||||
const functionalList = ref([
|
const scenarioSetTree = ref<ModuleTreeNode[]>();
|
||||||
{
|
|
||||||
id: 'project',
|
async function initTestSet() {
|
||||||
name: t('ms.case.associate.project'),
|
if (props.extraTableParams?.testPlanId) {
|
||||||
},
|
try {
|
||||||
]);
|
apiSetTree.value = await getApiCaseModule({
|
||||||
|
testPlanId: props.extraTableParams.testPlanId,
|
||||||
|
treeType: 'COLLECTION',
|
||||||
|
});
|
||||||
|
scenarioSetTree.value = await getApiScenarioModule({
|
||||||
|
testPlanId: props.extraTableParams.testPlanId,
|
||||||
|
treeType: 'COLLECTION',
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function changeProjectHandler(visible: boolean) {
|
function changeProjectHandler(visible: boolean) {
|
||||||
if (visible && !getIsVisited()) {
|
if (visible && !getIsVisited()) {
|
||||||
|
@ -527,6 +651,7 @@
|
||||||
associationType.value = props.associatedType;
|
associationType.value = props.associatedType;
|
||||||
activeFolder.value = 'all';
|
activeFolder.value = 'all';
|
||||||
initProjectList();
|
initProjectList();
|
||||||
|
initTestSet();
|
||||||
}
|
}
|
||||||
selectPopVisible.value = false;
|
selectPopVisible.value = false;
|
||||||
keyword.value = '';
|
keyword.value = '';
|
||||||
|
@ -542,6 +667,142 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 选中当前节点 && 取消当前节点
|
||||||
|
function selectParent(nodeData: MsTreeNodeData, isSelected: boolean) {
|
||||||
|
selectedModulesMaps.value[nodeData.id] = {
|
||||||
|
selectAll: !isSelected,
|
||||||
|
selectIds: new Set(),
|
||||||
|
excludeIds: new Set(),
|
||||||
|
count: nodeData.count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化左侧模块节点选中当前以及子节点
|
||||||
|
function processAllCurrentNode(node: MsTreeNodeData, check: boolean) {
|
||||||
|
if (node.children && node.children.length) {
|
||||||
|
node.children?.forEach((childrenNode: MsTreeNodeData) => processAllCurrentNode(childrenNode, check));
|
||||||
|
}
|
||||||
|
selectedModulesMaps.value[node.id] = {
|
||||||
|
selectAll: check,
|
||||||
|
selectIds: new Set(),
|
||||||
|
excludeIds: new Set(),
|
||||||
|
count: node.count,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选中当前节点以及子节点
|
||||||
|
function checkNode(_checkedKeys: Array<string | number>, checkedData: MsTreeNodeData) {
|
||||||
|
const { checked, node } = checkedData;
|
||||||
|
processAllCurrentNode(node, checked);
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => selectedModulesMaps.value,
|
||||||
|
(val) => {
|
||||||
|
const checkedKeysSet = new Set(checkedKeys.value);
|
||||||
|
const halfCheckedKeysSet = new Set(halfCheckedKeys.value);
|
||||||
|
|
||||||
|
if (!Object.keys(val).length) {
|
||||||
|
checkedKeysSet.clear();
|
||||||
|
halfCheckedKeysSet.clear();
|
||||||
|
isCheckedAll.value = false;
|
||||||
|
indeterminate.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Object.entries(val).forEach(([moduleId, selectedProps]) => {
|
||||||
|
const { selectAll: selectIdsAll, selectIds, count } = selectedProps;
|
||||||
|
|
||||||
|
// 全选和取消全选
|
||||||
|
if (selectIdsAll) {
|
||||||
|
checkedKeysSet.add(moduleId);
|
||||||
|
} else {
|
||||||
|
checkedKeysSet.delete(moduleId);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 半选状态
|
||||||
|
if (selectIds.size > 0 && selectIds.size < count) {
|
||||||
|
halfCheckedKeysSet.add(moduleId);
|
||||||
|
} else {
|
||||||
|
halfCheckedKeysSet.delete(moduleId);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新 checkedKeys 和 halfCheckedKeys
|
||||||
|
checkedKeys.value = Array.from(checkedKeysSet);
|
||||||
|
halfCheckedKeys.value = Array.from(halfCheckedKeysSet);
|
||||||
|
|
||||||
|
// 更新全选和半选状态
|
||||||
|
const isAllCheckedModuleProps = val.all;
|
||||||
|
|
||||||
|
if (isAllCheckedModuleProps) {
|
||||||
|
const { selectAll, selectIds, count } = isAllCheckedModuleProps;
|
||||||
|
|
||||||
|
isCheckedAll.value = selectAll;
|
||||||
|
|
||||||
|
if (selectIds.size > 0 && selectIds.size < count) {
|
||||||
|
indeterminate.value = true;
|
||||||
|
} else {
|
||||||
|
indeterminate.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
);
|
||||||
|
|
||||||
|
// 全选全部&取消全选
|
||||||
|
function setSelectAll(tree: MsTreeNodeData[], checkedAll: boolean) {
|
||||||
|
tree.forEach((node) => {
|
||||||
|
processAllCurrentNode(node, checkedAll);
|
||||||
|
|
||||||
|
if (node.children) {
|
||||||
|
setSelectAll(node.children, checkedAll);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAllModule() {
|
||||||
|
selectedModulesMaps.value.all = {
|
||||||
|
selectAll: isCheckedAll.value,
|
||||||
|
selectIds: new Set(),
|
||||||
|
excludeIds: new Set(),
|
||||||
|
count: modulesCount.value.all,
|
||||||
|
};
|
||||||
|
setSelectAll(moduleTree.value, isCheckedAll.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 切换项目的时候清空
|
||||||
|
function clearSelector() {
|
||||||
|
Object.keys(selectedModulesMaps.value).forEach((key) => {
|
||||||
|
delete selectedModulesMaps.value[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => innerProject.value,
|
||||||
|
(val) => {
|
||||||
|
if (val) {
|
||||||
|
clearSelector();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.modulesMaps,
|
||||||
|
(val) => {
|
||||||
|
if (val && Object.keys(val).length) {
|
||||||
|
Object.entries(val).forEach(([moduleId, selectedProps]) => {
|
||||||
|
const { selectAll, selectIds, excludeIds, count } = selectedProps;
|
||||||
|
selectedModulesMaps.value[moduleId] = {
|
||||||
|
selectAll,
|
||||||
|
count,
|
||||||
|
selectIds: new Set(selectIds),
|
||||||
|
excludeIds: new Set(excludeIds),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="less">
|
<style scoped lang="less">
|
||||||
|
|
|
@ -22,4 +22,14 @@ export default {
|
||||||
'ms.case.associate.switchProject': 'Switch project?',
|
'ms.case.associate.switchProject': 'Switch project?',
|
||||||
'ms.case.associate.switchProjectPopTip': 'After switching, the selected data will be cleared',
|
'ms.case.associate.switchProjectPopTip': 'After switching, the selected data will be cleared',
|
||||||
'ms.case.associate.apiSearchPlaceholder': 'Support ID/ name/tag/ path search',
|
'ms.case.associate.apiSearchPlaceholder': 'Support ID/ name/tag/ path search',
|
||||||
|
'ms.case.associate.selectCurrentNode': 'Select current',
|
||||||
|
'ms.case.associate.syncFunctionalCase': 'Add function in cases of related cases',
|
||||||
|
'ms.case.associate.addAutomaticallyCase': 'Automatically add associated interface use cases and scenario use cases',
|
||||||
|
'ms.case.associate.api': 'Api',
|
||||||
|
'ms.case.associate.scenario': 'Scenario',
|
||||||
|
'ms.case.associate.selectCurrent': 'Select current',
|
||||||
|
'ms.case.associate.cancelCurrent': 'Cancel current',
|
||||||
|
'ms.case.associate.allData': 'All',
|
||||||
|
'ms.case.associate.allSelected': 'Selected',
|
||||||
|
'ms.case.associate.allSelectedItem': 'Item data',
|
||||||
};
|
};
|
||||||
|
|
|
@ -22,4 +22,14 @@ export default {
|
||||||
'ms.case.associate.switchProject': '切换项目?',
|
'ms.case.associate.switchProject': '切换项目?',
|
||||||
'ms.case.associate.switchProjectPopTip': '切换后,已选数据将清空',
|
'ms.case.associate.switchProjectPopTip': '切换后,已选数据将清空',
|
||||||
'ms.case.associate.apiSearchPlaceholder': '通过 ID/名称/标签/路径搜索',
|
'ms.case.associate.apiSearchPlaceholder': '通过 ID/名称/标签/路径搜索',
|
||||||
|
'ms.case.associate.selectCurrentNode': '选择当前',
|
||||||
|
'ms.case.associate.syncFunctionalCase': '同步添加功能用例的关联用例',
|
||||||
|
'ms.case.associate.addAutomaticallyCase': '自动添加已关联的接口用例、场景用例',
|
||||||
|
'ms.case.associate.api': '接口',
|
||||||
|
'ms.case.associate.scenario': '场景',
|
||||||
|
'ms.case.associate.selectCurrent': '选择当前',
|
||||||
|
'ms.case.associate.cancelCurrent': '取消当前',
|
||||||
|
'ms.case.associate.allData': '全部',
|
||||||
|
'ms.case.associate.allSelected': '已选',
|
||||||
|
'ms.case.associate.allSelectedItem': '项数据',
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,6 +9,9 @@
|
||||||
}"
|
}"
|
||||||
v-on="propsEvent"
|
v-on="propsEvent"
|
||||||
@filter-change="getModuleCount"
|
@filter-change="getModuleCount"
|
||||||
|
@row-select-change="rowSelectChange"
|
||||||
|
@select-all-change="selectAllChange"
|
||||||
|
@clear-selector="clearSelector"
|
||||||
>
|
>
|
||||||
<template #num="{ record }">
|
<template #num="{ record }">
|
||||||
<MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton>
|
<MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton>
|
||||||
|
@ -34,6 +37,9 @@
|
||||||
<div class="one-line-text">{{ record.createUserName }}</div>
|
<div class="one-line-text">{{ record.createUserName }}</div>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</template>
|
</template>
|
||||||
|
<template #count>
|
||||||
|
<slot></slot>
|
||||||
|
</template>
|
||||||
</MsBaseTable>
|
</MsBaseTable>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -45,6 +51,7 @@
|
||||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||||
import useTable from '@/components/pure/ms-table/useTable';
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||||
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
|
import ExecutionStatus from '@/views/api-test/report/component/reportStatus.vue';
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
@ -61,6 +68,8 @@
|
||||||
import { SpecialColumnEnum, TableKeyEnum } from '@/enums/tableEnum';
|
import { SpecialColumnEnum, TableKeyEnum } from '@/enums/tableEnum';
|
||||||
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||||
|
|
||||||
|
import type { moduleKeysType } from './types';
|
||||||
|
import useModuleSelection from './useModuleSelection';
|
||||||
import { getPublicLinkCaseListMap } from './utils/page';
|
import { getPublicLinkCaseListMap } from './utils/page';
|
||||||
import { casePriorityOptions } from '@/views/api-test/components/config';
|
import { casePriorityOptions } from '@/views/api-test/components/config';
|
||||||
|
|
||||||
|
@ -77,6 +86,7 @@
|
||||||
keyword: string;
|
keyword: string;
|
||||||
getPageApiType: keyof typeof CasePageApiTypeEnum; // 获取未关联分页Api
|
getPageApiType: keyof typeof CasePageApiTypeEnum; // 获取未关联分页Api
|
||||||
extraTableParams?: TableQueryParams; // 查询表格的额外参数
|
extraTableParams?: TableQueryParams; // 查询表格的额外参数
|
||||||
|
moduleTree: MsTreeNodeData[];
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
@ -89,6 +99,10 @@
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const tableStore = useTableStore();
|
const tableStore = useTableStore();
|
||||||
|
|
||||||
|
const innerSelectedModulesMaps = defineModel<Record<string, moduleKeysType>>('selectedModulesMaps', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
|
||||||
const statusList = computed(() => {
|
const statusList = computed(() => {
|
||||||
return Object.keys(ReportStatus).map((key) => {
|
return Object.keys(ReportStatus).map((key) => {
|
||||||
return {
|
return {
|
||||||
|
@ -191,8 +205,10 @@
|
||||||
const getPageList = computed(() => {
|
const getPageList = computed(() => {
|
||||||
return getPublicLinkCaseListMap[props.getPageApiType][props.activeSourceType];
|
return getPublicLinkCaseListMap[props.getPageApiType][props.activeSourceType];
|
||||||
});
|
});
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, resetFilterParams, setTableSelected } =
|
|
||||||
useTable(getPageList.value, {
|
const { propsRes, propsEvent, loadList, setLoadListParams, resetFilterParams, setTableSelected } = useTable(
|
||||||
|
getPageList.value,
|
||||||
|
{
|
||||||
tableKey: TableKeyEnum.ASSOCIATE_CASE_API_SCENARIO,
|
tableKey: TableKeyEnum.ASSOCIATE_CASE_API_SCENARIO,
|
||||||
showSetting: true,
|
showSetting: true,
|
||||||
isSimpleSetting: true,
|
isSimpleSetting: true,
|
||||||
|
@ -201,7 +217,8 @@
|
||||||
showSelectAll: true,
|
showSelectAll: true,
|
||||||
heightUsed: 310,
|
heightUsed: 310,
|
||||||
showSelectorAll: false,
|
showSelectorAll: false,
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
async function getTableParams() {
|
async function getTableParams() {
|
||||||
const { excludeKeys } = propsRes.value;
|
const { excludeKeys } = propsRes.value;
|
||||||
|
@ -265,6 +282,22 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { rowSelectChange, selectAllChange, clearSelector, setModuleTree } = useModuleSelection(
|
||||||
|
innerSelectedModulesMaps.value,
|
||||||
|
propsRes.value,
|
||||||
|
props.moduleTree
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => props.moduleTree,
|
||||||
|
(val) => {
|
||||||
|
setModuleTree(val);
|
||||||
|
},
|
||||||
|
{
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// 去接口场景详情页面
|
// 去接口场景详情页面
|
||||||
function toDetail(record: ApiCaseDetail) {
|
function toDetail(record: ApiCaseDetail) {
|
||||||
openNewPage(ApiTestRouteEnum.API_TEST_SCENARIO, {
|
openNewPage(ApiTestRouteEnum.API_TEST_SCENARIO, {
|
||||||
|
@ -273,7 +306,6 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
watch([() => props.currentProject, () => props.activeModule], () => {
|
watch([() => props.currentProject, () => props.activeModule], () => {
|
||||||
resetSelector();
|
|
||||||
resetFilterParams();
|
resetFilterParams();
|
||||||
loadScenarioList();
|
loadScenarioList();
|
||||||
});
|
});
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
<template>
|
||||||
|
<div class="text-[var(--color-text-2)]">
|
||||||
|
{{ t('ms.case.associate.allSelected') }}
|
||||||
|
<span class="mx-[4px] text-[rgb(var(--primary-5))]">{{ props.totalCount }}</span>
|
||||||
|
{{ t('ms.case.associate.allSelectedItem') }}
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
totalCount: number;
|
||||||
|
}>();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped></style>
|
|
@ -0,0 +1,13 @@
|
||||||
|
export interface moduleKeysType {
|
||||||
|
selectAll: boolean;
|
||||||
|
selectIds: Set<string>;
|
||||||
|
excludeIds: Set<string>;
|
||||||
|
count: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface saveParams {
|
||||||
|
selectAll: boolean;
|
||||||
|
selectIds: string[];
|
||||||
|
excludeIds: string[];
|
||||||
|
count: number;
|
||||||
|
}
|
|
@ -0,0 +1,217 @@
|
||||||
|
import { ref, watch } from 'vue';
|
||||||
|
|
||||||
|
import type { MsTableProps } from '@/components/pure/ms-table/type';
|
||||||
|
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||||
|
|
||||||
|
import { findNodeByKey } from '@/utils';
|
||||||
|
|
||||||
|
import { SelectAllEnum } from '@/enums/tableEnum';
|
||||||
|
|
||||||
|
import type { moduleKeysType } from './types';
|
||||||
|
|
||||||
|
export default function useModuleSelections<T>(
|
||||||
|
innerSelectedModulesMaps: Record<string, moduleKeysType>,
|
||||||
|
propsRes: MsTableProps<T>,
|
||||||
|
modulesTree: MsTreeNodeData[]
|
||||||
|
) {
|
||||||
|
const moduleSelectedMap = ref<Record<string, string[]>>({});
|
||||||
|
|
||||||
|
const moduleTree = ref<MsTreeNodeData[]>(modulesTree);
|
||||||
|
|
||||||
|
// 初始化表格数据的选择
|
||||||
|
function initTableDataSelected() {
|
||||||
|
propsRes.selectedKeys = new Set([]);
|
||||||
|
propsRes.excludeKeys = new Set([]);
|
||||||
|
const allSelectIds: Set<string> = new Set();
|
||||||
|
propsRes.data.forEach((item: any) => {
|
||||||
|
const selectAllProps = innerSelectedModulesMaps[item.moduleId];
|
||||||
|
if (
|
||||||
|
selectAllProps &&
|
||||||
|
selectAllProps.selectAll &&
|
||||||
|
!selectAllProps.selectIds.size &&
|
||||||
|
!selectAllProps.excludeIds.size
|
||||||
|
) {
|
||||||
|
(moduleSelectedMap.value[item.moduleId] || []).forEach((id) => allSelectIds.add(id));
|
||||||
|
} else if (selectAllProps && !selectAllProps.selectAll && selectAllProps.selectIds.size) {
|
||||||
|
selectAllProps.selectIds.forEach((id) => allSelectIds.add(id));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
propsRes.selectedKeys = new Set([...allSelectIds]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化模块分类
|
||||||
|
function initPropsDataSort() {
|
||||||
|
propsRes.data.forEach((item: any) => {
|
||||||
|
if (!moduleSelectedMap.value[item.moduleId]) {
|
||||||
|
moduleSelectedMap.value[item.moduleId] = [item.id];
|
||||||
|
}
|
||||||
|
if (!moduleSelectedMap.value[item.moduleId].includes(item.id)) {
|
||||||
|
moduleSelectedMap.value[item.moduleId].push(item.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单选或复选时处理全选状态
|
||||||
|
function setSelectedAll(moduleId: string) {
|
||||||
|
innerSelectedModulesMaps[moduleId].selectAll = true;
|
||||||
|
innerSelectedModulesMaps[moduleId].selectIds = new Set([]);
|
||||||
|
innerSelectedModulesMaps[moduleId].excludeIds = new Set([]);
|
||||||
|
const selectedProp = innerSelectedModulesMaps[moduleId];
|
||||||
|
if (selectedProp) {
|
||||||
|
if (selectedProp.selectAll && !selectedProp.selectIds.size && !selectedProp.excludeIds.size) {
|
||||||
|
moduleSelectedMap.value[moduleId].forEach((key) => {
|
||||||
|
propsRes.selectedKeys.add(key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化节点,防止选择时报错
|
||||||
|
function setUnSelectNode(moduleId: string, key = 'id') {
|
||||||
|
if (!innerSelectedModulesMaps[moduleId]) {
|
||||||
|
const node = findNodeByKey<MsTreeNodeData>(moduleTree.value, moduleId, key);
|
||||||
|
innerSelectedModulesMaps[moduleId] = {
|
||||||
|
selectAll: false,
|
||||||
|
selectIds: new Set(),
|
||||||
|
excludeIds: new Set(),
|
||||||
|
count: node?.count || 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置最新状态
|
||||||
|
function setSelectedModuleStatus(moduleId: string) {
|
||||||
|
const selectedProps = innerSelectedModulesMaps[moduleId];
|
||||||
|
if (selectedProps) {
|
||||||
|
const { selectIds: selectModuleIds, count, excludeIds } = selectedProps;
|
||||||
|
if (selectModuleIds.size < count) {
|
||||||
|
innerSelectedModulesMaps[moduleId].selectAll = false;
|
||||||
|
}
|
||||||
|
if (selectModuleIds.size === count) {
|
||||||
|
setSelectedAll(moduleId);
|
||||||
|
}
|
||||||
|
if (excludeIds.size === count) {
|
||||||
|
innerSelectedModulesMaps[moduleId].selectAll = false;
|
||||||
|
innerSelectedModulesMaps[moduleId].selectIds = new Set([]);
|
||||||
|
innerSelectedModulesMaps[moduleId].excludeIds = new Set([]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新选择数据
|
||||||
|
function updateSelectModule(moduleId: string, id: string) {
|
||||||
|
const selectedProps = innerSelectedModulesMaps[moduleId];
|
||||||
|
if (selectedProps) {
|
||||||
|
const selectedSet = selectedProps.selectIds;
|
||||||
|
const excludedSet = selectedProps.excludeIds;
|
||||||
|
const isSelectAllModule =
|
||||||
|
selectedProps.selectAll && !selectedProps.selectIds.size && !selectedProps.excludeIds.size;
|
||||||
|
|
||||||
|
if (isSelectAllModule && moduleId === 'all') {
|
||||||
|
Object.entries(moduleSelectedMap.value).forEach(([item, allSelectIds]) => {
|
||||||
|
allSelectIds.forEach((key) => {
|
||||||
|
selectedProps.selectIds.add(key);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} else if (isSelectAllModule && moduleId !== 'all') {
|
||||||
|
moduleSelectedMap.value[moduleId].forEach((key) => {
|
||||||
|
selectedProps.selectIds.add(key);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedSet.has(id)) {
|
||||||
|
selectedProps.excludeIds.add(id);
|
||||||
|
selectedProps.selectIds.delete(id);
|
||||||
|
} else if (excludedSet.has(id)) {
|
||||||
|
selectedProps.excludeIds.delete(id);
|
||||||
|
selectedProps.selectIds.add(id);
|
||||||
|
} else if (!selectedSet.has(id) && !excludedSet.has(id)) {
|
||||||
|
selectedProps.selectIds.add(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
innerSelectedModulesMaps[moduleId] = selectedProps;
|
||||||
|
setSelectedModuleStatus(moduleId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function rowSelectChange(record: Record<string, any>) {
|
||||||
|
const { moduleId } = record;
|
||||||
|
setUnSelectNode(moduleId);
|
||||||
|
updateSelectModule(moduleId, record.id);
|
||||||
|
updateSelectModule('all', record.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
function selectAllChange(v: SelectAllEnum) {
|
||||||
|
const { data } = propsRes;
|
||||||
|
if (v === 'current') {
|
||||||
|
propsRes.selectedKeys = new Set([]);
|
||||||
|
data.forEach((item: any) => {
|
||||||
|
const { moduleId } = item;
|
||||||
|
setUnSelectNode(moduleId);
|
||||||
|
const lastSelectedProps = innerSelectedModulesMaps[moduleId];
|
||||||
|
if (!lastSelectedProps.selectAll) {
|
||||||
|
innerSelectedModulesMaps[moduleId].selectIds.add(item.id);
|
||||||
|
innerSelectedModulesMaps[moduleId].excludeIds.delete(item.id);
|
||||||
|
setSelectedModuleStatus(moduleId);
|
||||||
|
}
|
||||||
|
updateSelectModule('all', item.id);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
data.forEach((item: any) => {
|
||||||
|
const { moduleId } = item;
|
||||||
|
setUnSelectNode(moduleId);
|
||||||
|
innerSelectedModulesMaps[item.moduleId].selectIds.delete(item.id);
|
||||||
|
innerSelectedModulesMaps[item.moduleId].excludeIds.add(item.id);
|
||||||
|
setSelectedModuleStatus(moduleId);
|
||||||
|
updateSelectModule('all', item.id);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function setModuleTree(tree: MsTreeNodeData[]) {
|
||||||
|
moduleTree.value = tree;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearSelector() {
|
||||||
|
Object.keys(innerSelectedModulesMaps).forEach((key) => {
|
||||||
|
delete innerSelectedModulesMaps[key];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => propsRes.data,
|
||||||
|
async () => {
|
||||||
|
await initPropsDataSort();
|
||||||
|
initTableDataSelected();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
immediate: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => innerSelectedModulesMaps,
|
||||||
|
() => {
|
||||||
|
initTableDataSelected();
|
||||||
|
},
|
||||||
|
{
|
||||||
|
deep: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
moduleSelectedMap,
|
||||||
|
rowSelectChange,
|
||||||
|
selectAllChange,
|
||||||
|
initTableDataSelected,
|
||||||
|
initPropsDataSort,
|
||||||
|
setUnSelectNode,
|
||||||
|
setSelectedModuleStatus,
|
||||||
|
setSelectedAll,
|
||||||
|
updateSelectModule,
|
||||||
|
clearSelector,
|
||||||
|
setModuleTree,
|
||||||
|
};
|
||||||
|
}
|
|
@ -12,7 +12,7 @@
|
||||||
:extra-modules-params="{
|
:extra-modules-params="{
|
||||||
testPlanId: props?.testPlanId,
|
testPlanId: props?.testPlanId,
|
||||||
}"
|
}"
|
||||||
:associated-ids="props.hasNotAssociatedIds || []"
|
:modules-maps="props.modulesMaps"
|
||||||
:associated-type="associationType"
|
:associated-type="associationType"
|
||||||
@save="saveHandler"
|
@save="saveHandler"
|
||||||
>
|
>
|
||||||
|
@ -24,18 +24,19 @@
|
||||||
import { Message } from '@arco-design/web-vue';
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
|
||||||
import MsCaseAssociate from '@/components/business/ms-associate-case/index.vue';
|
import MsCaseAssociate from '@/components/business/ms-associate-case/index.vue';
|
||||||
|
import type { saveParams } from '@/components/business/ms-associate-case/types';
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
import type { AssociateCaseRequest, AssociateCaseRequestType } from '@/models/testPlan/testPlan';
|
import type { AssociateCaseRequestParams, AssociateCaseRequestType } from '@/models/testPlan/testPlan';
|
||||||
import { CaseCountApiTypeEnum, CaseModulesApiTypeEnum, CasePageApiTypeEnum } from '@/enums/associateCaseEnum';
|
import { CaseCountApiTypeEnum, CaseModulesApiTypeEnum, CasePageApiTypeEnum } from '@/enums/associateCaseEnum';
|
||||||
import { CaseLinkEnum } from '@/enums/caseEnum';
|
import { CaseLinkEnum } from '@/enums/caseEnum';
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
associationType: CaseLinkEnum;
|
associationType: CaseLinkEnum;
|
||||||
hasNotAssociatedIds?: string[];
|
modulesMaps?: Record<string, saveParams>;
|
||||||
saveApi?: (params: AssociateCaseRequestType) => Promise<any>;
|
saveApi?: (params: AssociateCaseRequestType) => Promise<any>;
|
||||||
testPlanId?: string;
|
testPlanId?: string;
|
||||||
}>();
|
}>();
|
||||||
|
@ -43,7 +44,7 @@
|
||||||
required: true,
|
required: true,
|
||||||
});
|
});
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'success', val: AssociateCaseRequest): void;
|
(e: 'success', val: AssociateCaseRequestParams): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
@ -53,7 +54,7 @@
|
||||||
const confirmLoading = ref<boolean>(false);
|
const confirmLoading = ref<boolean>(false);
|
||||||
const planId = ref(route.query.id as string);
|
const planId = ref(route.query.id as string);
|
||||||
|
|
||||||
async function saveHandler(params: AssociateCaseRequest) {
|
async function saveHandler(params: AssociateCaseRequestParams) {
|
||||||
if (typeof props.saveApi !== 'function') {
|
if (typeof props.saveApi !== 'function') {
|
||||||
emit('success', { ...params });
|
emit('success', { ...params });
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -105,10 +105,7 @@
|
||||||
<a-divider margin="4px" direction="vertical" />
|
<a-divider margin="4px" direction="vertical" />
|
||||||
<MsButton
|
<MsButton
|
||||||
type="text"
|
type="text"
|
||||||
:disabled="
|
:disabled="!hasEditPermission || selectedAssociateCasesParams.totalCount === 0"
|
||||||
!hasEditPermission ||
|
|
||||||
(selectedAssociateCasesParams.totalCount || selectedAssociateCasesParams.selectIds.length) === 0
|
|
||||||
"
|
|
||||||
@click="clearSelectedCases"
|
@click="clearSelectedCases"
|
||||||
>
|
>
|
||||||
{{ t('caseManagement.caseReview.clearSelectedCases') }}
|
{{ t('caseManagement.caseReview.clearSelectedCases') }}
|
||||||
|
@ -120,9 +117,7 @@
|
||||||
<div class="text-[var(--color-text-2)]">
|
<div class="text-[var(--color-text-2)]">
|
||||||
{{
|
{{
|
||||||
t('ms.minders.selectedCases', {
|
t('ms.minders.selectedCases', {
|
||||||
count: selectedAssociateCasesParams.selectAll
|
count: selectedAssociateCasesParams.totalCount,
|
||||||
? selectedAssociateCasesParams.totalCount
|
|
||||||
: selectedAssociateCasesParams.selectIds.length,
|
|
||||||
})
|
})
|
||||||
}}
|
}}
|
||||||
</div>
|
</div>
|
||||||
|
@ -243,8 +238,8 @@
|
||||||
<caseAssociate
|
<caseAssociate
|
||||||
v-model:visible="caseAssociateVisible"
|
v-model:visible="caseAssociateVisible"
|
||||||
:association-type="currentSelectCase"
|
:association-type="currentSelectCase"
|
||||||
:has-not-associated-ids="selectedAssociateCasesParams.selectIds"
|
|
||||||
:test-plan-id="props.planId"
|
:test-plan-id="props.planId"
|
||||||
|
:modules-maps="selectedAssociateCasesParams.moduleMaps"
|
||||||
@success="writeAssociateCases"
|
@success="writeAssociateCases"
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
|
@ -273,7 +268,7 @@
|
||||||
import { hasAnyPermission } from '@/utils/permission';
|
import { hasAnyPermission } from '@/utils/permission';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
AssociateCaseRequest,
|
AssociateCaseRequestParams,
|
||||||
PlanMinderEditListItem,
|
PlanMinderEditListItem,
|
||||||
PlanMinderNode,
|
PlanMinderNode,
|
||||||
PlanMinderNodeData,
|
PlanMinderNodeData,
|
||||||
|
@ -569,7 +564,7 @@
|
||||||
const caseAssociateVisible = ref<boolean>(false);
|
const caseAssociateVisible = ref<boolean>(false);
|
||||||
|
|
||||||
// 批量关联用例表格参数
|
// 批量关联用例表格参数
|
||||||
const selectedAssociateCasesParams = ref<AssociateCaseRequest>({
|
const selectedAssociateCasesParams = ref<AssociateCaseRequestParams>({
|
||||||
excludeIds: [],
|
excludeIds: [],
|
||||||
selectIds: [],
|
selectIds: [],
|
||||||
selectAll: false,
|
selectAll: false,
|
||||||
|
@ -578,26 +573,30 @@
|
||||||
versionId: '',
|
versionId: '',
|
||||||
refId: '',
|
refId: '',
|
||||||
projectId: '',
|
projectId: '',
|
||||||
|
moduleMaps: {},
|
||||||
|
syncCase: true,
|
||||||
|
apiCaseCollectionId: '',
|
||||||
|
apiScenarioCollectionId: '',
|
||||||
|
selectAllModule: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
function writeAssociateCases(param: AssociateCaseRequest) {
|
function writeAssociateCases(param: AssociateCaseRequestParams) {
|
||||||
selectedAssociateCasesParams.value = { ...param };
|
selectedAssociateCasesParams.value = { ...param };
|
||||||
const node: PlanMinderNode = window.minder.getSelectedNode();
|
const node: PlanMinderNode = window.minder.getSelectedNode();
|
||||||
let associateType: string = '';
|
let associateType: string = '';
|
||||||
if (node.data.type === PlanMinderCollectionType.SCENARIO) {
|
if (node.data.type === PlanMinderCollectionType.SCENARIO) {
|
||||||
associateType = PlanMinderAssociateType.SCENARIO_CASE;
|
associateType = PlanMinderAssociateType.SCENARIO_CASE;
|
||||||
} else {
|
} else {
|
||||||
associateType =
|
associateType = param?.associateType ?? node.data.type;
|
||||||
node.data.type === PlanMinderCollectionType.API && param.associateApiType
|
|
||||||
? param.associateApiType
|
|
||||||
: node.data.type;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
node.data.associateDTOS = [
|
node.data.associateDTOS = [
|
||||||
{
|
{
|
||||||
ids: param.selectIds,
|
...cloneDeep(param),
|
||||||
associateType,
|
associateType,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
caseAssociateVisible.value = false;
|
caseAssociateVisible.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -611,6 +610,11 @@
|
||||||
versionId: '',
|
versionId: '',
|
||||||
refId: '',
|
refId: '',
|
||||||
projectId: '',
|
projectId: '',
|
||||||
|
moduleMaps: {},
|
||||||
|
syncCase: true,
|
||||||
|
apiCaseCollectionId: '',
|
||||||
|
apiScenarioCollectionId: '',
|
||||||
|
selectAllModule: false,
|
||||||
};
|
};
|
||||||
const node: PlanMinderNode = window.minder.getNodeById(activePlanSet.value?.data.id);
|
const node: PlanMinderNode = window.minder.getNodeById(activePlanSet.value?.data.id);
|
||||||
if (node?.data) {
|
if (node?.data) {
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
ref="treeRef"
|
ref="treeRef"
|
||||||
v-model:selected-keys="selectedKeys"
|
v-model:selected-keys="selectedKeys"
|
||||||
v-model:checked-keys="checkedKeys"
|
v-model:checked-keys="checkedKeys"
|
||||||
|
v-model:half-checked-keys="halfCheckedKeys"
|
||||||
:data="filterTreeData"
|
:data="filterTreeData"
|
||||||
class="ms-tree"
|
class="ms-tree"
|
||||||
:allow-drop="handleAllowDrop"
|
:allow-drop="handleAllowDrop"
|
||||||
|
@ -119,6 +120,7 @@
|
||||||
emptyText?: string; // 空数据时的文案
|
emptyText?: string; // 空数据时的文案
|
||||||
checkable?: boolean; // 是否可选中
|
checkable?: boolean; // 是否可选中
|
||||||
checkedStrategy?: 'all' | 'parent' | 'child'; // 选中节点时的策略
|
checkedStrategy?: 'all' | 'parent' | 'child'; // 选中节点时的策略
|
||||||
|
checkStrictly?: boolean; // 是否取消父子节点关联
|
||||||
virtualListProps?: VirtualListProps; // 虚拟滚动列表的属性
|
virtualListProps?: VirtualListProps; // 虚拟滚动列表的属性
|
||||||
disabledTitleTooltip?: boolean; // 是否禁用标题 tooltip
|
disabledTitleTooltip?: boolean; // 是否禁用标题 tooltip
|
||||||
actionOnNodeClick?: 'expand'; // 点击节点时的操作
|
actionOnNodeClick?: 'expand'; // 点击节点时的操作
|
||||||
|
@ -167,7 +169,7 @@
|
||||||
): void;
|
): void;
|
||||||
(e: 'moreActionSelect', item: ActionsItem, node: MsTreeNodeData): void;
|
(e: 'moreActionSelect', item: ActionsItem, node: MsTreeNodeData): void;
|
||||||
(e: 'moreActionsClose'): void;
|
(e: 'moreActionsClose'): void;
|
||||||
(e: 'check', val: Array<string | number>): void;
|
(e: 'check', val: Array<string | number>, node: MsTreeNodeData): void;
|
||||||
(e: 'expand', node: MsTreeExpandedData): void;
|
(e: 'expand', node: MsTreeExpandedData): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
@ -180,6 +182,9 @@
|
||||||
const checkedKeys = defineModel<(string | number)[]>('checkedKeys', {
|
const checkedKeys = defineModel<(string | number)[]>('checkedKeys', {
|
||||||
default: [],
|
default: [],
|
||||||
});
|
});
|
||||||
|
const halfCheckedKeys = defineModel<(string | number)[]>('halfCheckedKeys', {
|
||||||
|
default: [],
|
||||||
|
});
|
||||||
const focusNodeKey = defineModel<string | number>('focusNodeKey', {
|
const focusNodeKey = defineModel<string | number>('focusNodeKey', {
|
||||||
default: '',
|
default: '',
|
||||||
});
|
});
|
||||||
|
@ -362,8 +367,8 @@
|
||||||
emit('select', _selectedKeys, selectNode);
|
emit('select', _selectedKeys, selectNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checked(_checkedKeys: Array<string | number>) {
|
function checked(_checkedKeys: Array<string | number>, checkedNodes: MsTreeNodeData) {
|
||||||
emit('check', _checkedKeys);
|
emit('check', _checkedKeys, checkedNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
const focusEl = ref<HTMLElement | null>(); // 存储聚焦的节点元素
|
const focusEl = ref<HTMLElement | null>(); // 存储聚焦的节点元素
|
||||||
|
|
|
@ -250,7 +250,9 @@
|
||||||
:class="{ 'justify-between': showBatchAction }"
|
:class="{ 'justify-between': showBatchAction }"
|
||||||
>
|
>
|
||||||
<span v-if="props.actionConfig && selectedCount > 0 && !showBatchAction" class="title text-[var(--color-text-2)]">
|
<span v-if="props.actionConfig && selectedCount > 0 && !showBatchAction" class="title text-[var(--color-text-2)]">
|
||||||
{{ t('msTable.batch.selected', { count: selectedCount }) }}
|
<slot name="count">
|
||||||
|
{{ t('msTable.batch.selected', { count: selectedCount }) }}
|
||||||
|
</slot>
|
||||||
<a-button class="clear-btn ml-[12px] px-2" type="text" @click="emit('clearSelector')">
|
<a-button class="clear-btn ml-[12px] px-2" type="text" @click="emit('clearSelector')">
|
||||||
{{ t('msTable.batch.clear') }}
|
{{ t('msTable.batch.clear') }}
|
||||||
</a-button>
|
</a-button>
|
||||||
|
@ -265,7 +267,11 @@
|
||||||
:size="props.paginationSize"
|
:size="props.paginationSize"
|
||||||
@batch-action="handleBatchAction"
|
@batch-action="handleBatchAction"
|
||||||
@clear="() => emit('clearSelector')"
|
@clear="() => emit('clearSelector')"
|
||||||
/>
|
>
|
||||||
|
<template #count>
|
||||||
|
<slot name="count"></slot>
|
||||||
|
</template>
|
||||||
|
</batch-action>
|
||||||
</div>
|
</div>
|
||||||
<ms-pagination
|
<ms-pagination
|
||||||
v-if="!!attrs.showPagination"
|
v-if="!!attrs.showPagination"
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
<template>
|
<template>
|
||||||
<div v-if="props.actionConfig" ref="refWrapper" class="flex flex-row flex-nowrap items-center">
|
<div v-if="props.actionConfig" ref="refWrapper" class="flex flex-row flex-nowrap items-center">
|
||||||
<div class="title one-line-text">{{ t('msTable.batch.selected', { count: props.selectRowCount }) }}</div>
|
<slot name="count">
|
||||||
|
<div class="title one-line-text">{{ t('msTable.batch.selected', { count: props.selectRowCount }) }}</div>
|
||||||
|
</slot>
|
||||||
<template v-for="(element, idx) in baseAction" :key="element.label">
|
<template v-for="(element, idx) in baseAction" :key="element.label">
|
||||||
<a-divider v-if="element.isDivider" class="divider mx-0 my-[6px]" />
|
<a-divider v-if="element.isDivider" class="divider mx-0 my-[6px]" />
|
||||||
<a-button
|
<a-button
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import type { MinderJsonNode, MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props';
|
import type { MinderJsonNode, MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props';
|
||||||
import type { BatchActionQueryParams } from '@/components/pure/ms-table/type';
|
import type { BatchActionQueryParams } from '@/components/pure/ms-table/type';
|
||||||
|
import type { saveParams } from '@/components/business/ms-associate-case/types';
|
||||||
|
|
||||||
import type { customFieldsItem } from '@/models/caseManagement/featureCase';
|
import type { customFieldsItem } from '@/models/caseManagement/featureCase';
|
||||||
import type { TableQueryParams } from '@/models/common';
|
import type { TableQueryParams } from '@/models/common';
|
||||||
|
@ -39,6 +40,16 @@ export interface AssociateCaseRequest extends BatchApiParams {
|
||||||
associateApiType?: string;
|
associateApiType?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AssociateCaseRequestParams extends AssociateCaseRequest {
|
||||||
|
associateType?: string;
|
||||||
|
moduleMaps?: Record<string, saveParams>;
|
||||||
|
syncCase: boolean;
|
||||||
|
apiCaseCollectionId: string;
|
||||||
|
apiScenarioCollectionId: string;
|
||||||
|
selectAllModule: boolean;
|
||||||
|
projectId: string;
|
||||||
|
}
|
||||||
|
|
||||||
export type AssociateCaseRequestType = Pick<AssociateCaseRequest, 'functionalSelectIds' | 'testPlanId'>;
|
export type AssociateCaseRequestType = Pick<AssociateCaseRequest, 'functionalSelectIds' | 'testPlanId'>;
|
||||||
|
|
||||||
export interface AddTestPlanParams {
|
export interface AddTestPlanParams {
|
||||||
|
|
Loading…
Reference in New Issue