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) { .ms-params-input:not(.arco-input-focus) {
@apply bg-transparent; @apply bg-transparent;
border-color: transparent; border-color: transparent !important;
&:not(:hover) { &:not(:hover) {
.arco-input::placeholder { .arco-input::placeholder {
@apply invisible; @apply invisible;
} }
border-color: transparent; border-color: transparent !important;
} }
} }
.ms-params-input, .ms-params-input,

View File

@ -1,5 +1,11 @@
<template> <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 }"> <template #label="{ data }">
<div class="flex items-center"> <div class="flex items-center">
{{ data.value }} {{ data.value }}
@ -18,6 +24,17 @@
<script setup lang="ts"> <script setup lang="ts">
import { useI18n } from '@/hooks/useI18n'; 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 { t } = useI18n();
const cron = defineModel<string>('modelValue', { const cron = defineModel<string>('modelValue', {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -290,26 +290,7 @@
</a-select> </a-select>
</a-form-item> </a-form-item>
<a-form-item :label="t('apiTestManagement.syncFrequency')"> <a-form-item :label="t('apiTestManagement.syncFrequency')">
<a-select v-model:model-value="cronValue" class="w-[240px]"> <MsCronSelect v-model:model-value="cronValue" class="min-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>
</a-form-item> </a-form-item>
</template> </template>
</a-form> </a-form>
@ -346,6 +327,7 @@
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import MsButton from '@/components/pure/ms-button/index.vue'; 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 MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue'; import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue'; import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
@ -437,12 +419,7 @@
} }
return !importForm.value.name || !importForm.value.swaggerUrl; 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 cronValue = ref('0 0 0/1 * * ?');
const importLoading = ref(false); const importLoading = ref(false);
const taskDrawerVisible = ref(false); const taskDrawerVisible = ref(false);

View File

@ -97,10 +97,6 @@ export default {
'apiTestManagement.taskNameRequired': 'Task name cannot be empty', 'apiTestManagement.taskNameRequired': 'Task name cannot be empty',
'apiTestManagement.syncFrequency': 'Sync Frequency', 'apiTestManagement.syncFrequency': 'Sync Frequency',
'apiTestManagement.timeTaskList': 'Time Task List', '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.customFrequency': 'Custom Frequency',
'apiTestManagement.case': 'Case', 'apiTestManagement.case': 'Case',
'apiTestManagement.definition': 'Definition', 'apiTestManagement.definition': 'Definition',

View File

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

View File

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