feat(测试计划): 测试计划关联用例选择交互&关联用例联调_中
This commit is contained in:
parent
ecb6200759
commit
59da4f4ebc
|
@ -1,8 +1,6 @@
|
|||
<template>
|
||||
<a-config-provider :locale="locale">
|
||||
<a-spin :loading="loading">
|
||||
<router-view />
|
||||
</a-spin>
|
||||
<router-view />
|
||||
<!-- <global-setting /> -->
|
||||
</a-config-provider>
|
||||
</template>
|
||||
|
@ -46,8 +44,6 @@
|
|||
}
|
||||
});
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
// 初始化平台风格和主题色
|
||||
watchStyle(appStore.pageConfig.style, appStore.pageConfig);
|
||||
watchTheme(appStore.pageConfig.theme, appStore.pageConfig);
|
||||
|
@ -94,24 +90,26 @@
|
|||
state.value = getQueryVariable('state') || '';
|
||||
if (state.value.split('#')[0] === 'fit2cloud-lark-qr') {
|
||||
try {
|
||||
loading.value = true;
|
||||
appStore.showLoading();
|
||||
const larkCallback = await getLarkCallback(code || '');
|
||||
userStore.qrCodeLogin(larkCallback);
|
||||
setLoginExpires();
|
||||
loading.value = false;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
} finally {
|
||||
appStore.hideLoading();
|
||||
}
|
||||
}
|
||||
if (state.value.split('#')[0] === 'fit2cloud-lark-suite-qr') {
|
||||
try {
|
||||
loading.value = true;
|
||||
appStore.showLoading();
|
||||
const larkCallback = await getLarkSuiteCallback(code || '');
|
||||
userStore.qrCodeLogin(larkCallback);
|
||||
setLoginExpires();
|
||||
loading.value = false;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
} finally {
|
||||
appStore.hideLoading();
|
||||
}
|
||||
}
|
||||
await userStore.checkIsLogin();
|
||||
|
|
|
@ -8,6 +8,9 @@
|
|||
moreAction: [],
|
||||
}"
|
||||
v-on="propsEvent"
|
||||
@row-select-change="rowSelectChange"
|
||||
@select-all-change="selectAllChange"
|
||||
@clear-selector="clearSelector"
|
||||
>
|
||||
<template #num="{ record }">
|
||||
<MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton>
|
||||
|
@ -41,6 +44,9 @@
|
|||
<template #[FilterSlotNameEnum.API_TEST_CASE_API_LAST_EXECUTE_STATUS]="{ filterContent }">
|
||||
<ExecutionStatus :module-type="ReportEnum.API_REPORT" :status="filterContent.value" />
|
||||
</template>
|
||||
<template #count>
|
||||
<slot></slot>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
</template>
|
||||
|
||||
|
@ -54,6 +60,7 @@
|
|||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.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 useOpenNewPage from '@/hooks/useOpenNewPage';
|
||||
|
@ -69,6 +76,8 @@
|
|||
import { SpecialColumnEnum, TableKeyEnum } from '@/enums/tableEnum';
|
||||
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||
|
||||
import type { moduleKeysType } from './types';
|
||||
import useModuleSelection from './useModuleSelection';
|
||||
import { getPublicLinkCaseListMap } from './utils/page';
|
||||
import { casePriorityOptions } from '@/views/api-test/components/config';
|
||||
|
||||
|
@ -87,6 +96,7 @@
|
|||
getPageApiType: keyof typeof CasePageApiTypeEnum; // 获取未关联分页Api
|
||||
extraTableParams?: TableQueryParams; // 查询表格的额外参数
|
||||
protocols: string[];
|
||||
moduleTree: MsTreeNodeData[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
@ -96,6 +106,10 @@
|
|||
(e: 'update:selectedIds'): void;
|
||||
}>();
|
||||
|
||||
const innerSelectedModulesMaps = defineModel<Record<string, moduleKeysType>>('selectedModulesMaps', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const tableStore = useTableStore();
|
||||
|
||||
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({
|
||||
getApiCaseSaveParams,
|
||||
loadCaseList,
|
||||
|
|
|
@ -10,6 +10,9 @@
|
|||
}"
|
||||
v-on="propsEvent"
|
||||
@filter-change="getModuleCount"
|
||||
@row-select-change="rowSelectChange"
|
||||
@select-all-change="selectAllChange"
|
||||
@clear-selector="clearSelector"
|
||||
>
|
||||
<template #num="{ record }">
|
||||
<MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton>
|
||||
|
@ -28,6 +31,9 @@
|
|||
<div class="one-line-text">{{ record.createUserName }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #count>
|
||||
<slot></slot>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
</template>
|
||||
|
||||
|
@ -36,6 +42,7 @@
|
|||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
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 { useI18n } from '@/hooks/useI18n';
|
||||
|
@ -52,6 +59,8 @@
|
|||
import { SpecialColumnEnum, TableKeyEnum } from '@/enums/tableEnum';
|
||||
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||
|
||||
import type { moduleKeysType } from './types';
|
||||
import useModuleSelection from './useModuleSelection';
|
||||
import { getPublicLinkCaseListMap } from './utils/page';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -71,6 +80,7 @@
|
|||
getPageApiType: keyof typeof CasePageApiTypeEnum; // 获取未关联分页Api
|
||||
extraTableParams?: TableQueryParams; // 查询表格的额外参数
|
||||
protocols: string[];
|
||||
moduleTree: MsTreeNodeData[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
@ -81,6 +91,9 @@
|
|||
}>();
|
||||
|
||||
const tableStore = useTableStore();
|
||||
const innerSelectedModulesMaps = defineModel<Record<string, moduleKeysType>>('selectedModulesMaps', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const requestMethodsOptions = computed(() => {
|
||||
return Object.values(RequestMethods).map((e) => {
|
||||
|
@ -180,10 +193,10 @@
|
|||
propsEvent,
|
||||
loadList,
|
||||
setLoadListParams,
|
||||
resetSelector,
|
||||
setPagination,
|
||||
resetFilterParams,
|
||||
setTableSelected,
|
||||
resetSelector,
|
||||
} = useTable(getPublicLinkCaseListMap[props.getPageApiType][props.activeSourceType].API, {
|
||||
tableKey: TableKeyEnum.ASSOCIATE_CASE_API,
|
||||
showSetting: true,
|
||||
|
@ -239,7 +252,6 @@
|
|||
setPagination({
|
||||
current: 1,
|
||||
});
|
||||
resetSelector();
|
||||
resetFilterParams();
|
||||
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({
|
||||
getApiSaveParams,
|
||||
loadApiList,
|
||||
|
|
|
@ -9,6 +9,9 @@
|
|||
}"
|
||||
v-on="propsEvent"
|
||||
@filter-change="getModuleCount"
|
||||
@row-select-change="rowSelectChange"
|
||||
@select-all-change="selectAllChange"
|
||||
@clear-selector="clearSelector"
|
||||
>
|
||||
<template #num="{ record }">
|
||||
<MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton>
|
||||
|
@ -38,6 +41,9 @@
|
|||
<template #lastExecResult="{ record }">
|
||||
<ExecuteResult :execute-result="record.lastExecResult" />
|
||||
</template>
|
||||
<template #count>
|
||||
<slot></slot>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
</template>
|
||||
|
||||
|
@ -51,6 +57,7 @@
|
|||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.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 useTableStore from '@/hooks/useTableStore';
|
||||
|
@ -63,6 +70,8 @@
|
|||
import { SpecialColumnEnum, TableKeyEnum } from '@/enums/tableEnum';
|
||||
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||
|
||||
import type { moduleKeysType } from './types';
|
||||
import useModuleSelection from './useModuleSelection';
|
||||
import { getPublicLinkCaseListMap } from './utils/page';
|
||||
import { casePriorityOptions } from '@/views/api-test/components/config';
|
||||
import { executionResultMap, statusIconMap } from '@/views/case-management/caseManagementFeature/components/utils';
|
||||
|
@ -77,6 +86,7 @@
|
|||
associatedIds?: string[]; // 已关联ids
|
||||
activeSourceType: keyof typeof CaseLinkEnum;
|
||||
keyword: string;
|
||||
moduleTree: MsTreeNodeData[];
|
||||
getPageApiType: keyof typeof CasePageApiTypeEnum; // 获取未关联分页Api
|
||||
extraTableParams?: TableQueryParams; // 查询表格的额外参数
|
||||
}>();
|
||||
|
@ -86,12 +96,17 @@
|
|||
(e: 'refresh'): void;
|
||||
(e: 'initModules'): void;
|
||||
(e: 'update:selectedIds'): void;
|
||||
(e: 'clearSelect'): void;
|
||||
}>();
|
||||
|
||||
const tableStore = useTableStore();
|
||||
|
||||
const innerSelectedIds = defineModel<string[]>('selectedIds', { required: true });
|
||||
|
||||
const innerSelectedModulesMaps = defineModel<Record<string, moduleKeysType>>('selectedModulesMaps', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const reviewResultOptions = computed(() => {
|
||||
return Object.keys(statusIconMap).map((key) => {
|
||||
return {
|
||||
|
@ -218,26 +233,25 @@
|
|||
return undefined;
|
||||
}
|
||||
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector, resetFilterParams, setTableSelected } =
|
||||
useTable(
|
||||
getPageList.value,
|
||||
{
|
||||
tableKey: TableKeyEnum.ASSOCIATE_CASE,
|
||||
showSetting: true,
|
||||
isSimpleSetting: true,
|
||||
onlyPageSize: true,
|
||||
selectable: true,
|
||||
showSelectAll: true,
|
||||
heightUsed: 310,
|
||||
showSelectorAll: false,
|
||||
},
|
||||
(record) => {
|
||||
return {
|
||||
...record,
|
||||
caseLevel: getCaseLevel(record),
|
||||
};
|
||||
}
|
||||
);
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetFilterParams, setTableSelected } = useTable(
|
||||
getPageList.value,
|
||||
{
|
||||
tableKey: TableKeyEnum.ASSOCIATE_CASE,
|
||||
showSetting: true,
|
||||
isSimpleSetting: true,
|
||||
onlyPageSize: true,
|
||||
selectable: true,
|
||||
showSelectAll: true,
|
||||
heightUsed: 310,
|
||||
showSelectorAll: false,
|
||||
},
|
||||
(record) => {
|
||||
return {
|
||||
...record,
|
||||
caseLevel: getCaseLevel(record),
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
async function getTableParams() {
|
||||
const { excludeKeys } = propsRes.value;
|
||||
|
@ -253,6 +267,7 @@
|
|||
|
||||
async function getModuleCount() {
|
||||
const tableParams = await getTableParams();
|
||||
// 这里的count始终都是全量的
|
||||
emit('getModuleCount', {
|
||||
...tableParams,
|
||||
current: propsRes.value.msPagination?.current,
|
||||
|
@ -301,6 +316,22 @@
|
|||
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(
|
||||
() => selectIds.value,
|
||||
(val) => {
|
||||
|
@ -309,7 +340,6 @@
|
|||
);
|
||||
|
||||
watch([() => props.currentProject, () => props.activeModule], () => {
|
||||
resetSelector();
|
||||
resetFilterParams();
|
||||
loadCaseList();
|
||||
});
|
||||
|
|
|
@ -28,10 +28,21 @@
|
|||
</a-button>
|
||||
</a-tooltip>
|
||||
</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
|
||||
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"
|
||||
:empty-text="t('common.noData')"
|
||||
:virtual-list-props="virtualListProps"
|
||||
|
@ -44,14 +55,30 @@
|
|||
:expand-all="isExpandAll"
|
||||
block-node
|
||||
title-tooltip-position="top"
|
||||
checkable
|
||||
check-strictly
|
||||
@select="folderNodeSelect"
|
||||
@check="checkNode"
|
||||
>
|
||||
<template #title="nodeData">
|
||||
<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="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>
|
||||
</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>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
@ -60,6 +87,7 @@
|
|||
import { ref } from 'vue';
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/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';
|
||||
|
@ -76,7 +104,7 @@
|
|||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
modulesCount?: Record<string, number>; // 模块数量统计对象
|
||||
modulesCount: Record<string, number>; // 模块数量统计对象
|
||||
selectedKeys: string[]; // 选中的节点 key
|
||||
currentProject: string;
|
||||
getModulesApiType: CaseModulesApiTypeEnum[keyof CaseModulesApiTypeEnum];
|
||||
|
@ -91,9 +119,25 @@
|
|||
(e: 'init', params: ModuleTreeNode[], selectedProtocols?: string[]): void;
|
||||
(e: 'changeProtocol', selectedProtocols: 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 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 activeFolder = ref<string>('all');
|
||||
const allCount = ref(0);
|
||||
|
@ -103,7 +147,7 @@
|
|||
|
||||
const virtualListProps = computed(() => {
|
||||
return {
|
||||
height: 'calc(100vh - 180px)',
|
||||
height: 'calc(100vh - 236px)',
|
||||
threshold: 200,
|
||||
fixedSize: true,
|
||||
buffer: 15, // 缓冲区默认 10 的时候,虚拟滚动的底部 padding 计算有问题
|
||||
|
@ -129,6 +173,42 @@
|
|||
}
|
||||
|
||||
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);
|
||||
caseTree.value = mapTree<ModuleTreeNode>(res, (node) => {
|
||||
return {
|
||||
...node,
|
||||
count: props.modulesCount?.[node.id] || 0,
|
||||
};
|
||||
});
|
||||
|
||||
calculateTreeCount(res);
|
||||
|
||||
if (setDefault) {
|
||||
setActiveFolder('all');
|
||||
}
|
||||
|
||||
emit('init', caseTree.value, selectedProtocols.value);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -166,22 +244,33 @@
|
|||
initModules();
|
||||
}
|
||||
|
||||
function checkNode(_checkedKeys: Array<string | number>, checkedNodes: MsTreeNodeData) {
|
||||
emit('check', _checkedKeys, checkedNodes);
|
||||
}
|
||||
|
||||
function selectCurrent(node: MsTreeNodeData, isSelected: boolean) {
|
||||
emit('selectParent', node, isSelected);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化模块文件数量
|
||||
*/
|
||||
watch(
|
||||
() => props.modulesCount,
|
||||
(obj) => {
|
||||
caseTree.value = mapTree<ModuleTreeNode>(caseTree.value, (node) => {
|
||||
return {
|
||||
...node,
|
||||
count: obj?.[node.id] || 0,
|
||||
};
|
||||
});
|
||||
allCount.value = obj?.all || 0;
|
||||
() => {
|
||||
calculateTreeCount(caseTree.value);
|
||||
},
|
||||
{
|
||||
deep: true,
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
function handleChangeAll(value: boolean | (string | number | boolean)[], ev: Event) {
|
||||
isCheckAll.value = value as boolean;
|
||||
emit('checkAllModule', isCheckAll.value);
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.showType,
|
||||
(val) => {
|
||||
|
|
|
@ -113,11 +113,15 @@
|
|||
</a-input-group>
|
||||
</div>
|
||||
</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]">
|
||||
<CaseTree
|
||||
ref="caseTreeRef"
|
||||
v-model:checkedKeys="checkedKeys"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
v-model:halfCheckedKeys="halfCheckedKeys"
|
||||
v-model:isCheckedAll="isCheckedAll"
|
||||
v-model:indeterminate="indeterminate"
|
||||
:modules-count="modulesCount"
|
||||
:get-modules-api-type="props.getModulesApiType"
|
||||
:current-project="innerProject"
|
||||
|
@ -128,9 +132,12 @@
|
|||
@folder-node-select="handleFolderNodeSelect"
|
||||
@init="initModuleTree"
|
||||
@change-protocol="handleProtocolChange"
|
||||
@select-parent="selectParent"
|
||||
@check="checkNode"
|
||||
@check-all-module="checkAllModule"
|
||||
/>
|
||||
</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
|
||||
v-model:keyword="keyword"
|
||||
:filter-config-list="[]"
|
||||
|
@ -166,18 +173,6 @@
|
|||
</div>
|
||||
</template>
|
||||
</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>
|
||||
</template>
|
||||
</MsAdvanceFilter>
|
||||
|
@ -186,6 +181,7 @@
|
|||
v-if="associationType === CaseLinkEnum.FUNCTIONAL"
|
||||
ref="functionalTableRef"
|
||||
v-model:selectedIds="selectedIds"
|
||||
v-model:selectedModulesMaps="selectedModulesMaps"
|
||||
:association-type="associateType"
|
||||
:get-page-api-type="getPageApiType"
|
||||
:active-module="activeFolder"
|
||||
|
@ -195,14 +191,18 @@
|
|||
:active-source-type="associationType"
|
||||
:extra-table-params="props.extraTableParams"
|
||||
:keyword="keyword"
|
||||
:module-tree="moduleTree"
|
||||
@get-module-count="initModulesCount"
|
||||
@refresh="loadCaseList"
|
||||
/>
|
||||
>
|
||||
<TotalCount :total-count="totalCount" />
|
||||
</CaseTable>
|
||||
<!-- 接口用例 API -->
|
||||
<ApiTable
|
||||
v-if="associationType === CaseLinkEnum.API && showType === 'API'"
|
||||
ref="apiTableRef"
|
||||
v-model:selectedIds="selectedIds"
|
||||
v-model:selectedModulesMaps="selectedModulesMaps"
|
||||
:get-page-api-type="getPageApiType"
|
||||
:extra-table-params="props.extraTableParams"
|
||||
:association-type="associateType"
|
||||
|
@ -214,13 +214,17 @@
|
|||
:keyword="keyword"
|
||||
:show-type="showType"
|
||||
:protocols="selectedProtocols"
|
||||
:module-tree="moduleTree"
|
||||
@get-module-count="initModulesCount"
|
||||
/>
|
||||
>
|
||||
<TotalCount :total-count="totalCount" />
|
||||
</ApiTable>
|
||||
<!-- 接口用例 CASE -->
|
||||
<ApiCaseTable
|
||||
v-if="associationType === CaseLinkEnum.API && showType === 'CASE'"
|
||||
ref="caseTableRef"
|
||||
v-model:selectedIds="selectedIds"
|
||||
v-model:selectedModulesMaps="selectedModulesMaps"
|
||||
:get-page-api-type="getPageApiType"
|
||||
:extra-table-params="props.extraTableParams"
|
||||
:association-type="associateType"
|
||||
|
@ -232,12 +236,16 @@
|
|||
:keyword="keyword"
|
||||
:show-type="showType"
|
||||
:protocols="selectedProtocols"
|
||||
:module-tree="moduleTree"
|
||||
@get-module-count="initModulesCount"
|
||||
/>
|
||||
>
|
||||
<TotalCount :total-count="totalCount" />
|
||||
</ApiCaseTable>
|
||||
<!-- 接口场景用例 -->
|
||||
<ScenarioCaseTable
|
||||
v-if="associationType === CaseLinkEnum.SCENARIO"
|
||||
ref="scenarioTableRef"
|
||||
v-model:selectedModulesMaps="selectedModulesMaps"
|
||||
v-model:selectedIds="selectedIds"
|
||||
:association-type="associateType"
|
||||
:modules-count="modulesCount"
|
||||
|
@ -249,30 +257,85 @@
|
|||
:get-page-api-type="getPageApiType"
|
||||
:extra-table-params="props.extraTableParams"
|
||||
:keyword="keyword"
|
||||
:module-tree="moduleTree"
|
||||
:total-count="totalCount"
|
||||
@get-module-count="initModulesCount"
|
||||
@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">
|
||||
<div class="flex flex-1 items-center">
|
||||
<slot name="footerLeft"></slot>
|
||||
<a-tree-select
|
||||
v-model="apiScenarioCollectionId"
|
||||
: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 class="flex items-center">
|
||||
<slot name="footerRight">
|
||||
<a-button type="secondary" :disabled="props.confirmLoading" class="mr-[12px]" @click="cancel">
|
||||
{{ t('common.cancel') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
:loading="props.confirmLoading"
|
||||
type="primary"
|
||||
:disabled="!selectedIds.length"
|
||||
@click="handleConfirm"
|
||||
>
|
||||
{{ t('ms.case.associate.associate') }}
|
||||
</a-button>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</slot>
|
||||
</div>
|
||||
<div class="flex items-center">
|
||||
<slot name="footerRight">
|
||||
<a-button type="secondary" :disabled="props.confirmLoading" class="mr-[12px]" @click="cancel">
|
||||
{{ t('common.cancel') }}
|
||||
</a-button>
|
||||
<a-button
|
||||
:loading="props.confirmLoading"
|
||||
type="primary"
|
||||
:disabled="!isDisabledSaveButton"
|
||||
@click="handleConfirm"
|
||||
>
|
||||
{{ t('ms.case.associate.associate') }}
|
||||
</a-button>
|
||||
</slot>
|
||||
</div>
|
||||
</div>
|
||||
</MsDrawer>
|
||||
|
@ -284,13 +347,16 @@
|
|||
|
||||
import { MsAdvanceFilter } from '@/components/pure/ms-advance-filter';
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||
import ApiCaseTable from './apiCaseTable.vue';
|
||||
import ApiTable from './apiTable.vue';
|
||||
import CaseTable from './caseTable.vue';
|
||||
import CaseTree from './caseTree.vue';
|
||||
import ScenarioCaseTable from './scenarioCaseTable.vue';
|
||||
import TotalCount from './totalCount.vue';
|
||||
|
||||
import { getAssociatedProjectOptions } from '@/api/modules/case-management/featureCase';
|
||||
import { getApiCaseModule, getApiScenarioModule } from '@/api/modules/test-plan/testPlan';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useVisit from '@/hooks/useVisit';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
@ -300,10 +366,10 @@
|
|||
import { CaseModulesApiTypeEnum, CasePageApiTypeEnum } from '@/enums/associateCaseEnum';
|
||||
import { CaseLinkEnum } from '@/enums/caseEnum';
|
||||
|
||||
import type { moduleKeysType, saveParams } from './types';
|
||||
import { initGetModuleCountFunc } from './utils/moduleCount';
|
||||
|
||||
const visitedKey = 'changeLinkProject';
|
||||
|
||||
const { addVisited, getIsVisited } = useVisit(visitedKey);
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
@ -321,6 +387,7 @@
|
|||
extraModuleCountParams?: TableQueryParams; // 查询模块数量额外参数
|
||||
okButtonDisabled?: boolean; // 确认按钮是否禁用
|
||||
confirmLoading?: boolean;
|
||||
modulesMaps?: Record<string, saveParams>;
|
||||
associatedIds?: string[]; // 已关联用例id集合用于去重已关联
|
||||
hideProjectSelect?: boolean; // 是否隐藏项目选择
|
||||
associatedType: keyof typeof CaseLinkEnum; // 关联类型
|
||||
|
@ -351,11 +418,24 @@
|
|||
const activeFolder = ref('all');
|
||||
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({
|
||||
get: () => [activeFolder.value],
|
||||
set: (val) => val,
|
||||
});
|
||||
|
||||
const syncCase = ref<boolean>(true);
|
||||
|
||||
const folderName = computed(() => {
|
||||
switch (associationType.value) {
|
||||
case CaseLinkEnum.FUNCTIONAL:
|
||||
|
@ -399,26 +479,54 @@
|
|||
const caseTableRef = ref<InstanceType<typeof ApiCaseTable>>();
|
||||
const scenarioTableRef = ref<InstanceType<typeof ScenarioCaseTable>>();
|
||||
|
||||
function makeParams() {
|
||||
switch (props.associatedType) {
|
||||
case CaseLinkEnum.FUNCTIONAL:
|
||||
return functionalTableRef.value?.getFunctionalSaveParams();
|
||||
case CaseLinkEnum.API:
|
||||
return showType.value === 'API'
|
||||
? apiTableRef.value?.getApiSaveParams()
|
||||
: caseTableRef.value?.getApiCaseSaveParams();
|
||||
case CaseLinkEnum.SCENARIO:
|
||||
return scenarioTableRef.value?.getScenarioSaveParams();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
function getMapParams() {
|
||||
const selectedParams: Record<string, saveParams> = {};
|
||||
Object.entries(selectedModulesMaps.value).forEach(([moduleId, selectedProps]) => {
|
||||
const { selectAll, selectIds, excludeIds, count } = selectedProps;
|
||||
selectedParams[moduleId] = {
|
||||
count,
|
||||
selectAll,
|
||||
selectIds: [...selectIds],
|
||||
excludeIds: [...excludeIds],
|
||||
};
|
||||
});
|
||||
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() {
|
||||
const params = makeParams();
|
||||
if (!params?.selectIds.length) {
|
||||
return;
|
||||
const params = {
|
||||
moduleMaps: getMapParams(),
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -445,11 +553,15 @@
|
|||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
const selectedProtocols = ref<string[]>([]);
|
||||
async function initModulesCount(params: TableQueryParams) {
|
||||
try {
|
||||
modulesCount.value = await initGetModuleCountFunc(props.getModuleCountApiType, associationType.value, {
|
||||
...params,
|
||||
moduleIds: [],
|
||||
filter: {},
|
||||
keyword: '',
|
||||
...props.extraModuleCountParams,
|
||||
protocols: associationType.value === CaseLinkEnum.API ? selectedProtocols.value : undefined,
|
||||
});
|
||||
|
@ -480,20 +592,32 @@
|
|||
|
||||
const moduleTree = ref<ModuleTreeNode[]>([]);
|
||||
function initModuleTree(tree: ModuleTreeNode[], _protocols?: string[]) {
|
||||
moduleTree.value = unref(tree);
|
||||
moduleTree.value = tree;
|
||||
selectedProtocols.value = _protocols || [];
|
||||
if (props.associatedType === CaseLinkEnum.API) {
|
||||
loadCaseList();
|
||||
}
|
||||
}
|
||||
|
||||
const functionalType = ref('project');
|
||||
const functionalList = ref([
|
||||
{
|
||||
id: 'project',
|
||||
name: t('ms.case.associate.project'),
|
||||
},
|
||||
]);
|
||||
const apiSetTree = ref<ModuleTreeNode[]>();
|
||||
const scenarioSetTree = ref<ModuleTreeNode[]>();
|
||||
|
||||
async function initTestSet() {
|
||||
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) {
|
||||
if (visible && !getIsVisited()) {
|
||||
|
@ -527,6 +651,7 @@
|
|||
associationType.value = props.associatedType;
|
||||
activeFolder.value = 'all';
|
||||
initProjectList();
|
||||
initTestSet();
|
||||
}
|
||||
selectPopVisible.value = false;
|
||||
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>
|
||||
|
||||
<style scoped lang="less">
|
||||
|
|
|
@ -22,4 +22,14 @@ export default {
|
|||
'ms.case.associate.switchProject': 'Switch project?',
|
||||
'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.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.switchProjectPopTip': '切换后,已选数据将清空',
|
||||
'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"
|
||||
@filter-change="getModuleCount"
|
||||
@row-select-change="rowSelectChange"
|
||||
@select-all-change="selectAllChange"
|
||||
@clear-selector="clearSelector"
|
||||
>
|
||||
<template #num="{ record }">
|
||||
<MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton>
|
||||
|
@ -34,6 +37,9 @@
|
|||
<div class="one-line-text">{{ record.createUserName }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template #count>
|
||||
<slot></slot>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
</template>
|
||||
|
||||
|
@ -45,6 +51,7 @@
|
|||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
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 { useI18n } from '@/hooks/useI18n';
|
||||
|
@ -61,6 +68,8 @@
|
|||
import { SpecialColumnEnum, TableKeyEnum } from '@/enums/tableEnum';
|
||||
import { FilterRemoteMethodsEnum, FilterSlotNameEnum } from '@/enums/tableFilterEnum';
|
||||
|
||||
import type { moduleKeysType } from './types';
|
||||
import useModuleSelection from './useModuleSelection';
|
||||
import { getPublicLinkCaseListMap } from './utils/page';
|
||||
import { casePriorityOptions } from '@/views/api-test/components/config';
|
||||
|
||||
|
@ -77,6 +86,7 @@
|
|||
keyword: string;
|
||||
getPageApiType: keyof typeof CasePageApiTypeEnum; // 获取未关联分页Api
|
||||
extraTableParams?: TableQueryParams; // 查询表格的额外参数
|
||||
moduleTree: MsTreeNodeData[];
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
@ -89,6 +99,10 @@
|
|||
const appStore = useAppStore();
|
||||
const tableStore = useTableStore();
|
||||
|
||||
const innerSelectedModulesMaps = defineModel<Record<string, moduleKeysType>>('selectedModulesMaps', {
|
||||
required: true,
|
||||
});
|
||||
|
||||
const statusList = computed(() => {
|
||||
return Object.keys(ReportStatus).map((key) => {
|
||||
return {
|
||||
|
@ -191,8 +205,10 @@
|
|||
const getPageList = computed(() => {
|
||||
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,
|
||||
showSetting: true,
|
||||
isSimpleSetting: true,
|
||||
|
@ -201,7 +217,8 @@
|
|||
showSelectAll: true,
|
||||
heightUsed: 310,
|
||||
showSelectorAll: false,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
async function getTableParams() {
|
||||
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) {
|
||||
openNewPage(ApiTestRouteEnum.API_TEST_SCENARIO, {
|
||||
|
@ -273,7 +306,6 @@
|
|||
});
|
||||
}
|
||||
watch([() => props.currentProject, () => props.activeModule], () => {
|
||||
resetSelector();
|
||||
resetFilterParams();
|
||||
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="{
|
||||
testPlanId: props?.testPlanId,
|
||||
}"
|
||||
:associated-ids="props.hasNotAssociatedIds || []"
|
||||
:modules-maps="props.modulesMaps"
|
||||
:associated-type="associationType"
|
||||
@save="saveHandler"
|
||||
>
|
||||
|
@ -24,18 +24,19 @@
|
|||
import { Message } from '@arco-design/web-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 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 { CaseLinkEnum } from '@/enums/caseEnum';
|
||||
|
||||
const { t } = useI18n();
|
||||
const props = defineProps<{
|
||||
associationType: CaseLinkEnum;
|
||||
hasNotAssociatedIds?: string[];
|
||||
modulesMaps?: Record<string, saveParams>;
|
||||
saveApi?: (params: AssociateCaseRequestType) => Promise<any>;
|
||||
testPlanId?: string;
|
||||
}>();
|
||||
|
@ -43,7 +44,7 @@
|
|||
required: true,
|
||||
});
|
||||
const emit = defineEmits<{
|
||||
(e: 'success', val: AssociateCaseRequest): void;
|
||||
(e: 'success', val: AssociateCaseRequestParams): void;
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
@ -53,7 +54,7 @@
|
|||
const confirmLoading = ref<boolean>(false);
|
||||
const planId = ref(route.query.id as string);
|
||||
|
||||
async function saveHandler(params: AssociateCaseRequest) {
|
||||
async function saveHandler(params: AssociateCaseRequestParams) {
|
||||
if (typeof props.saveApi !== 'function') {
|
||||
emit('success', { ...params });
|
||||
} else {
|
||||
|
|
|
@ -105,10 +105,7 @@
|
|||
<a-divider margin="4px" direction="vertical" />
|
||||
<MsButton
|
||||
type="text"
|
||||
:disabled="
|
||||
!hasEditPermission ||
|
||||
(selectedAssociateCasesParams.totalCount || selectedAssociateCasesParams.selectIds.length) === 0
|
||||
"
|
||||
:disabled="!hasEditPermission || selectedAssociateCasesParams.totalCount === 0"
|
||||
@click="clearSelectedCases"
|
||||
>
|
||||
{{ t('caseManagement.caseReview.clearSelectedCases') }}
|
||||
|
@ -120,9 +117,7 @@
|
|||
<div class="text-[var(--color-text-2)]">
|
||||
{{
|
||||
t('ms.minders.selectedCases', {
|
||||
count: selectedAssociateCasesParams.selectAll
|
||||
? selectedAssociateCasesParams.totalCount
|
||||
: selectedAssociateCasesParams.selectIds.length,
|
||||
count: selectedAssociateCasesParams.totalCount,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
|
@ -243,8 +238,8 @@
|
|||
<caseAssociate
|
||||
v-model:visible="caseAssociateVisible"
|
||||
:association-type="currentSelectCase"
|
||||
:has-not-associated-ids="selectedAssociateCasesParams.selectIds"
|
||||
:test-plan-id="props.planId"
|
||||
:modules-maps="selectedAssociateCasesParams.moduleMaps"
|
||||
@success="writeAssociateCases"
|
||||
/>
|
||||
</template>
|
||||
|
@ -273,7 +268,7 @@
|
|||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import {
|
||||
AssociateCaseRequest,
|
||||
AssociateCaseRequestParams,
|
||||
PlanMinderEditListItem,
|
||||
PlanMinderNode,
|
||||
PlanMinderNodeData,
|
||||
|
@ -569,7 +564,7 @@
|
|||
const caseAssociateVisible = ref<boolean>(false);
|
||||
|
||||
// 批量关联用例表格参数
|
||||
const selectedAssociateCasesParams = ref<AssociateCaseRequest>({
|
||||
const selectedAssociateCasesParams = ref<AssociateCaseRequestParams>({
|
||||
excludeIds: [],
|
||||
selectIds: [],
|
||||
selectAll: false,
|
||||
|
@ -578,26 +573,30 @@
|
|||
versionId: '',
|
||||
refId: '',
|
||||
projectId: '',
|
||||
moduleMaps: {},
|
||||
syncCase: true,
|
||||
apiCaseCollectionId: '',
|
||||
apiScenarioCollectionId: '',
|
||||
selectAllModule: false,
|
||||
});
|
||||
|
||||
function writeAssociateCases(param: AssociateCaseRequest) {
|
||||
function writeAssociateCases(param: AssociateCaseRequestParams) {
|
||||
selectedAssociateCasesParams.value = { ...param };
|
||||
const node: PlanMinderNode = window.minder.getSelectedNode();
|
||||
let associateType: string = '';
|
||||
if (node.data.type === PlanMinderCollectionType.SCENARIO) {
|
||||
associateType = PlanMinderAssociateType.SCENARIO_CASE;
|
||||
} else {
|
||||
associateType =
|
||||
node.data.type === PlanMinderCollectionType.API && param.associateApiType
|
||||
? param.associateApiType
|
||||
: node.data.type;
|
||||
associateType = param?.associateType ?? node.data.type;
|
||||
}
|
||||
|
||||
node.data.associateDTOS = [
|
||||
{
|
||||
ids: param.selectIds,
|
||||
...cloneDeep(param),
|
||||
associateType,
|
||||
},
|
||||
];
|
||||
|
||||
caseAssociateVisible.value = false;
|
||||
}
|
||||
|
||||
|
@ -611,6 +610,11 @@
|
|||
versionId: '',
|
||||
refId: '',
|
||||
projectId: '',
|
||||
moduleMaps: {},
|
||||
syncCase: true,
|
||||
apiCaseCollectionId: '',
|
||||
apiScenarioCollectionId: '',
|
||||
selectAllModule: false,
|
||||
};
|
||||
const node: PlanMinderNode = window.minder.getNodeById(activePlanSet.value?.data.id);
|
||||
if (node?.data) {
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
ref="treeRef"
|
||||
v-model:selected-keys="selectedKeys"
|
||||
v-model:checked-keys="checkedKeys"
|
||||
v-model:half-checked-keys="halfCheckedKeys"
|
||||
:data="filterTreeData"
|
||||
class="ms-tree"
|
||||
:allow-drop="handleAllowDrop"
|
||||
|
@ -119,6 +120,7 @@
|
|||
emptyText?: string; // 空数据时的文案
|
||||
checkable?: boolean; // 是否可选中
|
||||
checkedStrategy?: 'all' | 'parent' | 'child'; // 选中节点时的策略
|
||||
checkStrictly?: boolean; // 是否取消父子节点关联
|
||||
virtualListProps?: VirtualListProps; // 虚拟滚动列表的属性
|
||||
disabledTitleTooltip?: boolean; // 是否禁用标题 tooltip
|
||||
actionOnNodeClick?: 'expand'; // 点击节点时的操作
|
||||
|
@ -167,7 +169,7 @@
|
|||
): void;
|
||||
(e: 'moreActionSelect', item: ActionsItem, node: MsTreeNodeData): 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;
|
||||
}>();
|
||||
|
||||
|
@ -180,6 +182,9 @@
|
|||
const checkedKeys = defineModel<(string | number)[]>('checkedKeys', {
|
||||
default: [],
|
||||
});
|
||||
const halfCheckedKeys = defineModel<(string | number)[]>('halfCheckedKeys', {
|
||||
default: [],
|
||||
});
|
||||
const focusNodeKey = defineModel<string | number>('focusNodeKey', {
|
||||
default: '',
|
||||
});
|
||||
|
@ -362,8 +367,8 @@
|
|||
emit('select', _selectedKeys, selectNode);
|
||||
}
|
||||
|
||||
function checked(_checkedKeys: Array<string | number>) {
|
||||
emit('check', _checkedKeys);
|
||||
function checked(_checkedKeys: Array<string | number>, checkedNodes: MsTreeNodeData) {
|
||||
emit('check', _checkedKeys, checkedNodes);
|
||||
}
|
||||
|
||||
const focusEl = ref<HTMLElement | null>(); // 存储聚焦的节点元素
|
||||
|
|
|
@ -250,7 +250,9 @@
|
|||
:class="{ 'justify-between': showBatchAction }"
|
||||
>
|
||||
<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')">
|
||||
{{ t('msTable.batch.clear') }}
|
||||
</a-button>
|
||||
|
@ -265,7 +267,11 @@
|
|||
:size="props.paginationSize"
|
||||
@batch-action="handleBatchAction"
|
||||
@clear="() => emit('clearSelector')"
|
||||
/>
|
||||
>
|
||||
<template #count>
|
||||
<slot name="count"></slot>
|
||||
</template>
|
||||
</batch-action>
|
||||
</div>
|
||||
<ms-pagination
|
||||
v-if="!!attrs.showPagination"
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
<template>
|
||||
<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">
|
||||
<a-divider v-if="element.isDivider" class="divider mx-0 my-[6px]" />
|
||||
<a-button
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { MinderJsonNode, MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props';
|
||||
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 { TableQueryParams } from '@/models/common';
|
||||
|
@ -39,6 +40,16 @@ export interface AssociateCaseRequest extends BatchApiParams {
|
|||
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 interface AddTestPlanParams {
|
||||
|
|
Loading…
Reference in New Issue