feat(环境管理): 断言文档添加验证项

This commit is contained in:
RubyLiu 2024-02-26 18:22:05 +08:00 committed by 刘瑞斌
parent 1dd0eaef55
commit 739fe5f18c
5 changed files with 340 additions and 149 deletions

View File

@ -14,7 +14,7 @@
:columns="jsonPathColumns" :columns="jsonPathColumns"
:scroll="{ minWidth: '700px' }" :scroll="{ minWidth: '700px' }"
:default-param-item="jsonPathDefaultParamItem" :default-param-item="jsonPathDefaultParamItem"
@change="handleChange" @change="(data, isInit) => handleChange(data, !!isInit, 'jsonPath')"
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)" @more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
> >
<template #expression="{ record, rowIndex }"> <template #expression="{ record, rowIndex }">
@ -99,7 +99,7 @@
:columns="xPathColumns" :columns="xPathColumns"
:scroll="{ minWidth: '700px' }" :scroll="{ minWidth: '700px' }"
:default-param-item="xPathDefaultParamItem" :default-param-item="xPathDefaultParamItem"
@change="handleChange" @change="(data, isInit) => handleChange(data, !!isInit, 'xPath')"
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)" @more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
> >
<template #expression="{ record, rowIndex }"> <template #expression="{ record, rowIndex }">
@ -168,6 +168,19 @@
</paramsTable> </paramsTable>
</div> </div>
<div v-if="activeTab === 'document'" class="relative mt-[16px]"> <div v-if="activeTab === 'document'" class="relative mt-[16px]">
<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 <paramsTable
v-model:params="innerParams.document.data" v-model:params="innerParams.document.data"
:selectable="false" :selectable="false"
@ -178,9 +191,18 @@
:height-used="580" :height-used="580"
:default-param-item="documentDefaultParamItem" :default-param-item="documentDefaultParamItem"
:span-method="documentSpanMethod" :span-method="documentSpanMethod"
@change="handleChange" is-tree-table
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)" @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 }"> <template #operationPre="{ record }">
<a-tooltip v-if="['object', 'array'].includes(record.paramType)" :content="t('ms.assertion.addChild')"> <a-tooltip v-if="['object', 'array'].includes(record.paramType)" :content="t('ms.assertion.addChild')">
<div <div
@ -190,7 +212,10 @@
<icon-plus size="16" /> <icon-plus size="16" />
</div> </div>
</a-tooltip> </a-tooltip>
<a-tooltip v-else :content="t('ms.assertion.validateChild')"> <a-tooltip
v-else-if="['string', 'integer', 'number', 'boolean'].includes(record.paramType) && record.id !== rootId"
:content="t('ms.assertion.validateChild')"
>
<div <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))]" 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)" @click="addValidateChild(record)"
@ -201,6 +226,7 @@
</template> </template>
</paramsTable> </paramsTable>
</div> </div>
</div>
<div v-if="activeTab === 'regular'" class="mt-[16px]"> <div v-if="activeTab === 'regular'" class="mt-[16px]">
<paramsTable <paramsTable
v-model:params="innerParams.regular" v-model:params="innerParams.regular"
@ -208,7 +234,7 @@
:columns="xPathColumns" :columns="xPathColumns"
:scroll="{ minWidth: '700px' }" :scroll="{ minWidth: '700px' }"
:default-param-item="xPathDefaultParamItem" :default-param-item="xPathDefaultParamItem"
@change="handleChange" @change="(data, isInit) => handleChange(data, !!isInit, 'regular')"
@more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)" @more-action-select="(e,r)=> handleExtractParamMoreActionSelect(e,r as ExpressionConfig)"
> >
<template #expression="{ record, rowIndex }"> <template #expression="{ record, rowIndex }">
@ -301,7 +327,14 @@
import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue'; import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { findFirstByGroupId, insertTreeByCurrentId, insertTreeByGroupId } from '@/utils/tree'; import {
countNodes,
countNodesByGroupId,
deleteNodeById,
deleteNodesByGroupId,
findFirstByGroupId,
insertNode,
} from '@/utils/tree';
import { import {
ExecuteConditionProcessor, ExecuteConditionProcessor,
@ -332,6 +365,7 @@
(e: 'change'): void; (e: 'change'): void;
}>(); }>();
const { t } = useI18n(); const { t } = useI18n();
const rootId = 0; // 1970-01-01 00:00:00 UTC
// const innerParams = defineModel<Param>('modelValue', { // const innerParams = defineModel<Param>('modelValue', {
// default: { // default: {
@ -350,7 +384,17 @@
jsonPath: [], jsonPath: [],
xPath: { responseFormat: 'XML', data: [] }, xPath: { responseFormat: 'XML', data: [] },
document: { document: {
data: [], data: [
{
id: rootId,
paramsName: 'root',
mustInclude: false,
typeChecking: false,
paramType: 'object',
matchCondition: '',
matchValue: '',
},
],
responseFormat: 'JSON', responseFormat: 'JSON',
followApi: false, followApi: false,
}, },
@ -439,8 +483,19 @@
matchValue: '', matchValue: '',
enable: true, enable: true,
}; };
const handleChange = () => { const handleChange = (data: any[], isInit: boolean, type: string) => {
emit('change'); 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) { function handleExpressionChange(rowIndex: number) {
extractParamsTableRef.value?.addTableLine(rowIndex); extractParamsTableRef.value?.addTableLine(rowIndex);
@ -481,6 +536,7 @@
title: 'ms.assertion.paramsName', title: 'ms.assertion.paramsName',
dataIndex: 'paramsName', dataIndex: 'paramsName',
slotName: 'key', slotName: 'key',
addLineDisabled: true,
width: 300, width: 300,
}, },
{ {
@ -506,6 +562,7 @@
showInTable: true, showInTable: true,
showDrag: true, showDrag: true,
columnSelectorDisabled: true, columnSelectorDisabled: true,
addLineDisabled: true,
typeOptions: [ typeOptions: [
{ label: 'object', value: 'object' }, { label: 'object', value: 'object' },
{ label: 'array', value: 'array' }, { label: 'array', value: 'array' },
@ -516,24 +573,27 @@
], ],
titleSlotName: 'typeTitle', titleSlotName: 'typeTitle',
typeTitleTooltip: t('project.environmental.paramTypeTooltip'), typeTitleTooltip: t('project.environmental.paramTypeTooltip'),
width: 100,
}, },
{ {
title: 'ms.assertion.matchCondition', title: 'ms.assertion.matchCondition',
dataIndex: 'matchCondition', dataIndex: 'matchCondition',
slotName: 'matchCondition', slotName: 'matchCondition',
options: statusCodeOptions, options: statusCodeOptions,
width: 120,
}, },
{ {
title: 'ms.assertion.matchValue', title: 'ms.assertion.matchValue',
dataIndex: 'matchValue', dataIndex: 'matchValue',
slotName: 'matchValue', slotName: 'matchValue',
hasRequired: true, hasRequired: true,
showDelete: true,
}, },
{ {
title: '', title: '',
slotName: 'operation', slotName: 'operation',
fixed: 'right', fixed: 'right',
width: 60, width: 100,
align: 'right', align: 'right',
}, },
]; ];
@ -626,29 +686,65 @@
if (record.groupId) { if (record.groupId) {
// //
const parent = innerParams.value.document.data.find((item: any) => item.id === record.groupId); const parent = innerParams.value.document.data.find((item: any) => item.id === record.groupId);
insertTreeByCurrentId(innerParams.value.document.data, record.id, { insertNode(
...documentDefaultParamItem, innerParams.value.document.data,
{ id: record.id, groupId: record.groupId },
{
...record,
id: new Date().getTime(), id: new Date().getTime(),
groupId: parent ? parent.id : record.groupId, groupId: parent ? parent.id : record.groupId,
}); matchValue: '',
matchCondition: '',
}
);
if (parent) { if (parent) {
parent.rowSpan = parent.rowSpan ? parent.rowSpan + 1 : 2; parent.rowSpan = parent.rowSpan ? parent.rowSpan + 1 : 2;
} else { } else {
// //
const fisrtChildNode = findFirstByGroupId(innerParams.value.document.data, record.groupId); const fisrtChildNode = findFirstByGroupId(innerParams.value.document.data, record.groupId);
if (fisrtChildNode) { 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 { } else {
// record.rowSpan = record.rowSpan ? record.rowSpan + 1 : 2;
insertTreeByCurrentId(innerParams.value.document.data, record.id, { //
...documentDefaultParamItem, insertNode(
innerParams.value.document.data,
{ id: record.id, groupId: record.groupId },
{
...record,
id: new Date().getTime(), id: new Date().getTime(),
groupId: record.id, 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: { const documentSpanMethod = (data: {
record: TableData; record: TableData;
column: TableColumnData | TableOperationColumn; column: TableColumnData | TableOperationColumn;
@ -660,25 +756,12 @@
const { record, column } = data; const { record, column } = data;
const currentColumn = column as TableColumnData; const currentColumn = column as TableColumnData;
if (record.rowSpan > 1) { if (record.rowSpan > 1) {
if (currentColumn.slotName === 'key') { const mergeColumns = ['key', 'mustContain', 'typeChecking', 'paramType', 'operation'];
if (mergeColumns.includes(currentColumn.title as string)) {
return { return {
rowspan: record.rowSpan, 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> </script>

View File

@ -15,7 +15,8 @@ export default {
'ms.assertion.regular': '正则', 'ms.assertion.regular': '正则',
'ms.assertion.script': '脚本', 'ms.assertion.script': '脚本',
'ms.assertion.expression': '表达式', 'ms.assertion.expression': '表达式',
'ms.assertion.responseContentType': '响应内容类型', 'ms.assertion.responseContentType': '响应内容格式',
'ms.assertion.followApi': '跟随API定义',
'ms.assertion.paramsName': '参数名', 'ms.assertion.paramsName': '参数名',
'ms.assertion.mustInclude': '必含', 'ms.assertion.mustInclude': '必含',
'ms.assertion.typeChecking': '类型校验', 'ms.assertion.typeChecking': '类型校验',

View File

@ -15,7 +15,7 @@ const useProjectEnvStore = defineStore(
'projectEnv', 'projectEnv',
() => { () => {
// 项目中的key值 // 项目中的key值
const currentId = ref<string>(ALL_PARAM); const currentId = ref<string>('1052215449649153');
// 项目组选中的key值 // 项目组选中的key值
const currentGroupId = ref<string>(''); const currentGroupId = ref<string>('');
const currentEnvDetailInfo = ref<EnvDetailItem>({ projectId: '', name: '', config: {} }); // 当前选中的环境详情 const currentEnvDetailInfo = ref<EnvDetailItem>({ projectId: '', name: '', config: {} }); // 当前选中的环境详情

View File

@ -1,72 +1,159 @@
interface TreeData { interface Tree {
id: number; id: number;
groupId?: number; groupId?: number;
children?: TreeData[]; children?: Tree[];
[key: string]: any; [key: string]: any;
} }
// 插入节点
export function insertTreeByCurrentId(tree: TreeData[], currentId: number, newData: TreeData) { export function insertNode(tree: Tree[], currentNode: Tree, nodeToInsert: Tree): boolean {
const stack: Array<{ node: TreeData; parent: TreeData | null }> = tree.map((node) => ({ node, parent: null })); // 查找并插入节点的辅助函数
function findAndInsert(node: Tree, parentNode: Tree | null = null): boolean {
while (stack.length > 0) { if (node.id === currentNode.id) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion // 如果 parentNode 为 null说明当前节点是根节点
const { node, parent } = stack.pop()!; if (parentNode === null) {
const index = tree.findIndex((n) => n.id === currentNode.id);
if (parent && parent.children) { // 根据 currentNode 的 groupId 是否存在,决定插入位置
const index = parent.children.findIndex((child) => child.id === currentId); if (currentNode.groupId !== undefined) {
if (index !== -1) { // 插入到当前节点的后面
// 在目标节点后面插入新数据 tree.splice(index + 1, 0, nodeToInsert);
parent.children.splice(index + 1, 0, newData); } else {
return true; // 插入到同级节点的末尾
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) => { node.children?.forEach((child) => {
stack.push({ node: child, parent: node }); 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) { // 根据 groupId 查找第一个匹配的节点
const stack: Array<{ node: TreeData; parent: TreeData | null }> = tree.map((node) => ({ node, parent: null })); 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
while (stack.length > 0) { if (foundNode) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion return foundNode;
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) { if (node.children) {
node.children.forEach((child) => { const found = findFirstByGroupId(node.children, groupId);
stack.push({ node: child, parent: node }); if (found) {
return found;
}
}
}); });
}
}
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; 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));
}

View File

@ -81,7 +81,7 @@
:placeholder="t('apiTestDebug.paramNamePlaceholder')" :placeholder="t('apiTestDebug.paramNamePlaceholder')"
class="param-input" class="param-input"
:max-length="255" :max-length="255"
@input="() => addTableLine(rowIndex)" @input="() => addTableLine(rowIndex, columnConfig.addLineDisabled)"
/> />
</a-popover> </a-popover>
</template> </template>
@ -105,7 +105,7 @@
v-model:model-value="record.paramType" v-model:model-value="record.paramType"
:options="columnConfig.typeOptions || []" :options="columnConfig.typeOptions || []"
class="param-input w-full" class="param-input w-full"
@change="(val) => handleTypeChange(val, record, rowIndex)" @change="(val) => handleTypeChange(val, record, rowIndex, columnConfig.addLineDisabled)"
/> />
</template> </template>
<template #expressionType="{ record, columnConfig, rowIndex }"> <template #expressionType="{ record, columnConfig, rowIndex }">
@ -251,7 +251,7 @@
/> />
</template> </template>
<template #operation="{ record, rowIndex, columnConfig }"> <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 <a-switch
v-if="columnConfig.hasDisable" v-if="columnConfig.hasDisable"
v-model:model-value="record.disable" v-model:model-value="record.disable"
@ -280,12 +280,17 @@
</div> </div>
</template> </template>
</a-trigger> </a-trigger>
<icon-minus-circle <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)]" class="ml-[8px] cursor-pointer text-[var(--color-text-4)]"
size="20" 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> </div>
</template> </template>
@ -300,6 +305,7 @@
</a-select> </a-select>
</template> </template>
<template #matchValue="{ record, rowIndex, columnConfig }"> <template #matchValue="{ record, rowIndex, columnConfig }">
<div class="flex flex-row items-center justify-between">
<a-tooltip <a-tooltip
v-if="columnConfig.hasRequired" v-if="columnConfig.hasRequired"
:content="t(record.required ? 'apiTestDebug.paramRequired' : 'apiTestDebug.paramNotRequired')" :content="t(record.required ? 'apiTestDebug.paramRequired' : 'apiTestDebug.paramNotRequired')"
@ -316,6 +322,8 @@
</MsButton> </MsButton>
</a-tooltip> </a-tooltip>
<a-input v-model="record.matchValue" class="param-input" /> <a-input v-model="record.matchValue" class="param-input" />
<slot name="matchValueDelete" v-bind="{ record, rowIndex, columnConfig }"></slot>
</div>
</template> </template>
<template #project="{ record, columnConfig, rowIndex }"> <template #project="{ record, columnConfig, rowIndex }">
<a-select <a-select
@ -418,6 +426,7 @@
hasDisable?: boolean; // operation enable hasDisable?: boolean; // operation enable
moreAction?: ActionsItem[]; // operation moreAction?: ActionsItem[]; // operation
format?: RequestBodyFormat; // operation format?: RequestBodyFormat; // operation
addLineDisabled?: boolean; //
}; };
const props = withDefaults( const props = withDefaults(
@ -440,6 +449,7 @@
showSelectorAll?: boolean; // showSelectorAll?: boolean; //
isSimpleSetting?: boolean; // Column isSimpleSetting?: boolean; // Column
response?: string; // response?: string; //
isTreeTable?: boolean; //
spanMethod?: (data: { spanMethod?: (data: {
record: TableData; record: TableData;
column: TableColumnData | TableOperationColumn; column: TableColumnData | TableOperationColumn;
@ -474,6 +484,7 @@
(e: 'change', data: any[], isInit?: boolean): void; // (e: 'change', data: any[], isInit?: boolean): void; //
(e: 'moreActionSelect', event: ActionsItem, record: Record<string, any>): void; (e: 'moreActionSelect', event: ActionsItem, record: Record<string, any>): void;
(e: 'projectChange', projectId: string): void; (e: 'projectChange', projectId: string): void;
(e: 'treeDelete', record: Record<string, any>): void;
}>(); }>();
const appStore = useAppStore(); const appStore = useAppStore();
@ -537,7 +548,11 @@
const paramsLength = computed(() => propsRes.value.data.length); 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); propsRes.value.data.splice(rowIndex, 1);
emit('change', propsRes.value.data); emit('change', propsRes.value.data);
} }
@ -611,7 +626,10 @@
* @param key 当前列的 key * @param key 当前列的 key
* @param isForce 是否强制添加 * @param isForce 是否强制添加
*/ */
function addTableLine(rowIndex: number) { function addTableLine(rowIndex: number, addLineDisabled?: boolean) {
if (addLineDisabled) {
return;
}
if (rowIndex === propsRes.value.data.length - 1) { if (rowIndex === propsRes.value.data.length - 1) {
// //
const id = new Date().getTime().toString(); const id = new Date().getTime().toString();
@ -741,9 +759,10 @@
function handleTypeChange( function handleTypeChange(
val: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[], val: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[],
record: Record<string, any>, record: Record<string, any>,
rowIndex: number rowIndex: number,
addLineDisabled?: boolean
) { ) {
addTableLine(rowIndex); addTableLine(rowIndex, addLineDisabled);
// Content-Type // Content-Type
if (record.contentType) { if (record.contentType) {
if (val === 'file') { if (val === 'file') {
@ -754,6 +773,7 @@
record.contentType = RequestContentTypeEnum.TEXT; record.contentType = RequestContentTypeEnum.TEXT;
} }
} }
emit('change', propsRes.value.data);
} }
/** /**