feat(环境管理): 断言文档添加验证项
This commit is contained in:
parent
1dd0eaef55
commit
739fe5f18c
|
@ -14,7 +14,7 @@
|
|||
:columns="jsonPathColumns"
|
||||
:scroll="{ minWidth: '700px' }"
|
||||
:default-param-item="jsonPathDefaultParamItem"
|
||||
@change="handleChange"
|
||||
@change="(data, isInit) => handleChange(data, !!isInit, 'jsonPath')"
|
||||
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
|
||||
>
|
||||
<template #expression="{ record, rowIndex }">
|
||||
|
@ -99,7 +99,7 @@
|
|||
:columns="xPathColumns"
|
||||
:scroll="{ minWidth: '700px' }"
|
||||
:default-param-item="xPathDefaultParamItem"
|
||||
@change="handleChange"
|
||||
@change="(data, isInit) => handleChange(data, !!isInit, 'xPath')"
|
||||
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
|
||||
>
|
||||
<template #expression="{ record, rowIndex }">
|
||||
|
@ -168,38 +168,64 @@
|
|||
</paramsTable>
|
||||
</div>
|
||||
<div v-if="activeTab === 'document'" class="relative mt-[16px]">
|
||||
<paramsTable
|
||||
v-model:params="innerParams.document.data"
|
||||
:selectable="false"
|
||||
:columns="documentColumns"
|
||||
:scroll="{
|
||||
minWidth: '700px',
|
||||
}"
|
||||
:height-used="580"
|
||||
:default-param-item="documentDefaultParamItem"
|
||||
:span-method="documentSpanMethod"
|
||||
@change="handleChange"
|
||||
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
|
||||
>
|
||||
<template #operationPre="{ record }">
|
||||
<a-tooltip v-if="['object', 'array'].includes(record.paramType)" :content="t('ms.assertion.addChild')">
|
||||
<div
|
||||
class="flex h-[24px] w-[24px] cursor-pointer items-center justify-center rounded text-[rgb(var(--primary-5))] hover:bg-[rgb(var(--primary-1))]"
|
||||
@click="addChild(record)"
|
||||
<div class="text-[var(--color-text-1)]">
|
||||
{{ t('ms.assertion.responseContentType') }}
|
||||
</div>
|
||||
<a-radio-group v-model:model-value="innerParams.document.responseFormat" class="mt-[16px]" size="small">
|
||||
<a-radio value="JSON">JSON</a-radio>
|
||||
<a-radio value="XML">XML</a-radio>
|
||||
</a-radio-group>
|
||||
<div class="mt-[16px]">
|
||||
<a-checkbox v-model:model-value="innerParams.document.followApi">
|
||||
<span class="text-[var(--color-text-1)]">{{ t('ms.assertion.followApi') }}</span>
|
||||
</a-checkbox>
|
||||
</div>
|
||||
<div class="mt-[16px]">
|
||||
<paramsTable
|
||||
v-model:params="innerParams.document.data"
|
||||
:selectable="false"
|
||||
:columns="documentColumns"
|
||||
:scroll="{
|
||||
minWidth: '700px',
|
||||
}"
|
||||
:height-used="580"
|
||||
:default-param-item="documentDefaultParamItem"
|
||||
:span-method="documentSpanMethod"
|
||||
is-tree-table
|
||||
@tree-delete="deleteAllParam"
|
||||
@change="(data, isInit) => handleChange(data, !!isInit, 'document')"
|
||||
>
|
||||
<template #matchValueDelete="{ record }">
|
||||
<icon-minus-circle
|
||||
v-if="showDeleteSingle && (record.rowSpan > 1 || record.groupId)"
|
||||
class="ml-[8px] cursor-pointer text-[var(--color-text-4)]"
|
||||
size="20"
|
||||
@click="deleteSingleParam(record)"
|
||||
/>
|
||||
</template>
|
||||
<template #operationPre="{ record }">
|
||||
<a-tooltip v-if="['object', 'array'].includes(record.paramType)" :content="t('ms.assertion.addChild')">
|
||||
<div
|
||||
class="flex h-[24px] w-[24px] cursor-pointer items-center justify-center rounded text-[rgb(var(--primary-5))] hover:bg-[rgb(var(--primary-1))]"
|
||||
@click="addChild(record)"
|
||||
>
|
||||
<icon-plus size="16" />
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-tooltip
|
||||
v-else-if="['string', 'integer', 'number', 'boolean'].includes(record.paramType) && record.id !== rootId"
|
||||
:content="t('ms.assertion.validateChild')"
|
||||
>
|
||||
<icon-plus size="16" />
|
||||
</div>
|
||||
</a-tooltip>
|
||||
<a-tooltip v-else :content="t('ms.assertion.validateChild')">
|
||||
<div
|
||||
class="flex h-[24px] w-[24px] cursor-pointer items-center justify-center rounded text-[rgb(var(--primary-5))] hover:bg-[rgb(var(--primary-1))]"
|
||||
@click="addValidateChild(record)"
|
||||
>
|
||||
<icon-bookmark size="16" />
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</paramsTable>
|
||||
<div
|
||||
class="flex h-[24px] w-[24px] cursor-pointer items-center justify-center rounded text-[rgb(var(--primary-5))] hover:bg-[rgb(var(--primary-1))]"
|
||||
@click="addValidateChild(record)"
|
||||
>
|
||||
<icon-bookmark size="16" />
|
||||
</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</paramsTable>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="activeTab === 'regular'" class="mt-[16px]">
|
||||
<paramsTable
|
||||
|
@ -208,7 +234,7 @@
|
|||
:columns="xPathColumns"
|
||||
:scroll="{ minWidth: '700px' }"
|
||||
:default-param-item="xPathDefaultParamItem"
|
||||
@change="handleChange"
|
||||
@change="(data, isInit) => handleChange(data, !!isInit, 'regular')"
|
||||
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
|
||||
>
|
||||
<template #expression="{ record, rowIndex }">
|
||||
|
@ -301,7 +327,14 @@
|
|||
import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import { findFirstByGroupId, insertTreeByCurrentId, insertTreeByGroupId } from '@/utils/tree';
|
||||
import {
|
||||
countNodes,
|
||||
countNodesByGroupId,
|
||||
deleteNodeById,
|
||||
deleteNodesByGroupId,
|
||||
findFirstByGroupId,
|
||||
insertNode,
|
||||
} from '@/utils/tree';
|
||||
|
||||
import {
|
||||
ExecuteConditionProcessor,
|
||||
|
@ -332,6 +365,7 @@
|
|||
(e: 'change'): void;
|
||||
}>();
|
||||
const { t } = useI18n();
|
||||
const rootId = 0; // 1970-01-01 00:00:00 UTC
|
||||
|
||||
// const innerParams = defineModel<Param>('modelValue', {
|
||||
// default: {
|
||||
|
@ -350,7 +384,17 @@
|
|||
jsonPath: [],
|
||||
xPath: { responseFormat: 'XML', data: [] },
|
||||
document: {
|
||||
data: [],
|
||||
data: [
|
||||
{
|
||||
id: rootId,
|
||||
paramsName: 'root',
|
||||
mustInclude: false,
|
||||
typeChecking: false,
|
||||
paramType: 'object',
|
||||
matchCondition: '',
|
||||
matchValue: '',
|
||||
},
|
||||
],
|
||||
responseFormat: 'JSON',
|
||||
followApi: false,
|
||||
},
|
||||
|
@ -439,8 +483,19 @@
|
|||
matchValue: '',
|
||||
enable: true,
|
||||
};
|
||||
const handleChange = () => {
|
||||
emit('change');
|
||||
const handleChange = (data: any[], isInit: boolean, type: string) => {
|
||||
if (isInit) {
|
||||
return;
|
||||
}
|
||||
if (type === 'jsonPath') {
|
||||
innerParams.value.jsonPath = data;
|
||||
} else if (type === 'xPath') {
|
||||
innerParams.value.xPath.data = data;
|
||||
} else if (type === 'document') {
|
||||
innerParams.value.document.data = data;
|
||||
} else if (type === 'regular') {
|
||||
innerParams.value.regular = data;
|
||||
}
|
||||
};
|
||||
function handleExpressionChange(rowIndex: number) {
|
||||
extractParamsTableRef.value?.addTableLine(rowIndex);
|
||||
|
@ -481,6 +536,7 @@
|
|||
title: 'ms.assertion.paramsName',
|
||||
dataIndex: 'paramsName',
|
||||
slotName: 'key',
|
||||
addLineDisabled: true,
|
||||
width: 300,
|
||||
},
|
||||
{
|
||||
|
@ -506,6 +562,7 @@
|
|||
showInTable: true,
|
||||
showDrag: true,
|
||||
columnSelectorDisabled: true,
|
||||
addLineDisabled: true,
|
||||
typeOptions: [
|
||||
{ label: 'object', value: 'object' },
|
||||
{ label: 'array', value: 'array' },
|
||||
|
@ -516,24 +573,27 @@
|
|||
],
|
||||
titleSlotName: 'typeTitle',
|
||||
typeTitleTooltip: t('project.environmental.paramTypeTooltip'),
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'ms.assertion.matchCondition',
|
||||
dataIndex: 'matchCondition',
|
||||
slotName: 'matchCondition',
|
||||
options: statusCodeOptions,
|
||||
width: 120,
|
||||
},
|
||||
{
|
||||
title: 'ms.assertion.matchValue',
|
||||
dataIndex: 'matchValue',
|
||||
slotName: 'matchValue',
|
||||
hasRequired: true,
|
||||
showDelete: true,
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
slotName: 'operation',
|
||||
fixed: 'right',
|
||||
width: 60,
|
||||
width: 100,
|
||||
align: 'right',
|
||||
},
|
||||
];
|
||||
|
@ -626,29 +686,65 @@
|
|||
if (record.groupId) {
|
||||
// 子项点击,找到父级
|
||||
const parent = innerParams.value.document.data.find((item: any) => item.id === record.groupId);
|
||||
insertTreeByCurrentId(innerParams.value.document.data, record.id, {
|
||||
...documentDefaultParamItem,
|
||||
id: new Date().getTime(),
|
||||
groupId: parent ? parent.id : record.groupId,
|
||||
});
|
||||
insertNode(
|
||||
innerParams.value.document.data,
|
||||
{ id: record.id, groupId: record.groupId },
|
||||
{
|
||||
...record,
|
||||
id: new Date().getTime(),
|
||||
groupId: parent ? parent.id : record.groupId,
|
||||
matchValue: '',
|
||||
matchCondition: '',
|
||||
}
|
||||
);
|
||||
if (parent) {
|
||||
parent.rowSpan = parent.rowSpan ? parent.rowSpan + 1 : 2;
|
||||
} else {
|
||||
// 找到第一个子节点
|
||||
const fisrtChildNode = findFirstByGroupId(innerParams.value.document.data, record.groupId);
|
||||
if (fisrtChildNode) {
|
||||
fisrtChildNode.rowSpan = fisrtChildNode.rowSpan ? fisrtChildNode.rowSpan + 1 : 2;
|
||||
fisrtChildNode.rowSpan = fisrtChildNode.rowSpan
|
||||
? fisrtChildNode.rowSpan + 1
|
||||
: countNodesByGroupId(innerParams.value.document.data, record.groupId) + 1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 父级点击,直接添加到末尾
|
||||
insertTreeByCurrentId(innerParams.value.document.data, record.id, {
|
||||
...documentDefaultParamItem,
|
||||
id: new Date().getTime(),
|
||||
groupId: record.id,
|
||||
});
|
||||
record.rowSpan = record.rowSpan ? record.rowSpan + 1 : 2;
|
||||
// 父级点击
|
||||
insertNode(
|
||||
innerParams.value.document.data,
|
||||
{ id: record.id, groupId: record.groupId },
|
||||
{
|
||||
...record,
|
||||
id: new Date().getTime(),
|
||||
groupId: record.id,
|
||||
matchValue: '',
|
||||
matchCondition: '',
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
const showDeleteSingle = computed(() => {
|
||||
return countNodes(innerParams.value.document.data) > 1;
|
||||
});
|
||||
const deleteSingleParam = (record: Record<string, any>) => {
|
||||
deleteNodeById(innerParams.value.document.data, record.id);
|
||||
};
|
||||
const deleteAllParam = (record: Record<string, any>) => {
|
||||
if (record.groupId) {
|
||||
// 验证子项,根据groupId删除
|
||||
deleteNodesByGroupId(innerParams.value.document.data, record.groupId);
|
||||
} else if (record.rowspan > 2) {
|
||||
// 验证主体, 根据主体的id 作为groupId删除
|
||||
deleteNodesByGroupId(innerParams.value.document.data, record.id);
|
||||
// 删除本体
|
||||
deleteNodeById(innerParams.value.document.data, record.id);
|
||||
} else {
|
||||
// 删除本体
|
||||
deleteNodeById(innerParams.value.document.data, record.id);
|
||||
}
|
||||
};
|
||||
|
||||
const documentSpanMethod = (data: {
|
||||
record: TableData;
|
||||
column: TableColumnData | TableOperationColumn;
|
||||
|
@ -660,25 +756,12 @@
|
|||
const { record, column } = data;
|
||||
const currentColumn = column as TableColumnData;
|
||||
if (record.rowSpan > 1) {
|
||||
if (currentColumn.slotName === 'key') {
|
||||
const mergeColumns = ['key', 'mustContain', 'typeChecking', 'paramType', 'operation'];
|
||||
if (mergeColumns.includes(currentColumn.title as string)) {
|
||||
return {
|
||||
rowspan: record.rowSpan,
|
||||
colspan: 1,
|
||||
};
|
||||
}
|
||||
if (currentColumn.slotName === 'operation') {
|
||||
return {
|
||||
rowspan: record.rowSpan,
|
||||
colspan: 1,
|
||||
};
|
||||
}
|
||||
if (currentColumn.slotName === 'mustContain') {
|
||||
return {
|
||||
rowspan: record.rowSpan,
|
||||
colspan: 3,
|
||||
};
|
||||
}
|
||||
}
|
||||
return { rowspan: record.rowSpan, colspan: 1 };
|
||||
};
|
||||
</script>
|
||||
|
|
|
@ -15,7 +15,8 @@ export default {
|
|||
'ms.assertion.regular': '正则',
|
||||
'ms.assertion.script': '脚本',
|
||||
'ms.assertion.expression': '表达式',
|
||||
'ms.assertion.responseContentType': '响应内容类型',
|
||||
'ms.assertion.responseContentType': '响应内容格式',
|
||||
'ms.assertion.followApi': '跟随API定义',
|
||||
'ms.assertion.paramsName': '参数名',
|
||||
'ms.assertion.mustInclude': '必含',
|
||||
'ms.assertion.typeChecking': '类型校验',
|
||||
|
|
|
@ -15,7 +15,7 @@ const useProjectEnvStore = defineStore(
|
|||
'projectEnv',
|
||||
() => {
|
||||
// 项目中的key值
|
||||
const currentId = ref<string>(ALL_PARAM);
|
||||
const currentId = ref<string>('1052215449649153');
|
||||
// 项目组选中的key值
|
||||
const currentGroupId = ref<string>('');
|
||||
const currentEnvDetailInfo = ref<EnvDetailItem>({ projectId: '', name: '', config: {} }); // 当前选中的环境详情
|
||||
|
|
|
@ -1,72 +1,159 @@
|
|||
interface TreeData {
|
||||
interface Tree {
|
||||
id: number;
|
||||
groupId?: number;
|
||||
children?: TreeData[];
|
||||
children?: Tree[];
|
||||
[key: string]: any;
|
||||
}
|
||||
|
||||
export function insertTreeByCurrentId(tree: TreeData[], currentId: number, newData: TreeData) {
|
||||
const stack: Array<{ node: TreeData; parent: TreeData | null }> = tree.map((node) => ({ node, parent: null }));
|
||||
|
||||
while (stack.length > 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const { node, parent } = stack.pop()!;
|
||||
|
||||
if (parent && parent.children) {
|
||||
const index = parent.children.findIndex((child) => child.id === currentId);
|
||||
if (index !== -1) {
|
||||
// 在目标节点后面插入新数据
|
||||
parent.children.splice(index + 1, 0, newData);
|
||||
return true;
|
||||
// 插入节点
|
||||
export function insertNode(tree: Tree[], currentNode: Tree, nodeToInsert: Tree): boolean {
|
||||
// 查找并插入节点的辅助函数
|
||||
function findAndInsert(node: Tree, parentNode: Tree | null = null): boolean {
|
||||
if (node.id === currentNode.id) {
|
||||
// 如果 parentNode 为 null,说明当前节点是根节点
|
||||
if (parentNode === null) {
|
||||
const index = tree.findIndex((n) => n.id === currentNode.id);
|
||||
// 根据 currentNode 的 groupId 是否存在,决定插入位置
|
||||
if (currentNode.groupId !== undefined) {
|
||||
// 插入到当前节点的后面
|
||||
tree.splice(index + 1, 0, nodeToInsert);
|
||||
} else {
|
||||
// 插入到同级节点的末尾
|
||||
tree.push(nodeToInsert);
|
||||
}
|
||||
} else {
|
||||
// 当前节点不是根节点
|
||||
const index = parentNode.children?.findIndex((n) => n.id === currentNode.id) ?? -1;
|
||||
// 根据 currentNode 的 groupId 是否存在,决定插入位置
|
||||
if (currentNode.groupId !== undefined) {
|
||||
// 插入到当前节点的后面
|
||||
parentNode.children?.splice(index + 1, 0, nodeToInsert);
|
||||
} else {
|
||||
// 插入到同级节点的末尾
|
||||
parentNode.children?.push(nodeToInsert);
|
||||
}
|
||||
}
|
||||
return true; // 插入成功
|
||||
}
|
||||
|
||||
if (node.children) {
|
||||
node.children.forEach((child) => {
|
||||
stack.push({ node: child, parent: node });
|
||||
});
|
||||
}
|
||||
// 递归搜索子节点
|
||||
node.children?.forEach((child) => {
|
||||
if (findAndInsert(child, node)) {
|
||||
return true; // 如果在子树中插入成功,提前结束
|
||||
}
|
||||
});
|
||||
|
||||
return false; // 未找到匹配的节点或插入失败
|
||||
}
|
||||
return false;
|
||||
// 从根节点开始搜索并尝试插入
|
||||
return tree.some((node) => findAndInsert(node));
|
||||
}
|
||||
|
||||
export function insertTreeByGroupId(tree: TreeData[], currentId: number, newData: TreeData) {
|
||||
const stack: Array<{ node: TreeData; parent: TreeData | null }> = tree.map((node) => ({ node, parent: null }));
|
||||
// 根据 groupId 查找第一个匹配的节点
|
||||
export function findFirstByGroupId(tree: Tree[], groupId: number): Tree | null {
|
||||
const foundNode = tree.find((node) => node.groupId === groupId); // Use array.find() method to find the first node with matching groupId
|
||||
if (foundNode) {
|
||||
return foundNode;
|
||||
}
|
||||
|
||||
while (stack.length > 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const { node, parent } = stack.pop()!;
|
||||
|
||||
if (parent && parent.children) {
|
||||
const index = parent.children.findIndex((child) => child.groupId === currentId);
|
||||
if (index !== -1) {
|
||||
// 在目标节点后面插入新数据
|
||||
parent.children.splice(index + 1, 0, newData);
|
||||
return true;
|
||||
tree.forEach((node) => {
|
||||
if (node.children) {
|
||||
const found = findFirstByGroupId(node.children, groupId);
|
||||
if (found) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (node.children) {
|
||||
node.children.forEach((child) => {
|
||||
stack.push({ node: child, parent: node });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function findFirstByGroupId(tree: TreeData[], groupId: number): TreeData | null {
|
||||
const queue: TreeData[] = [...tree];
|
||||
while (queue.length > 0) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const node = queue.shift()!; // 取出队列的第一个元素
|
||||
if (node.groupId === groupId) {
|
||||
return node; // 如果匹配,返回当前节点
|
||||
}
|
||||
if (node.children) {
|
||||
queue.push(...node.children);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
// 根据id删除node
|
||||
export function deleteNodeById(tree: Tree[], nodeId: number): void {
|
||||
// 查找并删除节点的辅助函数
|
||||
function findAndDelete(node: Tree, parentNode: Tree | null = null): boolean {
|
||||
// 如果当前节点就是要删除的节点
|
||||
if (node.id === nodeId) {
|
||||
if (parentNode) {
|
||||
// 如果存在父节点,从父节点的 children 中删除当前节点
|
||||
parentNode.children = parentNode.children?.filter((child) => child.id !== nodeId) ?? [];
|
||||
} else {
|
||||
// 如果不存在父节点,说明当前节点是根节点,直接从树中删除
|
||||
const index = tree.findIndex((n) => n.id === nodeId);
|
||||
if (index !== -1) {
|
||||
tree.splice(index, 1);
|
||||
}
|
||||
}
|
||||
return true; // 删除成功
|
||||
}
|
||||
|
||||
// 递归搜索子节点
|
||||
if (node.children) {
|
||||
for (let i = 0; i < node.children.length; i++) {
|
||||
if (findAndDelete(node.children[i], node)) {
|
||||
return true; // 如果在子树中删除成功,提前结束
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false; // 未找到匹配的节点或删除失败
|
||||
}
|
||||
// 从根节点开始搜索并尝试删除
|
||||
tree.slice().forEach((rootNode) => {
|
||||
findAndDelete(rootNode);
|
||||
});
|
||||
}
|
||||
|
||||
// 统计整棵树的节点数
|
||||
export function countNodes(tree: Tree[]): number {
|
||||
// 统计节点数的辅助函数
|
||||
function count(node: Tree): number {
|
||||
// 计算当前节点的子节点数
|
||||
let childCount = 0;
|
||||
if (node.children) {
|
||||
childCount = node.children.reduce((acc, child) => acc + count(child), 0);
|
||||
}
|
||||
// 返回当前节点加上子节点的总数
|
||||
return 1 + childCount; // 当前节点自身也算一个节点
|
||||
}
|
||||
|
||||
// 对树的每个根节点调用 count 函数,并累加结果
|
||||
return tree.reduce((acc, rootNode) => acc + count(rootNode), 0);
|
||||
}
|
||||
|
||||
// 根据 groupId 统计元素数量
|
||||
export function countNodesByGroupId(tree: Tree[], targetGroupId: number): number {
|
||||
// 统计符合条件的节点数的辅助函数
|
||||
function count(node: Tree): number {
|
||||
// 检查当前节点的 groupId 是否符合条件
|
||||
const matches = node.groupId === targetGroupId ? 1 : 0;
|
||||
// 计算当前节点的子节点中符合条件的节点数
|
||||
let childMatches = 0;
|
||||
if (node.children) {
|
||||
childMatches = node.children.reduce((acc, child) => acc + count(child), 0);
|
||||
}
|
||||
// 返回当前节点和子节点中符合条件的节点总数
|
||||
return matches + childMatches;
|
||||
}
|
||||
|
||||
// 对树的每个根节点调用 count 函数,并累加结果
|
||||
return tree.reduce((acc, rootNode) => acc + count(rootNode), 0);
|
||||
}
|
||||
|
||||
// 根据 groupId 删除所有符合条件的节点
|
||||
export function deleteNodesByGroupId(tree: Tree[], targetGroupId: number): void {
|
||||
// 递归删除符合条件的节点的辅助函数
|
||||
function deleteMatchingNodes(nodes: Tree[]): Tree[] {
|
||||
return nodes
|
||||
.map((node) => {
|
||||
// 先处理子节点
|
||||
if (node.children) {
|
||||
node.children = deleteMatchingNodes(node.children);
|
||||
}
|
||||
return node;
|
||||
})
|
||||
.filter((node) => node.groupId !== targetGroupId); // 过滤掉符合条件的节点
|
||||
}
|
||||
|
||||
// 对树的每个根节点调用 deleteMatchingNodes 函数,并更新树
|
||||
tree.splice(0, tree.length, ...deleteMatchingNodes(tree));
|
||||
}
|
||||
|
|
|
@ -81,7 +81,7 @@
|
|||
:placeholder="t('apiTestDebug.paramNamePlaceholder')"
|
||||
class="param-input"
|
||||
:max-length="255"
|
||||
@input="() => addTableLine(rowIndex)"
|
||||
@input="() => addTableLine(rowIndex, columnConfig.addLineDisabled)"
|
||||
/>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
@ -105,7 +105,7 @@
|
|||
v-model:model-value="record.paramType"
|
||||
:options="columnConfig.typeOptions || []"
|
||||
class="param-input w-full"
|
||||
@change="(val) => handleTypeChange(val, record, rowIndex)"
|
||||
@change="(val) => handleTypeChange(val, record, rowIndex, columnConfig.addLineDisabled)"
|
||||
/>
|
||||
</template>
|
||||
<template #expressionType="{ record, columnConfig, rowIndex }">
|
||||
|
@ -251,7 +251,7 @@
|
|||
/>
|
||||
</template>
|
||||
<template #operation="{ record, rowIndex, columnConfig }">
|
||||
<div class="flex flex-row items-center">
|
||||
<div class="flex flex-row items-center" :class="{ 'justify-end': columnConfig.align === 'right' }">
|
||||
<a-switch
|
||||
v-if="columnConfig.hasDisable"
|
||||
v-model:model-value="record.disable"
|
||||
|
@ -280,12 +280,17 @@
|
|||
</div>
|
||||
</template>
|
||||
</a-trigger>
|
||||
|
||||
<icon-minus-circle
|
||||
v-if="paramsLength > 1 && rowIndex !== paramsLength - 1"
|
||||
v-if="props.isTreeTable && record.id !== 0"
|
||||
class="ml-[8px] cursor-pointer text-[var(--color-text-4)]"
|
||||
size="20"
|
||||
@click="deleteParam(rowIndex)"
|
||||
@click="deleteParam(record, rowIndex)"
|
||||
/>
|
||||
<icon-minus-circle
|
||||
v-else-if="paramsLength > 1 && rowIndex !== paramsLength - 1"
|
||||
class="ml-[8px] cursor-pointer text-[var(--color-text-4)]"
|
||||
size="20"
|
||||
@click="deleteParam(record, rowIndex)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -300,22 +305,25 @@
|
|||
</a-select>
|
||||
</template>
|
||||
<template #matchValue="{ record, rowIndex, columnConfig }">
|
||||
<a-tooltip
|
||||
v-if="columnConfig.hasRequired"
|
||||
:content="t(record.required ? 'apiTestDebug.paramRequired' : 'apiTestDebug.paramNotRequired')"
|
||||
>
|
||||
<MsButton
|
||||
type="icon"
|
||||
:class="[
|
||||
record.required ? '!text-[rgb(var(--danger-5))]' : '!text-[var(--color-text-brand)]',
|
||||
'!mr-[4px] !p-[4px]',
|
||||
]"
|
||||
@click="toggleRequired(record, rowIndex)"
|
||||
<div class="flex flex-row items-center justify-between">
|
||||
<a-tooltip
|
||||
v-if="columnConfig.hasRequired"
|
||||
:content="t(record.required ? 'apiTestDebug.paramRequired' : 'apiTestDebug.paramNotRequired')"
|
||||
>
|
||||
<div>*</div>
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<a-input v-model="record.matchValue" class="param-input" />
|
||||
<MsButton
|
||||
type="icon"
|
||||
:class="[
|
||||
record.required ? '!text-[rgb(var(--danger-5))]' : '!text-[var(--color-text-brand)]',
|
||||
'!mr-[4px] !p-[4px]',
|
||||
]"
|
||||
@click="toggleRequired(record, rowIndex)"
|
||||
>
|
||||
<div>*</div>
|
||||
</MsButton>
|
||||
</a-tooltip>
|
||||
<a-input v-model="record.matchValue" class="param-input" />
|
||||
<slot name="matchValueDelete" v-bind="{ record, rowIndex, columnConfig }"></slot>
|
||||
</div>
|
||||
</template>
|
||||
<template #project="{ record, columnConfig, rowIndex }">
|
||||
<a-select
|
||||
|
@ -418,6 +426,7 @@
|
|||
hasDisable?: boolean; // 用于 operation 列区分是否有 enable 开关
|
||||
moreAction?: ActionsItem[]; // 用于 operation 列更多操作按钮配置
|
||||
format?: RequestBodyFormat; // 用于 operation 列区分是否有请求体格式选择器
|
||||
addLineDisabled?: boolean; // 用于 是否禁用添加新行
|
||||
};
|
||||
|
||||
const props = withDefaults(
|
||||
|
@ -440,6 +449,7 @@
|
|||
showSelectorAll?: boolean; // 是否显示全选
|
||||
isSimpleSetting?: boolean; // 是否简单Column设置
|
||||
response?: string; // 响应内容
|
||||
isTreeTable?: boolean; // 是否树形表格
|
||||
spanMethod?: (data: {
|
||||
record: TableData;
|
||||
column: TableColumnData | TableOperationColumn;
|
||||
|
@ -474,6 +484,7 @@
|
|||
(e: 'change', data: any[], isInit?: boolean): void; // 都触发这个事件以通知父组件参数数组被更改
|
||||
(e: 'moreActionSelect', event: ActionsItem, record: Record<string, any>): void;
|
||||
(e: 'projectChange', projectId: string): void;
|
||||
(e: 'treeDelete', record: Record<string, any>): void;
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
|
@ -537,7 +548,11 @@
|
|||
|
||||
const paramsLength = computed(() => propsRes.value.data.length);
|
||||
|
||||
function deleteParam(rowIndex: number) {
|
||||
function deleteParam(record: Record<string, any>, rowIndex: number) {
|
||||
if (props.isTreeTable) {
|
||||
emit('treeDelete', record);
|
||||
return;
|
||||
}
|
||||
propsRes.value.data.splice(rowIndex, 1);
|
||||
emit('change', propsRes.value.data);
|
||||
}
|
||||
|
@ -611,7 +626,10 @@
|
|||
* @param key 当前列的 key
|
||||
* @param isForce 是否强制添加
|
||||
*/
|
||||
function addTableLine(rowIndex: number) {
|
||||
function addTableLine(rowIndex: number, addLineDisabled?: boolean) {
|
||||
if (addLineDisabled) {
|
||||
return;
|
||||
}
|
||||
if (rowIndex === propsRes.value.data.length - 1) {
|
||||
// 最后一行的更改才会触发添加新一行
|
||||
const id = new Date().getTime().toString();
|
||||
|
@ -741,9 +759,10 @@
|
|||
function handleTypeChange(
|
||||
val: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[],
|
||||
record: Record<string, any>,
|
||||
rowIndex: number
|
||||
rowIndex: number,
|
||||
addLineDisabled?: boolean
|
||||
) {
|
||||
addTableLine(rowIndex);
|
||||
addTableLine(rowIndex, addLineDisabled);
|
||||
// 根据参数类型自动推断 Content-Type 类型
|
||||
if (record.contentType) {
|
||||
if (val === 'file') {
|
||||
|
@ -754,6 +773,7 @@
|
|||
record.contentType = RequestContentTypeEnum.TEXT;
|
||||
}
|
||||
}
|
||||
emit('change', propsRes.value.data);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in New Issue