feat(接口测试): 请求参数的参数名称唯一的校验(请求头,请求体,Query,REST)
This commit is contained in:
parent
ce975229c9
commit
087bfb417f
|
@ -1,4 +1,5 @@
|
|||
<template>
|
||||
<a-form ref="formRef" :model="propsRes">
|
||||
<MsBaseTable
|
||||
v-bind="propsRes"
|
||||
:hoverable="false"
|
||||
|
@ -7,6 +8,7 @@
|
|||
:class="!props.selectable && !props.draggable ? 'ms-form-table-no-left-action' : ''"
|
||||
v-on="propsEvent"
|
||||
@drag-change="tableChange"
|
||||
@init-end="validateAndUpdateErrorMessageList"
|
||||
>
|
||||
<!-- 展开行-->
|
||||
<template #expand-icon="{ expanded, record }">
|
||||
|
@ -17,9 +19,22 @@
|
|||
</template>
|
||||
<template
|
||||
v-for="item of props.columns.filter((e) => e.slotName !== undefined)"
|
||||
:key="item.toString()"
|
||||
#[item.slotName!]="{ record, rowIndex, column }"
|
||||
>
|
||||
<slot :name="item.slotName" v-bind="{ record, rowIndex, column, dataIndex: item.dataIndex, columnConfig: item }">
|
||||
<a-form-item
|
||||
:field="`data[${rowIndex}].${item.dataIndex}`"
|
||||
label=""
|
||||
:rules="{
|
||||
validator: (value, callback) => {
|
||||
validRepeat(rowIndex, item.dataIndex as string, value, callback);
|
||||
},
|
||||
}"
|
||||
>
|
||||
<slot
|
||||
:name="item.slotName"
|
||||
v-bind="{ record, rowIndex, column, dataIndex: item.dataIndex, columnConfig: item }"
|
||||
>
|
||||
<a-tooltip
|
||||
v-if="item.hasRequired"
|
||||
:content="t(record.required ? 'msFormTable.paramRequired' : 'msFormTable.paramNotRequired')"
|
||||
|
@ -101,7 +116,9 @@
|
|||
</template>
|
||||
<template v-else-if="item.inputType === 'text'">
|
||||
{{
|
||||
typeof item.valueFormat === 'function' ? item.valueFormat(record) : record[item.dataIndex as string] || '-'
|
||||
typeof item.valueFormat === 'function'
|
||||
? item.valueFormat(record)
|
||||
: record[item.dataIndex as string] || '-'
|
||||
}}
|
||||
</template>
|
||||
<template v-else-if="item.dataIndex === 'action'">
|
||||
|
@ -128,6 +145,7 @@
|
|||
</div>
|
||||
</template>
|
||||
</slot>
|
||||
</a-form-item>
|
||||
</template>
|
||||
<template
|
||||
v-for="item of props.columns.filter((e) => e.titleSlotName !== undefined)"
|
||||
|
@ -140,9 +158,11 @@
|
|||
</slot>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormInstance } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
|
@ -167,6 +187,7 @@
|
|||
required?: boolean; // 是否必填
|
||||
inputType?: 'input' | 'select' | 'tags' | 'switch' | 'text' | 'checkbox' | 'autoComplete'; // 输入组件类型
|
||||
autoCompleteParams?: SelectOptionData[]; // 自动补全参数
|
||||
needValidRepeat?: boolean; // 是否需要判断重复
|
||||
valueFormat?: (record: Record<string, any>) => string; // 展示值格式化,仅在inputType为text时生效
|
||||
[key: string]: any; // 扩展属性
|
||||
}
|
||||
|
@ -258,6 +279,35 @@
|
|||
|
||||
const dataLength = computed(() => propsRes.value.data.length);
|
||||
|
||||
// 校验重复
|
||||
const formRef = ref<FormInstance>();
|
||||
async function validRepeat(rowIndex: number, dataIndex: string, value: any, callback: (error?: string) => void) {
|
||||
const currentColumn = props.columns.find((item) => item.dataIndex === dataIndex);
|
||||
if (!currentColumn?.needValidRepeat) {
|
||||
callback();
|
||||
return;
|
||||
}
|
||||
propsRes.value.data?.forEach((row, index) => {
|
||||
if (row[dataIndex].length && index !== rowIndex && row[dataIndex] === value) {
|
||||
callback(`${t(currentColumn?.title as string)}${t('msFormTable.paramRepeatMessage')}`);
|
||||
}
|
||||
});
|
||||
callback();
|
||||
}
|
||||
|
||||
// 校验并更新错误信息列表
|
||||
const setErrorMessageList: ((params: string[]) => void) | undefined = inject('setErrorMessageList', undefined);
|
||||
const errorMessageList = ref<string[]>([]); // 错误信息列表
|
||||
async function validateAndUpdateErrorMessageList() {
|
||||
if (typeof setErrorMessageList === 'function' && props.columns.some((item) => item.needValidRepeat)) {
|
||||
await nextTick();
|
||||
formRef.value?.validate((errors) => {
|
||||
errorMessageList.value = !errors ? [] : [...new Set(Object.values(errors).map(({ message }) => message))];
|
||||
setErrorMessageList(errorMessageList.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => selectedKeys.value,
|
||||
(arr) => {
|
||||
|
@ -276,6 +326,7 @@
|
|||
() => props.data,
|
||||
(arr) => {
|
||||
propsRes.value.data = arr as any[];
|
||||
validateAndUpdateErrorMessageList();
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
|
@ -468,4 +519,20 @@
|
|||
line-height: 16px;
|
||||
color: var(--color-text-1);
|
||||
}
|
||||
:deep(.arco-form-item) {
|
||||
margin-bottom: 0;
|
||||
.arco-form-item-label-col {
|
||||
display: none;
|
||||
}
|
||||
.arco-form-item-content,
|
||||
.arco-form-item-wrapper-col {
|
||||
min-height: auto;
|
||||
}
|
||||
.arco-form-item-message {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
.arco-form-item-content-flex {
|
||||
flex-wrap: nowrap;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export default {
|
||||
'msFormTable.paramRequired': '必填',
|
||||
'msFormTable.paramNotRequired': '非必填',
|
||||
'msFormTable.paramRepeatMessage': '唯一,不能重复',
|
||||
};
|
||||
|
|
|
@ -331,6 +331,7 @@
|
|||
(e: 'clearSelector'): void;
|
||||
(e: 'filterChange', dataIndex: string, value: (string | number)[], multiple: boolean, isCustomParam: boolean): void;
|
||||
(e: 'moduleChange'): void;
|
||||
(e: 'initEnd'): void;
|
||||
}>();
|
||||
const attrs = useAttrs();
|
||||
|
||||
|
@ -408,6 +409,7 @@
|
|||
scrollObj.value = {};
|
||||
currentColumns.value = arr || tmpArr;
|
||||
}
|
||||
emit('initEnd');
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('InitColumn failed', error);
|
||||
|
|
|
@ -208,6 +208,7 @@
|
|||
{
|
||||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'key',
|
||||
needValidRepeat: true,
|
||||
slotName: 'key',
|
||||
width: 240,
|
||||
},
|
||||
|
|
|
@ -59,6 +59,7 @@
|
|||
dataIndex: 'key',
|
||||
slotName: 'key',
|
||||
inputType: 'autoComplete',
|
||||
needValidRepeat: true,
|
||||
autoCompleteParams: responseHeaderOption,
|
||||
},
|
||||
{
|
||||
|
|
|
@ -496,6 +496,12 @@
|
|||
() => import('@/views/api-test/management/components/addDependencyDrawer.vue')
|
||||
);
|
||||
|
||||
export interface TabErrorMessage {
|
||||
value: string;
|
||||
label: string;
|
||||
messageList: string[];
|
||||
}
|
||||
|
||||
export interface RequestCustomAttr {
|
||||
type: 'api' | 'case' | 'mock' | 'doc'; // 展示的请求 tab 类型;api包含了接口调试和接口定义
|
||||
isNew: boolean;
|
||||
|
@ -505,6 +511,9 @@
|
|||
executeLoading: boolean; // 执行中loading
|
||||
isCopy?: boolean; // 是否是复制
|
||||
isExecute?: boolean; // 是否是执行
|
||||
errorMessageInfo?: {
|
||||
[key: string]: Record<string, any>;
|
||||
};
|
||||
}
|
||||
export type RequestParam = ExecuteApiRequestFullParams & {
|
||||
responseDefinition?: ResponseItem[];
|
||||
|
@ -1346,6 +1355,59 @@
|
|||
}
|
||||
}
|
||||
|
||||
function initErrorMessageInfoItem(key) {
|
||||
if (requestVModel.value.errorMessageInfo && !requestVModel.value.errorMessageInfo[key]) {
|
||||
requestVModel.value.errorMessageInfo[key] = {};
|
||||
}
|
||||
}
|
||||
|
||||
function changeTabErrorMessageList(tabKey: string, formErrorMessageList: string[]) {
|
||||
if (!requestVModel.value.errorMessageInfo) return;
|
||||
const label = contentTabList.value.find((item) => item.value === tabKey)?.label ?? '';
|
||||
const listItem: TabErrorMessage = {
|
||||
value: tabKey,
|
||||
label,
|
||||
messageList: formErrorMessageList,
|
||||
};
|
||||
// TODO: 处理前置的sql 后置的sql和提取 断言的响应头和变量
|
||||
if (requestVModel.value.activeTab === RequestComposition.BODY) {
|
||||
initErrorMessageInfoItem(RequestComposition.BODY);
|
||||
requestVModel.value.errorMessageInfo[RequestComposition.BODY][requestVModel.value.body.bodyType] =
|
||||
cloneDeep(listItem);
|
||||
} else {
|
||||
requestVModel.value.errorMessageInfo[requestVModel.value.activeTab] = cloneDeep(listItem);
|
||||
}
|
||||
}
|
||||
|
||||
const setErrorMessageList = debounce((list: string[]) => {
|
||||
changeTabErrorMessageList(requestVModel.value.activeTab, list);
|
||||
}, 300);
|
||||
provide('setErrorMessageList', setErrorMessageList);
|
||||
|
||||
// 需要最终提示的信息
|
||||
function getFlattenedMessages() {
|
||||
if (!requestVModel.value.errorMessageInfo) return;
|
||||
const flattenedMessages: { label: string; messageList: string[] }[] = [];
|
||||
const { errorMessageInfo } = requestVModel.value;
|
||||
Object.values(errorMessageInfo).forEach((item) => {
|
||||
const label = item.label || (Object.values(item)[0] && Object.values(item)[0].label);
|
||||
const messageList: string[] =
|
||||
item.messageList || [...new Set(Object.values(item).flatMap((bodyType) => bodyType.messageList))] || [];
|
||||
if (messageList.length) {
|
||||
flattenedMessages.push({ label, messageList: [...new Set(messageList)] });
|
||||
}
|
||||
});
|
||||
return flattenedMessages;
|
||||
}
|
||||
|
||||
function showMessage() {
|
||||
getFlattenedMessages()?.forEach(({ label, messageList }) => {
|
||||
messageList?.forEach((message) => {
|
||||
Message.error(`${label}${message}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function handleSave(done: (closed: boolean) => void) {
|
||||
saveModalFormRef.value?.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
|
@ -1368,6 +1430,11 @@
|
|||
// 插件需要校验动态表单
|
||||
await fApi.value?.validate();
|
||||
}
|
||||
// 检查全部的校验信息
|
||||
if (getFlattenedMessages()?.length) {
|
||||
showMessage();
|
||||
return;
|
||||
}
|
||||
if (!requestVModel.value.isNew) {
|
||||
// 更新接口不需要弹窗,直接更新保存
|
||||
updateRequest();
|
||||
|
@ -1487,6 +1554,11 @@
|
|||
if (errors) {
|
||||
requestVModel.value.activeTab = RequestComposition.BASE_INFO;
|
||||
} else {
|
||||
// 检查全部的校验信息
|
||||
if (getFlattenedMessages()?.length) {
|
||||
showMessage();
|
||||
return;
|
||||
}
|
||||
switch (value) {
|
||||
case 'save':
|
||||
handleSaveShortcut();
|
||||
|
@ -1565,6 +1637,8 @@
|
|||
execute,
|
||||
makeRequestParams,
|
||||
changeVerticalExpand,
|
||||
getFlattenedMessages,
|
||||
showMessage,
|
||||
});
|
||||
</script>
|
||||
|
||||
|
|
|
@ -68,6 +68,7 @@
|
|||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'key',
|
||||
slotName: 'key',
|
||||
needValidRepeat: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramType',
|
||||
|
|
|
@ -69,6 +69,7 @@
|
|||
title: 'apiTestDebug.paramName',
|
||||
dataIndex: 'key',
|
||||
slotName: 'key',
|
||||
needValidRepeat: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.paramType',
|
||||
|
|
|
@ -212,6 +212,7 @@
|
|||
response: cloneDeep(defaultResponse),
|
||||
isNew: true,
|
||||
executeLoading: false,
|
||||
errorMessageInfo: {},
|
||||
};
|
||||
const debugTabs = ref<RequestParam[]>([cloneDeep(defaultDebugParams)]);
|
||||
const activeDebug = ref<RequestParam>(debugTabs.value[0]);
|
||||
|
|
|
@ -275,6 +275,7 @@
|
|||
executeLoading: false,
|
||||
preDependency: [], // 前置依赖
|
||||
postDependency: [], // 后置依赖
|
||||
errorMessageInfo: {},
|
||||
};
|
||||
|
||||
function addApiTab(defaultProps?: Partial<TabItem>) {
|
||||
|
|
|
@ -237,6 +237,11 @@
|
|||
function handleDrawerConfirm(isContinue: boolean) {
|
||||
formRef.value?.validate(async (errors) => {
|
||||
if (!errors) {
|
||||
// 检查全部的校验信息
|
||||
if (requestCompositionRef.value?.getFlattenedMessages()?.length) {
|
||||
requestCompositionRef.value?.showMessage();
|
||||
return;
|
||||
}
|
||||
drawerLoading.value = true;
|
||||
// 给后端传的参数
|
||||
if (!requestCompositionRef.value?.makeRequestParams()) return;
|
||||
|
|
|
@ -205,6 +205,7 @@
|
|||
executeLoading: false,
|
||||
preDependency: [], // 前置依赖
|
||||
postDependency: [], // 后置依赖
|
||||
errorMessageInfo: {},
|
||||
};
|
||||
|
||||
// 监听模块树的激活节点变化,记录表格数据的模块 id
|
||||
|
|
Loading…
Reference in New Issue