feat(接口测试): 批量添加参数支持标准添加&ms-json-schema组件 enable 调整

This commit is contained in:
baiqi 2024-07-05 11:47:10 +08:00 committed by 刘瑞斌
parent e4c6d9af17
commit fc8f947ef1
15 changed files with 152 additions and 86 deletions

View File

@ -696,13 +696,13 @@
.ms-params-input:not(.arco-input-focus) {
@apply bg-transparent;
border-color: transparent;
border-color: transparent !important;
&:not(:hover) {
.arco-input::placeholder {
@apply invisible;
}
border-color: transparent;
border-color: transparent !important;
}
}
.ms-params-input,

View File

@ -1,5 +1,11 @@
<template>
<a-select v-model:model-value="cron" allow-create>
<a-select
v-model:model-value="cron"
:disabled="props.disabled"
:class="props.class"
allow-create
@change="emit('change', $event)"
>
<template #label="{ data }">
<div class="flex items-center">
{{ data.value }}
@ -18,6 +24,17 @@
<script setup lang="ts">
import { useI18n } from '@/hooks/useI18n';
const props = defineProps<{
class?: string;
disabled?: boolean;
}>();
const emit = defineEmits<{
(
e: 'change',
value: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
): void;
}>();
const { t } = useI18n();
const cron = defineModel<string>('modelValue', {

View File

@ -13,6 +13,7 @@
@drag-change="tableChange"
@init-end="validateAndUpdateErrorMessageList"
@select="handleSelect"
@select-all="handleSelectAll"
>
<!-- 展开行-->
<template #expand-icon="{ expanded, record }">
@ -297,7 +298,8 @@
columnConfig: FormTableColumn,
rowIndex: number
): void;
(e: 'select', rowKeys: (string | number)[], _rowKey: string | number, record: TableData): void;
(e: 'rowSelect', rowKeys: (string | number)[], _rowKey: string | number, record: TableData): void;
(e: 'selectAll', checked: boolean): void;
}>();
const { t } = useI18n();
@ -489,7 +491,11 @@
}
function handleSelect(rowKeys: (string | number)[], rowKey: string | number, record: TableData) {
emit('select', rowKeys, rowKey, record);
emit('rowSelect', rowKeys, rowKey, record);
}
function handleSelectAll(checked: boolean) {
emit('selectAll', checked);
}
await initColumns();

View File

@ -16,10 +16,8 @@
:scroll="{ x: 'max-content' }"
show-setting
class="ms-json-schema"
@select="
(rowKeys: (string | number)[], rowKey: string | number, record: Record<string, any>) =>
handleSelect(rowKeys, rowKey, record as JsonSchemaTableItem)
"
@row-select="handleSelect"
@select-all="handleSelectAll"
>
<template #batchAddTitle>
<MsButton type="text" size="mini" class="!mr-0" @click="batchAdd">
@ -59,14 +57,17 @@
>
<MsButton
type="icon"
:class="[
record.required ? '!text-[rgb(var(--danger-5))]' : '!text-[var(--color-text-brand)]',
'!mr-[4px] !p-[4px]',
]"
class="!mr-[4px] !p-[4px]"
size="mini"
@click="() => (record.required = !record.required)"
>
<div>*</div>
<div
:style="{
color: record.required ? 'rgb(var(--danger-5)) !important' : 'var(--color-text-brand) !important',
}"
>
*
</div>
</MsButton>
</a-tooltip>
<a-select
@ -489,7 +490,7 @@
</template>
<script setup lang="ts">
import { SelectOptionData } from '@arco-design/web-vue';
import { SelectOptionData, TableData } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import MsButton from '@/components/pure/ms-button/index.vue';
@ -846,8 +847,9 @@
/**
* 行选择处理
*/
function handleSelect(rowKeys: (string | number)[], rowKey: string | number, record: JsonSchemaTableItem) {
function handleSelect(rowKeys: (string | number)[], rowKey: string | number, record: TableData) {
nextTick(() => {
record.enable = !selectedKeys.value.includes(record.id);
if (record.enable && record.children && record.children.length > 0) {
//
traverseTree<JsonSchemaTableItem>(record.children, (item) => {
@ -860,6 +862,12 @@
});
}
function handleSelectAll(checked: boolean) {
traverseTree<JsonSchemaTableItem>(data.value, (item) => {
item.enable = checked;
});
}
const batchAddDrawerVisible = ref(false);
const batchAddValue = ref('');
const batchAddType = ref<'json' | 'schema'>('json');

View File

@ -14,6 +14,7 @@
:columns="currentColumns"
:span-all="props.spanAll"
@select="originalRowSelectChange"
@select-all="originalSelectAll"
@expand="(rowKey, record) => emit('expand', record)"
@change="(data: TableData[], extra: TableChangeExtra, currentData: TableData[]) => handleDragChange(data, extra, currentData)"
@sorter-change="(dataIndex: string,direction: string) => handleSortChange(dataIndex, direction)"
@ -384,6 +385,7 @@
(e: 'initEnd'): void;
(e: 'reset'): void;
(e: 'select', rowKeys: (string | number)[], _rowKey: string | number, record: TableData): void;
(e: 'selectAll', checked: boolean): void;
}>();
const attrs = useAttrs();
@ -531,6 +533,11 @@
emit('select', rowKeys, _rowKey, record);
}
//
function originalSelectAll(checked: boolean) {
emit('selectAll', checked);
}
// change
const pageChange = (v: number) => {
emit('pageChange', v);

View File

@ -31,9 +31,19 @@
:show-full-screen="false"
:show-theme-change="false"
>
<template v-if="props.hasStandard" #leftTitle>
<a-radio-group v-model:model-value="addType" type="button" @change="() => parseParams()">
<a-radio value="standard">{{ t('apiTestDebug.standardAdditions') }}</a-radio>
<a-radio value="quick">{{ t('apiTestDebug.quickAdditions') }}</a-radio>
</a-radio-group>
</template>
<template v-if="!props?.addTypeText" #rightTitle>
<div class="text-[12px] text-[var(--color-text-4)]">
{{ t('apiTestDebug.batchAddParamsTip1') }}
{{
props.hasStandard && addType === 'standard'
? t('apiTestDebug.standardAdditionsTip')
: t('apiTestDebug.batchAddParamsTip1')
}}
</div>
</template>
</MsCodeEditor>
@ -48,6 +58,9 @@
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import { useI18n } from '@/hooks/useI18n';
import { getGenerateId } from '@/utils';
import { RequestParamsType } from '@/enums/apiEnum';
const props = withDefaults(
defineProps<{
@ -57,6 +70,7 @@
addTypeText?: string; //
disabled?: boolean;
typeTitle?: string;
hasStandard?: boolean; //
}>(),
{
noParamType: false,
@ -72,15 +86,27 @@
required: true,
});
const batchParamsCode = ref('');
const addType = ref<'standard' | 'quick'>('standard');
function parseParams() {
if (props.hasStandard && addType.value === 'standard') {
batchParamsCode.value = props.params
.filter((e) => e && (!isEmpty(e.key) || !isEmpty(e.value)))
.map((item) => `${item.key},${item.paramType},${item.required},${item.value}`)
.join('\n');
} else {
batchParamsCode.value = props.params
.filter((e) => e && (!isEmpty(e.key) || !isEmpty(e.value)))
.map((item) => `${item.key}:${item.value}`)
.join('\n');
}
}
watch(
() => visible.value,
(val) => {
if (val) {
batchParamsCode.value = props.params
.filter((e) => e && (!isEmpty(e.key) || !isEmpty(e.value)))
.map((item) => `${item.key}:${item.value}`)
.join('\n');
parseParams();
}
},
{
@ -89,9 +115,9 @@
);
/**
* 批量参数代码转换为参数表格数据
* 批量参数代码转换为参数表格数据-快捷模式-参数名参数值
*/
function applyBatchParams() {
function quickApply() {
const arr = batchParamsCode.value.replaceAll('\r', '\n').split('\n'); //
const tempObj: Record<string, any> = {}; //
for (let i = 0; i < arr.length; i++) {
@ -100,7 +126,7 @@
const index = arr[i].indexOf(':');
if (index === -1) {
tempObj[arr[i].trim()] = {
id: new Date().getTime() + i,
id: getGenerateId(),
...cloneDeep(props.defaultParamItem), //
key: arr[i].trim(),
value: '',
@ -109,7 +135,7 @@
const [key, value] = [arr[i].substring(0, index).trim(), arr[i].substring(index + 1).trim()];
if (key || value) {
tempObj[key.trim()] = {
id: new Date().getTime() + i,
id: getGenerateId(),
...cloneDeep(props.defaultParamItem), //
key: key.trim(),
value: value?.trim(),
@ -122,6 +148,53 @@
batchParamsCode.value = '';
emit('apply', Object.values(tempObj));
}
function stringToBoolean(str?: string) {
if (str === 'true') {
return true;
}
if (str === 'false') {
return false;
}
return false;
}
/**
* 批量参数代码转换为参数表格数据-标准模式-参数名,参数类型,必填,参数值
*/
function standardApply() {
const arr = batchParamsCode.value.replaceAll('\r', '\n').split('\n'); //
const tempObj: Record<string, any> = {}; //
for (let i = 0; i < arr.length; i++) {
if (arr[i] !== '') {
const [key, type, required, value] = arr[i].split(',');
if (key) {
tempObj[key.trim()] = {
id: getGenerateId(),
...cloneDeep(props.defaultParamItem), //
key: key.trim(),
paramType: Object.values(RequestParamsType).includes(type?.trim() as unknown as RequestParamsType)
? type?.trim()
: RequestParamsType.STRING,
required: stringToBoolean(required),
value: value?.trim(),
};
}
}
}
visible.value = false;
batchParamsCode.value = '';
addType.value = 'standard';
emit('apply', Object.values(tempObj));
}
function applyBatchParams() {
if (props.hasStandard && addType.value === 'standard') {
standardApply();
} else {
quickApply();
}
}
</script>
<style lang="less" scoped></style>

View File

@ -156,6 +156,7 @@
:disabled="props.disabledExceptParam"
:params="currentTableParams"
:default-param-item="defaultBodyParamsItem"
has-standard
@apply="handleBatchParamApply"
/>
</template>

View File

@ -28,6 +28,7 @@
:params="innerParams"
:disabled="props.disabledExceptParam"
:default-param-item="defaultRequestParamsItem"
has-standard
@apply="handleBatchParamApply"
/>
</template>

View File

@ -28,6 +28,7 @@
:params="innerParams"
:disabled="props.disabledExceptParam"
:default-param-item="defaultRequestParamsItem"
has-standard
@apply="handleBatchParamApply"
/>
</template>

View File

@ -209,4 +209,8 @@ export default {
'apiTestDebug.actualValue': 'Actual value',
'apiTestDebug.condition': 'condition',
'apiTestDebug.expectedValue': 'Expected value',
'apiTestDebug.standardAdditions': 'Standard',
'apiTestDebug.standardAdditionsTip':
'Writing format: parameter name, type, required, parameter value; multiple records separated by newlines',
'apiTestDebug.quickAdditions': 'Quick',
};

View File

@ -196,4 +196,7 @@ export default {
'apiTestDebug.condition': '匹配条件',
'apiTestDebug.expectedValue': '匹配值',
'apiTestDebug.extractValue': '提取值',
'apiTestDebug.standardAdditions': '标准添加',
'apiTestDebug.standardAdditionsTip': '书写格式:参数名,类型,必填,参数值;多条记录换行分隔',
'apiTestDebug.quickAdditions': '快捷添加',
};

View File

@ -290,26 +290,7 @@
</a-select>
</a-form-item>
<a-form-item :label="t('apiTestManagement.syncFrequency')">
<a-select v-model:model-value="cronValue" class="w-[240px]">
<template #label="{ data }">
<div class="flex items-center">
{{ data.value }}
<div class="ml-[4px] text-[var(--color-text-4)]">{{ data.label.split('?')[1] }}</div>
</div>
</template>
<a-option v-for="item of syncFrequencyOptions" :key="item.value" :value="item.value" class="block">
<div class="flex w-full items-center justify-between">
{{ item.value }}
<div class="ml-[4px] text-[var(--color-text-4)]">{{ item.label }}</div>
</div>
</a-option>
<!-- TODO:第一版不做自定义 -->
<!-- <template #footer>
<div class="flex items-center p-[4px_8px]">
<MsButton type="text">{{ t('apiTestManagement.customFrequency') }}</MsButton>
</div>
</template> -->
</a-select>
<MsCronSelect v-model:model-value="cronValue" class="min-w-[240px]" />
</a-form-item>
</template>
</a-form>
@ -346,6 +327,7 @@
import dayjs from 'dayjs';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsCronSelect from '@/components/pure/ms-cron-select/index.vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
@ -437,12 +419,7 @@
}
return !importForm.value.name || !importForm.value.swaggerUrl;
});
const syncFrequencyOptions = [
{ label: t('apiTestManagement.timeTaskHour'), value: '0 0 0/1 * * ?' },
{ label: t('apiTestManagement.timeTaskSixHour'), value: '0 0 0/6 * * ?' },
{ label: t('apiTestManagement.timeTaskTwelveHour'), value: '0 0 0/12 * * ?' },
{ label: t('apiTestManagement.timeTaskDay'), value: '0 0 0 * * ?' },
];
const cronValue = ref('0 0 0/1 * * ?');
const importLoading = ref(false);
const taskDrawerVisible = ref(false);

View File

@ -97,10 +97,6 @@ export default {
'apiTestManagement.taskNameRequired': 'Task name cannot be empty',
'apiTestManagement.syncFrequency': 'Sync Frequency',
'apiTestManagement.timeTaskList': 'Time Task List',
'apiTestManagement.timeTaskHour': '(Every Hour)',
'apiTestManagement.timeTaskSixHour': '(Every 6 Hours)',
'apiTestManagement.timeTaskTwelveHour': '(Every 12 Hours)',
'apiTestManagement.timeTaskDay': '(Every Day)',
'apiTestManagement.customFrequency': 'Custom Frequency',
'apiTestManagement.case': 'Case',
'apiTestManagement.definition': 'Definition',

View File

@ -92,10 +92,6 @@ export default {
'apiTestManagement.taskNameRequired': '任务名称不能为空',
'apiTestManagement.syncFrequency': '同步频率',
'apiTestManagement.timeTaskList': '定时任务列表',
'apiTestManagement.timeTaskHour': '(每小时)',
'apiTestManagement.timeTaskSixHour': '(每 6 小时)',
'apiTestManagement.timeTaskTwelveHour': '(每 12 小时)',
'apiTestManagement.timeTaskDay': '(每天)',
'apiTestManagement.customFrequency': '自定义频率',
'apiTestManagement.case': '用例',
'apiTestManagement.definition': '定义',

View File

@ -82,20 +82,12 @@
</a-tooltip>
</template>
<template #value="{ record }">
<a-select
<MsCronSelect
v-model:model-value="record.value"
:placeholder="t('common.pleaseSelect')"
class="param-input w-full min-w-[250px]"
:disabled="!record.enable || !hasAnyPermission(permissionsMap[props.group][props.moduleType]?.edit)"
@change="() => changeRunRules(record)"
>
<a-option v-for="item of syncFrequencyOptions" :key="item.value" :value="item.value">
<span class="text-[var(--color-text-2)]"> {{ item.value }}</span
><span class="ml-1 text-[var(--color-text-n4)] hover:text-[rgb(var(--primary-5))]">
{{ item.label }}
</span>
</a-option>
</a-select>
/>
</template>
<template #operation="{ record }">
<a-switch
@ -128,10 +120,10 @@
import dayjs from 'dayjs';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsCronSelect from '@/components/pure/ms-cron-select/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { BatchActionParams, BatchActionQueryParams, MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
import {
batchDisableScheduleOrgTask,
@ -184,12 +176,6 @@
const keyword = ref<string>('');
type ReportShowType = 'All' | 'TEST_PLAN' | 'GROUP';
const showType = ref<ReportShowType>('All');
const syncFrequencyOptions = [
{ label: t('apiTestManagement.timeTaskHour'), value: '0 0 0/1 * * ?' },
{ label: t('apiTestManagement.timeTaskSixHour'), value: '0 0 0/6 * * ?' },
{ label: t('apiTestManagement.timeTaskTwelveHour'), value: '0 0 0/12 * * ?' },
{ label: t('apiTestManagement.timeTaskDay'), value: '0 0 0 * * ?' },
];
const loadRealMap = ref({
system: {
@ -492,8 +478,6 @@
],
};
function edit(record: any) {}
function delSchedule(record: any) {
openModal({
type: 'error',
@ -569,14 +553,6 @@
}
}
const moreActions: ActionsItem[] = [
{
label: 'common.delete',
danger: true,
eventTag: 'delete',
},
];
const batchParams = ref<BatchApiParams>({
selectIds: [],
selectAll: false,