feat(项目管理): 环境管理-添加http-模块增加包含新增子模块的功能
This commit is contained in:
parent
cbc8cd8a7a
commit
1032b30df4
|
@ -190,3 +190,28 @@ body {
|
|||
@apply mt-0;
|
||||
}
|
||||
}
|
||||
|
||||
/** 更多按钮 **/
|
||||
.ms-more-action-trigger-content {
|
||||
@apply flex items-center;
|
||||
.more-icon-btn {
|
||||
padding: 2px;
|
||||
border-radius: var(--border-radius-mini);
|
||||
&:hover {
|
||||
background-color: rgb(var(--primary-9)) !important;
|
||||
.arco-icon {
|
||||
color: rgb(var(--primary-5)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ms-more-action-trigger-content--focus {
|
||||
.more-icon-btn {
|
||||
@apply !visible;
|
||||
|
||||
background-color: rgb(var(--primary-9));
|
||||
.arco-icon {
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -629,6 +629,9 @@
|
|||
&:hover {
|
||||
background-color: transparent;
|
||||
}
|
||||
.ms-tree-node-extra {
|
||||
background-color: transparent;
|
||||
}
|
||||
* {
|
||||
color: var(--color-text-4) !important;
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ export type MsTreeNodeData = {
|
|||
hideMoreAction?: boolean; // 隐藏更多操作
|
||||
parentId?: string;
|
||||
expanded?: boolean; // 是否展开
|
||||
containChildModule?: boolean; // 包含子模块
|
||||
[key: string]: any;
|
||||
} & TreeNodeData;
|
||||
|
||||
|
|
|
@ -129,27 +129,4 @@
|
|||
color: rgb(var(--danger-6));
|
||||
}
|
||||
}
|
||||
.ms-more-action-trigger-content {
|
||||
@apply flex items-center;
|
||||
.more-icon-btn {
|
||||
padding: 2px;
|
||||
border-radius: var(--border-radius-mini);
|
||||
&:hover {
|
||||
background-color: rgb(var(--primary-9)) !important;
|
||||
.arco-icon {
|
||||
color: rgb(var(--primary-5)) !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ms-more-action-trigger-content--focus {
|
||||
.more-icon-btn {
|
||||
@apply !visible;
|
||||
|
||||
background-color: rgb(var(--primary-9));
|
||||
.arco-icon {
|
||||
color: rgb(var(--primary-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -44,8 +44,9 @@
|
|||
<MsTree
|
||||
v-model:checked-keys="checkedKeys"
|
||||
v-model:halfCheckedKeys="halfCheckedKeys"
|
||||
:selectable="false"
|
||||
v-model:focus-node-key="focusNodeKey"
|
||||
:data="treeData"
|
||||
:selectable="false"
|
||||
:keyword="inputValue"
|
||||
:empty-text="t('common.noData')"
|
||||
:virtual-list-props="virtualListProps"
|
||||
|
@ -57,20 +58,21 @@
|
|||
:checkable="props.treeCheckable"
|
||||
:check-strictly="props.treeCheckStrictly"
|
||||
v-bind="$attrs"
|
||||
@check="checkNode"
|
||||
@check="handleCheck"
|
||||
>
|
||||
<template #title="nodeData">
|
||||
<div
|
||||
class="one-line-text w-full cursor-pointer text-[var(--color-text-1)]"
|
||||
@click="checkNode(checkedKeys, { checked: !checkedKeys.includes(nodeData.id), node: nodeData })"
|
||||
@click="handleCheck(checkedKeys, { checked: !checkedKeys.includes(nodeData.id), node: nodeData })"
|
||||
>
|
||||
{{ nodeData.name }}
|
||||
</div>
|
||||
</template>
|
||||
<template #extra="nodeData">
|
||||
<MsButton
|
||||
v-if="nodeData.children && nodeData.children.length"
|
||||
@click="selectParent(nodeData, !!checkedKeys.includes(nodeData.id))"
|
||||
v-if="nodeData.children && nodeData.children.length && !nodeData.disabled"
|
||||
class="!mr-[8px]"
|
||||
@click="handleSelectCurrent(nodeData)"
|
||||
>
|
||||
{{
|
||||
checkedKeys.includes(nodeData.id)
|
||||
|
@ -78,6 +80,15 @@
|
|||
: t('ms.case.associate.selectCurrent')
|
||||
}}
|
||||
</MsButton>
|
||||
<MoreMenuDropdown
|
||||
v-if="props.showContainChildModule && !nodeData.disabled && nodeData.children && nodeData.children.length"
|
||||
v-model:contain-child-module="nodeData.containChildModule"
|
||||
@handle-contain-child-module="
|
||||
(containChildModule) => handleContainChildModule(nodeData, containChildModule)
|
||||
"
|
||||
@close="resetFocusNodeKey"
|
||||
@open="setFocusKey(nodeData)"
|
||||
/>
|
||||
</template>
|
||||
</MsTree>
|
||||
</template>
|
||||
|
@ -92,6 +103,7 @@
|
|||
import useTreeSelection from '@/components/business/ms-associate-case/useTreeSelection';
|
||||
import MsTree from '@/components/business/ms-tree/index.vue';
|
||||
import type { MsTreeFieldNames, MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||
import MoreMenuDropdown from './moreMenuDropdown.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useSelect from '@/hooks/useSelect';
|
||||
|
@ -101,12 +113,12 @@
|
|||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data: TreeNodeData[];
|
||||
fieldNames?: TreeFieldNames | MsTreeFieldNames;
|
||||
multiple?: boolean;
|
||||
shouldCalculateMaxTag?: boolean;
|
||||
treeCheckStrictly?: boolean;
|
||||
treeCheckable?: boolean;
|
||||
showContainChildModule?: boolean;
|
||||
}>(),
|
||||
{
|
||||
shouldCalculateMaxTag: true,
|
||||
|
@ -126,6 +138,66 @@
|
|||
const { selectedModulesMaps, checkedKeys, halfCheckedKeys, selectParent, checkNode, clearSelector } =
|
||||
useTreeSelection(selectedModuleProps.value);
|
||||
|
||||
/**
|
||||
* 设置子节点的属性值
|
||||
* @param trees 属性数组
|
||||
* @param targetKey 需要匹配的属性值
|
||||
*/
|
||||
function updateChildNodesState(node: MsTreeNodeData, targetKey: keyof MsTreeNodeData, state: boolean) {
|
||||
if (node.children) {
|
||||
node.children.forEach((child: MsTreeNodeData) => {
|
||||
child[targetKey] = state;
|
||||
updateChildNodesState(child, targetKey, state);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function handleCheck(_checkedKeys: Array<string | number>, checkedNodes: MsTreeNodeData) {
|
||||
if (props.showContainChildModule) {
|
||||
const realNode = findNodeByKey<MsTreeNodeData>(treeData.value, checkedNodes.node.id, 'id');
|
||||
if (!realNode) return;
|
||||
if (checkedNodes.checked) {
|
||||
// 父级勾选,且父级“包含新增子模块”勾选,那么下面所有子级:禁用和勾选“包含新增子模块”
|
||||
if (realNode.containChildModule) {
|
||||
updateChildNodesState(realNode, 'containChildModule', true);
|
||||
updateChildNodesState(realNode, 'disabled', true);
|
||||
}
|
||||
} else {
|
||||
// 父级取消勾选,父级和所有子级“包含新增子模块”取消勾选,所有子级取消禁用
|
||||
realNode.containChildModule = false;
|
||||
updateChildNodesState(realNode, 'containChildModule', false);
|
||||
updateChildNodesState(realNode, 'disabled', false);
|
||||
}
|
||||
}
|
||||
checkNode(_checkedKeys, checkedNodes);
|
||||
}
|
||||
|
||||
function handleSelectCurrent(nodeData: MsTreeNodeData) {
|
||||
if (props.showContainChildModule && checkedKeys.value.includes(nodeData.id)) {
|
||||
// 取消当前,“包含新增子模块”取消勾选,下面一层的子级取消禁用
|
||||
const realNode = findNodeByKey<MsTreeNodeData>(treeData.value, nodeData.id, 'id');
|
||||
if (!realNode) return;
|
||||
realNode.containChildModule = false;
|
||||
realNode.children?.forEach((child) => {
|
||||
child.disabled = false;
|
||||
});
|
||||
}
|
||||
selectParent(nodeData, !!checkedKeys.value.includes(nodeData.id));
|
||||
}
|
||||
|
||||
function handleContainChildModule(nodeData: MsTreeNodeData, containChildModule: boolean) {
|
||||
const realNode = findNodeByKey<MsTreeNodeData>(treeData.value, nodeData.id, 'id');
|
||||
if (!realNode) return;
|
||||
realNode.containChildModule = containChildModule;
|
||||
if (containChildModule) {
|
||||
handleCheck(checkedKeys.value, { checked: true, node: realNode });
|
||||
} else {
|
||||
realNode.children?.forEach((child) => {
|
||||
child.disabled = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const skipSelectValueWatch = ref(false);
|
||||
watch(
|
||||
() => selectValue.value,
|
||||
|
@ -170,7 +242,7 @@
|
|||
}
|
||||
);
|
||||
watch(
|
||||
() => props.data,
|
||||
() => treeData.value,
|
||||
() => {
|
||||
if (props.shouldCalculateMaxTag !== false && props.multiple) {
|
||||
calculateMaxTag();
|
||||
|
@ -182,11 +254,11 @@
|
|||
return () => {
|
||||
let treeSelectTooltip = '';
|
||||
const values = Array.isArray(checkedKeys.value) ? checkedKeys.value : [checkedKeys.value];
|
||||
if (props.data) {
|
||||
if (treeData.value) {
|
||||
treeSelectTooltip = values
|
||||
?.map((valueItem: string | number) => {
|
||||
const optItem = findNodeByKey<MsTreeNodeData>(
|
||||
props.data as MsTreeNodeData[],
|
||||
treeData.value as MsTreeNodeData[],
|
||||
valueItem,
|
||||
props?.fieldNames?.key
|
||||
);
|
||||
|
@ -272,6 +344,14 @@
|
|||
buffer: 15, // 缓冲区默认 10 的时候,虚拟滚动的底部 padding 计算有问题
|
||||
};
|
||||
});
|
||||
|
||||
const focusNodeKey = ref<string | number>('');
|
||||
function setFocusKey(node: MsTreeNodeData) {
|
||||
focusNodeKey.value = node.id || '';
|
||||
}
|
||||
function resetFocusNodeKey() {
|
||||
focusNodeKey.value = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
<template>
|
||||
<a-dropdown
|
||||
v-model:popup-visible="visible"
|
||||
class="contain-child-dropdown"
|
||||
position="br"
|
||||
trigger="click"
|
||||
:hide-on-select="false"
|
||||
>
|
||||
<div :class="['ms-more-action-trigger-content', visible ? 'ms-more-action-trigger-content--focus' : '']">
|
||||
<MsButton type="text" size="mini" class="more-icon-btn" @click="visible = !visible">
|
||||
<MsIcon type="icon-icon_more_outlined" size="16" class="text-[var(--color-text-4)]" />
|
||||
</MsButton>
|
||||
</div>
|
||||
<template #content>
|
||||
<a-doption>
|
||||
<a-checkbox
|
||||
v-model="containChildModule"
|
||||
@change="
|
||||
(containChildModule: boolean | (string | number | boolean)[]) =>
|
||||
emit('handleContainChildModule', containChildModule as boolean)
|
||||
"
|
||||
>
|
||||
{{ t('project.environmental.http.containChildModule') }}
|
||||
</a-checkbox>
|
||||
<a-tooltip :content="t('project.environmental.http.containChildModuleTip')" position="br">
|
||||
<MsIcon
|
||||
class="text-[var(--color-text-brand)] hover:text-[rgb(var(--primary-5))]"
|
||||
type="icon-icon-maybe_outlined"
|
||||
/>
|
||||
</a-tooltip>
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'close'): void;
|
||||
(e: 'open'): void;
|
||||
(e: 'handleContainChildModule', containChildModule: boolean): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const visible = ref(false);
|
||||
const containChildModule = defineModel<boolean>('containChildModule', { required: false, default: false });
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
(val) => {
|
||||
if (val) {
|
||||
emit('open');
|
||||
} else {
|
||||
emit('close');
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
|
@ -114,7 +114,7 @@ export interface SelectedModule {
|
|||
// 选中的模块
|
||||
moduleId: string;
|
||||
containChildModule: boolean; // 是否包含新增子模块
|
||||
disabled: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
// 定义-获取环境的模块树参数
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { EnableKeyValueParam, ExecuteConditionProcessor } from '@/models/apiTest/common';
|
||||
import type { SelectedModule } from '@/models/apiTest/management';
|
||||
import { RequestAuthType } from '@/enums/apiEnum';
|
||||
|
||||
export interface EnvListItem {
|
||||
|
@ -154,10 +155,7 @@ export interface HttpForm {
|
|||
condition: string;
|
||||
moduleId: string[];
|
||||
moduleMatchRule: {
|
||||
modules: {
|
||||
moduleId: string;
|
||||
containChildModule: boolean;
|
||||
}[];
|
||||
modules: SelectedModule[];
|
||||
};
|
||||
url: string;
|
||||
pathMatchRule: {
|
||||
|
|
|
@ -107,31 +107,17 @@
|
|||
<span><MsTableMoreAction :list="moreActions" @select="handleMoreActionSelect($event, nodeData)" /></span>
|
||||
</template>
|
||||
</ApiTree> -->
|
||||
<a-tree-select
|
||||
v-model="form.moduleId"
|
||||
:data="envTree"
|
||||
class="w-full"
|
||||
<MsTreeSelect
|
||||
v-model:model-value="form.moduleId"
|
||||
v-model:data="envTree"
|
||||
allow-clear
|
||||
:multiple="true"
|
||||
tree-check-strictly
|
||||
:tree-checkable="true"
|
||||
allow-search
|
||||
:field-names="{
|
||||
title: 'name',
|
||||
key: 'id',
|
||||
children: 'children',
|
||||
}"
|
||||
:filter-tree-node="filterTreeNode"
|
||||
tree-checked-strategy="child"
|
||||
:tree-props="{
|
||||
virtualListProps: {
|
||||
height: 200,
|
||||
},
|
||||
}"
|
||||
>
|
||||
<template #tree-slot-title="node">
|
||||
<a-tooltip :content="`${node.name}`" position="tl">
|
||||
<div class="one-line-text w-[300px]">{{ node.name }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-tree-select>
|
||||
show-contain-child-module
|
||||
:placeholder="t('common.pleaseSelect')"
|
||||
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||
/>
|
||||
</a-form-item>
|
||||
<!-- 路径 -->
|
||||
<a-form-item
|
||||
|
@ -228,13 +214,16 @@
|
|||
import { Message, ValidatedError } from '@arco-design/web-vue';
|
||||
|
||||
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
|
||||
import MsTreeSelect from '@/components/pure/ms-tree-select/index.vue';
|
||||
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
|
||||
|
||||
import { getEnvModules } from '@/api/modules/api-test/management';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { useAppStore } from '@/store';
|
||||
import useProjectEnvStore from '@/store/modules/setting/useProjectEnvStore';
|
||||
import { filterTreeNode, getGenerateId } from '@/utils';
|
||||
import { findNodeByKey, getGenerateId, mapTree } from '@/utils';
|
||||
|
||||
import type { SelectedModule } from '@/models/apiTest/management';
|
||||
import type { ModuleTreeNode } from '@/models/common';
|
||||
import { HttpForm } from '@/models/projectManagement/environmental';
|
||||
import { RequestAuthType } from '@/enums/apiEnum';
|
||||
|
@ -244,7 +233,6 @@
|
|||
const props = defineProps<{
|
||||
currentId: string;
|
||||
isCopy: boolean;
|
||||
moduleTree: ModuleTreeNode[];
|
||||
}>();
|
||||
const appStore = useAppStore();
|
||||
const store = useProjectEnvStore();
|
||||
|
@ -307,6 +295,9 @@
|
|||
|
||||
const visible = defineModel('visible', { required: true, type: Boolean, default: false });
|
||||
|
||||
const envTree = ref<MsTreeNodeData[]>([]);
|
||||
const moduleTree = ref<MsTreeNodeData[]>([]);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
function resetForm() {
|
||||
|
@ -323,7 +314,7 @@
|
|||
modules = form.value.moduleId.map((item) => {
|
||||
return {
|
||||
moduleId: item,
|
||||
containChildModule: false,
|
||||
containChildModule: findNodeByKey<MsTreeNodeData>(envTree.value, item, 'id')?.containChildModule ?? false,
|
||||
};
|
||||
});
|
||||
}
|
||||
|
@ -378,11 +369,26 @@
|
|||
});
|
||||
};
|
||||
|
||||
const envTree = ref<ModuleTreeNode[]>([]);
|
||||
|
||||
const title = ref<string>('');
|
||||
|
||||
function initHttpDetail() {
|
||||
async function initModuleTree(selectedModules?: SelectedModule[]) {
|
||||
try {
|
||||
const res = await getEnvModules({
|
||||
projectId: appStore.currentProjectId,
|
||||
selectedModules,
|
||||
});
|
||||
moduleTree.value = res.moduleTree;
|
||||
store.currentEnvDetailInfo.config.httpConfig.forEach((item) => {
|
||||
if (item.id === props.currentId) {
|
||||
item.moduleMatchRule.modules = res.selectedModules;
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
async function initHttpDetail() {
|
||||
title.value = props.currentId ? t('project.environmental.http.edit') : t('project.environmental.http.add');
|
||||
if (props.isCopy) {
|
||||
title.value = t('project.environmental.http.copy');
|
||||
|
@ -391,6 +397,15 @@
|
|||
const currentItem = store.currentEnvDetailInfo.config.httpConfig.find(
|
||||
(item) => item.id === props.currentId
|
||||
) as HttpForm;
|
||||
await initModuleTree(currentItem.moduleMatchRule.modules);
|
||||
envTree.value = mapTree<ModuleTreeNode>(moduleTree.value, (node) => {
|
||||
return {
|
||||
...node,
|
||||
containChildModule:
|
||||
currentItem.moduleMatchRule.modules.find((item) => item.moduleId === node.id)?.containChildModule ?? false,
|
||||
disabled: !!node.parent?.containChildModule,
|
||||
};
|
||||
});
|
||||
if (currentItem) {
|
||||
const { path, condition } = currentItem.pathMatchRule;
|
||||
const urlPath = currentItem.url.match(/\/\/(.*)/);
|
||||
|
@ -403,21 +418,12 @@
|
|||
};
|
||||
}
|
||||
} else {
|
||||
await initModuleTree();
|
||||
envTree.value = moduleTree.value;
|
||||
resetForm();
|
||||
}
|
||||
}
|
||||
|
||||
async function initModuleTree() {
|
||||
try {
|
||||
const res = await getEnvModules({
|
||||
projectId: appStore.currentProjectId,
|
||||
});
|
||||
envTree.value = res.moduleTree;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => visible.value,
|
||||
(val) => {
|
||||
|
@ -431,10 +437,6 @@
|
|||
visible.value = false;
|
||||
resetForm();
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
initModuleTree();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -68,6 +68,9 @@ export default {
|
|||
'project.environmental.http.uiModuleSelect': 'Select UI Test Module',
|
||||
'project.environmental.http.pathRequired': 'Path is required',
|
||||
'project.environmental.http.pathPlaceholder': 'Please enter the path',
|
||||
'project.environmental.http.containChildModule': 'Include newly added submodules',
|
||||
'project.environmental.http.containChildModuleTip':
|
||||
'Automatically include sub modules added after the selected module',
|
||||
'project.environmental.database.addDatabase': 'Add Database',
|
||||
'project.environmental.database.updateDatabase': 'Update Database {name}',
|
||||
'project.environmental.database.name': 'Database Name',
|
||||
|
|
|
@ -71,6 +71,8 @@ export default {
|
|||
'project.environmental.http.uiModuleSelect': '选择UI测试模块',
|
||||
'project.environmental.http.pathRequired': '路径必填',
|
||||
'project.environmental.http.pathPlaceholder': '请输入路径',
|
||||
'project.environmental.http.containChildModule': '包含新增子模块',
|
||||
'project.environmental.http.containChildModuleTip': '自动包含所选模块后续添加的子模块',
|
||||
'project.environmental.database.title': '数据库',
|
||||
'project.environmental.database.addDatabase': '添加数据源',
|
||||
'project.environmental.database.updateDatabase': '更新数据源{name}',
|
||||
|
|
Loading…
Reference in New Issue