feat(接口管理): 接口定义-详情
This commit is contained in:
parent
690b819470
commit
da550b75f7
|
@ -53,6 +53,7 @@ module.exports = {
|
||||||
'@typescript-eslint/no-unused-vars': 1,
|
'@typescript-eslint/no-unused-vars': 1,
|
||||||
'@typescript-eslint/no-empty-function': 1,
|
'@typescript-eslint/no-empty-function': 1,
|
||||||
'@typescript-eslint/no-explicit-any': 0,
|
'@typescript-eslint/no-explicit-any': 0,
|
||||||
|
'@typescript-eslint/no-duplicate-enum-values': 0,
|
||||||
'consistent-return': 'off',
|
'consistent-return': 'off',
|
||||||
'import/extensions': [
|
'import/extensions': [
|
||||||
2,
|
2,
|
||||||
|
|
|
@ -92,8 +92,8 @@
|
||||||
"@types/lodash-es": "^4.17.9",
|
"@types/lodash-es": "^4.17.9",
|
||||||
"@types/nprogress": "^0.2.0",
|
"@types/nprogress": "^0.2.0",
|
||||||
"@types/sortablejs": "^1.15.2",
|
"@types/sortablejs": "^1.15.2",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
||||||
"@typescript-eslint/parser": "^5.62.0",
|
"@typescript-eslint/parser": "^7.1.1",
|
||||||
"@vitejs/plugin-vue": "^3.2.0",
|
"@vitejs/plugin-vue": "^3.2.0",
|
||||||
"@vitejs/plugin-vue-jsx": "^2.1.1",
|
"@vitejs/plugin-vue-jsx": "^2.1.1",
|
||||||
"@vitest/coverage-c8": "^0.31.4",
|
"@vitest/coverage-c8": "^0.31.4",
|
||||||
|
|
|
@ -24,6 +24,7 @@ import {
|
||||||
MoveModuleUrl,
|
MoveModuleUrl,
|
||||||
SortDefinitionUrl,
|
SortDefinitionUrl,
|
||||||
SwitchDefinitionScheduleUrl,
|
SwitchDefinitionScheduleUrl,
|
||||||
|
ToggleFollowDefinitionUrl,
|
||||||
TransferFileModuleOptionUrl,
|
TransferFileModuleOptionUrl,
|
||||||
TransferFileUrl,
|
TransferFileUrl,
|
||||||
UpdateDefinitionScheduleUrl,
|
UpdateDefinitionScheduleUrl,
|
||||||
|
@ -203,6 +204,11 @@ export function debugDefinition(data: ExecuteRequestParams) {
|
||||||
return MSR.post({ url: DebugDefinitionUrl, data });
|
return MSR.post({ url: DebugDefinitionUrl, data });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关注/取消关注接口定义
|
||||||
|
export function toggleFollowDefinition(id: string | number) {
|
||||||
|
return MSR.get({ url: ToggleFollowDefinitionUrl, params: id });
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mock
|
* Mock
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -6,6 +6,9 @@ export const GetEnvModuleUrl = '/api/definition/module/env/tree'; // 获取环
|
||||||
export const GetModuleCountUrl = '/api/definition/module/count'; // 获取模块统计数量
|
export const GetModuleCountUrl = '/api/definition/module/count'; // 获取模块统计数量
|
||||||
export const AddModuleUrl = '/api/definition/module/add'; // 添加模块
|
export const AddModuleUrl = '/api/definition/module/add'; // 添加模块
|
||||||
export const DeleteModuleUrl = '/api/definition/module/delete'; // 删除模块
|
export const DeleteModuleUrl = '/api/definition/module/delete'; // 删除模块
|
||||||
|
/**
|
||||||
|
* 接口定义
|
||||||
|
*/
|
||||||
export const DefinitionPageUrl = '/api/definition/page'; // 接口定义列表
|
export const DefinitionPageUrl = '/api/definition/page'; // 接口定义列表
|
||||||
export const AddDefinitionUrl = '/api/definition/add'; // 添加接口定义
|
export const AddDefinitionUrl = '/api/definition/add'; // 添加接口定义
|
||||||
export const UpdateDefinitionUrl = '/api/definition/update'; // 更新接口定义
|
export const UpdateDefinitionUrl = '/api/definition/update'; // 更新接口定义
|
||||||
|
@ -13,9 +16,6 @@ export const GetDefinitionDetailUrl = '/api/definition/get-detail'; // 获取接
|
||||||
export const TransferFileUrl = '/api/definition/transfer'; // 文件转存
|
export const TransferFileUrl = '/api/definition/transfer'; // 文件转存
|
||||||
export const TransferFileModuleOptionUrl = '/api/definition/transfer/options'; // 文件转存目录
|
export const TransferFileModuleOptionUrl = '/api/definition/transfer/options'; // 文件转存目录
|
||||||
export const UploadTempFileUrl = '/api/definition/upload/temp/file'; // 临时文件上传
|
export const UploadTempFileUrl = '/api/definition/upload/temp/file'; // 临时文件上传
|
||||||
export const DefinitionMockPageUrl = '/api/definition/mock/page'; // mock列表
|
|
||||||
export const UpdateMockStatusUrl = '/api/definition/mock/enable/'; // 更新mock状态
|
|
||||||
export const DeleteMockUrl = '/api/definition/mock/delete'; // 刪除mock
|
|
||||||
export const DeleteDefinitionUrl = '/api/definition/delete-to-gc'; // 删除接口定义
|
export const DeleteDefinitionUrl = '/api/definition/delete-to-gc'; // 删除接口定义
|
||||||
export const ImportDefinitionUrl = '/api/definition/import'; // 导入接口定义
|
export const ImportDefinitionUrl = '/api/definition/import'; // 导入接口定义
|
||||||
export const SortDefinitionUrl = '/api/definition/edit/pos'; // 接口定义拖拽
|
export const SortDefinitionUrl = '/api/definition/edit/pos'; // 接口定义拖拽
|
||||||
|
@ -30,3 +30,10 @@ export const SwitchDefinitionScheduleUrl = '/api/definition/schedule/switch'; //
|
||||||
export const GetDefinitionScheduleUrl = '/api/definition/schedule/get'; // 接口定义-定时同步-查询
|
export const GetDefinitionScheduleUrl = '/api/definition/schedule/get'; // 接口定义-定时同步-查询
|
||||||
export const DeleteDefinitionScheduleUrl = '/api/definition/schedule/delete'; // 接口定义-定时同步-删除
|
export const DeleteDefinitionScheduleUrl = '/api/definition/schedule/delete'; // 接口定义-定时同步-删除
|
||||||
export const DebugDefinitionUrl = '/api/definition/debug'; // 接口定义-调试
|
export const DebugDefinitionUrl = '/api/definition/debug'; // 接口定义-调试
|
||||||
|
export const ToggleFollowDefinitionUrl = '/api/definition/follow'; // 接口定义-关注/取消关注
|
||||||
|
/**
|
||||||
|
* Mock
|
||||||
|
*/
|
||||||
|
export const DefinitionMockPageUrl = '/api/definition/mock/page'; // mock列表
|
||||||
|
export const UpdateMockStatusUrl = '/api/definition/mock/enable/'; // 更新mock状态
|
||||||
|
export const DeleteMockUrl = '/api/definition/mock/delete'; // 刪除mock
|
||||||
|
|
|
@ -632,6 +632,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.ms-params-input:not(.arco-input-focus) {
|
.ms-params-input:not(.arco-input-focus) {
|
||||||
|
@apply bg-transparent;
|
||||||
|
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
&:not(:hover) {
|
&:not(:hover) {
|
||||||
.arco-input::placeholder {
|
.arco-input::placeholder {
|
||||||
|
|
|
@ -0,0 +1,106 @@
|
||||||
|
<template>
|
||||||
|
<div class="ms-detail-card">
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center gap-[4px]">
|
||||||
|
<a-tooltip :content="t(props.title)">
|
||||||
|
<div class="one-line-text flex-1 font-medium text-[var(--color-text-1)]">
|
||||||
|
{{ t(props.title) }}
|
||||||
|
</div>
|
||||||
|
</a-tooltip>
|
||||||
|
<slot name="titleAppend"></slot>
|
||||||
|
</div>
|
||||||
|
<div v-if="$slots.titleRight" class="flex items-center">
|
||||||
|
<slot name="titleRight"></slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="ms-detail-card-desc">
|
||||||
|
<div
|
||||||
|
v-for="item of showingDescription"
|
||||||
|
:key="item.key"
|
||||||
|
class="flex w-[calc(100%/3)] items-center gap-[8px]"
|
||||||
|
:style="{ width: item.width }"
|
||||||
|
>
|
||||||
|
<div class="text-[var(--color-text-4)]">{{ t(item.locale) }}</div>
|
||||||
|
<MsTagGroup v-if="Array.isArray(item.value)" :tag-list="item.value" size="small" is-string-tag />
|
||||||
|
<slot v-else :name="item.key" :value="item.value">
|
||||||
|
<a-tooltip :content="item.value" :disabled="isEmpty(item.value)">
|
||||||
|
<div class="text-[var(--color-text-1)]">{{ item.value || '-' }}</div>
|
||||||
|
</a-tooltip>
|
||||||
|
</slot>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MsButton type="text" class="more-btn" @click="toggleExpand">
|
||||||
|
<div v-if="isExpand" class="flex items-center gap-[4px]">
|
||||||
|
{{ t('msDetailCard.collapse') }}
|
||||||
|
<icon-up :size="14" />
|
||||||
|
</div>
|
||||||
|
<div v-else class="flex items-center gap-[4px]">
|
||||||
|
{{ t('msDetailCard.more') }}
|
||||||
|
<icon-down :size="14" />
|
||||||
|
</div>
|
||||||
|
</MsButton>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { isEmpty } from 'lodash-es';
|
||||||
|
|
||||||
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
|
import MsTagGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
export interface Description {
|
||||||
|
key: string;
|
||||||
|
locale: string;
|
||||||
|
value: string | string[];
|
||||||
|
width?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
title: string;
|
||||||
|
description: Description[];
|
||||||
|
simpleShowCount?: number; // 简单展示的数量,超过的内容隐藏,并展示更多按钮
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const isExpand = ref(false);
|
||||||
|
|
||||||
|
function toggleExpand() {
|
||||||
|
isExpand.value = !isExpand.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const showingDescription = computed(() => {
|
||||||
|
if (isExpand.value) {
|
||||||
|
return props.description;
|
||||||
|
}
|
||||||
|
if (props.simpleShowCount && props.description.length > props.simpleShowCount) {
|
||||||
|
return props.description.slice(0, props.simpleShowCount);
|
||||||
|
}
|
||||||
|
return props.description;
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.ms-detail-card {
|
||||||
|
@apply relative flex flex-col;
|
||||||
|
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
|
background-color: var(--color-text-n9);
|
||||||
|
gap: 8px;
|
||||||
|
.ms-detail-card-desc {
|
||||||
|
@apply flex flex-wrap overflow-hidden; // TODO:过渡动画
|
||||||
|
}
|
||||||
|
.more-btn {
|
||||||
|
@apply absolute;
|
||||||
|
|
||||||
|
bottom: 2px;
|
||||||
|
left: 50%;
|
||||||
|
font-size: 12px;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
'msDetailCard.more': 'Expand more',
|
||||||
|
'msDetailCard.collapse': 'Collapse',
|
||||||
|
};
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
'msDetailCard.more': '展开更多',
|
||||||
|
'msDetailCard.collapse': '收起',
|
||||||
|
};
|
|
@ -2,17 +2,120 @@
|
||||||
<MsBaseTable
|
<MsBaseTable
|
||||||
v-bind="propsRes"
|
v-bind="propsRes"
|
||||||
:hoverable="false"
|
:hoverable="false"
|
||||||
no-disable
|
|
||||||
is-simple-setting
|
is-simple-setting
|
||||||
:span-method="props.spanMethod"
|
:span-method="props.spanMethod"
|
||||||
|
:class="!props.selectable && !props.draggable ? 'ms-form-table-no-left-action' : ''"
|
||||||
v-on="propsEvent"
|
v-on="propsEvent"
|
||||||
>
|
>
|
||||||
|
<!-- 展开行-->
|
||||||
|
<template #expand-icon="{ expanded, record }">
|
||||||
|
<div class="flex items-center gap-[2px] text-[var(--color-text-4)]">
|
||||||
|
<MsIcon :type="expanded ? 'icon-icon_split_turn-down_arrow' : 'icon-icon_split-turn-down-left'" />
|
||||||
|
<div v-if="record.children">{{ record.children.length }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<template
|
<template
|
||||||
v-for="item of props.columns.filter((e) => e.slotName !== undefined)"
|
v-for="item of props.columns.filter((e) => e.slotName !== undefined)"
|
||||||
#[item.slotName!]="{ record, rowIndex, column }"
|
#[item.slotName!]="{ record, rowIndex, column }"
|
||||||
>
|
>
|
||||||
<slot :name="item.slotName" v-bind="{ record, rowIndex, column, dataIndex: item.dataIndex, columnConfig: item }">
|
<slot :name="item.slotName" v-bind="{ record, rowIndex, column, dataIndex: item.dataIndex, columnConfig: item }">
|
||||||
{{ record[item.dataIndex as string] || '-' }}
|
<a-tooltip
|
||||||
|
v-if="item.hasRequired"
|
||||||
|
:content="t(record.required ? 'msFormTable.paramRequired' : 'msFormTable.paramNotRequired')"
|
||||||
|
>
|
||||||
|
<MsButton
|
||||||
|
type="icon"
|
||||||
|
:class="[
|
||||||
|
record.required ? '!text-[rgb(var(--danger-5))]' : '!text-[var(--color-text-brand)]',
|
||||||
|
'!mr-[4px] !p-[4px]',
|
||||||
|
]"
|
||||||
|
size="mini"
|
||||||
|
@click="toggleRequired(record, rowIndex, item)"
|
||||||
|
>
|
||||||
|
<div>*</div>
|
||||||
|
</MsButton>
|
||||||
|
</a-tooltip>
|
||||||
|
<a-input
|
||||||
|
v-if="item.inputType === 'input'"
|
||||||
|
v-model:model-value="record[item.dataIndex as string]"
|
||||||
|
:placeholder="t(item.locale)"
|
||||||
|
class="ms-form-table-input"
|
||||||
|
:max-length="255"
|
||||||
|
size="mini"
|
||||||
|
@input="() => handleFormChange(record, rowIndex, item)"
|
||||||
|
/>
|
||||||
|
<a-select
|
||||||
|
v-else-if="item.inputType === 'select'"
|
||||||
|
v-model:model-value="record[item.dataIndex as string]"
|
||||||
|
:options="item.typeOptions || []"
|
||||||
|
class="ms-form-table-input w-full"
|
||||||
|
size="mini"
|
||||||
|
@change="() => handleFormChange(record, rowIndex, item)"
|
||||||
|
/>
|
||||||
|
<a-popover
|
||||||
|
v-else-if="item.inputType === 'tags'"
|
||||||
|
position="tl"
|
||||||
|
:disabled="record[item.dataIndex as string].length === 0"
|
||||||
|
class="ms-params-input-popover"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<div class="ms-form-table-popover-title">
|
||||||
|
{{ t('common.tag') }}
|
||||||
|
</div>
|
||||||
|
<div class="ms-form-table-popover-value">
|
||||||
|
<MsTagsGroup is-string-tag :tag-list="record[item.dataIndex as string]" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<MsTagsInput
|
||||||
|
v-model:model-value="record[item.dataIndex as string]"
|
||||||
|
:max-tag-count="1"
|
||||||
|
input-class="ms-form-table-input"
|
||||||
|
size="mini"
|
||||||
|
@change="() => handleFormChange(record, rowIndex, item)"
|
||||||
|
@clear="() => handleFormChange(record, rowIndex, item)"
|
||||||
|
/>
|
||||||
|
</a-popover>
|
||||||
|
<a-switch
|
||||||
|
v-else-if="item.inputType === 'switch'"
|
||||||
|
v-model:model-value="record[item.dataIndex as string]"
|
||||||
|
size="small"
|
||||||
|
class="ms-form-table-input-switch"
|
||||||
|
type="line"
|
||||||
|
@change="() => handleFormChange(record, rowIndex, item)"
|
||||||
|
/>
|
||||||
|
<a-checkbox
|
||||||
|
v-else-if="item.inputType === 'checkbox'"
|
||||||
|
v-model:model-value="record[item.dataIndex as string]"
|
||||||
|
@change="() => handleFormChange(record, rowIndex, item)"
|
||||||
|
/>
|
||||||
|
<template v-else-if="item.inputType === 'text'">
|
||||||
|
{{
|
||||||
|
typeof item.valueFormat === 'function' ? item.valueFormat(record) : record[item.dataIndex as string] || '-'
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
<template v-else-if="item.dataIndex === 'action'">
|
||||||
|
<div
|
||||||
|
:key="item.dataIndex"
|
||||||
|
class="flex flex-row items-center"
|
||||||
|
:class="{ 'justify-end': item.align === 'right' }"
|
||||||
|
>
|
||||||
|
<slot
|
||||||
|
name="operationPre"
|
||||||
|
v-bind="{ record, rowIndex, column, dataIndex: item.dataIndex, columnConfig: item }"
|
||||||
|
></slot>
|
||||||
|
<MsTableMoreAction
|
||||||
|
v-if="item.moreAction"
|
||||||
|
:list="getMoreActionList(item.moreAction, record)"
|
||||||
|
@select="(e) => handleMoreActionSelect(e, record, item, rowIndex)"
|
||||||
|
/>
|
||||||
|
<icon-minus-circle
|
||||||
|
v-if="dataLength > 1 && rowIndex !== dataLength - 1"
|
||||||
|
class="ml-[8px] cursor-pointer text-[var(--color-text-4)]"
|
||||||
|
size="20"
|
||||||
|
@click="deleteParam(record, rowIndex)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</slot>
|
</slot>
|
||||||
</template>
|
</template>
|
||||||
</MsBaseTable>
|
</MsBaseTable>
|
||||||
|
@ -20,26 +123,38 @@
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { TableColumnData, TableData } from '@arco-design/web-vue';
|
import { TableColumnData, TableData } from '@arco-design/web-vue';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
|
import MsButton from '@/components/pure/ms-button/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';
|
||||||
import type { MsTableColumnData } from '@/components/pure/ms-table/type';
|
import type { MsTableColumnData } from '@/components/pure/ms-table/type';
|
||||||
import useTable from '@/components/pure/ms-table/useTable';
|
import useTable from '@/components/pure/ms-table/useTable';
|
||||||
|
import MsTableMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||||
|
import MsTagsGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
|
||||||
|
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||||
|
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useTableStore from '@/hooks/useTableStore';
|
import useTableStore from '@/hooks/useTableStore';
|
||||||
|
|
||||||
import { SelectAllEnum, TableKeyEnum } from '@/enums/tableEnum';
|
import { SelectAllEnum, TableKeyEnum } from '@/enums/tableEnum';
|
||||||
|
|
||||||
|
import { ActionsItem } from '../ms-table-more-action/types';
|
||||||
import { TableOperationColumn } from '@arco-design/web-vue/es/table/interface';
|
import { TableOperationColumn } from '@arco-design/web-vue/es/table/interface';
|
||||||
|
|
||||||
export interface FormTableColumn extends MsTableColumnData {
|
export interface FormTableColumn extends MsTableColumnData {
|
||||||
enable?: boolean; // 是否启用
|
enable?: boolean; // 是否启用
|
||||||
|
required?: boolean; // 是否必填
|
||||||
|
inputType?: 'input' | 'select' | 'tags' | 'switch' | 'text' | 'checkbox'; // 输入组件类型
|
||||||
|
valueFormat?: (record: Record<string, any>) => string; // 展示值格式化,仅在inputType为text时生效
|
||||||
[key: string]: any; // 扩展属性
|
[key: string]: any; // 扩展属性
|
||||||
}
|
}
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
data?: any[];
|
data?: Record<string, any>[];
|
||||||
columns: FormTableColumn[];
|
columns: FormTableColumn[];
|
||||||
|
defaultParamDataItem?: Record<string, any>;
|
||||||
scroll?: {
|
scroll?: {
|
||||||
x?: number | string;
|
x?: number | string;
|
||||||
y?: number | string;
|
y?: number | string;
|
||||||
|
@ -53,7 +168,6 @@
|
||||||
tableKey?: TableKeyEnum; // 表格key showSetting为true时必传
|
tableKey?: TableKeyEnum; // 表格key showSetting为true时必传
|
||||||
disabled?: boolean; // 是否禁用
|
disabled?: boolean; // 是否禁用
|
||||||
showSelectorAll?: boolean; // 是否显示全选
|
showSelectorAll?: boolean; // 是否显示全选
|
||||||
isTreeTable?: boolean; // 是否树形表格
|
|
||||||
spanMethod?: (data: {
|
spanMethod?: (data: {
|
||||||
record: TableData;
|
record: TableData;
|
||||||
column: TableColumnData | TableOperationColumn;
|
column: TableColumnData | TableOperationColumn;
|
||||||
|
@ -69,9 +183,18 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'change', data: any[]): void; // 都触发这个事件以通知父组件表格数据被更改
|
(e: 'change', data: Record<string, any>[]): void; // 都触发这个事件以通知父组件表格数据被更改
|
||||||
|
(e: 'formChange', record: Record<string, any>, columnConfig: FormTableColumn, rowIndex: number): void; // 输入项变化
|
||||||
|
(
|
||||||
|
e: 'moreActionSelect',
|
||||||
|
event: ActionsItem,
|
||||||
|
record: Record<string, any>,
|
||||||
|
columnConfig: FormTableColumn,
|
||||||
|
rowIndex: number
|
||||||
|
): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
const tableStore = useTableStore();
|
const tableStore = useTableStore();
|
||||||
|
|
||||||
async function initColumns() {
|
async function initColumns() {
|
||||||
|
@ -112,6 +235,8 @@
|
||||||
emit('change', propsRes.value.data);
|
emit('change', propsRes.value.data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const dataLength = computed(() => propsRes.value.data.length);
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => selectedKeys.value,
|
() => selectedKeys.value,
|
||||||
(arr) => {
|
(arr) => {
|
||||||
|
@ -128,14 +253,83 @@
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => props.data,
|
() => props.data,
|
||||||
(val) => {
|
(arr) => {
|
||||||
propsRes.value.data = val;
|
propsRes.value.data = arr as any[];
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
immediate: true,
|
immediate: true,
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
function emitChange(from: string, isInit?: boolean) {
|
||||||
|
if (!isInit) {
|
||||||
|
emit('change', propsRes.value.data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当表格输入框变化时,给参数表格添加一行数据行
|
||||||
|
* @param val 输入值
|
||||||
|
* @param key 当前列的 key
|
||||||
|
* @param isForce 是否强制添加
|
||||||
|
*/
|
||||||
|
function addTableLine(rowIndex: number, addLineDisabled?: boolean, isInit?: boolean) {
|
||||||
|
if (addLineDisabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (rowIndex === props.data.length - 1) {
|
||||||
|
// 最后一行的更改才会触发添加新一行
|
||||||
|
const id = new Date().getTime().toString();
|
||||||
|
propsRes.value.data.push({
|
||||||
|
id,
|
||||||
|
...cloneDeep(props.defaultParamDataItem), // 深拷贝,避免有嵌套引用类型,数据隔离
|
||||||
|
enable: true, // 是否勾选
|
||||||
|
} as any);
|
||||||
|
emitChange('addTableLine', isInit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleFormChange(record: Record<string, any>, rowIndex: number, columnConfig: FormTableColumn) {
|
||||||
|
emit('formChange', record, columnConfig, rowIndex);
|
||||||
|
addTableLine(rowIndex, columnConfig.addLineDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
function toggleRequired(record: Record<string, any>, rowIndex: number, columnConfig: FormTableColumn) {
|
||||||
|
record.required = !record.required;
|
||||||
|
emit('formChange', record, columnConfig, rowIndex);
|
||||||
|
addTableLine(rowIndex, columnConfig.addLineDisabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取更多操作按钮列表
|
||||||
|
* @param actions 按钮列表
|
||||||
|
* @param record 当前行数据
|
||||||
|
*/
|
||||||
|
function getMoreActionList(actions: ActionsItem[], record: Record<string, any>) {
|
||||||
|
if (props.columns.findIndex((e) => e.dataIndex === 'expression') !== -1) {
|
||||||
|
// 如果有expression列,就需要根据expression的值来判断按钮列表是否禁用
|
||||||
|
if (record.expression === '' || record.expression === undefined || record.expression === null) {
|
||||||
|
return actions.map((e) => ({ ...e, disabled: true }));
|
||||||
|
}
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleMoreActionSelect(
|
||||||
|
event: ActionsItem,
|
||||||
|
record: Record<string, any>,
|
||||||
|
columnConfig: FormTableColumn,
|
||||||
|
rowIndex: number
|
||||||
|
) {
|
||||||
|
emit('moreActionSelect', event, record, columnConfig, rowIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
function deleteParam(record: Record<string, any>, rowIndex: number) {
|
||||||
|
propsRes.value.data.splice(rowIndex, 1);
|
||||||
|
emitChange('deleteParam');
|
||||||
|
}
|
||||||
|
|
||||||
await initColumns();
|
await initColumns();
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -147,6 +341,11 @@
|
||||||
:deep(.arco-table .arco-table-cell) {
|
:deep(.arco-table .arco-table-cell) {
|
||||||
padding: 8px 2px;
|
padding: 8px 2px;
|
||||||
}
|
}
|
||||||
|
.ms-form-table-no-left-action {
|
||||||
|
:deep(.arco-table .arco-table-cell) {
|
||||||
|
padding: 8px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
:deep(.arco-table-cell-align-left) {
|
:deep(.arco-table-cell-align-left) {
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
|
@ -155,8 +354,18 @@
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.param-input:not(.arco-input-focus, .arco-select-view-focus)) {
|
:deep(.ms-table-row-disabled) {
|
||||||
|
td {
|
||||||
|
background-color: white !important;
|
||||||
|
}
|
||||||
|
* {
|
||||||
|
color: var(--color-text-4) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.ms-form-table-input:not(.arco-input-focus, .arco-select-view-focus)) {
|
||||||
&:not(:hover) {
|
&:not(:hover) {
|
||||||
|
@apply bg-transparent;
|
||||||
|
|
||||||
border-color: transparent !important;
|
border-color: transparent !important;
|
||||||
.arco-input::placeholder {
|
.arco-input::placeholder {
|
||||||
@apply invisible;
|
@apply invisible;
|
||||||
|
@ -172,8 +381,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
:deep(.param-input-number) {
|
:deep(.ms-form-table-input-number) {
|
||||||
@apply pr-0;
|
@apply bg-transparent pr-0;
|
||||||
.arco-input {
|
.arco-input {
|
||||||
@apply text-right;
|
@apply text-right;
|
||||||
}
|
}
|
||||||
|
@ -193,4 +402,20 @@
|
||||||
:deep(.arco-table-expand-btn) {
|
:deep(.arco-table-expand-btn) {
|
||||||
background: transparent;
|
background: transparent;
|
||||||
}
|
}
|
||||||
|
.ms-form-table-popover-title {
|
||||||
|
@apply font-medium;
|
||||||
|
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 16px;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
.ms-form-table-popover-value {
|
||||||
|
min-width: 100px;
|
||||||
|
max-width: 280px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 16px;
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
export default {};
|
|
@ -0,0 +1,4 @@
|
||||||
|
export default {
|
||||||
|
'msFormTable.paramRequired': '必填',
|
||||||
|
'msFormTable.paramNotRequired': '非必填',
|
||||||
|
};
|
|
@ -147,6 +147,7 @@
|
||||||
placement="top"
|
placement="top"
|
||||||
content-class="max-w-[400px]"
|
content-class="max-w-[400px]"
|
||||||
:content="String(record[item.dataIndex as string])"
|
:content="String(record[item.dataIndex as string])"
|
||||||
|
:disabled="record[item.dataIndex as string] === '' || record[item.dataIndex as string] === undefined || record[item.dataIndex as string] === null"
|
||||||
>
|
>
|
||||||
<div class="one-line-text">
|
<div class="one-line-text">
|
||||||
<slot :name="item.slotName" v-bind="{ record, rowIndex, column, columnConfig: item }">
|
<slot :name="item.slotName" v-bind="{ record, rowIndex, column, columnConfig: item }">
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<template>
|
<template>
|
||||||
<a-tooltip :content="tagsTooltip">
|
<a-tooltip :content="tagsTooltip">
|
||||||
<div class="flex max-w-[440px] flex-row">
|
<div class="flex max-w-[440px] flex-row">
|
||||||
<MsTag v-for="tag of showTagList" :key="tag.id" :width="getTagWidth(tag)" v-bind="attrs">
|
<MsTag v-for="tag of showTagList" :key="tag.id" :width="getTagWidth(tag)" :size="props.size" v-bind="attrs">
|
||||||
{{ props.isStringTag ? tag : tag[props.nameKey] }}
|
{{ props.isStringTag ? tag : tag[props.nameKey] }}
|
||||||
</MsTag>
|
</MsTag>
|
||||||
<MsTag v-if="props.tagList.length > props.showNum" :width="numberTagWidth" v-bind="attrs">
|
<MsTag v-if="props.tagList.length > props.showNum" :width="numberTagWidth" :size="props.size" v-bind="attrs">
|
||||||
+{{ props.tagList.length - props.showNum }}</MsTag
|
+{{ props.tagList.length - props.showNum }}</MsTag
|
||||||
>
|
>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,7 +14,7 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, useAttrs } from 'vue';
|
import { computed, useAttrs } from 'vue';
|
||||||
|
|
||||||
import MsTag from './ms-tag.vue';
|
import MsTag, { Size } from './ms-tag.vue';
|
||||||
|
|
||||||
const props = withDefaults(
|
const props = withDefaults(
|
||||||
defineProps<{
|
defineProps<{
|
||||||
|
@ -22,10 +22,12 @@
|
||||||
showNum?: number;
|
showNum?: number;
|
||||||
nameKey?: string;
|
nameKey?: string;
|
||||||
isStringTag?: boolean; // 是否是字符串数组的标签
|
isStringTag?: boolean; // 是否是字符串数组的标签
|
||||||
|
size?: Size;
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
showNum: 2,
|
showNum: 2,
|
||||||
nameKey: 'name',
|
nameKey: 'name',
|
||||||
|
size: 'medium',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
// @ts-ignore @typescript-eslint/no-duplicate-enum-values
|
||||||
export enum StatusType {
|
export enum StatusType {
|
||||||
UN_REVIEWED = 'icon-icon_block_filled', // 未评审
|
UN_REVIEWED = 'icon-icon_block_filled', // 未评审
|
||||||
UNDER_REVIEWED = 'icon-icon_testing', // 评审中
|
UNDER_REVIEWED = 'icon-icon_testing', // 评审中
|
||||||
|
|
|
@ -43,7 +43,7 @@ export enum TableKeyEnum {
|
||||||
CASE_MANAGEMENT_REVIEW = 'caseManagementReview',
|
CASE_MANAGEMENT_REVIEW = 'caseManagementReview',
|
||||||
CASE_MANAGEMENT_REVIEW_CASE = 'caseManagementReviewCase',
|
CASE_MANAGEMENT_REVIEW_CASE = 'caseManagementReviewCase',
|
||||||
CASE_MANAGEMENT_TAB_DEFECT = 'caseManagementTabDefect',
|
CASE_MANAGEMENT_TAB_DEFECT = 'caseManagementTabDefect',
|
||||||
CASE_MANAGEMENT_TAB_DEFECT_TEST_PLAN = 'caseManagementTabTestPlan',
|
// CASE_MANAGEMENT_TAB_DEFECT_TEST_PLAN = 'caseManagementTabTestPlan',
|
||||||
CASE_MANAGEMENT_TAB_DEPENDENCY_PRE_CASE = 'caseManagementTabPreDependency',
|
CASE_MANAGEMENT_TAB_DEPENDENCY_PRE_CASE = 'caseManagementTabPreDependency',
|
||||||
CASE_MANAGEMENT_TAB_DEPENDENCY_POST_CASE = 'caseManagementTabPostDependency',
|
CASE_MANAGEMENT_TAB_DEPENDENCY_POST_CASE = 'caseManagementTabPostDependency',
|
||||||
CASE_MANAGEMENT_TAB_REVIEW = 'caseManagementTabCaseReview',
|
CASE_MANAGEMENT_TAB_REVIEW = 'caseManagementTabCaseReview',
|
||||||
|
|
|
@ -14,7 +14,7 @@ export enum ExecutionMethods {
|
||||||
SCHEDULE = 'SCHEDULE', // 定时任务
|
SCHEDULE = 'SCHEDULE', // 定时任务
|
||||||
MANUAL = 'MANUAL', // 手动执行
|
MANUAL = 'MANUAL', // 手动执行
|
||||||
API = 'API', // 接口调用
|
API = 'API', // 接口调用
|
||||||
BATCH = 'API', // 批量执行
|
// BATCH = 'API', // 批量执行
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum ExecutionMethodsLabel {
|
export enum ExecutionMethodsLabel {
|
||||||
|
|
|
@ -126,4 +126,8 @@ export default {
|
||||||
'common.module': 'Module',
|
'common.module': 'Module',
|
||||||
'common.yes': 'Yes',
|
'common.yes': 'Yes',
|
||||||
'common.no': 'No',
|
'common.no': 'No',
|
||||||
|
'common.creator': 'Creator',
|
||||||
|
'common.followSuccess': 'Followed',
|
||||||
|
'common.unFollowSuccess': 'Unfollow successfully',
|
||||||
|
'common.share': 'Share',
|
||||||
};
|
};
|
||||||
|
|
|
@ -129,4 +129,8 @@ export default {
|
||||||
'common.module': '模块',
|
'common.module': '模块',
|
||||||
'common.yes': '是',
|
'common.yes': '是',
|
||||||
'common.no': '否',
|
'common.no': '否',
|
||||||
|
'common.creator': '创建人',
|
||||||
|
'common.followSuccess': '关注成功',
|
||||||
|
'common.unFollowSuccess': '取消关注成功',
|
||||||
|
'common.share': '分享',
|
||||||
};
|
};
|
||||||
|
|
|
@ -145,7 +145,7 @@ export interface JsonSchema {
|
||||||
export interface ExecuteJsonBody {
|
export interface ExecuteJsonBody {
|
||||||
enableJsonSchema?: boolean;
|
enableJsonSchema?: boolean;
|
||||||
enableTransition?: boolean;
|
enableTransition?: boolean;
|
||||||
jsonSchema?: JsonSchema;
|
jsonSchema?: JsonSchema[];
|
||||||
jsonValue: string;
|
jsonValue: string;
|
||||||
}
|
}
|
||||||
// 执行请求配置
|
// 执行请求配置
|
||||||
|
|
|
@ -17,8 +17,8 @@ function setupPageGuard(router: Router) {
|
||||||
// 取消上个路由未完成的请求(不包含设置了ignoreCancelToken的请求)
|
// 取消上个路由未完成的请求(不包含设置了ignoreCancelToken的请求)
|
||||||
axiosCanceler.removeAllPending();
|
axiosCanceler.removeAllPending();
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const urlOrgId = to.query.organizationId;
|
const urlOrgId = to.query.orgId;
|
||||||
const urlProjectId = to.query.projectId;
|
const urlProjectId = to.query.pId;
|
||||||
// 如果访问页面的时候携带了项目 ID 或组织 ID,则将页面上的组织 ID和项目 ID设置为当前选中的组织和项目
|
// 如果访问页面的时候携带了项目 ID 或组织 ID,则将页面上的组织 ID和项目 ID设置为当前选中的组织和项目
|
||||||
if (urlOrgId) {
|
if (urlOrgId) {
|
||||||
appStore.setCurrentOrgId(urlOrgId as string);
|
appStore.setCurrentOrgId(urlOrgId as string);
|
||||||
|
@ -31,7 +31,7 @@ function setupPageGuard(router: Router) {
|
||||||
if (urlOrgId === undefined) {
|
if (urlOrgId === undefined) {
|
||||||
to.query = {
|
to.query = {
|
||||||
...to.query,
|
...to.query,
|
||||||
organizationId: appStore.currentOrgId,
|
orgId: appStore.currentOrgId,
|
||||||
};
|
};
|
||||||
next(to);
|
next(to);
|
||||||
return;
|
return;
|
||||||
|
@ -41,8 +41,8 @@ function setupPageGuard(router: Router) {
|
||||||
if (urlOrgId === undefined && urlProjectId === undefined) {
|
if (urlOrgId === undefined && urlProjectId === undefined) {
|
||||||
to.query = {
|
to.query = {
|
||||||
...to.query,
|
...to.query,
|
||||||
organizationId: appStore.currentOrgId,
|
orgId: appStore.currentOrgId,
|
||||||
projectId: appStore.currentProjectId,
|
pId: appStore.currentProjectId,
|
||||||
};
|
};
|
||||||
|
|
||||||
next(to);
|
next(to);
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
<template>
|
<template>
|
||||||
<MsTag :self-style="status.style"> {{ status.text }}</MsTag>
|
<MsTag :self-style="status.style" :size="props.size"> {{ status.text }}</MsTag>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import MsTag from '@/components/pure/ms-tag/ms-tag.vue';
|
import MsTag, { Size } from '@/components/pure/ms-tag/ms-tag.vue';
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
|
@ -11,6 +11,7 @@
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
status: RequestDefinitionStatus;
|
status: RequestDefinitionStatus;
|
||||||
|
size?: Size;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
v-if="showBatchAddParamDrawer"
|
v-if="showBatchAddParamDrawer"
|
||||||
v-model:model-value="batchParamsCode"
|
v-model:model-value="batchParamsCode"
|
||||||
class="flex-1"
|
class="flex-1"
|
||||||
theme="MS-text"
|
theme="vs"
|
||||||
height="100%"
|
height="100%"
|
||||||
:show-full-screen="false"
|
:show-full-screen="false"
|
||||||
:show-theme-change="false"
|
:show-theme-change="false"
|
||||||
|
|
|
@ -52,6 +52,8 @@
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.param-input:not(.arco-input-focus) {
|
.param-input:not(.arco-input-focus) {
|
||||||
&:not(:hover) {
|
&:not(:hover) {
|
||||||
|
@apply bg-transparent;
|
||||||
|
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,7 +72,7 @@
|
||||||
<a-input
|
<a-input
|
||||||
v-model:model-value="record[columnConfig.dataIndex as string]"
|
v-model:model-value="record[columnConfig.dataIndex as string]"
|
||||||
:placeholder="t('apiTestDebug.paramNamePlaceholder')"
|
:placeholder="t('apiTestDebug.paramNamePlaceholder')"
|
||||||
class="param-input"
|
class="ms-form-table-input"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
size="mini"
|
size="mini"
|
||||||
@input="() => addTableLine(rowIndex, columnConfig.addLineDisabled)"
|
@input="() => addTableLine(rowIndex, columnConfig.addLineDisabled)"
|
||||||
|
@ -99,7 +99,7 @@
|
||||||
<a-select
|
<a-select
|
||||||
v-model:model-value="record.paramType"
|
v-model:model-value="record.paramType"
|
||||||
:options="columnConfig.typeOptions || []"
|
:options="columnConfig.typeOptions || []"
|
||||||
class="param-input w-full"
|
class="ms-form-table-input w-full"
|
||||||
size="mini"
|
size="mini"
|
||||||
@change="(val) => handleTypeChange(val, record, rowIndex, columnConfig.addLineDisabled)"
|
@change="(val) => handleTypeChange(val, record, rowIndex, columnConfig.addLineDisabled)"
|
||||||
/>
|
/>
|
||||||
|
@ -108,7 +108,7 @@
|
||||||
<a-select
|
<a-select
|
||||||
v-model:model-value="record.extractType"
|
v-model:model-value="record.extractType"
|
||||||
:options="columnConfig.typeOptions || []"
|
:options="columnConfig.typeOptions || []"
|
||||||
class="param-input w-[110px]"
|
class="ms-form-table-input w-[110px]"
|
||||||
size="mini"
|
size="mini"
|
||||||
@change="() => addTableLine(rowIndex)"
|
@change="() => addTableLine(rowIndex)"
|
||||||
/>
|
/>
|
||||||
|
@ -117,7 +117,7 @@
|
||||||
<a-select
|
<a-select
|
||||||
v-model:model-value="record.variableType"
|
v-model:model-value="record.variableType"
|
||||||
:options="columnConfig.typeOptions || []"
|
:options="columnConfig.typeOptions || []"
|
||||||
class="param-input w-[110px]"
|
class="ms-form-table-input w-[110px]"
|
||||||
size="mini"
|
size="mini"
|
||||||
@change="() => addTableLine(rowIndex)"
|
@change="() => addTableLine(rowIndex)"
|
||||||
/>
|
/>
|
||||||
|
@ -126,7 +126,7 @@
|
||||||
<a-select
|
<a-select
|
||||||
v-model:model-value="record.extractScope"
|
v-model:model-value="record.extractScope"
|
||||||
:options="columnConfig.typeOptions || []"
|
:options="columnConfig.typeOptions || []"
|
||||||
class="param-input w-[180px]"
|
class="ms-form-table-input w-[180px]"
|
||||||
size="mini"
|
size="mini"
|
||||||
@change="() => addTableLine(rowIndex)"
|
@change="() => addTableLine(rowIndex)"
|
||||||
/>
|
/>
|
||||||
|
@ -151,7 +151,7 @@
|
||||||
</template>
|
</template>
|
||||||
<a-input
|
<a-input
|
||||||
v-model:model-value="record.value"
|
v-model:model-value="record.value"
|
||||||
class="param-input"
|
class="ms-form-table-input"
|
||||||
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
size="mini"
|
size="mini"
|
||||||
|
@ -170,7 +170,7 @@
|
||||||
:file-save-as-source-id="props.fileSaveAsSourceId"
|
:file-save-as-source-id="props.fileSaveAsSourceId"
|
||||||
:file-save-as-api="props.fileSaveAsApi"
|
:file-save-as-api="props.fileSaveAsApi"
|
||||||
:file-module-options-api="props.fileModuleOptionsApi"
|
:file-module-options-api="props.fileModuleOptionsApi"
|
||||||
input-class="param-input h-[24px]"
|
input-class="ms-form-table-input h-[24px]"
|
||||||
input-size="small"
|
input-size="small"
|
||||||
tag-size="small"
|
tag-size="small"
|
||||||
@change="(files, file) => handleFileChange(files, record, rowIndex, file)"
|
@change="(files, file) => handleFileChange(files, record, rowIndex, file)"
|
||||||
|
@ -190,8 +190,9 @@
|
||||||
v-model:model-value="record.minLength"
|
v-model:model-value="record.minLength"
|
||||||
:placeholder="t('apiTestDebug.paramMin')"
|
:placeholder="t('apiTestDebug.paramMin')"
|
||||||
:min="0"
|
:min="0"
|
||||||
class="param-input param-input-number"
|
class="ms-form-table-input ms-form-table-input-number"
|
||||||
size="mini"
|
size="mini"
|
||||||
|
model-event="input"
|
||||||
@change="() => addTableLine(rowIndex)"
|
@change="() => addTableLine(rowIndex)"
|
||||||
/>
|
/>
|
||||||
<div class="mx-[4px]">{{ t('common.to') }}</div>
|
<div class="mx-[4px]">{{ t('common.to') }}</div>
|
||||||
|
@ -199,13 +200,14 @@
|
||||||
v-model:model-value="record.maxLength"
|
v-model:model-value="record.maxLength"
|
||||||
:placeholder="t('apiTestDebug.paramMax')"
|
:placeholder="t('apiTestDebug.paramMax')"
|
||||||
:min="0"
|
:min="0"
|
||||||
class="param-input"
|
class="ms-form-table-input"
|
||||||
size="mini"
|
size="mini"
|
||||||
|
model-event="input"
|
||||||
@change="() => addTableLine(rowIndex)"
|
@change="() => addTableLine(rowIndex)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #tag="{ record, columnConfig }">
|
<template #tag="{ record, columnConfig, rowIndex }">
|
||||||
<a-popover
|
<a-popover
|
||||||
position="tl"
|
position="tl"
|
||||||
:disabled="record[columnConfig.dataIndex as string].length === 0"
|
:disabled="record[columnConfig.dataIndex as string].length === 0"
|
||||||
|
@ -222,8 +224,10 @@
|
||||||
<MsTagsInput
|
<MsTagsInput
|
||||||
v-model:model-value="record[columnConfig.dataIndex as string]"
|
v-model:model-value="record[columnConfig.dataIndex as string]"
|
||||||
:max-tag-count="1"
|
:max-tag-count="1"
|
||||||
input-class="param-input"
|
input-class="ms-form-table-input"
|
||||||
size="mini"
|
size="mini"
|
||||||
|
@change="() => addTableLine(rowIndex)"
|
||||||
|
@clear="() => addTableLine(rowIndex)"
|
||||||
/>
|
/>
|
||||||
</a-popover>
|
</a-popover>
|
||||||
</template>
|
</template>
|
||||||
|
@ -240,7 +244,7 @@
|
||||||
<a-switch
|
<a-switch
|
||||||
v-model:model-value="record.encode"
|
v-model:model-value="record.encode"
|
||||||
size="small"
|
size="small"
|
||||||
class="param-input-switch"
|
class="ms-form-table-input-switch"
|
||||||
type="line"
|
type="line"
|
||||||
@change="() => addTableLine(rowIndex)"
|
@change="() => addTableLine(rowIndex)"
|
||||||
/>
|
/>
|
||||||
|
@ -259,13 +263,13 @@
|
||||||
</template>
|
</template>
|
||||||
<!-- 响应头 -->
|
<!-- 响应头 -->
|
||||||
<template #header="{ record, columnConfig, rowIndex }">
|
<template #header="{ record, columnConfig, rowIndex }">
|
||||||
<a-select v-model="record.header" class="param-input" size="mini" @change="() => addTableLine(rowIndex)">
|
<a-select v-model="record.header" class="ms-form-table-input" size="mini" @change="() => addTableLine(rowIndex)">
|
||||||
<a-option v-for="item in columnConfig.options" :key="item.value">{{ t(item.label) }}</a-option>
|
<a-option v-for="item in columnConfig.options" :key="item.value">{{ t(item.label) }}</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
</template>
|
</template>
|
||||||
<!-- 匹配条件 -->
|
<!-- 匹配条件 -->
|
||||||
<template #condition="{ record, columnConfig }">
|
<template #condition="{ record, columnConfig }">
|
||||||
<a-select v-model="record.condition" size="mini" class="param-input">
|
<a-select v-model="record.condition" size="mini" class="ms-form-table-input">
|
||||||
<a-option v-for="item in columnConfig.options" :key="item.value" :value="item.value">{{
|
<a-option v-for="item in columnConfig.options" :key="item.value" :value="item.value">{{
|
||||||
t(item.label)
|
t(item.label)
|
||||||
}}</a-option>
|
}}</a-option>
|
||||||
|
@ -289,12 +293,12 @@
|
||||||
<div>*</div>
|
<div>*</div>
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<a-input v-model="record.expectedValue" size="mini" class="param-input" />
|
<a-input v-model="record.expectedValue" size="mini" class="ms-form-table-input" />
|
||||||
</template>
|
</template>
|
||||||
<template #project="{ record, rowIndex }">
|
<template #project="{ record, rowIndex }">
|
||||||
<a-select
|
<a-select
|
||||||
v-model:model-value="record.projectId"
|
v-model:model-value="record.projectId"
|
||||||
class="param-input w-max-[200px] focus-within:!bg-[var(--color-text-n8)] hover:!bg-[var(--color-text-n8)]"
|
class="ms-form-table-input w-max-[200px] focus-within:!bg-[var(--color-text-n8)] hover:!bg-[var(--color-text-n8)]"
|
||||||
:bordered="false"
|
:bordered="false"
|
||||||
allow-search
|
allow-search
|
||||||
@change="(val) => handleProjectChange(val as string,record.projectId, rowIndex)"
|
@change="(val) => handleProjectChange(val as string,record.projectId, rowIndex)"
|
||||||
|
@ -330,7 +334,7 @@
|
||||||
:search-keys="['name']"
|
:search-keys="['name']"
|
||||||
size="mini"
|
size="mini"
|
||||||
allow-search
|
allow-search
|
||||||
class="param-input"
|
class="ms-form-table-input"
|
||||||
:remote-func="initEnvOptions"
|
:remote-func="initEnvOptions"
|
||||||
:remote-extra-params="{ projectId: record.projectId, keyword: record.environmentInput }"
|
:remote-extra-params="{ projectId: record.projectId, keyword: record.environmentInput }"
|
||||||
@change-object="(val) => handleEnvironment(val, record)"
|
@change-object="(val) => handleEnvironment(val, record)"
|
||||||
|
@ -413,7 +417,7 @@
|
||||||
<MsCodeEditor
|
<MsCodeEditor
|
||||||
v-if="showQuickInputParam"
|
v-if="showQuickInputParam"
|
||||||
v-model:model-value="quickInputParamValue"
|
v-model:model-value="quickInputParamValue"
|
||||||
theme="MS-text"
|
theme="vs"
|
||||||
height="300px"
|
height="300px"
|
||||||
:show-full-screen="false"
|
:show-full-screen="false"
|
||||||
>
|
>
|
||||||
|
|
|
@ -150,7 +150,6 @@
|
||||||
emit('renameFinish', form.value.field, props.nodeId);
|
emit('renameFinish', form.value.field, props.nodeId);
|
||||||
} else if (props.mode === 'tabRename') {
|
} else if (props.mode === 'tabRename') {
|
||||||
// 响应 tab 重命名
|
// 响应 tab 重命名
|
||||||
Message.success(t('common.updateSuccess'));
|
|
||||||
emit('renameFinish', form.value.field);
|
emit('renameFinish', form.value.field);
|
||||||
}
|
}
|
||||||
if (done) {
|
if (done) {
|
||||||
|
|
|
@ -69,6 +69,11 @@
|
||||||
dataIndex: 'driver',
|
dataIndex: 'driver',
|
||||||
showTooltip: true,
|
showTooltip: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: 'URL',
|
||||||
|
dataIndex: 'dbUrl',
|
||||||
|
showTooltip: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
title: 'apiTestDebug.username',
|
title: 'apiTestDebug.username',
|
||||||
dataIndex: 'username',
|
dataIndex: 'username',
|
||||||
|
|
|
@ -249,8 +249,8 @@
|
||||||
v-show="showResponse"
|
v-show="showResponse"
|
||||||
v-model:active-layout="activeLayout"
|
v-model:active-layout="activeLayout"
|
||||||
v-model:active-tab="requestVModel.responseActiveTab"
|
v-model:active-tab="requestVModel.responseActiveTab"
|
||||||
|
v-model:response-definition="requestVModel.responseDefinition"
|
||||||
:is-expanded="isExpanded"
|
:is-expanded="isExpanded"
|
||||||
:response-definition="requestVModel.responseDefinition"
|
|
||||||
:hide-layout-switch="props.hideResponseLayoutSwitch"
|
:hide-layout-switch="props.hideResponseLayoutSwitch"
|
||||||
:request-task-result="requestVModel.response"
|
:request-task-result="requestVModel.response"
|
||||||
:is-edit="props.isDefinition && isHttpProtocol"
|
:is-edit="props.isDefinition && isHttpProtocol"
|
||||||
|
@ -376,6 +376,7 @@
|
||||||
activeTab: RequestComposition;
|
activeTab: RequestComposition;
|
||||||
mode?: 'definition' | 'debug';
|
mode?: 'definition' | 'debug';
|
||||||
executeLoading: boolean; // 执行中loading
|
executeLoading: boolean; // 执行中loading
|
||||||
|
isCopy?: boolean; // 是否是复制
|
||||||
}
|
}
|
||||||
export type RequestParam = ExecuteApiRequestFullParams & {
|
export type RequestParam = ExecuteApiRequestFullParams & {
|
||||||
responseDefinition?: ResponseItem[];
|
responseDefinition?: ResponseItem[];
|
||||||
|
@ -616,7 +617,7 @@
|
||||||
*/
|
*/
|
||||||
function setPluginFormData() {
|
function setPluginFormData() {
|
||||||
const tempForm = temporaryPluginFormMap[requestVModel.value.id];
|
const tempForm = temporaryPluginFormMap[requestVModel.value.id];
|
||||||
if (tempForm || !requestVModel.value.isNew) {
|
if (tempForm || !requestVModel.value.isNew || requestVModel.value.isCopy) {
|
||||||
// 如果缓存的表单数据存在或者是编辑状态,则需要将之前的输入数据填充
|
// 如果缓存的表单数据存在或者是编辑状态,则需要将之前的输入数据填充
|
||||||
const formData = tempForm || requestVModel.value;
|
const formData = tempForm || requestVModel.value;
|
||||||
if (fApi.value) {
|
if (fApi.value) {
|
||||||
|
@ -918,13 +919,17 @@
|
||||||
let requestModuleId = '';
|
let requestModuleId = '';
|
||||||
let apiDefinitionParams: Record<string, any> = {};
|
let apiDefinitionParams: Record<string, any> = {};
|
||||||
if (props.isDefinition) {
|
if (props.isDefinition) {
|
||||||
|
// 接口定义有响应内容定义
|
||||||
requestName = requestVModel.value.name;
|
requestName = requestVModel.value.name;
|
||||||
requestModuleId = requestVModel.value.moduleId;
|
requestModuleId = requestVModel.value.moduleId;
|
||||||
apiDefinitionParams = {
|
apiDefinitionParams = {
|
||||||
tags: requestVModel.value.tags,
|
tags: requestVModel.value.tags,
|
||||||
description: requestVModel.value.description,
|
description: requestVModel.value.description,
|
||||||
status: requestVModel.value.status,
|
status: requestVModel.value.status,
|
||||||
response: requestVModel.value.responseDefinition,
|
response: requestVModel.value.responseDefinition?.map((e) => ({
|
||||||
|
...e,
|
||||||
|
headers: filterKeyValParams(e.headers, defaultKeyValueParamItem).validParams,
|
||||||
|
})),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
requestName = requestVModel.value.isNew ? saveModalForm.value.name : requestVModel.value.name;
|
requestName = requestVModel.value.isNew ? saveModalForm.value.name : requestVModel.value.name;
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
<template #label="{ tab }">
|
<template #label="{ tab }">
|
||||||
<div class="response-tab">
|
<div class="response-tab">
|
||||||
<div v-if="tab.defaultFlag" class="response-tab-default-icon"></div>
|
<div v-if="tab.defaultFlag" class="response-tab-default-icon"></div>
|
||||||
{{ t(tab.label || '') }}({{ tab.statusCode }})
|
{{ t(tab.name || tab.label) }}({{ tab.statusCode }})
|
||||||
<MsMoreAction
|
<MsMoreAction
|
||||||
:list="
|
:list="
|
||||||
tab.defaultFlag
|
tab.defaultFlag
|
||||||
|
@ -23,12 +23,13 @@
|
||||||
<popConfirm
|
<popConfirm
|
||||||
v-model:visible="tab.showRenamePopConfirm"
|
v-model:visible="tab.showRenamePopConfirm"
|
||||||
mode="tabRename"
|
mode="tabRename"
|
||||||
:field-config="{ field: t(tab.label || '') }"
|
:field-config="{ field: t(tab.label || tab.name) }"
|
||||||
:all-names="responseTabs.map((e) => t(tab.label || ''))"
|
:all-names="responseTabs.map((e) => t(e.label || e.name))"
|
||||||
:popup-offset="20"
|
:popup-offset="20"
|
||||||
@rename-finish="
|
@rename-finish="
|
||||||
(val) => {
|
(val) => {
|
||||||
tab.label = val;
|
tab.label = val;
|
||||||
|
tab.name = val;
|
||||||
emit('change');
|
emit('change');
|
||||||
}
|
}
|
||||||
"
|
"
|
||||||
|
@ -48,7 +49,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template #content>
|
<template #content>
|
||||||
<div class="font-semibold text-[var(--color-text-1)]">
|
<div class="font-semibold text-[var(--color-text-1)]">
|
||||||
{{ t('apiTestManagement.confirmDelete', { name: tab.label }) }}
|
{{ t('apiTestManagement.confirmDelete', { name: tab.label || tab.name }) }}
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<div class="relative"></div>
|
<div class="relative"></div>
|
||||||
|
@ -72,11 +73,16 @@
|
||||||
size="small"
|
size="small"
|
||||||
@change="(val) => changeBodyFormat(val as ResponseBodyFormat)"
|
@change="(val) => changeBodyFormat(val as ResponseBodyFormat)"
|
||||||
>
|
>
|
||||||
<a-radio v-for="item of ResponseBodyFormat" :key="item" :value="item">
|
<a-radio
|
||||||
|
v-for="item of ResponseBodyFormat"
|
||||||
|
v-show="item !== ResponseBodyFormat.NONE"
|
||||||
|
:key="item"
|
||||||
|
:value="item"
|
||||||
|
>
|
||||||
{{ ResponseBodyFormat[item].toLowerCase() }}
|
{{ ResponseBodyFormat[item].toLowerCase() }}
|
||||||
</a-radio>
|
</a-radio>
|
||||||
</a-radio-group>
|
</a-radio-group>
|
||||||
<div v-if="activeResponse.body.bodyType === ResponseBodyFormat.JSON" class="ml-auto flex items-center">
|
<!-- <div v-if="activeResponse.body.bodyType === ResponseBodyFormat.JSON" class="ml-auto flex items-center">
|
||||||
<a-radio-group
|
<a-radio-group
|
||||||
v-model:model-value="activeResponse.body.jsonBody.enableJsonSchema"
|
v-model:model-value="activeResponse.body.jsonBody.enableJsonSchema"
|
||||||
size="mini"
|
size="mini"
|
||||||
|
@ -89,7 +95,7 @@
|
||||||
<a-switch v-model:model-value="activeResponse.body.jsonBody.enableTransition" size="small" type="line" />
|
<a-switch v-model:model-value="activeResponse.body.jsonBody.enableTransition" size="small" type="line" />
|
||||||
{{ t('apiTestManagement.dynamicConversion') }}
|
{{ t('apiTestManagement.dynamicConversion') }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="
|
v-if="
|
||||||
|
@ -99,6 +105,11 @@
|
||||||
"
|
"
|
||||||
class="h-[calc(100%-35px)]"
|
class="h-[calc(100%-35px)]"
|
||||||
>
|
>
|
||||||
|
<!-- <MsJsonSchema
|
||||||
|
v-if="activeResponse.body.jsonBody.enableJsonSchema"
|
||||||
|
:data="activeResponse.body.jsonBody.jsonSchema"
|
||||||
|
:columns="jsonSchemaColumns"
|
||||||
|
/> -->
|
||||||
<MsCodeEditor
|
<MsCodeEditor
|
||||||
ref="responseEditorRef"
|
ref="responseEditorRef"
|
||||||
v-model:model-value="currentBodyCode"
|
v-model:model-value="currentBodyCode"
|
||||||
|
@ -137,16 +148,17 @@
|
||||||
v-else-if="activeResponse.responseActiveTab === ResponseComposition.HEADER"
|
v-else-if="activeResponse.responseActiveTab === ResponseComposition.HEADER"
|
||||||
v-model:params="activeResponse.headers"
|
v-model:params="activeResponse.headers"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:default-param-item="[
|
:default-param-item="defaultKeyValueParamItem"
|
||||||
{
|
|
||||||
key: '',
|
|
||||||
value: '',
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
:selectable="false"
|
:selectable="false"
|
||||||
@change="emit('change')"
|
@change="handleResponseTableChange"
|
||||||
|
/>
|
||||||
|
<a-select
|
||||||
|
v-else
|
||||||
|
v-model:model-value="activeResponse.statusCode"
|
||||||
|
:options="statusCodeOptions"
|
||||||
|
class="w-[200px]"
|
||||||
|
@change="handleStatusCodeChange"
|
||||||
/>
|
/>
|
||||||
<a-select v-else v-model:model-value="activeResponse.statusCode" :options="statusCodeOptions" class="w-[200px]" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -157,6 +169,8 @@
|
||||||
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
||||||
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
||||||
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||||
|
// import { FormTableColumn } from '@/components/pure/ms-form-table/index.vue';
|
||||||
|
// import MsJsonSchema from '@/components/pure/ms-json-schema/index.vue';
|
||||||
import MsMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
import MsMoreAction from '@/components/pure/ms-table-more-action/index.vue';
|
||||||
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
import { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||||
import { MsFileItem } from '@/components/pure/ms-upload/types';
|
import { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||||
|
@ -170,7 +184,7 @@
|
||||||
import { ResponseDefinition } from '@/models/apiTest/common';
|
import { ResponseDefinition } from '@/models/apiTest/common';
|
||||||
import { ResponseBodyFormat, ResponseComposition } from '@/enums/apiEnum';
|
import { ResponseBodyFormat, ResponseComposition } from '@/enums/apiEnum';
|
||||||
|
|
||||||
import { defaultResponseItem, statusCodes } from '../../config';
|
import { defaultKeyValueParamItem, defaultResponseItem, statusCodes } from '../../config';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
responseDefinition: ResponseDefinition[];
|
responseDefinition: ResponseDefinition[];
|
||||||
|
@ -193,19 +207,11 @@
|
||||||
});
|
});
|
||||||
const activeResponse = ref<ResponseItem>(responseTabs.value[0]);
|
const activeResponse = ref<ResponseItem>(responseTabs.value[0]);
|
||||||
|
|
||||||
watch(
|
|
||||||
() => responseTabs.value,
|
|
||||||
(arr) => {
|
|
||||||
if (arr[0]) {
|
|
||||||
[activeResponse.value] = arr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
function addResponseTab(defaultProps?: Partial<ResponseItem>) {
|
function addResponseTab(defaultProps?: Partial<ResponseItem>) {
|
||||||
responseTabs.value.push({
|
responseTabs.value.push({
|
||||||
...cloneDeep(defaultResponseItem),
|
...cloneDeep(defaultResponseItem),
|
||||||
label: t('apiTestManagement.response', { count: responseTabs.value.length + 1 }),
|
label: t('apiTestManagement.response', { count: responseTabs.value.length + 1 }),
|
||||||
|
name: t('apiTestManagement.response', { count: responseTabs.value.length + 1 }),
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
id: new Date().getTime(),
|
id: new Date().getTime(),
|
||||||
defaultFlag: false,
|
defaultFlag: false,
|
||||||
|
@ -243,17 +249,23 @@
|
||||||
function handleMoreActionSelect(e: ActionsItem, _tab: ResponseItem) {
|
function handleMoreActionSelect(e: ActionsItem, _tab: ResponseItem) {
|
||||||
switch (e.eventTag) {
|
switch (e.eventTag) {
|
||||||
case 'setDefault':
|
case 'setDefault':
|
||||||
responseTabs.value = responseTabs.value.map((tab) => {
|
_tab.defaultFlag = true;
|
||||||
tab.defaultFlag = _tab.id === tab.id;
|
responseTabs.value = [
|
||||||
return tab;
|
_tab,
|
||||||
});
|
...responseTabs.value.filter((tab) => {
|
||||||
|
if (tab.id !== _tab.id) {
|
||||||
|
tab.defaultFlag = false;
|
||||||
|
}
|
||||||
|
return tab.id !== _tab.id;
|
||||||
|
}),
|
||||||
|
];
|
||||||
break;
|
break;
|
||||||
case 'rename':
|
case 'rename':
|
||||||
renameValue.value = _tab.label || '';
|
renameValue.value = _tab.label || _tab.name || '';
|
||||||
document.querySelector(`#renameSpan${_tab.id}`)?.dispatchEvent(new Event('click'));
|
document.querySelector(`#renameSpan${_tab.id}`)?.dispatchEvent(new Event('click'));
|
||||||
break;
|
break;
|
||||||
case 'copy':
|
case 'copy':
|
||||||
addResponseTab({ ..._tab, label: `${_tab.label}-Copy` });
|
addResponseTab({ ..._tab, label: `${_tab.label || _tab.name}-Copy`, name: `${_tab.label || _tab.name}-Copy` });
|
||||||
break;
|
break;
|
||||||
case 'delete':
|
case 'delete':
|
||||||
_tab.showPopConfirm = true;
|
_tab.showPopConfirm = true;
|
||||||
|
@ -291,6 +303,21 @@
|
||||||
emit('change');
|
emit('change');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const jsonSchemaColumns: FormTableColumn[] = [
|
||||||
|
// {
|
||||||
|
// title: 'apiTestManagement.paramName',
|
||||||
|
// dataIndex: 'key',
|
||||||
|
// slotName: 'key',
|
||||||
|
// inputType: 'input',
|
||||||
|
// },
|
||||||
|
// {
|
||||||
|
// title: 'apiTestManagement.paramVal',
|
||||||
|
// dataIndex: 'value',
|
||||||
|
// slotName: 'value',
|
||||||
|
// inputType: 'input',
|
||||||
|
// },
|
||||||
|
// ];
|
||||||
|
|
||||||
// 当前显示的代码
|
// 当前显示的代码
|
||||||
const currentBodyCode = computed({
|
const currentBodyCode = computed({
|
||||||
get() {
|
get() {
|
||||||
|
@ -365,12 +392,20 @@
|
||||||
title: 'apiTestManagement.paramName',
|
title: 'apiTestManagement.paramName',
|
||||||
dataIndex: 'key',
|
dataIndex: 'key',
|
||||||
slotName: 'key',
|
slotName: 'key',
|
||||||
|
inputType: 'input',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'apiTestManagement.paramVal',
|
title: 'apiTestManagement.paramVal',
|
||||||
dataIndex: 'value',
|
dataIndex: 'value',
|
||||||
slotName: 'value',
|
slotName: 'value',
|
||||||
isNormal: true,
|
isNormal: true,
|
||||||
|
inputType: 'input',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '',
|
||||||
|
dataIndex: 'operation',
|
||||||
|
slotName: 'operation',
|
||||||
|
width: 35,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -378,10 +413,22 @@
|
||||||
label: e.toString(),
|
label: e.toString(),
|
||||||
value: e,
|
value: e,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
function handleResponseTableChange(arr: any[]) {
|
||||||
|
activeResponse.value.headers = [...arr];
|
||||||
|
emit('change');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleStatusCodeChange() {
|
||||||
|
emit('change');
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
.response-container {
|
.response-container {
|
||||||
|
@apply overflow-y-auto;
|
||||||
|
.ms-scroll-bar();
|
||||||
|
|
||||||
margin-top: 8px;
|
margin-top: 8px;
|
||||||
height: calc(100% - 88px);
|
height: calc(100% - 88px);
|
||||||
.response-header-pre {
|
.response-header-pre {
|
||||||
|
|
|
@ -117,14 +117,14 @@
|
||||||
</div>
|
</div>
|
||||||
<a-spin :loading="props.loading" class="h-[calc(100%-35px)] w-full px-[18px] pb-[18px]">
|
<a-spin :loading="props.loading" class="h-[calc(100%-35px)] w-full px-[18px] pb-[18px]">
|
||||||
<edit
|
<edit
|
||||||
v-if="props.isEdit && activeResponseType === 'content' && validResponseDefinition"
|
v-if="props.isEdit && activeResponseType === 'content' && innerResponseDefinition"
|
||||||
:response-definition="validResponseDefinition"
|
v-model:response-definition="innerResponseDefinition"
|
||||||
:upload-temp-file-api="props.uploadTempFileApi"
|
:upload-temp-file-api="props.uploadTempFileApi"
|
||||||
@change="handleResponseChange"
|
@change="handleResponseChange"
|
||||||
/>
|
/>
|
||||||
<result
|
<result
|
||||||
v-else-if="!props.isEdit || (props.isEdit && activeResponseType === 'result')"
|
v-else-if="!props.isEdit || (props.isEdit && activeResponseType === 'result')"
|
||||||
v-model:activeTab="activeTab"
|
v-model:active-tab="innerActiveTab"
|
||||||
:request-result="props.requestTaskResult?.requestResults[0]"
|
:request-result="props.requestTaskResult?.requestResults[0]"
|
||||||
:console="props.requestTaskResult?.console"
|
:console="props.requestTaskResult?.console"
|
||||||
/>
|
/>
|
||||||
|
@ -133,8 +133,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useVModel } from '@vueuse/core';
|
|
||||||
|
|
||||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||||
import type { Direction } from '@/components/pure/ms-split-box/index.vue';
|
import type { Direction } from '@/components/pure/ms-split-box/index.vue';
|
||||||
import edit, { ResponseItem } from './edit.vue';
|
import edit, { ResponseItem } from './edit.vue';
|
||||||
|
@ -164,8 +162,6 @@
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'update:activeLayout', value: Direction): void;
|
|
||||||
(e: 'update:activeTab', value: ResponseComposition): void;
|
|
||||||
(e: 'changeExpand', value: boolean): void;
|
(e: 'changeExpand', value: boolean): void;
|
||||||
(e: 'changeLayout', value: Direction): void;
|
(e: 'changeLayout', value: Direction): void;
|
||||||
(e: 'change'): void;
|
(e: 'change'): void;
|
||||||
|
@ -173,8 +169,15 @@
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const innerLayout = useVModel(props, 'activeLayout', emit);
|
const innerLayout = defineModel<Direction>('activeLayout', {
|
||||||
const activeTab = useVModel(props, 'activeTab', emit);
|
default: 'vertical',
|
||||||
|
});
|
||||||
|
const innerActiveTab = defineModel<ResponseComposition>('activeTab', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
const innerResponseDefinition = defineModel<ResponseItem[]>('responseDefinition', {
|
||||||
|
default: [],
|
||||||
|
});
|
||||||
// 响应时间信息
|
// 响应时间信息
|
||||||
const timingInfo = computed(() => {
|
const timingInfo = computed(() => {
|
||||||
if (props.requestTaskResult) {
|
if (props.requestTaskResult) {
|
||||||
|
@ -215,24 +218,31 @@
|
||||||
}
|
}
|
||||||
return '';
|
return '';
|
||||||
});
|
});
|
||||||
// 过滤无效数据后的有效响应数据
|
watchEffect(() => {
|
||||||
const validResponseDefinition = computed(() => {
|
// 过滤无效数据后的有效响应数据;当接口导入时会存在部分字段为 null 的数据,需要设置默认值
|
||||||
return props.responseDefinition?.map((item, i) => {
|
let hasInvalid = false;
|
||||||
|
let validResponseDefinition: ResponseItem[] = [];
|
||||||
|
if (props.responseDefinition && props.responseDefinition.length > 0) {
|
||||||
|
validResponseDefinition = props.responseDefinition.map((item, i) => {
|
||||||
// 某些字段在导入时接口返回 null,需要设置默认值
|
// 某些字段在导入时接口返回 null,需要设置默认值
|
||||||
if (!item.headers) {
|
if (!item.headers) {
|
||||||
item.headers = [];
|
item.headers = [];
|
||||||
|
hasInvalid = true;
|
||||||
}
|
}
|
||||||
if (!item.id) {
|
if (!item.id) {
|
||||||
item.id = new Date().getTime() + i;
|
item.id = new Date().getTime() + i;
|
||||||
|
hasInvalid = true;
|
||||||
}
|
}
|
||||||
if (item.body.bodyType === ResponseBodyFormat.NONE) {
|
if (item.body.bodyType === ResponseBodyFormat.NONE) {
|
||||||
item.body.bodyType = ResponseBodyFormat.RAW;
|
item.body.bodyType = ResponseBodyFormat.RAW;
|
||||||
|
hasInvalid = true;
|
||||||
}
|
}
|
||||||
if (!item.body.binaryBody) {
|
if (!item.body.binaryBody) {
|
||||||
item.body.binaryBody = {
|
item.body.binaryBody = {
|
||||||
description: '',
|
description: '',
|
||||||
file: undefined,
|
file: undefined,
|
||||||
};
|
};
|
||||||
|
hasInvalid = true;
|
||||||
}
|
}
|
||||||
if (!item.body.jsonBody) {
|
if (!item.body.jsonBody) {
|
||||||
item.body.jsonBody = {
|
item.body.jsonBody = {
|
||||||
|
@ -250,9 +260,14 @@
|
||||||
value: '',
|
value: '',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
hasInvalid = true;
|
||||||
}
|
}
|
||||||
return item;
|
return item;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
if (hasInvalid) {
|
||||||
|
innerResponseDefinition.value = validResponseDefinition;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
function handleResponseChange() {
|
function handleResponseChange() {
|
||||||
|
|
|
@ -1,8 +1,17 @@
|
||||||
import { cloneDeep, isEqual } from 'lodash-es';
|
import { cloneDeep, isEqual } from 'lodash-es';
|
||||||
|
|
||||||
|
import { RequestParam } from './requestComposition/index.vue';
|
||||||
|
|
||||||
import { ExecuteBody } from '@/models/apiTest/common';
|
import { ExecuteBody } from '@/models/apiTest/common';
|
||||||
import { RequestParamsType } from '@/enums/apiEnum';
|
import { RequestParamsType } from '@/enums/apiEnum';
|
||||||
|
|
||||||
|
import {
|
||||||
|
defaultBodyParamsItem,
|
||||||
|
defaultHeaderParamsItem,
|
||||||
|
defaultKeyValueParamItem,
|
||||||
|
defaultRequestParamsItem,
|
||||||
|
} from './config';
|
||||||
|
|
||||||
export interface ParseResult {
|
export interface ParseResult {
|
||||||
uploadFileIds: string[];
|
uploadFileIds: string[];
|
||||||
linkFileIds: string[];
|
linkFileIds: string[];
|
||||||
|
@ -133,3 +142,23 @@ export function filterKeyValParams<T>(params: (T & Record<string, any>)[], defau
|
||||||
validParams,
|
validParams,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取有效的请求表格参数
|
||||||
|
* @param requestVModel 请求参数对象
|
||||||
|
*/
|
||||||
|
export function getValidRequestTableParams(requestVModel: RequestParam) {
|
||||||
|
const { formDataBody, wwwFormBody } = requestVModel.body;
|
||||||
|
return {
|
||||||
|
formDataBodyTableParams: filterKeyValParams(formDataBody.formValues, defaultBodyParamsItem).validParams,
|
||||||
|
wwwFormBodyTableParams: filterKeyValParams(wwwFormBody.formValues, defaultBodyParamsItem).validParams,
|
||||||
|
headers: filterKeyValParams(requestVModel.headers, defaultHeaderParamsItem).validParams,
|
||||||
|
query: filterKeyValParams(requestVModel.query, defaultRequestParamsItem).validParams,
|
||||||
|
rest: filterKeyValParams(requestVModel.rest, defaultRequestParamsItem).validParams,
|
||||||
|
response:
|
||||||
|
requestVModel.responseDefinition?.map((e) => ({
|
||||||
|
...e,
|
||||||
|
headers: filterKeyValParams(e.headers, defaultKeyValueParamItem).validParams,
|
||||||
|
})) || [],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
|
@ -284,7 +284,7 @@
|
||||||
const res = await getDebugDetail(apiInfo.id);
|
const res = await getDebugDetail(apiInfo.id);
|
||||||
let parseRequestBodyResult;
|
let parseRequestBodyResult;
|
||||||
if (res.protocol === 'HTTP') {
|
if (res.protocol === 'HTTP') {
|
||||||
parseRequestBodyResult = parseRequestBodyFiles(res.request.body);
|
parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件
|
||||||
}
|
}
|
||||||
addDebugTab({
|
addDebugTab({
|
||||||
label: apiInfo.name,
|
label: apiInfo.name,
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
@selected-change="handleTableSelect"
|
@selected-change="handleTableSelect"
|
||||||
@batch-action="handleTableBatch"
|
@batch-action="handleTableBatch"
|
||||||
>
|
>
|
||||||
<template #methodFilter="{ columnConfig }">
|
<template v-if="props.protocol === 'HTTP'" #methodFilter="{ columnConfig }">
|
||||||
<a-trigger
|
<a-trigger
|
||||||
v-model:popup-visible="methodFilterVisible"
|
v-model:popup-visible="methodFilterVisible"
|
||||||
trigger="click"
|
trigger="click"
|
||||||
|
@ -78,6 +78,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template #method="{ record }">
|
<template #method="{ record }">
|
||||||
<a-select
|
<a-select
|
||||||
|
v-if="props.protocol === 'HTTP'"
|
||||||
v-model:model-value="record.method"
|
v-model:model-value="record.method"
|
||||||
class="param-input w-full"
|
class="param-input w-full"
|
||||||
@change="() => handleMethodChange(record)"
|
@change="() => handleMethodChange(record)"
|
||||||
|
@ -89,6 +90,7 @@
|
||||||
<apiMethodName :method="item" is-tag />
|
<apiMethodName :method="item" is-tag />
|
||||||
</a-option>
|
</a-option>
|
||||||
</a-select>
|
</a-select>
|
||||||
|
<apiMethodName v-else :method="record.method" is-tag />
|
||||||
</template>
|
</template>
|
||||||
<template #status="{ record }">
|
<template #status="{ record }">
|
||||||
<a-select
|
<a-select
|
||||||
|
@ -430,7 +432,11 @@
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
moduleIds: moduleIds.value,
|
moduleIds: moduleIds.value,
|
||||||
protocol: props.protocol,
|
protocol: props.protocol,
|
||||||
filter: { status: statusFilters.value, method: methodFilters.value },
|
filter: {
|
||||||
|
status:
|
||||||
|
statusFilters.value.length === Object.keys(RequestDefinitionStatus).length ? undefined : statusFilters.value,
|
||||||
|
method: methodFilters.value.length === Object.keys(RequestMethods).length ? undefined : methodFilters.value,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
setLoadListParams(params);
|
setLoadListParams(params);
|
||||||
loadList();
|
loadList();
|
||||||
|
|
|
@ -32,7 +32,21 @@
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="activeApiTab.id !== 'all'" class="flex-1 overflow-hidden">
|
<div v-if="activeApiTab.id !== 'all'" class="flex-1 overflow-hidden">
|
||||||
<a-tabs default-active-key="definition" animation lazy-load class="ms-api-tab-nav">
|
<a-tabs v-model:active-key="definitionActiveKey" animation lazy-load class="ms-api-tab-nav">
|
||||||
|
<a-tab-pane
|
||||||
|
v-if="!activeApiTab.isNew"
|
||||||
|
key="preview"
|
||||||
|
:title="t('apiTestManagement.preview')"
|
||||||
|
class="ms-api-tab-pane"
|
||||||
|
>
|
||||||
|
<preview
|
||||||
|
v-if="definitionActiveKey === 'preview'"
|
||||||
|
:detail="activeApiTab"
|
||||||
|
:module-tree="props.moduleTree"
|
||||||
|
:protocols="protocols"
|
||||||
|
@update-follow="activeApiTab.follow = !activeApiTab.follow"
|
||||||
|
/>
|
||||||
|
</a-tab-pane>
|
||||||
<a-tab-pane key="definition" :title="t('apiTestManagement.definition')" class="ms-api-tab-pane">
|
<a-tab-pane key="definition" :title="t('apiTestManagement.definition')" class="ms-api-tab-pane">
|
||||||
<MsSplitBox
|
<MsSplitBox
|
||||||
ref="splitBoxRef"
|
ref="splitBoxRef"
|
||||||
|
@ -68,8 +82,8 @@
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #second>
|
<template #second>
|
||||||
<div class="p-[24px]">
|
<div class="p-[18px]">
|
||||||
<!-- 第一版没有模板 -->
|
<!-- TODO:第一版没有模板 -->
|
||||||
<!-- <MsFormCreate v-model:api="fApi" :rule="currentApiTemplateRules" :option="options" /> -->
|
<!-- <MsFormCreate v-model:api="fApi" :rule="currentApiTemplateRules" :option="options" /> -->
|
||||||
<a-form ref="activeApiTabFormRef" :model="activeApiTab" layout="vertical">
|
<a-form ref="activeApiTabFormRef" :model="activeApiTab" layout="vertical">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
|
@ -127,7 +141,8 @@
|
||||||
/>
|
/>
|
||||||
</a-form-item>
|
</a-form-item>
|
||||||
</a-form>
|
</a-form>
|
||||||
<div class="mb-[8px] flex items-center">
|
<!-- TODO:第一版先不做依赖 -->
|
||||||
|
<!-- <div class="mb-[8px] flex items-center">
|
||||||
<div class="text-[var(--color-text-2)]">
|
<div class="text-[var(--color-text-2)]">
|
||||||
{{ t('apiTestManagement.addDependency') }}
|
{{ t('apiTestManagement.addDependency') }}
|
||||||
</div>
|
</div>
|
||||||
|
@ -168,7 +183,7 @@
|
||||||
{{ t('apiTestManagement.addPostDependency') }}
|
{{ t('apiTestManagement.addPostDependency') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div> -->
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</MsSplitBox>
|
</MsSplitBox>
|
||||||
|
@ -183,10 +198,10 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { FormInstance, Message } from '@arco-design/web-vue';
|
import { FormInstance, Message, SelectOptionData } 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';
|
||||||
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
||||||
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||||
// import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue';
|
// import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue';
|
||||||
|
@ -197,7 +212,7 @@
|
||||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||||
|
|
||||||
import { localExecuteApiDebug } from '@/api/modules/api-test/common';
|
import { getProtocolList, localExecuteApiDebug } from '@/api/modules/api-test/common';
|
||||||
import {
|
import {
|
||||||
addDefinition,
|
addDefinition,
|
||||||
debugDefinition,
|
debugDefinition,
|
||||||
|
@ -211,7 +226,7 @@
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
import { filterTree } from '@/utils';
|
import { filterTree } from '@/utils';
|
||||||
|
|
||||||
import { ExecuteBody, RequestTaskResult } from '@/models/apiTest/common';
|
import { ExecuteBody, ProtocolItem, RequestTaskResult } from '@/models/apiTest/common';
|
||||||
import {
|
import {
|
||||||
ApiDefinitionCreateParams,
|
ApiDefinitionCreateParams,
|
||||||
ApiDefinitionDetail,
|
ApiDefinitionDetail,
|
||||||
|
@ -229,10 +244,12 @@
|
||||||
|
|
||||||
import { defaultResponseItem } from '@/views/api-test/components/config';
|
import { defaultResponseItem } from '@/views/api-test/components/config';
|
||||||
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
import { parseRequestBodyFiles } from '@/views/api-test/components/utils';
|
||||||
// 懒加载requestComposition组件
|
// 懒加载requestComposition组件
|
||||||
const requestComposition = defineAsyncComponent(
|
const requestComposition = defineAsyncComponent(
|
||||||
() => import('@/views/api-test/components/requestComposition/index.vue')
|
() => import('@/views/api-test/components/requestComposition/index.vue')
|
||||||
);
|
);
|
||||||
|
const preview = defineAsyncComponent(() => import('./preview.vue'));
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
activeModule: string;
|
activeModule: string;
|
||||||
|
@ -241,12 +258,27 @@
|
||||||
protocol: string;
|
protocol: string;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits(['addDone']);
|
const emit = defineEmits(['addDone']);
|
||||||
|
const definitionActiveKey = ref('definition');
|
||||||
const setActiveApi: ((params: RequestParam) => void) | undefined = inject('setActiveApi');
|
const setActiveApi: ((params: RequestParam) => void) | undefined = inject('setActiveApi');
|
||||||
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
|
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
const protocols = ref<ProtocolItem[]>([]);
|
||||||
|
async function initProtocolList() {
|
||||||
|
try {
|
||||||
|
protocols.value = await getProtocolList(appStore.currentOrgId);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onBeforeMount(() => {
|
||||||
|
initProtocolList();
|
||||||
|
});
|
||||||
|
|
||||||
const apiTabs = ref<RequestParam[]>([
|
const apiTabs = ref<RequestParam[]>([
|
||||||
{
|
{
|
||||||
id: 'all',
|
id: 'all',
|
||||||
|
@ -401,7 +433,7 @@
|
||||||
isNew: !defaultProps?.id, // 新开的tab标记为前端新增的调试,因为此时都已经有id了;但是如果是查看打开的会有携带id
|
isNew: !defaultProps?.id, // 新开的tab标记为前端新增的调试,因为此时都已经有id了;但是如果是查看打开的会有携带id
|
||||||
...defaultProps,
|
...defaultProps,
|
||||||
});
|
});
|
||||||
activeApiTab.value = apiTabs.value[apiTabs.value.length - 1] as RequestParam;
|
activeApiTab.value = apiTabs.value[apiTabs.value.length - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiTableRef = ref<InstanceType<typeof apiTable>>();
|
const apiTableRef = ref<InstanceType<typeof apiTable>>();
|
||||||
|
@ -413,8 +445,10 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
async function openApiTab(apiInfo: ModuleTreeNode | ApiDefinitionDetail, isCopy = false) {
|
async function openApiTab(apiInfo: ModuleTreeNode | ApiDefinitionDetail | string, isCopy = false) {
|
||||||
const isLoadedTabIndex = apiTabs.value.findIndex((e) => e.id === apiInfo.id);
|
const isLoadedTabIndex = apiTabs.value.findIndex(
|
||||||
|
(e) => e.id === (typeof apiInfo === 'string' ? apiInfo : apiInfo.id)
|
||||||
|
);
|
||||||
if (isLoadedTabIndex > -1 && !isCopy) {
|
if (isLoadedTabIndex > -1 && !isCopy) {
|
||||||
// 如果点击的请求在tab中已经存在,则直接切换到该tab
|
// 如果点击的请求在tab中已经存在,则直接切换到该tab
|
||||||
activeApiTab.value = apiTabs.value[isLoadedTabIndex] as RequestParam;
|
activeApiTab.value = apiTabs.value[isLoadedTabIndex] as RequestParam;
|
||||||
|
@ -422,8 +456,13 @@
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const res = await getDefinitionDetail(apiInfo.id);
|
const res = await getDefinitionDetail(typeof apiInfo === 'string' ? apiInfo : apiInfo.id);
|
||||||
const name = isCopy ? `${res.name}-copy` : res.name;
|
const name = isCopy ? `${res.name}-copy` : res.name;
|
||||||
|
definitionActiveKey.value = isCopy ? 'definition' : 'preview';
|
||||||
|
let parseRequestBodyResult;
|
||||||
|
if (res.protocol === 'HTTP') {
|
||||||
|
parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件
|
||||||
|
}
|
||||||
addApiTab({
|
addApiTab({
|
||||||
label: name,
|
label: name,
|
||||||
...res.request,
|
...res.request,
|
||||||
|
@ -434,6 +473,8 @@
|
||||||
name, // request里面还有个name但是是null
|
name, // request里面还有个name但是是null
|
||||||
isNew: isCopy,
|
isNew: isCopy,
|
||||||
unSaved: isCopy,
|
unSaved: isCopy,
|
||||||
|
isCopy,
|
||||||
|
...parseRequestBodyResult,
|
||||||
});
|
});
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
// 等待内容渲染出来再隐藏loading
|
// 等待内容渲染出来再隐藏loading
|
||||||
|
@ -465,25 +506,25 @@
|
||||||
const showAddDependencyDrawer = ref(false);
|
const showAddDependencyDrawer = ref(false);
|
||||||
const addDependencyMode = ref<'pre' | 'post'>('pre');
|
const addDependencyMode = ref<'pre' | 'post'>('pre');
|
||||||
|
|
||||||
function handleDddDependency(value: string | number | Record<string, any> | undefined) {
|
// function handleDddDependency(value: string | number | Record<string, any> | undefined) {
|
||||||
switch (value) {
|
// switch (value) {
|
||||||
case 'pre':
|
// case 'pre':
|
||||||
addDependencyMode.value = 'pre';
|
// addDependencyMode.value = 'pre';
|
||||||
showAddDependencyDrawer.value = true;
|
// showAddDependencyDrawer.value = true;
|
||||||
break;
|
// break;
|
||||||
case 'post':
|
// case 'post':
|
||||||
addDependencyMode.value = 'post';
|
// addDependencyMode.value = 'post';
|
||||||
showAddDependencyDrawer.value = true;
|
// showAddDependencyDrawer.value = true;
|
||||||
break;
|
// break;
|
||||||
default:
|
// default:
|
||||||
break;
|
// break;
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
function clearAllDependency() {
|
// function clearAllDependency() {
|
||||||
activeApiTab.value.preDependency = [];
|
// activeApiTab.value.preDependency = [];
|
||||||
activeApiTab.value.postDependency = [];
|
// activeApiTab.value.postDependency = [];
|
||||||
}
|
// }
|
||||||
|
|
||||||
const splitBoxRef = ref<InstanceType<typeof MsSplitBox>>();
|
const splitBoxRef = ref<InstanceType<typeof MsSplitBox>>();
|
||||||
const activeApiTabFormRef = ref<FormInstance>();
|
const activeApiTabFormRef = ref<FormInstance>();
|
||||||
|
|
|
@ -0,0 +1,862 @@
|
||||||
|
<template>
|
||||||
|
<a-spin :loading="pluginLoading" class="h-full w-full overflow-hidden">
|
||||||
|
<div class="px-[18px] pt-[16px]">
|
||||||
|
<MsDetailCard
|
||||||
|
:title="`【${preivewDetail.num}】${preivewDetail.name}`"
|
||||||
|
:description="description"
|
||||||
|
:simple-show-count="4"
|
||||||
|
>
|
||||||
|
<template #titleAppend>
|
||||||
|
<apiStatus :status="preivewDetail.status" size="small" />
|
||||||
|
</template>
|
||||||
|
<template #titleRight>
|
||||||
|
<a-button
|
||||||
|
type="outline"
|
||||||
|
:loading="followLoading"
|
||||||
|
size="mini"
|
||||||
|
class="arco-btn-outline--secondary mr-[4px] !bg-transparent"
|
||||||
|
@click="toggleFollowReview"
|
||||||
|
>
|
||||||
|
<div class="flex items-center gap-[4px]">
|
||||||
|
<MsIcon
|
||||||
|
:type="preivewDetail.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
||||||
|
:class="`${preivewDetail.follow ? 'text-[rgb(var(--warning-6))]' : 'text-[var(--color-text-4)]'}`"
|
||||||
|
:size="14"
|
||||||
|
/>
|
||||||
|
{{ t(preivewDetail.follow ? 'common.forked' : 'common.fork') }}
|
||||||
|
</div>
|
||||||
|
</a-button>
|
||||||
|
<a-button type="outline" size="mini" class="arco-btn-outline--secondary !bg-transparent" @click="share">
|
||||||
|
<div class="flex items-center gap-[4px]">
|
||||||
|
<MsIcon type="icon-icon_share1" class="text-[var(--color-text-4)]" :size="14" />
|
||||||
|
{{ t('common.share') }}
|
||||||
|
</div>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
<template #type="{ value }">
|
||||||
|
<apiMethodName :method="value as RequestMethods" tag-size="small" is-tag />
|
||||||
|
</template>
|
||||||
|
</MsDetailCard>
|
||||||
|
</div>
|
||||||
|
<div class="h-[calc(100%-124px)]">
|
||||||
|
<a-tabs v-model:active-key="activeKey" class="h-full" animation lazy-load>
|
||||||
|
<a-tab-pane key="detail" :title="t('apiTestManagement.detail')" class="overflow-y-auto px-[18px] py-[16px]">
|
||||||
|
<a-collapse v-model:active-key="activeDetailKey" :bordered="false">
|
||||||
|
<a-collapse-item key="request">
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center gap-[4px]">
|
||||||
|
<div v-if="activeDetailKey.includes('request')" class="down-icon">
|
||||||
|
<icon-down :size="10" class="block" />
|
||||||
|
</div>
|
||||||
|
<div v-else class="h-[16px] w-[16px] !rounded-full p-[4px]">
|
||||||
|
<icon-right :size="10" class="block" />
|
||||||
|
</div>
|
||||||
|
<div class="font-medium">{{ t('apiTestManagement.requestParams') }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div class="detail-collapse-item">
|
||||||
|
<template v-if="props.detail.protocol === 'HTTP'">
|
||||||
|
<div v-if="preivewDetail.headers.length > 0" class="detail-item">
|
||||||
|
<div class="detail-item-title">
|
||||||
|
<div class="detail-item-title-text">{{ t('apiTestManagement.requestHeader') }}</div>
|
||||||
|
<a-radio-group v-model:model-value="headerShowType" type="button" size="mini">
|
||||||
|
<a-radio value="table">Table</a-radio>
|
||||||
|
<a-radio value="raw">Raw</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</div>
|
||||||
|
<MsFormTable
|
||||||
|
v-show="headerShowType === 'table'"
|
||||||
|
:columns="headerColumns"
|
||||||
|
:data="preivewDetail.headers || []"
|
||||||
|
:selectable="false"
|
||||||
|
/>
|
||||||
|
<MsCodeEditor
|
||||||
|
v-show="headerShowType === 'raw'"
|
||||||
|
:model-value="headerRawCode"
|
||||||
|
class="flex-1"
|
||||||
|
theme="MS-text"
|
||||||
|
height="200px"
|
||||||
|
:show-full-screen="false"
|
||||||
|
:show-theme-change="false"
|
||||||
|
read-only
|
||||||
|
>
|
||||||
|
<template #rightTitle>
|
||||||
|
<a-button
|
||||||
|
type="outline"
|
||||||
|
class="arco-btn-outline--secondary p-[0_8px]"
|
||||||
|
size="mini"
|
||||||
|
@click="copyScript(headerRawCode)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<MsIcon type="icon-icon_copy_outlined" class="text-var(--color-text-4)" size="12" />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</MsCodeEditor>
|
||||||
|
<a-divider type="dashed" :margin="0" class="!mt-[16px] border-[var(--color-text-n8)]" />
|
||||||
|
</div>
|
||||||
|
<div v-if="preivewDetail.query.length > 0" class="detail-item">
|
||||||
|
<div class="detail-item-title">
|
||||||
|
<div class="detail-item-title-text">Query</div>
|
||||||
|
<a-radio-group v-model:model-value="queryShowType" type="button" size="mini">
|
||||||
|
<a-radio value="table">Table</a-radio>
|
||||||
|
<a-radio value="raw">Raw</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</div>
|
||||||
|
<MsFormTable
|
||||||
|
v-show="queryShowType === 'table'"
|
||||||
|
:columns="queryRestColumns"
|
||||||
|
:data="preivewDetail.query || []"
|
||||||
|
:selectable="false"
|
||||||
|
/>
|
||||||
|
<MsCodeEditor
|
||||||
|
v-show="queryShowType === 'raw'"
|
||||||
|
:model-value="queryRawCode"
|
||||||
|
class="flex-1"
|
||||||
|
theme="MS-text"
|
||||||
|
height="200px"
|
||||||
|
:show-full-screen="false"
|
||||||
|
:show-theme-change="false"
|
||||||
|
read-only
|
||||||
|
>
|
||||||
|
<template #rightTitle>
|
||||||
|
<a-button
|
||||||
|
type="outline"
|
||||||
|
class="arco-btn-outline--secondary p-[0_8px]"
|
||||||
|
size="mini"
|
||||||
|
@click="copyScript(queryRawCode)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<MsIcon type="icon-icon_copy_outlined" class="text-var(--color-text-4)" size="12" />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</MsCodeEditor>
|
||||||
|
<a-divider type="dashed" :margin="0" class="!mt-[16px] border-[var(--color-text-n8)]" />
|
||||||
|
</div>
|
||||||
|
<div v-if="preivewDetail.rest.length > 0" class="detail-item">
|
||||||
|
<div class="detail-item-title">
|
||||||
|
<div class="detail-item-title-text">Rest</div>
|
||||||
|
<a-radio-group v-model:model-value="restShowType" type="button" size="mini">
|
||||||
|
<a-radio value="table">Table</a-radio>
|
||||||
|
<a-radio value="raw">Raw</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</div>
|
||||||
|
<MsFormTable
|
||||||
|
v-show="restShowType === 'table'"
|
||||||
|
:columns="queryRestColumns"
|
||||||
|
:data="preivewDetail.rest || []"
|
||||||
|
:selectable="false"
|
||||||
|
/>
|
||||||
|
<MsCodeEditor
|
||||||
|
v-show="restShowType === 'raw'"
|
||||||
|
:model-value="restRawCode"
|
||||||
|
class="flex-1"
|
||||||
|
theme="MS-text"
|
||||||
|
height="200px"
|
||||||
|
:show-full-screen="false"
|
||||||
|
:show-theme-change="false"
|
||||||
|
read-only
|
||||||
|
>
|
||||||
|
<template #rightTitle>
|
||||||
|
<a-button
|
||||||
|
type="outline"
|
||||||
|
class="arco-btn-outline--secondary p-[0_8px]"
|
||||||
|
size="mini"
|
||||||
|
@click="copyScript(restRawCode)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<MsIcon type="icon-icon_copy_outlined" class="text-var(--color-text-4)" size="12" />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</MsCodeEditor>
|
||||||
|
<a-divider type="dashed" :margin="0" class="!mt-[16px] border-[var(--color-text-n8)]" />
|
||||||
|
</div>
|
||||||
|
<div class="detail-item">
|
||||||
|
<div class="detail-item-title">
|
||||||
|
<div class="detail-item-title-text">
|
||||||
|
{{ `${t('apiTestManagement.requestBody')}-${preivewDetail.body.bodyType}` }}
|
||||||
|
</div>
|
||||||
|
<!-- <a-radio-group
|
||||||
|
v-if="preivewDetail.body.bodyType !== RequestBodyFormat.NONE"
|
||||||
|
v-model:model-value="bodyShowType"
|
||||||
|
type="button"
|
||||||
|
size="mini"
|
||||||
|
>
|
||||||
|
<a-radio value="table">Table</a-radio>
|
||||||
|
<a-radio value="code">Code</a-radio>
|
||||||
|
</a-radio-group> -->
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="preivewDetail.body.bodyType === RequestBodyFormat.NONE"
|
||||||
|
class="flex h-[100px] items-center justify-center rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] text-[var(--color-text-4)]"
|
||||||
|
>
|
||||||
|
{{ t('apiTestDebug.noneBody') }}
|
||||||
|
</div>
|
||||||
|
<MsFormTable
|
||||||
|
v-else-if="
|
||||||
|
preivewDetail.body.bodyType === RequestBodyFormat.FORM_DATA ||
|
||||||
|
preivewDetail.body.bodyType === RequestBodyFormat.WWW_FORM
|
||||||
|
"
|
||||||
|
:columns="bodyColumns"
|
||||||
|
:data="bodyTableData"
|
||||||
|
:selectable="false"
|
||||||
|
/>
|
||||||
|
<MsCodeEditor
|
||||||
|
v-else-if="
|
||||||
|
[RequestBodyFormat.JSON, RequestBodyFormat.RAW, RequestBodyFormat.XML].includes(
|
||||||
|
preivewDetail.body.bodyType
|
||||||
|
)
|
||||||
|
"
|
||||||
|
:model-value="bodyCode"
|
||||||
|
class="flex-1"
|
||||||
|
theme="vs"
|
||||||
|
height="200px"
|
||||||
|
:language="bodyCodeLanguage"
|
||||||
|
:show-full-screen="false"
|
||||||
|
:show-theme-change="false"
|
||||||
|
read-only
|
||||||
|
>
|
||||||
|
<template #rightTitle>
|
||||||
|
<a-button
|
||||||
|
type="outline"
|
||||||
|
class="arco-btn-outline--secondary p-[0_8px]"
|
||||||
|
size="mini"
|
||||||
|
@click="copyScript(bodyCode)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<MsIcon type="icon-icon_copy_outlined" class="text-var(--color-text-4)" size="12" />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</MsCodeEditor>
|
||||||
|
<a-divider type="dashed" :margin="0" class="!mt-[16px] border-[var(--color-text-n8)]" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<div v-else class="detail-item">
|
||||||
|
<div class="detail-item-title">
|
||||||
|
<div class="detail-item-title-text">{{ t('apiTestManagement.requestData') }}</div>
|
||||||
|
<a-radio-group v-model:model-value="pluginShowType" type="button" size="mini">
|
||||||
|
<a-radio value="table">Table</a-radio>
|
||||||
|
<a-radio value="raw">Raw</a-radio>
|
||||||
|
</a-radio-group>
|
||||||
|
</div>
|
||||||
|
<MsFormTable
|
||||||
|
v-show="pluginShowType === 'table'"
|
||||||
|
:columns="pluginTableColumns"
|
||||||
|
:data="pluginTableData"
|
||||||
|
:selectable="false"
|
||||||
|
/>
|
||||||
|
<MsCodeEditor
|
||||||
|
v-show="pluginShowType === 'raw'"
|
||||||
|
:model-value="pluginRawCode"
|
||||||
|
class="flex-1"
|
||||||
|
theme="MS-text"
|
||||||
|
height="400px"
|
||||||
|
:show-full-screen="false"
|
||||||
|
:show-theme-change="false"
|
||||||
|
read-only
|
||||||
|
>
|
||||||
|
<template #rightTitle>
|
||||||
|
<a-button
|
||||||
|
type="outline"
|
||||||
|
class="arco-btn-outline--secondary p-[0_8px]"
|
||||||
|
size="mini"
|
||||||
|
@click="copyScript(pluginRawCode)"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<MsIcon type="icon-icon_copy_outlined" class="text-var(--color-text-4)" size="12" />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</MsCodeEditor>
|
||||||
|
<a-divider type="dashed" :margin="0" class="!mt-[16px] border-[var(--color-text-n8)]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</a-collapse-item>
|
||||||
|
<a-collapse-item
|
||||||
|
v-if="
|
||||||
|
preivewDetail.responseDefinition &&
|
||||||
|
preivewDetail.responseDefinition.length > 0 &&
|
||||||
|
props.detail.protocol === 'HTTP'
|
||||||
|
"
|
||||||
|
key="response"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center gap-[4px]">
|
||||||
|
<div v-if="activeDetailKey.includes('response')" class="down-icon">
|
||||||
|
<icon-down :size="10" class="block" />
|
||||||
|
</div>
|
||||||
|
<div v-else class="h-[16px] w-[16px] !rounded-full p-[4px]">
|
||||||
|
<icon-right :size="10" class="block" />
|
||||||
|
</div>
|
||||||
|
<div class="font-medium">{{ t('apiTestManagement.responseContent') }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<MsEditableTab
|
||||||
|
v-model:active-tab="activeResponse"
|
||||||
|
:tabs="preivewDetail.responseDefinition?.map((e) => ({ ...e, closable: false })) || []"
|
||||||
|
hide-more-action
|
||||||
|
readonly
|
||||||
|
class="my-[8px]"
|
||||||
|
>
|
||||||
|
<template #label="{ tab }">
|
||||||
|
<div class="response-tab">
|
||||||
|
<div v-if="tab.defaultFlag" class="response-tab-default-icon"></div>
|
||||||
|
{{ t(tab.label || tab.name) }}({{ tab.statusCode }})
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</MsEditableTab>
|
||||||
|
<div class="detail-item !pt-0">
|
||||||
|
<div class="detail-item-title">
|
||||||
|
<div class="detail-item-title-text">
|
||||||
|
{{ `${t('apiTestDebug.responseBody')}-${activeResponse?.body.bodyType}` }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MsCodeEditor
|
||||||
|
v-if="activeResponse?.body.bodyType !== ResponseBodyFormat.BINARY"
|
||||||
|
:model-value="responseCode"
|
||||||
|
class="flex-1"
|
||||||
|
theme="vs"
|
||||||
|
height="200px"
|
||||||
|
:language="responseCodeLanguage"
|
||||||
|
:show-full-screen="false"
|
||||||
|
:show-theme-change="false"
|
||||||
|
read-only
|
||||||
|
>
|
||||||
|
<template #rightTitle>
|
||||||
|
<a-button
|
||||||
|
type="outline"
|
||||||
|
class="arco-btn-outline--secondary p-[0_8px]"
|
||||||
|
size="mini"
|
||||||
|
@click="copyScript(responseCode || '')"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<MsIcon type="icon-icon_copy_outlined" class="text-var(--color-text-4)" size="12" />
|
||||||
|
</template>
|
||||||
|
</a-button>
|
||||||
|
</template>
|
||||||
|
</MsCodeEditor>
|
||||||
|
</div>
|
||||||
|
<div v-if="activeResponse?.headers && activeResponse?.headers.length > 0" class="detail-item">
|
||||||
|
<div class="detail-item-title">
|
||||||
|
<div class="detail-item-title-text">
|
||||||
|
{{ t('apiTestDebug.responseHeader') }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<MsFormTable
|
||||||
|
:columns="responseHeaderColumns"
|
||||||
|
:data="activeResponse?.headers || []"
|
||||||
|
:selectable="false"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-collapse-item>
|
||||||
|
</a-collapse>
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="reference" :title="t('apiTestManagement.reference')" class="px-[18px] py-[16px]"> </a-tab-pane>
|
||||||
|
<a-tab-pane key="dependencies" :title="t('apiTestManagement.dependencies')" class="px-[18px] py-[16px]">
|
||||||
|
</a-tab-pane>
|
||||||
|
<a-tab-pane key="changeHistory" :title="t('apiTestManagement.changeHistory')" class="px-[18px] py-[16px]">
|
||||||
|
</a-tab-pane>
|
||||||
|
</a-tabs>
|
||||||
|
</div>
|
||||||
|
</a-spin>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { useClipboard } from '@vueuse/core';
|
||||||
|
import { Message } from '@arco-design/web-vue';
|
||||||
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
|
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||||
|
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
||||||
|
import MsDetailCard from '@/components/pure/ms-detail-card/index.vue';
|
||||||
|
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
||||||
|
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||||
|
import MsFormTable, { FormTableColumn } from '@/components/pure/ms-form-table/index.vue';
|
||||||
|
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||||
|
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||||
|
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||||
|
import { ResponseItem } from '@/views/api-test/components/requestComposition/response/edit.vue';
|
||||||
|
|
||||||
|
import { getPluginScript } from '@/api/modules/api-test/common';
|
||||||
|
import { toggleFollowDefinition } from '@/api/modules/api-test/management';
|
||||||
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import { findNodeByKey } from '@/utils';
|
||||||
|
|
||||||
|
import { PluginConfig, ProtocolItem } from '@/models/apiTest/common';
|
||||||
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
|
import { RequestBodyFormat, RequestMethods, RequestParamsType, ResponseBodyFormat } from '@/enums/apiEnum';
|
||||||
|
|
||||||
|
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||||
|
import { getValidRequestTableParams } from '@/views/api-test/components/utils';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
detail: RequestParam;
|
||||||
|
moduleTree: ModuleTreeNode[];
|
||||||
|
protocols: ProtocolItem[];
|
||||||
|
}>();
|
||||||
|
const emit = defineEmits(['updateFollow']);
|
||||||
|
|
||||||
|
const { t } = useI18n();
|
||||||
|
const { copy, isSupported } = useClipboard();
|
||||||
|
|
||||||
|
const preivewDetail = ref<RequestParam>(cloneDeep(props.detail));
|
||||||
|
const activeResponse = ref<TabItem & ResponseItem>();
|
||||||
|
|
||||||
|
const pluginLoading = ref(false);
|
||||||
|
const pluginScriptMap = ref<Record<string, PluginConfig>>({}); // 存储初始化过后的插件配置
|
||||||
|
const pluginShowType = ref('table');
|
||||||
|
const pluginTableColumns: FormTableColumn[] = [
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.paramName',
|
||||||
|
dataIndex: 'key',
|
||||||
|
inputType: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.paramVal',
|
||||||
|
dataIndex: 'value',
|
||||||
|
inputType: 'text',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const pluginTableData = computed(() => {
|
||||||
|
if (pluginScriptMap.value[preivewDetail.value.protocol]) {
|
||||||
|
return (
|
||||||
|
pluginScriptMap.value[preivewDetail.value.protocol].apiDefinitionFields?.map((e) => ({
|
||||||
|
key: e,
|
||||||
|
value: preivewDetail.value[e],
|
||||||
|
})) || []
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return [];
|
||||||
|
});
|
||||||
|
const pluginRawCode = computed(() => {
|
||||||
|
if (pluginScriptMap.value[preivewDetail.value.protocol]) {
|
||||||
|
return (
|
||||||
|
pluginScriptMap.value[preivewDetail.value.protocol].apiDefinitionFields
|
||||||
|
?.map((e) => `${e}:${preivewDetail.value[e]}`)
|
||||||
|
.join('\n') || ''
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
});
|
||||||
|
const pluginError = ref(false);
|
||||||
|
async function initPluginScript(protocol: string) {
|
||||||
|
const pluginId = props.protocols.find((e) => e.protocol === protocol)?.pluginId;
|
||||||
|
if (!pluginId) {
|
||||||
|
Message.warning(t('apiTestDebug.noPluginTip'));
|
||||||
|
pluginError.value = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pluginError.value = false;
|
||||||
|
if (pluginScriptMap.value[protocol] !== undefined) {
|
||||||
|
// 已经初始化过
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
pluginLoading.value = true;
|
||||||
|
const res = await getPluginScript(pluginId);
|
||||||
|
pluginScriptMap.value[protocol] = res;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
pluginLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
preivewDetail.value = cloneDeep(props.detail); // props.detail是嵌套的引用类型,防止不必要的修改来源影响props.detail的数据
|
||||||
|
const tableParam = getValidRequestTableParams(preivewDetail.value); // 在编辑props.detail时,参数表格会多出一行默认数据,需要去除
|
||||||
|
preivewDetail.value = {
|
||||||
|
...preivewDetail.value,
|
||||||
|
body: {
|
||||||
|
...preivewDetail.value.body,
|
||||||
|
formDataBody: {
|
||||||
|
formValues: tableParam.formDataBodyTableParams,
|
||||||
|
},
|
||||||
|
wwwFormBody: {
|
||||||
|
formValues: tableParam.wwwFormBodyTableParams,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
headers: tableParam.headers,
|
||||||
|
rest: tableParam.rest,
|
||||||
|
query: tableParam.query,
|
||||||
|
responseDefinition: tableParam.response,
|
||||||
|
};
|
||||||
|
[activeResponse.value] = tableParam.response;
|
||||||
|
if (preivewDetail.value.protocol !== 'HTTP') {
|
||||||
|
// 初始化插件脚本
|
||||||
|
initPluginScript(preivewDetail.value.protocol);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const description = computed(() => [
|
||||||
|
{
|
||||||
|
key: 'type',
|
||||||
|
locale: 'apiTestManagement.apiType',
|
||||||
|
value: preivewDetail.value.method,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'path',
|
||||||
|
locale: 'apiTestManagement.path',
|
||||||
|
value: preivewDetail.value.path,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tags',
|
||||||
|
locale: 'common.tag',
|
||||||
|
value: preivewDetail.value.tags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'description',
|
||||||
|
locale: 'common.desc',
|
||||||
|
value: preivewDetail.value.description,
|
||||||
|
width: '100%',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'belongModule',
|
||||||
|
locale: 'apiTestManagement.belongModule',
|
||||||
|
value: findNodeByKey<ModuleTreeNode>(props.moduleTree, preivewDetail.value.moduleId, 'id')?.path,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'creator',
|
||||||
|
locale: 'common.creator',
|
||||||
|
value: preivewDetail.value.createUserName,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'createTime',
|
||||||
|
locale: 'apiTestManagement.createTime',
|
||||||
|
value: dayjs(preivewDetail.value.createTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'updateTime',
|
||||||
|
locale: 'apiTestManagement.updateTime',
|
||||||
|
value: dayjs(preivewDetail.value.updateTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
const followLoading = ref(false);
|
||||||
|
async function toggleFollowReview() {
|
||||||
|
try {
|
||||||
|
followLoading.value = true;
|
||||||
|
await toggleFollowDefinition(preivewDetail.value.id);
|
||||||
|
Message.success(preivewDetail.value.follow ? t('common.unFollowSuccess') : t('common.followSuccess'));
|
||||||
|
emit('updateFollow');
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
} finally {
|
||||||
|
followLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function share() {
|
||||||
|
if (isSupported) {
|
||||||
|
copy(`${window.location.href}&dId=${preivewDetail.value.id}`);
|
||||||
|
Message.success(t('apiTestManagement.shareUrlCopied'));
|
||||||
|
} else {
|
||||||
|
Message.error(t('common.copyNotSupport'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const activeKey = ref('detail');
|
||||||
|
const activeDetailKey = ref(['request', 'response']);
|
||||||
|
|
||||||
|
async function copyScript(val: string) {
|
||||||
|
if (isSupported) {
|
||||||
|
await copy(val);
|
||||||
|
Message.success(t('common.copySuccess'));
|
||||||
|
} else {
|
||||||
|
Message.warning(t('apiTestDebug.copyNotSupport'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求头
|
||||||
|
*/
|
||||||
|
const headerColumns: FormTableColumn[] = [
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.paramName',
|
||||||
|
dataIndex: 'key',
|
||||||
|
inputType: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.paramVal',
|
||||||
|
dataIndex: 'value',
|
||||||
|
inputType: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'common.desc',
|
||||||
|
dataIndex: 'description',
|
||||||
|
inputType: 'text',
|
||||||
|
showTooltip: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const headerShowType = ref('table');
|
||||||
|
const headerRawCode = computed(() => {
|
||||||
|
return preivewDetail.value.headers?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query & Rest
|
||||||
|
*/
|
||||||
|
const queryRestColumns: FormTableColumn[] = [
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.paramName',
|
||||||
|
dataIndex: 'key',
|
||||||
|
inputType: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.paramType',
|
||||||
|
dataIndex: 'paramType',
|
||||||
|
inputType: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.paramVal',
|
||||||
|
dataIndex: 'value',
|
||||||
|
inputType: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.required',
|
||||||
|
dataIndex: 'required',
|
||||||
|
slotName: 'required',
|
||||||
|
inputType: 'text',
|
||||||
|
valueFormat: (record) => {
|
||||||
|
return record.required ? t('common.yes') : t('common.no');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.paramLengthRange',
|
||||||
|
dataIndex: 'lengthRange',
|
||||||
|
slotName: 'lengthRange',
|
||||||
|
inputType: 'text',
|
||||||
|
valueFormat: (record) => {
|
||||||
|
return [null, undefined].includes(record.minLength) && [null, undefined].includes(record.maxLength)
|
||||||
|
? '-'
|
||||||
|
: `${record.minLength} ${t('common.to')} ${record.maxLength}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.encode',
|
||||||
|
dataIndex: 'encode',
|
||||||
|
slotName: 'encode',
|
||||||
|
inputType: 'text',
|
||||||
|
valueFormat: (record) => {
|
||||||
|
return record.encode ? t('common.yes') : t('common.no');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'common.desc',
|
||||||
|
dataIndex: 'description',
|
||||||
|
inputType: 'text',
|
||||||
|
showTooltip: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const queryShowType = ref('table');
|
||||||
|
const queryRawCode = computed(() => {
|
||||||
|
return preivewDetail.value.query?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||||
|
});
|
||||||
|
const restShowType = ref('table');
|
||||||
|
const restRawCode = computed(() => {
|
||||||
|
return preivewDetail.value.rest?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 请求体
|
||||||
|
*/
|
||||||
|
const bodyColumns: FormTableColumn[] = [
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.paramName',
|
||||||
|
dataIndex: 'key',
|
||||||
|
inputType: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.paramsType',
|
||||||
|
dataIndex: 'paramType',
|
||||||
|
inputType: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.paramVal',
|
||||||
|
dataIndex: 'value',
|
||||||
|
inputType: 'text',
|
||||||
|
showTooltip: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.required',
|
||||||
|
dataIndex: 'required',
|
||||||
|
slotName: 'required',
|
||||||
|
inputType: 'text',
|
||||||
|
valueFormat: (record) => {
|
||||||
|
return record.required ? t('common.yes') : t('common.no');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.paramLengthRange',
|
||||||
|
dataIndex: 'lengthRange',
|
||||||
|
slotName: 'lengthRange',
|
||||||
|
inputType: 'text',
|
||||||
|
valueFormat: (record) => {
|
||||||
|
return [null, undefined].includes(record.minLength) && [null, undefined].includes(record.maxLength)
|
||||||
|
? '-'
|
||||||
|
: `${record.minLength} ${t('common.to')} ${record.maxLength}`;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestDebug.encode',
|
||||||
|
dataIndex: 'encode',
|
||||||
|
slotName: 'encode',
|
||||||
|
inputType: 'text',
|
||||||
|
valueFormat: (record) => {
|
||||||
|
return record.encode ? t('common.yes') : t('common.no');
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'common.desc',
|
||||||
|
dataIndex: 'description',
|
||||||
|
inputType: 'text',
|
||||||
|
showTooltip: true,
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
// const bodyShowType = ref('table');
|
||||||
|
const bodyTableData = computed(() => {
|
||||||
|
switch (preivewDetail.value.body.bodyType) {
|
||||||
|
case RequestBodyFormat.FORM_DATA:
|
||||||
|
return (preivewDetail.value.body.formDataBody?.formValues || []).map((e) => ({
|
||||||
|
...e,
|
||||||
|
value: e.paramType === RequestParamsType.FILE ? e.files?.map((file) => file.fileName).join('\n') : e.value,
|
||||||
|
}));
|
||||||
|
case RequestBodyFormat.WWW_FORM:
|
||||||
|
return preivewDetail.value.body.wwwFormBody?.formValues || [];
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const bodyCode = computed(() => {
|
||||||
|
switch (preivewDetail.value.body.bodyType) {
|
||||||
|
case RequestBodyFormat.FORM_DATA:
|
||||||
|
return preivewDetail.value.body.formDataBody?.formValues?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||||
|
case RequestBodyFormat.WWW_FORM:
|
||||||
|
return preivewDetail.value.body.wwwFormBody?.formValues?.map((item) => `${item.key}:${item.value}`).join('\n');
|
||||||
|
case RequestBodyFormat.RAW:
|
||||||
|
return preivewDetail.value.body.rawBody?.value;
|
||||||
|
case RequestBodyFormat.JSON:
|
||||||
|
return preivewDetail.value.body.jsonBody?.jsonValue;
|
||||||
|
case RequestBodyFormat.XML:
|
||||||
|
return preivewDetail.value.body.xmlBody?.value;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const bodyCodeLanguage = computed(() => {
|
||||||
|
if (preivewDetail.value.body.bodyType === RequestBodyFormat.JSON) {
|
||||||
|
return LanguageEnum.JSON;
|
||||||
|
}
|
||||||
|
if (preivewDetail.value.body.bodyType === RequestBodyFormat.XML) {
|
||||||
|
return LanguageEnum.XML;
|
||||||
|
}
|
||||||
|
return LanguageEnum.PLAINTEXT;
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应内容
|
||||||
|
*/
|
||||||
|
const responseCode = computed(() => {
|
||||||
|
switch (activeResponse.value?.body.bodyType) {
|
||||||
|
case ResponseBodyFormat.JSON:
|
||||||
|
return activeResponse.value?.body.jsonBody?.jsonValue;
|
||||||
|
case ResponseBodyFormat.XML:
|
||||||
|
return activeResponse.value?.body.xmlBody?.value;
|
||||||
|
case ResponseBodyFormat.RAW:
|
||||||
|
return activeResponse.value?.body.rawBody?.value;
|
||||||
|
default:
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const responseCodeLanguage = computed(() => {
|
||||||
|
if (activeResponse.value?.body.bodyType === ResponseBodyFormat.JSON) {
|
||||||
|
return LanguageEnum.JSON;
|
||||||
|
}
|
||||||
|
if (activeResponse.value?.body.bodyType === ResponseBodyFormat.XML) {
|
||||||
|
return LanguageEnum.XML;
|
||||||
|
}
|
||||||
|
return LanguageEnum.PLAINTEXT;
|
||||||
|
});
|
||||||
|
const responseHeaderColumns: FormTableColumn[] = [
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.paramName',
|
||||||
|
dataIndex: 'key',
|
||||||
|
inputType: 'text',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'apiTestManagement.paramVal',
|
||||||
|
dataIndex: 'value',
|
||||||
|
inputType: 'text',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less" scoped>
|
||||||
|
.down-icon {
|
||||||
|
padding: 4px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: rgb(var(--primary-5));
|
||||||
|
background-color: rgb(var(--primary-1));
|
||||||
|
}
|
||||||
|
.arco-collapse {
|
||||||
|
@apply h-full overflow-y-auto;
|
||||||
|
.ms-scroll-bar();
|
||||||
|
}
|
||||||
|
.detail-collapse-item {
|
||||||
|
@apply overflow-y-auto;
|
||||||
|
|
||||||
|
margin-bottom: 16px;
|
||||||
|
.ms-scroll-bar();
|
||||||
|
}
|
||||||
|
.detail-item {
|
||||||
|
padding-top: 16px;
|
||||||
|
.detail-item-title {
|
||||||
|
@apply flex items-center;
|
||||||
|
|
||||||
|
margin-bottom: 8px;
|
||||||
|
gap: 16px;
|
||||||
|
.detail-item-title-text {
|
||||||
|
@apply font-medium;
|
||||||
|
|
||||||
|
color: var(--color-text-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
:deep(.arco-collapse) {
|
||||||
|
border-radius: 0;
|
||||||
|
.arco-collapse-item-icon-hover {
|
||||||
|
@apply !hidden;
|
||||||
|
}
|
||||||
|
.arco-collapse-item-header {
|
||||||
|
.arco-collapse-item-header-title {
|
||||||
|
@apply block w-full;
|
||||||
|
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: var(--border-radius-small);
|
||||||
|
background-color: var(--color-text-n9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.response-tab {
|
||||||
|
@apply flex items-center;
|
||||||
|
.response-tab-default-icon {
|
||||||
|
@apply rounded-full;
|
||||||
|
|
||||||
|
margin-right: 4px;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background: url('@/assets/svg/icons/default.svg') no-repeat;
|
||||||
|
background-size: contain;
|
||||||
|
box-shadow: 0 0 7px 0 rgb(15 0 78 / 9%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
|
@ -66,7 +66,7 @@
|
||||||
const activeTab = ref('api');
|
const activeTab = ref('api');
|
||||||
const apiRef = ref<InstanceType<typeof api>>();
|
const apiRef = ref<InstanceType<typeof api>>();
|
||||||
|
|
||||||
function newTab(apiInfo?: ModuleTreeNode) {
|
function newTab(apiInfo?: ModuleTreeNode | string) {
|
||||||
if (apiInfo) {
|
if (apiInfo) {
|
||||||
apiRef.value?.openApiTab(apiInfo);
|
apiRef.value?.openApiTab(apiInfo);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -54,6 +54,7 @@
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { provide } from 'vue';
|
import { provide } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
|
|
||||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||||
|
@ -64,6 +65,8 @@
|
||||||
|
|
||||||
import { ModuleTreeNode } from '@/models/common';
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
|
|
||||||
|
const route = useRoute();
|
||||||
|
|
||||||
const activeModule = ref<string>('all');
|
const activeModule = ref<string>('all');
|
||||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||||
const folderTreePathMap = ref<Record<string, any>>({});
|
const folderTreePathMap = ref<Record<string, any>>({});
|
||||||
|
@ -110,6 +113,13 @@
|
||||||
managementRef.value?.refreshApiTable();
|
managementRef.value?.refreshApiTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
if (route.query.dId) {
|
||||||
|
// 携带 dId 参数,自动打开接口定义详情 tab
|
||||||
|
managementRef.value?.newTab(route.query.dId as string);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
/** 向子孙组件提供方法和值 */
|
/** 向子孙组件提供方法和值 */
|
||||||
provide('setActiveApi', setActiveApi);
|
provide('setActiveApi', setActiveApi);
|
||||||
provide('refreshModuleTree', refreshModuleTree);
|
provide('refreshModuleTree', refreshModuleTree);
|
||||||
|
|
|
@ -118,4 +118,17 @@ export default {
|
||||||
'mockManagement.batchDisEnable': 'Batch disable',
|
'mockManagement.batchDisEnable': 'Batch disable',
|
||||||
'mockManagement.batchDeleteMockTip': 'Are you sure you want to delete the selected {count} mocks?',
|
'mockManagement.batchDeleteMockTip': 'Are you sure you want to delete the selected {count} mocks?',
|
||||||
'apiTestManagement.deleteMockTip': 'After deletion, it cannot be restored. Are you sure you want to delete it?',
|
'apiTestManagement.deleteMockTip': 'After deletion, it cannot be restored. Are you sure you want to delete it?',
|
||||||
|
'apiTestManagement.preview': 'Preview',
|
||||||
|
'apiTestManagement.shareUrlCopied': 'Sharing link copied to clipboard',
|
||||||
|
'apiTestManagement.detail': 'Detail',
|
||||||
|
'apiTestManagement.reference': 'Reference',
|
||||||
|
'apiTestManagement.dependencies': 'Dependency',
|
||||||
|
'apiTestManagement.changeHistory': 'Change history',
|
||||||
|
'apiTestManagement.requestParams': 'Request parameters',
|
||||||
|
'apiTestManagement.responseContent': 'Response content',
|
||||||
|
'apiTestManagement.requestHeader': 'Request header',
|
||||||
|
'apiTestManagement.requestBody': 'Request body',
|
||||||
|
'apiTestManagement.paramsType': 'Param type',
|
||||||
|
'apiTestManagement.required': 'Required',
|
||||||
|
'apiTestManagement.requestData': 'Request data',
|
||||||
};
|
};
|
||||||
|
|
|
@ -111,5 +111,18 @@ export default {
|
||||||
'mockManagement.batchEnable': '批量启用',
|
'mockManagement.batchEnable': '批量启用',
|
||||||
'mockManagement.batchDisEnable': '批量禁用',
|
'mockManagement.batchDisEnable': '批量禁用',
|
||||||
'mockManagement.batchDeleteMockTip': '确认删除已选中的 {count} 个Mock吗?',
|
'mockManagement.batchDeleteMockTip': '确认删除已选中的 {count} 个Mock吗?',
|
||||||
'apiTestManagement.deleteMockTip': '刪除后將不可恢復,确认刪除嗎?',
|
'apiTestManagement.deleteMockTip': '删除后不可恢复,确认删除吗?',
|
||||||
|
'apiTestManagement.preview': '预览',
|
||||||
|
'apiTestManagement.shareUrlCopied': '分享链接已复制到剪贴板',
|
||||||
|
'apiTestManagement.detail': '详情',
|
||||||
|
'apiTestManagement.reference': '引用关系',
|
||||||
|
'apiTestManagement.dependencies': '依赖关系',
|
||||||
|
'apiTestManagement.changeHistory': '变更历史',
|
||||||
|
'apiTestManagement.requestParams': '请求参数',
|
||||||
|
'apiTestManagement.responseContent': '响应内容',
|
||||||
|
'apiTestManagement.requestHeader': '请求头',
|
||||||
|
'apiTestManagement.requestBody': '请求体',
|
||||||
|
'apiTestManagement.paramsType': '参数类型',
|
||||||
|
'apiTestManagement.required': '必填',
|
||||||
|
'apiTestManagement.requestData': '请求数据',
|
||||||
};
|
};
|
||||||
|
|
|
@ -86,7 +86,7 @@
|
||||||
Message.success(t('caseManagement.featureCase.editSuccess'));
|
Message.success(t('caseManagement.featureCase.editSuccess'));
|
||||||
router.push({
|
router.push({
|
||||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE,
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE,
|
||||||
query: { organizationId: route.query.organizationId, projectId: route.query.projectId },
|
query: { organizationId: route.query.orgId, projectId: route.query.pId },
|
||||||
});
|
});
|
||||||
setState(true);
|
setState(true);
|
||||||
// 创建用例
|
// 创建用例
|
||||||
|
@ -120,8 +120,8 @@
|
||||||
router.push({
|
router.push({
|
||||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE,
|
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE,
|
||||||
query: {
|
query: {
|
||||||
organizationId: route.query.organizationId,
|
organizationId: route.query.orgId,
|
||||||
projectId: route.query.projectId,
|
projectId: route.query.pId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue