mirror of
https://gitee.com/fit2cloud-feizhiyun/MeterSphere.git
synced 2024-12-02 12:09:13 +08:00
feat(脑图): 支持节点自定义下拉菜单&测试规划脑图快捷配置
This commit is contained in:
parent
cb72ff6b96
commit
3445189a78
@ -206,7 +206,7 @@
|
||||
</CaseTable>
|
||||
<!-- 接口用例 API -->
|
||||
<ApiTable
|
||||
v-if="associationType === CaseLinkEnum.API && showType === 'API'"
|
||||
v-else-if="associationType === CaseLinkEnum.API && showType === 'API'"
|
||||
ref="apiTableRef"
|
||||
v-model:selectedIds="selectedIds"
|
||||
v-model:selectedModulesMaps="selectedModulesMaps"
|
||||
@ -229,7 +229,7 @@
|
||||
</ApiTable>
|
||||
<!-- 接口用例 CASE -->
|
||||
<ApiCaseTable
|
||||
v-if="associationType === CaseLinkEnum.API && showType === 'CASE'"
|
||||
v-else-if="associationType === CaseLinkEnum.API && showType === 'CASE'"
|
||||
ref="caseTableRef"
|
||||
v-model:selectedIds="selectedIds"
|
||||
v-model:selectedModulesMaps="selectedModulesMaps"
|
||||
@ -252,7 +252,7 @@
|
||||
</ApiCaseTable>
|
||||
<!-- 接口场景用例 -->
|
||||
<ScenarioCaseTable
|
||||
v-if="associationType === CaseLinkEnum.SCENARIO"
|
||||
v-else-if="associationType === CaseLinkEnum.SCENARIO"
|
||||
ref="scenarioTableRef"
|
||||
v-model:selectedModulesMaps="selectedModulesMaps"
|
||||
v-model:selectedIds="selectedIds"
|
||||
@ -637,7 +637,7 @@
|
||||
selectedProtocols.value = _protocols || [];
|
||||
}
|
||||
|
||||
function changeSyncCase(value: string | number | boolean, ev: Event) {
|
||||
function changeSyncCase(value: string | number | boolean) {
|
||||
if (value) {
|
||||
if (props.nodeApiTestSet && props.nodeScenarioTestSet) {
|
||||
apiCaseCollectionId.value = props.nodeApiTestSet?.[0]?.id ?? '';
|
||||
|
@ -16,6 +16,9 @@
|
||||
:can-show-delete-menu="canShowDeleteMenu"
|
||||
:disabled="!hasEditPermission"
|
||||
:can-show-batch-delete="canShowBatchDelete"
|
||||
:can-show-dropdown="canShowDropdown"
|
||||
:dropdown-list="dropdownList"
|
||||
:checked-val="checkedVal"
|
||||
custom-priority
|
||||
single-tag
|
||||
tag-enable
|
||||
@ -239,8 +242,10 @@
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsMinderEditor from '@/components/pure/ms-minder-editor/minderEditor.vue';
|
||||
import {
|
||||
MinderDropdownListItem,
|
||||
MinderEvent,
|
||||
MinderJson,
|
||||
MinderJsonNode,
|
||||
@ -306,7 +311,6 @@
|
||||
*/
|
||||
function checkNodeCanShowMenu(node: PlanMinderNode) {
|
||||
const { data } = node;
|
||||
|
||||
if (!hasEditPermission.value && (data?.level === 1 || data?.level === 2)) {
|
||||
// 没有编辑权限,只能查看配置菜单(功能用例只有关联用例,所以配置菜单也不能看)
|
||||
if (data?.type === PlanMinderCollectionType.FUNCTIONAL) {
|
||||
@ -608,19 +612,38 @@
|
||||
selectedAssociateCasesParams.value = { ...param };
|
||||
const node: PlanMinderNode = window.minder.getSelectedNode();
|
||||
let associateType: string = '';
|
||||
if (node && node.data?.type === PlanMinderCollectionType.SCENARIO) {
|
||||
let nodeDataType = node?.data?.type;
|
||||
if (!extraVisible.value) {
|
||||
// 配置抽屉关闭时,选中的节点是测试点下的用例数节点,需要找到测试点节点
|
||||
nodeDataType = node?.parent?.data?.type;
|
||||
}
|
||||
if (nodeDataType === PlanMinderCollectionType.SCENARIO) {
|
||||
associateType = PlanMinderAssociateType.SCENARIO_CASE;
|
||||
} else {
|
||||
associateType = param?.associateType ?? node.data.type;
|
||||
associateType = param?.associateType ?? nodeDataType;
|
||||
}
|
||||
if (extraVisible.value) {
|
||||
// 配置抽屉打开,则选中的节点是测试点节点
|
||||
node.data.associateDTOS = [
|
||||
{
|
||||
...cloneDeep(param),
|
||||
associateType,
|
||||
},
|
||||
];
|
||||
} else if (node.parent?.data) {
|
||||
// 配置抽屉关闭,则选中的节点是测试点下的用例数节点,需要找到测试点节点赋值
|
||||
node.parent.data.associateDTOS = [
|
||||
{
|
||||
...cloneDeep(param),
|
||||
associateType,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
node.data.associateDTOS = [
|
||||
{
|
||||
...cloneDeep(param),
|
||||
associateType,
|
||||
},
|
||||
];
|
||||
|
||||
if (!extraVisible.value) {
|
||||
// 派发SAVE_MINDER事件触发脑图的保存处理
|
||||
minderStore.dispatchEvent(MinderEventName.SAVE_MINDER);
|
||||
}
|
||||
caseAssociateVisible.value = false;
|
||||
}
|
||||
|
||||
@ -645,16 +668,14 @@
|
||||
*/
|
||||
function associateCase() {
|
||||
const node: PlanMinderNode = window.minder.getSelectedNode();
|
||||
activePlanSet.value = node;
|
||||
switchingConfigFormData.value = true;
|
||||
configForm.value = cloneDeep(activePlanSet.value?.data);
|
||||
extraVisible.value = true;
|
||||
if (extraVisible.value) {
|
||||
activePlanSet.value = node;
|
||||
} else if (node.parent) {
|
||||
activePlanSet.value = node.parent as PlanMinderNode;
|
||||
}
|
||||
currentSelectCase.value = (activePlanSet.value?.data.type as unknown as CaseLinkEnum) || CaseLinkEnum.FUNCTIONAL;
|
||||
setCaseSelectedSet();
|
||||
caseAssociateVisible.value = true;
|
||||
nextTick(() => {
|
||||
switchingConfigFormData.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function openCaseAssociateDrawer() {
|
||||
@ -681,6 +702,19 @@
|
||||
() => {
|
||||
if ([MinderEventName.EXPAND, MinderEventName.COLLAPSE].includes(minderStore.event.name)) {
|
||||
setCustomPriorityView(priorityTextMap);
|
||||
} else if (minderStore.event.name === MinderEventName.DROPDOWN_SELECT) {
|
||||
const node: PlanMinderNode = window.minder.getSelectedNode();
|
||||
if (node?.data?.level === 3 && node?.data?.resource?.[0] === resourcePoolTag) {
|
||||
if (node.parent?.data) {
|
||||
node.parent.data.testResourcePoolId = minderStore.event.params;
|
||||
minderStore.dispatchEvent(MinderEventName.SAVE_MINDER);
|
||||
}
|
||||
} else if (node?.data?.level === 3 && node?.data?.resource?.[0] === envTag) {
|
||||
if (node.parent?.data) {
|
||||
node.parent.data.environmentId = minderStore.event.params;
|
||||
minderStore.dispatchEvent(MinderEventName.SAVE_MINDER);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
@ -716,6 +750,13 @@
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* 是否可以显示下拉菜单
|
||||
*/
|
||||
const canShowDropdown = ref(false);
|
||||
const dropdownList = ref<MinderDropdownListItem[]>([]);
|
||||
const checkedVal = ref<string>();
|
||||
|
||||
/**
|
||||
* 处理节点选中
|
||||
* @param node 节点
|
||||
@ -730,8 +771,7 @@
|
||||
selectNodeExecuteMethod.value = undefined;
|
||||
}
|
||||
if (node.data?.level === 3 && node.data?.resource?.[0] === caseCountTag) {
|
||||
window.minder.toggleSelect(node);
|
||||
window.minder.selectById(node.parent?.data?.id);
|
||||
canShowFloatMenu.value = false;
|
||||
if (!inInsertingNode.value && hasEditPermission && hasAnyPermission(['PROJECT_TEST_PLAN:READ+ASSOCIATION'])) {
|
||||
// 新增测试点时不自动弹出关联用例
|
||||
associateCase();
|
||||
@ -740,8 +780,21 @@
|
||||
node.data?.level === 3 &&
|
||||
(node.data?.resource?.[0] === resourcePoolTag || node.data?.resource?.[0] === envTag)
|
||||
) {
|
||||
window.minder.toggleSelect(node);
|
||||
window.minder.selectById(node.parent?.data?.id);
|
||||
canShowFloatMenu.value = false;
|
||||
canShowDropdown.value = !node.parent?.data?.extended; // 继承上级配置的测试点节点不显示下拉菜单
|
||||
if (node.data?.resource?.[0] === resourcePoolTag) {
|
||||
dropdownList.value = resourcePoolOptions.value.map((item) => ({
|
||||
label: item.label || '',
|
||||
value: item.value as string,
|
||||
}));
|
||||
checkedVal.value = node.parent?.data?.testResourcePoolId;
|
||||
} else {
|
||||
dropdownList.value = environmentOptions.value.map((item) => ({
|
||||
label: item.label || '',
|
||||
value: item.value as string,
|
||||
}));
|
||||
checkedVal.value = node.parent?.data?.environmentId;
|
||||
}
|
||||
} else {
|
||||
checkNodeCanShowMenu(node);
|
||||
if (extraVisible.value) {
|
||||
@ -968,7 +1021,7 @@
|
||||
}
|
||||
if (!configFormValidResult) return;
|
||||
loading.value = true;
|
||||
await editPlanMinder(makeMinderParams(extraVisible.value ? window.minder.exportJson() : fullJson));
|
||||
await editPlanMinder(makeMinderParams(window.minder.exportJson()));
|
||||
Message.success(t('common.saveSuccess'));
|
||||
emit('save');
|
||||
clearSelectedCases();
|
||||
|
@ -0,0 +1,64 @@
|
||||
import useMinderStore from '@/store/modules/components/minder-editor';
|
||||
import type { MinderCustomEvent, MinderNodePosition } from '@/store/modules/components/minder-editor/types';
|
||||
import { sleep } from '@/utils';
|
||||
|
||||
import { MinderEventName } from '@/enums/minderEnum';
|
||||
|
||||
import type { MinderJsonNode } from '../props';
|
||||
import { isNodeInMinderView } from '../script/tool/utils';
|
||||
|
||||
export default function useMinderTrigger(
|
||||
handleSelect?: (event: MinderCustomEvent, selectedNodes: MinderJsonNode[]) => void
|
||||
) {
|
||||
const minderStore = useMinderStore();
|
||||
|
||||
const triggerVisible = ref(false);
|
||||
const triggerOffset = ref([0, 0]);
|
||||
|
||||
watch(
|
||||
() => minderStore.event.eventId,
|
||||
async () => {
|
||||
if (window.minder) {
|
||||
let nodePosition: MinderNodePosition | undefined;
|
||||
const selectedNodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
if (minderStore.event.name === MinderEventName.NODE_SELECT) {
|
||||
nodePosition = minderStore.event.nodePosition;
|
||||
if (handleSelect) {
|
||||
handleSelect(minderStore.event, selectedNodes);
|
||||
}
|
||||
}
|
||||
if (selectedNodes.length > 1) {
|
||||
// 多选时隐藏悬浮菜单
|
||||
triggerVisible.value = false;
|
||||
return;
|
||||
}
|
||||
if ([MinderEventName.VIEW_CHANGE, MinderEventName.DRAG_FINISH].includes(minderStore.event.name)) {
|
||||
// 脑图画布移动时,重新计算节点位置
|
||||
await sleep(300); // 拖拽完毕后会有 300ms 的动画,等待动画结束后再计算
|
||||
nodePosition = window.minder.getSelectedNode()?.getRenderBox();
|
||||
}
|
||||
const state = window.editor.fsm.state();
|
||||
if (
|
||||
nodePosition &&
|
||||
isNodeInMinderView(undefined, nodePosition, Math.min(nodePosition.width / 2, 200)) &&
|
||||
state !== 'input'
|
||||
) {
|
||||
// 判断节点在脑图可视区域内且遮挡的节点不超过节点宽度的一半(超过 200px 则按 200px 算)且当前不是编辑名称状态,则显示菜单
|
||||
const nodeDomHeight = nodePosition.height || 0;
|
||||
triggerOffset.value = [nodePosition.x, nodePosition.y + nodeDomHeight + 4]; // trigger显示在节点下方4px处
|
||||
triggerVisible.value = true;
|
||||
} else {
|
||||
triggerVisible.value = false;
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
return {
|
||||
triggerVisible,
|
||||
triggerOffset,
|
||||
};
|
||||
}
|
@ -29,6 +29,7 @@
|
||||
<slot name="extractMenu"></slot>
|
||||
</template>
|
||||
</nodeFloatMenu>
|
||||
<nodeDropdown v-if="props.canShowDropdown" :dropdown-list="props.dropdownList" :checked-val="props.checkedVal" />
|
||||
<batchMenu v-bind="props" />
|
||||
</div>
|
||||
</template>
|
||||
@ -37,6 +38,7 @@
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import batchMenu from '../menu/batchMenu.vue';
|
||||
import nodeDropdown from '../menu/nodeDropdown.vue';
|
||||
import nodeFloatMenu from '../menu/nodeFloatMenu.vue';
|
||||
import minderHeader from './header.vue';
|
||||
import Navigator from './navigator.vue';
|
||||
@ -50,6 +52,7 @@
|
||||
import useEventListener from '../hooks/useMinderEventListener';
|
||||
import {
|
||||
batchMenuProps,
|
||||
dropdownMenuProps,
|
||||
editMenuProps,
|
||||
floatMenuProps,
|
||||
headerProps,
|
||||
@ -72,6 +75,7 @@
|
||||
...tagProps,
|
||||
...priorityProps,
|
||||
...batchMenuProps,
|
||||
...dropdownMenuProps,
|
||||
});
|
||||
const emit = defineEmits<{
|
||||
(e: 'save', data: MinderJson, callback: () => void): void;
|
||||
|
@ -0,0 +1,54 @@
|
||||
<template>
|
||||
<a-dropdown
|
||||
v-model:popup-visible="triggerVisible"
|
||||
class="ms-minder-node-dropdown"
|
||||
:popup-translate="triggerOffset"
|
||||
position="bl"
|
||||
trigger="click"
|
||||
@select="(val) => handleSelect(val)"
|
||||
>
|
||||
<span></span>
|
||||
<template #content>
|
||||
<a-doption
|
||||
v-for="item in props.dropdownList"
|
||||
:key="item.value"
|
||||
v-permission="item.permission || []"
|
||||
:value="item.value"
|
||||
:class="props.checkedVal === item.value ? 'ms-minder-node-dropdown-item--active' : ''"
|
||||
@click="item.onClick && item.onClick()"
|
||||
>
|
||||
<div>{{ item.label }}</div>
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import useMinderStore from '@/store/modules/components/minder-editor';
|
||||
|
||||
import { MinderEventName } from '@/enums/minderEnum';
|
||||
|
||||
import useMinderTrigger from '../hooks/useMinderTrigger';
|
||||
import { dropdownMenuProps } from '../props';
|
||||
|
||||
const props = defineProps(dropdownMenuProps);
|
||||
|
||||
const minderStore = useMinderStore();
|
||||
const { triggerVisible, triggerOffset } = useMinderTrigger();
|
||||
|
||||
function handleSelect(val?: string | number | Record<string, any>) {
|
||||
if (props.checkedVal !== val) {
|
||||
minderStore.dispatchEvent(MinderEventName.DROPDOWN_SELECT, val as string);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.ms-minder-node-dropdown {
|
||||
max-height: 350px;
|
||||
.ms-minder-node-dropdown-item--active {
|
||||
color: rgb(var(--primary-5)) !important;
|
||||
background-color: rgb(var(--primary-1)) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -195,15 +195,13 @@
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useMinderStore from '@/store/modules/components/minder-editor/index';
|
||||
import { MinderNodePosition } from '@/store/modules/components/minder-editor/types';
|
||||
import { sleep } from '@/utils';
|
||||
|
||||
import { MinderEventName } from '@/enums/minderEnum';
|
||||
|
||||
import useMinderOperation from '../hooks/useMinderOperation';
|
||||
import usePriority from '../hooks/useMinderPriority';
|
||||
import useMinderTrigger from '../hooks/useMinderTrigger';
|
||||
import { floatMenuProps, mainEditorProps, MinderJsonNode, priorityColorMap, priorityProps, tagProps } from '../props';
|
||||
import { isNodeInMinderView } from '../script/tool/utils';
|
||||
|
||||
const props = defineProps({
|
||||
...mainEditorProps,
|
||||
@ -227,45 +225,29 @@
|
||||
});
|
||||
const menuPopupOffset = ref<TriggerPopupTranslate>([0, 0]);
|
||||
|
||||
const { triggerOffset, triggerVisible } = useMinderTrigger((event, selectedNodes) => {
|
||||
currentNodeTags.value = event.nodes?.[0].data?.resource || [];
|
||||
if (props.replaceableTags && !props.disabled) {
|
||||
tags.value = props.replaceableTags(selectedNodes);
|
||||
} else {
|
||||
tags.value = [];
|
||||
}
|
||||
});
|
||||
|
||||
watch(
|
||||
() => minderStore.event.eventId,
|
||||
async () => {
|
||||
if (window.minder) {
|
||||
let nodePosition: MinderNodePosition | undefined;
|
||||
const selectedNodes: MinderJsonNode[] = window.minder.getSelectedNodes();
|
||||
if (minderStore.event.name === MinderEventName.NODE_SELECT) {
|
||||
nodePosition = minderStore.event.nodePosition;
|
||||
currentNodeTags.value = minderStore.event.nodes?.[0].data?.resource || [];
|
||||
if (props.replaceableTags && !props.disabled) {
|
||||
tags.value = props.replaceableTags(selectedNodes);
|
||||
} else {
|
||||
tags.value = [];
|
||||
}
|
||||
}
|
||||
if (selectedNodes.length > 1) {
|
||||
// 多选时隐藏悬浮菜单
|
||||
menuVisible.value = false;
|
||||
return;
|
||||
}
|
||||
if ([MinderEventName.VIEW_CHANGE, MinderEventName.DRAG_FINISH].includes(minderStore.event.name)) {
|
||||
// 脑图画布移动时,重新计算节点位置
|
||||
await sleep(300); // 拖拽完毕后会有 300ms 的动画,等待动画结束后再计算
|
||||
nodePosition = window.minder.getSelectedNode()?.getRenderBox();
|
||||
}
|
||||
const state = window.editor.fsm.state();
|
||||
if (
|
||||
nodePosition &&
|
||||
isNodeInMinderView(undefined, nodePosition, Math.min(nodePosition.width / 2, 200)) &&
|
||||
state !== 'input'
|
||||
) {
|
||||
// 判断节点在脑图可视区域内且遮挡的节点不超过节点宽度的一半(超过 200px 则按 200px 算)且当前不是编辑名称状态,则显示菜单
|
||||
const nodeDomHeight = nodePosition.height || 0;
|
||||
menuPopupOffset.value = [nodePosition.x, nodePosition.y + nodeDomHeight + 4]; // 菜单显示在节点下方4px处
|
||||
menuVisible.value = true;
|
||||
} else {
|
||||
menuVisible.value = false;
|
||||
}
|
||||
}
|
||||
() => triggerOffset.value,
|
||||
(val) => {
|
||||
menuPopupOffset.value = [val[0], val[1]];
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => triggerVisible.value,
|
||||
(val) => {
|
||||
menuVisible.value = val;
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
|
@ -39,6 +39,7 @@
|
||||
import {
|
||||
batchMenuProps,
|
||||
delProps,
|
||||
dropdownMenuProps,
|
||||
editMenuProps,
|
||||
floatMenuProps,
|
||||
headerProps,
|
||||
@ -77,6 +78,7 @@
|
||||
...delProps,
|
||||
...viewMenuProps,
|
||||
...batchMenuProps,
|
||||
...dropdownMenuProps,
|
||||
});
|
||||
|
||||
const minderStore = useMinderStore();
|
||||
|
@ -216,6 +216,31 @@ export const floatMenuProps = {
|
||||
default: false,
|
||||
},
|
||||
};
|
||||
|
||||
export interface MinderDropdownListItem {
|
||||
value: string;
|
||||
label: string;
|
||||
permission?: string[];
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
export const dropdownMenuProps = {
|
||||
// 是否显示Dropdown
|
||||
canShowDropdown: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
dropdownList: {
|
||||
type: Array as PropType<MinderDropdownListItem[]>,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
checkedVal: {
|
||||
type: String as PropType<string>,
|
||||
default: undefined,
|
||||
},
|
||||
};
|
||||
export const batchMenuProps = {
|
||||
canShowMoreBatchMenu: {
|
||||
type: Boolean,
|
||||
|
@ -16,6 +16,7 @@ export enum MinderEventName {
|
||||
'MINDER_CHANGED' = 'MINDER_CHANGED', // 脑图更改事件
|
||||
'SAVE_MINDER' = 'SAVE_MINDER', // 脑图保存事件
|
||||
'DRAG_FINISH' = 'DRAG_FINISH', // 脑图节点拖拽结束事件
|
||||
'DROPDOWN_SELECT' = 'DROPDOWN_SELECT', // 下拉菜单选中事件
|
||||
}
|
||||
|
||||
export enum MinderKeyEnum {
|
||||
|
Loading…
Reference in New Issue
Block a user