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-empty-function': 1,
|
||||
'@typescript-eslint/no-explicit-any': 0,
|
||||
'@typescript-eslint/no-duplicate-enum-values': 0,
|
||||
'consistent-return': 'off',
|
||||
'import/extensions': [
|
||||
2,
|
||||
|
|
|
@ -92,8 +92,8 @@
|
|||
"@types/lodash-es": "^4.17.9",
|
||||
"@types/nprogress": "^0.2.0",
|
||||
"@types/sortablejs": "^1.15.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.62.0",
|
||||
"@typescript-eslint/parser": "^5.62.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.1.1",
|
||||
"@typescript-eslint/parser": "^7.1.1",
|
||||
"@vitejs/plugin-vue": "^3.2.0",
|
||||
"@vitejs/plugin-vue-jsx": "^2.1.1",
|
||||
"@vitest/coverage-c8": "^0.31.4",
|
||||
|
|
|
@ -24,6 +24,7 @@ import {
|
|||
MoveModuleUrl,
|
||||
SortDefinitionUrl,
|
||||
SwitchDefinitionScheduleUrl,
|
||||
ToggleFollowDefinitionUrl,
|
||||
TransferFileModuleOptionUrl,
|
||||
TransferFileUrl,
|
||||
UpdateDefinitionScheduleUrl,
|
||||
|
@ -203,6 +204,11 @@ export function debugDefinition(data: ExecuteRequestParams) {
|
|||
return MSR.post({ url: DebugDefinitionUrl, data });
|
||||
}
|
||||
|
||||
// 关注/取消关注接口定义
|
||||
export function toggleFollowDefinition(id: string | number) {
|
||||
return MSR.get({ url: ToggleFollowDefinitionUrl, params: id });
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock
|
||||
*/
|
||||
|
|
|
@ -6,6 +6,9 @@ export const GetEnvModuleUrl = '/api/definition/module/env/tree'; // 获取环
|
|||
export const GetModuleCountUrl = '/api/definition/module/count'; // 获取模块统计数量
|
||||
export const AddModuleUrl = '/api/definition/module/add'; // 添加模块
|
||||
export const DeleteModuleUrl = '/api/definition/module/delete'; // 删除模块
|
||||
/**
|
||||
* 接口定义
|
||||
*/
|
||||
export const DefinitionPageUrl = '/api/definition/page'; // 接口定义列表
|
||||
export const AddDefinitionUrl = '/api/definition/add'; // 添加接口定义
|
||||
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 TransferFileModuleOptionUrl = '/api/definition/transfer/options'; // 文件转存目录
|
||||
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 ImportDefinitionUrl = '/api/definition/import'; // 导入接口定义
|
||||
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 DeleteDefinitionScheduleUrl = '/api/definition/schedule/delete'; // 接口定义-定时同步-删除
|
||||
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) {
|
||||
@apply bg-transparent;
|
||||
|
||||
border-color: transparent;
|
||||
&:not(:hover) {
|
||||
.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
|
||||
v-bind="propsRes"
|
||||
:hoverable="false"
|
||||
no-disable
|
||||
is-simple-setting
|
||||
:span-method="props.spanMethod"
|
||||
:class="!props.selectable && !props.draggable ? 'ms-form-table-no-left-action' : ''"
|
||||
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
|
||||
v-for="item of props.columns.filter((e) => e.slotName !== undefined)"
|
||||
#[item.slotName!]="{ record, rowIndex, column }"
|
||||
>
|
||||
<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>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
|
@ -20,26 +123,38 @@
|
|||
|
||||
<script setup lang="ts">
|
||||
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 type { MsTableColumnData } from '@/components/pure/ms-table/type';
|
||||
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 { SelectAllEnum, TableKeyEnum } from '@/enums/tableEnum';
|
||||
|
||||
import { ActionsItem } from '../ms-table-more-action/types';
|
||||
import { TableOperationColumn } from '@arco-design/web-vue/es/table/interface';
|
||||
|
||||
export interface FormTableColumn extends MsTableColumnData {
|
||||
enable?: boolean; // 是否启用
|
||||
required?: boolean; // 是否必填
|
||||
inputType?: 'input' | 'select' | 'tags' | 'switch' | 'text' | 'checkbox'; // 输入组件类型
|
||||
valueFormat?: (record: Record<string, any>) => string; // 展示值格式化,仅在inputType为text时生效
|
||||
[key: string]: any; // 扩展属性
|
||||
}
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
data?: any[];
|
||||
data?: Record<string, any>[];
|
||||
columns: FormTableColumn[];
|
||||
defaultParamDataItem?: Record<string, any>;
|
||||
scroll?: {
|
||||
x?: number | string;
|
||||
y?: number | string;
|
||||
|
@ -53,7 +168,6 @@
|
|||
tableKey?: TableKeyEnum; // 表格key showSetting为true时必传
|
||||
disabled?: boolean; // 是否禁用
|
||||
showSelectorAll?: boolean; // 是否显示全选
|
||||
isTreeTable?: boolean; // 是否树形表格
|
||||
spanMethod?: (data: {
|
||||
record: TableData;
|
||||
column: TableColumnData | TableOperationColumn;
|
||||
|
@ -69,9 +183,18 @@
|
|||
}
|
||||
);
|
||||
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();
|
||||
|
||||
async function initColumns() {
|
||||
|
@ -112,6 +235,8 @@
|
|||
emit('change', propsRes.value.data);
|
||||
};
|
||||
|
||||
const dataLength = computed(() => propsRes.value.data.length);
|
||||
|
||||
watch(
|
||||
() => selectedKeys.value,
|
||||
(arr) => {
|
||||
|
@ -128,14 +253,83 @@
|
|||
|
||||
watch(
|
||||
() => props.data,
|
||||
(val) => {
|
||||
propsRes.value.data = val;
|
||||
(arr) => {
|
||||
propsRes.value.data = arr as any[];
|
||||
},
|
||||
{
|
||||
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();
|
||||
</script>
|
||||
|
||||
|
@ -147,6 +341,11 @@
|
|||
:deep(.arco-table .arco-table-cell) {
|
||||
padding: 8px 2px;
|
||||
}
|
||||
.ms-form-table-no-left-action {
|
||||
:deep(.arco-table .arco-table-cell) {
|
||||
padding: 8px 16px;
|
||||
}
|
||||
}
|
||||
:deep(.arco-table-cell-align-left) {
|
||||
padding: 8px;
|
||||
}
|
||||
|
@ -155,8 +354,18 @@
|
|||
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) {
|
||||
@apply bg-transparent;
|
||||
|
||||
border-color: transparent !important;
|
||||
.arco-input::placeholder {
|
||||
@apply invisible;
|
||||
|
@ -172,8 +381,8 @@
|
|||
}
|
||||
}
|
||||
}
|
||||
:deep(.param-input-number) {
|
||||
@apply pr-0;
|
||||
:deep(.ms-form-table-input-number) {
|
||||
@apply bg-transparent pr-0;
|
||||
.arco-input {
|
||||
@apply text-right;
|
||||
}
|
||||
|
@ -193,4 +402,20 @@
|
|||
:deep(.arco-table-expand-btn) {
|
||||
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>
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export default {};
|
|
@ -0,0 +1,4 @@
|
|||
export default {
|
||||
'msFormTable.paramRequired': '必填',
|
||||
'msFormTable.paramNotRequired': '非必填',
|
||||
};
|
|
@ -147,6 +147,7 @@
|
|||
placement="top"
|
||||
content-class="max-w-[400px]"
|
||||
: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">
|
||||
<slot :name="item.slotName" v-bind="{ record, rowIndex, column, columnConfig: item }">
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<a-tooltip :content="tagsTooltip">
|
||||
<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] }}
|
||||
</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
|
||||
>
|
||||
</div>
|
||||
|
@ -14,7 +14,7 @@
|
|||
<script setup lang="ts">
|
||||
import { computed, useAttrs } from 'vue';
|
||||
|
||||
import MsTag from './ms-tag.vue';
|
||||
import MsTag, { Size } from './ms-tag.vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
|
@ -22,10 +22,12 @@
|
|||
showNum?: number;
|
||||
nameKey?: string;
|
||||
isStringTag?: boolean; // 是否是字符串数组的标签
|
||||
size?: Size;
|
||||
}>(),
|
||||
{
|
||||
showNum: 2,
|
||||
nameKey: 'name',
|
||||
size: 'medium',
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
// @ts-ignore @typescript-eslint/no-duplicate-enum-values
|
||||
export enum StatusType {
|
||||
UN_REVIEWED = 'icon-icon_block_filled', // 未评审
|
||||
UNDER_REVIEWED = 'icon-icon_testing', // 评审中
|
||||
|
|
|
@ -43,7 +43,7 @@ export enum TableKeyEnum {
|
|||
CASE_MANAGEMENT_REVIEW = 'caseManagementReview',
|
||||
CASE_MANAGEMENT_REVIEW_CASE = 'caseManagementReviewCase',
|
||||
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_POST_CASE = 'caseManagementTabPostDependency',
|
||||
CASE_MANAGEMENT_TAB_REVIEW = 'caseManagementTabCaseReview',
|
||||
|
|
|
@ -14,7 +14,7 @@ export enum ExecutionMethods {
|
|||
SCHEDULE = 'SCHEDULE', // 定时任务
|
||||
MANUAL = 'MANUAL', // 手动执行
|
||||
API = 'API', // 接口调用
|
||||
BATCH = 'API', // 批量执行
|
||||
// BATCH = 'API', // 批量执行
|
||||
}
|
||||
|
||||
export enum ExecutionMethodsLabel {
|
||||
|
|
|
@ -126,4 +126,8 @@ export default {
|
|||
'common.module': 'Module',
|
||||
'common.yes': 'Yes',
|
||||
'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.yes': '是',
|
||||
'common.no': '否',
|
||||
'common.creator': '创建人',
|
||||
'common.followSuccess': '关注成功',
|
||||
'common.unFollowSuccess': '取消关注成功',
|
||||
'common.share': '分享',
|
||||
};
|
||||
|
|
|
@ -145,7 +145,7 @@ export interface JsonSchema {
|
|||
export interface ExecuteJsonBody {
|
||||
enableJsonSchema?: boolean;
|
||||
enableTransition?: boolean;
|
||||
jsonSchema?: JsonSchema;
|
||||
jsonSchema?: JsonSchema[];
|
||||
jsonValue: string;
|
||||
}
|
||||
// 执行请求配置
|
||||
|
|
|
@ -17,8 +17,8 @@ function setupPageGuard(router: Router) {
|
|||
// 取消上个路由未完成的请求(不包含设置了ignoreCancelToken的请求)
|
||||
axiosCanceler.removeAllPending();
|
||||
const appStore = useAppStore();
|
||||
const urlOrgId = to.query.organizationId;
|
||||
const urlProjectId = to.query.projectId;
|
||||
const urlOrgId = to.query.orgId;
|
||||
const urlProjectId = to.query.pId;
|
||||
// 如果访问页面的时候携带了项目 ID 或组织 ID,则将页面上的组织 ID和项目 ID设置为当前选中的组织和项目
|
||||
if (urlOrgId) {
|
||||
appStore.setCurrentOrgId(urlOrgId as string);
|
||||
|
@ -31,7 +31,7 @@ function setupPageGuard(router: Router) {
|
|||
if (urlOrgId === undefined) {
|
||||
to.query = {
|
||||
...to.query,
|
||||
organizationId: appStore.currentOrgId,
|
||||
orgId: appStore.currentOrgId,
|
||||
};
|
||||
next(to);
|
||||
return;
|
||||
|
@ -41,8 +41,8 @@ function setupPageGuard(router: Router) {
|
|||
if (urlOrgId === undefined && urlProjectId === undefined) {
|
||||
to.query = {
|
||||
...to.query,
|
||||
organizationId: appStore.currentOrgId,
|
||||
projectId: appStore.currentProjectId,
|
||||
orgId: appStore.currentOrgId,
|
||||
pId: appStore.currentProjectId,
|
||||
};
|
||||
|
||||
next(to);
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
<template>
|
||||
<MsTag :self-style="status.style"> {{ status.text }}</MsTag>
|
||||
<MsTag :self-style="status.style" :size="props.size"> {{ status.text }}</MsTag>
|
||||
</template>
|
||||
|
||||
<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';
|
||||
|
||||
|
@ -11,6 +11,7 @@
|
|||
|
||||
const props = defineProps<{
|
||||
status: RequestDefinitionStatus;
|
||||
size?: Size;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
|
|
@ -30,7 +30,7 @@
|
|||
v-if="showBatchAddParamDrawer"
|
||||
v-model:model-value="batchParamsCode"
|
||||
class="flex-1"
|
||||
theme="MS-text"
|
||||
theme="vs"
|
||||
height="100%"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
|
|
|
@ -52,6 +52,8 @@
|
|||
<style lang="less" scoped>
|
||||
.param-input:not(.arco-input-focus) {
|
||||
&:not(:hover) {
|
||||
@apply bg-transparent;
|
||||
|
||||
border-color: transparent;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
<a-input
|
||||
v-model:model-value="record[columnConfig.dataIndex as string]"
|
||||
:placeholder="t('apiTestDebug.paramNamePlaceholder')"
|
||||
class="param-input"
|
||||
class="ms-form-table-input"
|
||||
:max-length="255"
|
||||
size="mini"
|
||||
@input="() => addTableLine(rowIndex, columnConfig.addLineDisabled)"
|
||||
|
@ -99,7 +99,7 @@
|
|||
<a-select
|
||||
v-model:model-value="record.paramType"
|
||||
:options="columnConfig.typeOptions || []"
|
||||
class="param-input w-full"
|
||||
class="ms-form-table-input w-full"
|
||||
size="mini"
|
||||
@change="(val) => handleTypeChange(val, record, rowIndex, columnConfig.addLineDisabled)"
|
||||
/>
|
||||
|
@ -108,7 +108,7 @@
|
|||
<a-select
|
||||
v-model:model-value="record.extractType"
|
||||
:options="columnConfig.typeOptions || []"
|
||||
class="param-input w-[110px]"
|
||||
class="ms-form-table-input w-[110px]"
|
||||
size="mini"
|
||||
@change="() => addTableLine(rowIndex)"
|
||||
/>
|
||||
|
@ -117,7 +117,7 @@
|
|||
<a-select
|
||||
v-model:model-value="record.variableType"
|
||||
:options="columnConfig.typeOptions || []"
|
||||
class="param-input w-[110px]"
|
||||
class="ms-form-table-input w-[110px]"
|
||||
size="mini"
|
||||
@change="() => addTableLine(rowIndex)"
|
||||
/>
|
||||
|
@ -126,7 +126,7 @@
|
|||
<a-select
|
||||
v-model:model-value="record.extractScope"
|
||||
:options="columnConfig.typeOptions || []"
|
||||
class="param-input w-[180px]"
|
||||
class="ms-form-table-input w-[180px]"
|
||||
size="mini"
|
||||
@change="() => addTableLine(rowIndex)"
|
||||
/>
|
||||
|
@ -151,7 +151,7 @@
|
|||
</template>
|
||||
<a-input
|
||||
v-model:model-value="record.value"
|
||||
class="param-input"
|
||||
class="ms-form-table-input"
|
||||
:placeholder="t('apiTestDebug.commonPlaceholder')"
|
||||
:max-length="255"
|
||||
size="mini"
|
||||
|
@ -170,7 +170,7 @@
|
|||
:file-save-as-source-id="props.fileSaveAsSourceId"
|
||||
:file-save-as-api="props.fileSaveAsApi"
|
||||
:file-module-options-api="props.fileModuleOptionsApi"
|
||||
input-class="param-input h-[24px]"
|
||||
input-class="ms-form-table-input h-[24px]"
|
||||
input-size="small"
|
||||
tag-size="small"
|
||||
@change="(files, file) => handleFileChange(files, record, rowIndex, file)"
|
||||
|
@ -190,8 +190,9 @@
|
|||
v-model:model-value="record.minLength"
|
||||
:placeholder="t('apiTestDebug.paramMin')"
|
||||
:min="0"
|
||||
class="param-input param-input-number"
|
||||
class="ms-form-table-input ms-form-table-input-number"
|
||||
size="mini"
|
||||
model-event="input"
|
||||
@change="() => addTableLine(rowIndex)"
|
||||
/>
|
||||
<div class="mx-[4px]">{{ t('common.to') }}</div>
|
||||
|
@ -199,13 +200,14 @@
|
|||
v-model:model-value="record.maxLength"
|
||||
:placeholder="t('apiTestDebug.paramMax')"
|
||||
:min="0"
|
||||
class="param-input"
|
||||
class="ms-form-table-input"
|
||||
size="mini"
|
||||
model-event="input"
|
||||
@change="() => addTableLine(rowIndex)"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #tag="{ record, columnConfig }">
|
||||
<template #tag="{ record, columnConfig, rowIndex }">
|
||||
<a-popover
|
||||
position="tl"
|
||||
:disabled="record[columnConfig.dataIndex as string].length === 0"
|
||||
|
@ -222,8 +224,10 @@
|
|||
<MsTagsInput
|
||||
v-model:model-value="record[columnConfig.dataIndex as string]"
|
||||
:max-tag-count="1"
|
||||
input-class="param-input"
|
||||
input-class="ms-form-table-input"
|
||||
size="mini"
|
||||
@change="() => addTableLine(rowIndex)"
|
||||
@clear="() => addTableLine(rowIndex)"
|
||||
/>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
@ -240,7 +244,7 @@
|
|||
<a-switch
|
||||
v-model:model-value="record.encode"
|
||||
size="small"
|
||||
class="param-input-switch"
|
||||
class="ms-form-table-input-switch"
|
||||
type="line"
|
||||
@change="() => addTableLine(rowIndex)"
|
||||
/>
|
||||
|
@ -259,13 +263,13 @@
|
|||
</template>
|
||||
<!-- 响应头 -->
|
||||
<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-select>
|
||||
</template>
|
||||
<!-- 匹配条件 -->
|
||||
<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">{{
|
||||
t(item.label)
|
||||
}}</a-option>
|
||||
|
@ -289,12 +293,12 @@
|
|||
<div>*</div>
|
||||
</MsButton>
|
||||
</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 #project="{ record, rowIndex }">
|
||||
<a-select
|
||||
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"
|
||||
allow-search
|
||||
@change="(val) => handleProjectChange(val as string,record.projectId, rowIndex)"
|
||||
|
@ -330,7 +334,7 @@
|
|||
:search-keys="['name']"
|
||||
size="mini"
|
||||
allow-search
|
||||
class="param-input"
|
||||
class="ms-form-table-input"
|
||||
:remote-func="initEnvOptions"
|
||||
:remote-extra-params="{ projectId: record.projectId, keyword: record.environmentInput }"
|
||||
@change-object="(val) => handleEnvironment(val, record)"
|
||||
|
@ -413,7 +417,7 @@
|
|||
<MsCodeEditor
|
||||
v-if="showQuickInputParam"
|
||||
v-model:model-value="quickInputParamValue"
|
||||
theme="MS-text"
|
||||
theme="vs"
|
||||
height="300px"
|
||||
:show-full-screen="false"
|
||||
>
|
||||
|
|
|
@ -150,7 +150,6 @@
|
|||
emit('renameFinish', form.value.field, props.nodeId);
|
||||
} else if (props.mode === 'tabRename') {
|
||||
// 响应 tab 重命名
|
||||
Message.success(t('common.updateSuccess'));
|
||||
emit('renameFinish', form.value.field);
|
||||
}
|
||||
if (done) {
|
||||
|
|
|
@ -69,6 +69,11 @@
|
|||
dataIndex: 'driver',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'URL',
|
||||
dataIndex: 'dbUrl',
|
||||
showTooltip: true,
|
||||
},
|
||||
{
|
||||
title: 'apiTestDebug.username',
|
||||
dataIndex: 'username',
|
||||
|
|
|
@ -249,8 +249,8 @@
|
|||
v-show="showResponse"
|
||||
v-model:active-layout="activeLayout"
|
||||
v-model:active-tab="requestVModel.responseActiveTab"
|
||||
v-model:response-definition="requestVModel.responseDefinition"
|
||||
:is-expanded="isExpanded"
|
||||
:response-definition="requestVModel.responseDefinition"
|
||||
:hide-layout-switch="props.hideResponseLayoutSwitch"
|
||||
:request-task-result="requestVModel.response"
|
||||
:is-edit="props.isDefinition && isHttpProtocol"
|
||||
|
@ -376,6 +376,7 @@
|
|||
activeTab: RequestComposition;
|
||||
mode?: 'definition' | 'debug';
|
||||
executeLoading: boolean; // 执行中loading
|
||||
isCopy?: boolean; // 是否是复制
|
||||
}
|
||||
export type RequestParam = ExecuteApiRequestFullParams & {
|
||||
responseDefinition?: ResponseItem[];
|
||||
|
@ -616,7 +617,7 @@
|
|||
*/
|
||||
function setPluginFormData() {
|
||||
const tempForm = temporaryPluginFormMap[requestVModel.value.id];
|
||||
if (tempForm || !requestVModel.value.isNew) {
|
||||
if (tempForm || !requestVModel.value.isNew || requestVModel.value.isCopy) {
|
||||
// 如果缓存的表单数据存在或者是编辑状态,则需要将之前的输入数据填充
|
||||
const formData = tempForm || requestVModel.value;
|
||||
if (fApi.value) {
|
||||
|
@ -918,13 +919,17 @@
|
|||
let requestModuleId = '';
|
||||
let apiDefinitionParams: Record<string, any> = {};
|
||||
if (props.isDefinition) {
|
||||
// 接口定义有响应内容定义
|
||||
requestName = requestVModel.value.name;
|
||||
requestModuleId = requestVModel.value.moduleId;
|
||||
apiDefinitionParams = {
|
||||
tags: requestVModel.value.tags,
|
||||
description: requestVModel.value.description,
|
||||
status: requestVModel.value.status,
|
||||
response: requestVModel.value.responseDefinition,
|
||||
response: requestVModel.value.responseDefinition?.map((e) => ({
|
||||
...e,
|
||||
headers: filterKeyValParams(e.headers, defaultKeyValueParamItem).validParams,
|
||||
})),
|
||||
};
|
||||
} else {
|
||||
requestName = requestVModel.value.isNew ? saveModalForm.value.name : requestVModel.value.name;
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
<template #label="{ tab }">
|
||||
<div class="response-tab">
|
||||
<div v-if="tab.defaultFlag" class="response-tab-default-icon"></div>
|
||||
{{ t(tab.label || '') }}({{ tab.statusCode }})
|
||||
{{ t(tab.name || tab.label) }}({{ tab.statusCode }})
|
||||
<MsMoreAction
|
||||
:list="
|
||||
tab.defaultFlag
|
||||
|
@ -23,12 +23,13 @@
|
|||
<popConfirm
|
||||
v-model:visible="tab.showRenamePopConfirm"
|
||||
mode="tabRename"
|
||||
:field-config="{ field: t(tab.label || '') }"
|
||||
:all-names="responseTabs.map((e) => t(tab.label || ''))"
|
||||
:field-config="{ field: t(tab.label || tab.name) }"
|
||||
:all-names="responseTabs.map((e) => t(e.label || e.name))"
|
||||
:popup-offset="20"
|
||||
@rename-finish="
|
||||
(val) => {
|
||||
tab.label = val;
|
||||
tab.name = val;
|
||||
emit('change');
|
||||
}
|
||||
"
|
||||
|
@ -48,7 +49,7 @@
|
|||
</template>
|
||||
<template #content>
|
||||
<div class="font-semibold text-[var(--color-text-1)]">
|
||||
{{ t('apiTestManagement.confirmDelete', { name: tab.label }) }}
|
||||
{{ t('apiTestManagement.confirmDelete', { name: tab.label || tab.name }) }}
|
||||
</div>
|
||||
</template>
|
||||
<div class="relative"></div>
|
||||
|
@ -72,11 +73,16 @@
|
|||
size="small"
|
||||
@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() }}
|
||||
</a-radio>
|
||||
</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
|
||||
v-model:model-value="activeResponse.body.jsonBody.enableJsonSchema"
|
||||
size="mini"
|
||||
|
@ -89,7 +95,7 @@
|
|||
<a-switch v-model:model-value="activeResponse.body.jsonBody.enableTransition" size="small" type="line" />
|
||||
{{ t('apiTestManagement.dynamicConversion') }}
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
<div
|
||||
v-if="
|
||||
|
@ -99,6 +105,11 @@
|
|||
"
|
||||
class="h-[calc(100%-35px)]"
|
||||
>
|
||||
<!-- <MsJsonSchema
|
||||
v-if="activeResponse.body.jsonBody.enableJsonSchema"
|
||||
:data="activeResponse.body.jsonBody.jsonSchema"
|
||||
:columns="jsonSchemaColumns"
|
||||
/> -->
|
||||
<MsCodeEditor
|
||||
ref="responseEditorRef"
|
||||
v-model:model-value="currentBodyCode"
|
||||
|
@ -137,16 +148,17 @@
|
|||
v-else-if="activeResponse.responseActiveTab === ResponseComposition.HEADER"
|
||||
v-model:params="activeResponse.headers"
|
||||
:columns="columns"
|
||||
:default-param-item="[
|
||||
{
|
||||
key: '',
|
||||
value: '',
|
||||
},
|
||||
]"
|
||||
:default-param-item="defaultKeyValueParamItem"
|
||||
: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>
|
||||
</template>
|
||||
|
||||
|
@ -157,6 +169,8 @@
|
|||
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
||||
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
||||
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 { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||
import { MsFileItem } from '@/components/pure/ms-upload/types';
|
||||
|
@ -170,7 +184,7 @@
|
|||
import { ResponseDefinition } from '@/models/apiTest/common';
|
||||
import { ResponseBodyFormat, ResponseComposition } from '@/enums/apiEnum';
|
||||
|
||||
import { defaultResponseItem, statusCodes } from '../../config';
|
||||
import { defaultKeyValueParamItem, defaultResponseItem, statusCodes } from '../../config';
|
||||
|
||||
const props = defineProps<{
|
||||
responseDefinition: ResponseDefinition[];
|
||||
|
@ -193,19 +207,11 @@
|
|||
});
|
||||
const activeResponse = ref<ResponseItem>(responseTabs.value[0]);
|
||||
|
||||
watch(
|
||||
() => responseTabs.value,
|
||||
(arr) => {
|
||||
if (arr[0]) {
|
||||
[activeResponse.value] = arr;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
function addResponseTab(defaultProps?: Partial<ResponseItem>) {
|
||||
responseTabs.value.push({
|
||||
...cloneDeep(defaultResponseItem),
|
||||
label: t('apiTestManagement.response', { count: responseTabs.value.length + 1 }),
|
||||
name: t('apiTestManagement.response', { count: responseTabs.value.length + 1 }),
|
||||
...defaultProps,
|
||||
id: new Date().getTime(),
|
||||
defaultFlag: false,
|
||||
|
@ -243,17 +249,23 @@
|
|||
function handleMoreActionSelect(e: ActionsItem, _tab: ResponseItem) {
|
||||
switch (e.eventTag) {
|
||||
case 'setDefault':
|
||||
responseTabs.value = responseTabs.value.map((tab) => {
|
||||
tab.defaultFlag = _tab.id === tab.id;
|
||||
return tab;
|
||||
});
|
||||
_tab.defaultFlag = true;
|
||||
responseTabs.value = [
|
||||
_tab,
|
||||
...responseTabs.value.filter((tab) => {
|
||||
if (tab.id !== _tab.id) {
|
||||
tab.defaultFlag = false;
|
||||
}
|
||||
return tab.id !== _tab.id;
|
||||
}),
|
||||
];
|
||||
break;
|
||||
case 'rename':
|
||||
renameValue.value = _tab.label || '';
|
||||
renameValue.value = _tab.label || _tab.name || '';
|
||||
document.querySelector(`#renameSpan${_tab.id}`)?.dispatchEvent(new Event('click'));
|
||||
break;
|
||||
case 'copy':
|
||||
addResponseTab({ ..._tab, label: `${_tab.label}-Copy` });
|
||||
addResponseTab({ ..._tab, label: `${_tab.label || _tab.name}-Copy`, name: `${_tab.label || _tab.name}-Copy` });
|
||||
break;
|
||||
case 'delete':
|
||||
_tab.showPopConfirm = true;
|
||||
|
@ -291,6 +303,21 @@
|
|||
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({
|
||||
get() {
|
||||
|
@ -365,12 +392,20 @@
|
|||
title: 'apiTestManagement.paramName',
|
||||
dataIndex: 'key',
|
||||
slotName: 'key',
|
||||
inputType: 'input',
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.paramVal',
|
||||
dataIndex: 'value',
|
||||
slotName: 'value',
|
||||
isNormal: true,
|
||||
inputType: 'input',
|
||||
},
|
||||
{
|
||||
title: '',
|
||||
dataIndex: 'operation',
|
||||
slotName: 'operation',
|
||||
width: 35,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -378,10 +413,22 @@
|
|||
label: e.toString(),
|
||||
value: e,
|
||||
}));
|
||||
|
||||
function handleResponseTableChange(arr: any[]) {
|
||||
activeResponse.value.headers = [...arr];
|
||||
emit('change');
|
||||
}
|
||||
|
||||
function handleStatusCodeChange() {
|
||||
emit('change');
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.response-container {
|
||||
@apply overflow-y-auto;
|
||||
.ms-scroll-bar();
|
||||
|
||||
margin-top: 8px;
|
||||
height: calc(100% - 88px);
|
||||
.response-header-pre {
|
||||
|
|
|
@ -117,14 +117,14 @@
|
|||
</div>
|
||||
<a-spin :loading="props.loading" class="h-[calc(100%-35px)] w-full px-[18px] pb-[18px]">
|
||||
<edit
|
||||
v-if="props.isEdit && activeResponseType === 'content' && validResponseDefinition"
|
||||
:response-definition="validResponseDefinition"
|
||||
v-if="props.isEdit && activeResponseType === 'content' && innerResponseDefinition"
|
||||
v-model:response-definition="innerResponseDefinition"
|
||||
:upload-temp-file-api="props.uploadTempFileApi"
|
||||
@change="handleResponseChange"
|
||||
/>
|
||||
<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]"
|
||||
:console="props.requestTaskResult?.console"
|
||||
/>
|
||||
|
@ -133,8 +133,6 @@
|
|||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useVModel } from '@vueuse/core';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import type { Direction } from '@/components/pure/ms-split-box/index.vue';
|
||||
import edit, { ResponseItem } from './edit.vue';
|
||||
|
@ -164,8 +162,6 @@
|
|||
}
|
||||
);
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:activeLayout', value: Direction): void;
|
||||
(e: 'update:activeTab', value: ResponseComposition): void;
|
||||
(e: 'changeExpand', value: boolean): void;
|
||||
(e: 'changeLayout', value: Direction): void;
|
||||
(e: 'change'): void;
|
||||
|
@ -173,8 +169,15 @@
|
|||
|
||||
const { t } = useI18n();
|
||||
|
||||
const innerLayout = useVModel(props, 'activeLayout', emit);
|
||||
const activeTab = useVModel(props, 'activeTab', emit);
|
||||
const innerLayout = defineModel<Direction>('activeLayout', {
|
||||
default: 'vertical',
|
||||
});
|
||||
const innerActiveTab = defineModel<ResponseComposition>('activeTab', {
|
||||
required: true,
|
||||
});
|
||||
const innerResponseDefinition = defineModel<ResponseItem[]>('responseDefinition', {
|
||||
default: [],
|
||||
});
|
||||
// 响应时间信息
|
||||
const timingInfo = computed(() => {
|
||||
if (props.requestTaskResult) {
|
||||
|
@ -215,44 +218,56 @@
|
|||
}
|
||||
return '';
|
||||
});
|
||||
// 过滤无效数据后的有效响应数据
|
||||
const validResponseDefinition = computed(() => {
|
||||
return props.responseDefinition?.map((item, i) => {
|
||||
// 某些字段在导入时接口返回 null,需要设置默认值
|
||||
if (!item.headers) {
|
||||
item.headers = [];
|
||||
}
|
||||
if (!item.id) {
|
||||
item.id = new Date().getTime() + i;
|
||||
}
|
||||
if (item.body.bodyType === ResponseBodyFormat.NONE) {
|
||||
item.body.bodyType = ResponseBodyFormat.RAW;
|
||||
}
|
||||
if (!item.body.binaryBody) {
|
||||
item.body.binaryBody = {
|
||||
description: '',
|
||||
file: undefined,
|
||||
};
|
||||
}
|
||||
if (!item.body.jsonBody) {
|
||||
item.body.jsonBody = {
|
||||
jsonValue: '',
|
||||
enableJsonSchema: false,
|
||||
enableTransition: false,
|
||||
};
|
||||
if (!item.body.xmlBody) {
|
||||
item.body.xmlBody = {
|
||||
value: '',
|
||||
};
|
||||
watchEffect(() => {
|
||||
// 过滤无效数据后的有效响应数据;当接口导入时会存在部分字段为 null 的数据,需要设置默认值
|
||||
let hasInvalid = false;
|
||||
let validResponseDefinition: ResponseItem[] = [];
|
||||
if (props.responseDefinition && props.responseDefinition.length > 0) {
|
||||
validResponseDefinition = props.responseDefinition.map((item, i) => {
|
||||
// 某些字段在导入时接口返回 null,需要设置默认值
|
||||
if (!item.headers) {
|
||||
item.headers = [];
|
||||
hasInvalid = true;
|
||||
}
|
||||
if (!item.body.rawBody) {
|
||||
item.body.rawBody = {
|
||||
value: '',
|
||||
};
|
||||
if (!item.id) {
|
||||
item.id = new Date().getTime() + i;
|
||||
hasInvalid = true;
|
||||
}
|
||||
}
|
||||
return item;
|
||||
});
|
||||
if (item.body.bodyType === ResponseBodyFormat.NONE) {
|
||||
item.body.bodyType = ResponseBodyFormat.RAW;
|
||||
hasInvalid = true;
|
||||
}
|
||||
if (!item.body.binaryBody) {
|
||||
item.body.binaryBody = {
|
||||
description: '',
|
||||
file: undefined,
|
||||
};
|
||||
hasInvalid = true;
|
||||
}
|
||||
if (!item.body.jsonBody) {
|
||||
item.body.jsonBody = {
|
||||
jsonValue: '',
|
||||
enableJsonSchema: false,
|
||||
enableTransition: false,
|
||||
};
|
||||
if (!item.body.xmlBody) {
|
||||
item.body.xmlBody = {
|
||||
value: '',
|
||||
};
|
||||
}
|
||||
if (!item.body.rawBody) {
|
||||
item.body.rawBody = {
|
||||
value: '',
|
||||
};
|
||||
}
|
||||
hasInvalid = true;
|
||||
}
|
||||
return item;
|
||||
});
|
||||
}
|
||||
if (hasInvalid) {
|
||||
innerResponseDefinition.value = validResponseDefinition;
|
||||
}
|
||||
});
|
||||
|
||||
function handleResponseChange() {
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
import { cloneDeep, isEqual } from 'lodash-es';
|
||||
|
||||
import { RequestParam } from './requestComposition/index.vue';
|
||||
|
||||
import { ExecuteBody } from '@/models/apiTest/common';
|
||||
import { RequestParamsType } from '@/enums/apiEnum';
|
||||
|
||||
import {
|
||||
defaultBodyParamsItem,
|
||||
defaultHeaderParamsItem,
|
||||
defaultKeyValueParamItem,
|
||||
defaultRequestParamsItem,
|
||||
} from './config';
|
||||
|
||||
export interface ParseResult {
|
||||
uploadFileIds: string[];
|
||||
linkFileIds: string[];
|
||||
|
@ -133,3 +142,23 @@ export function filterKeyValParams<T>(params: (T & Record<string, any>)[], defau
|
|||
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);
|
||||
let parseRequestBodyResult;
|
||||
if (res.protocol === 'HTTP') {
|
||||
parseRequestBodyResult = parseRequestBodyFiles(res.request.body);
|
||||
parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件
|
||||
}
|
||||
addDebugTab({
|
||||
label: apiInfo.name,
|
||||
|
|
|
@ -27,7 +27,7 @@
|
|||
@selected-change="handleTableSelect"
|
||||
@batch-action="handleTableBatch"
|
||||
>
|
||||
<template #methodFilter="{ columnConfig }">
|
||||
<template v-if="props.protocol === 'HTTP'" #methodFilter="{ columnConfig }">
|
||||
<a-trigger
|
||||
v-model:popup-visible="methodFilterVisible"
|
||||
trigger="click"
|
||||
|
@ -78,6 +78,7 @@
|
|||
</template>
|
||||
<template #method="{ record }">
|
||||
<a-select
|
||||
v-if="props.protocol === 'HTTP'"
|
||||
v-model:model-value="record.method"
|
||||
class="param-input w-full"
|
||||
@change="() => handleMethodChange(record)"
|
||||
|
@ -89,6 +90,7 @@
|
|||
<apiMethodName :method="item" is-tag />
|
||||
</a-option>
|
||||
</a-select>
|
||||
<apiMethodName v-else :method="record.method" is-tag />
|
||||
</template>
|
||||
<template #status="{ record }">
|
||||
<a-select
|
||||
|
@ -430,7 +432,11 @@
|
|||
projectId: appStore.currentProjectId,
|
||||
moduleIds: moduleIds.value,
|
||||
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);
|
||||
loadList();
|
||||
|
|
|
@ -32,7 +32,21 @@
|
|||
/>
|
||||
</div>
|
||||
<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">
|
||||
<MsSplitBox
|
||||
ref="splitBoxRef"
|
||||
|
@ -68,8 +82,8 @@
|
|||
/>
|
||||
</template>
|
||||
<template #second>
|
||||
<div class="p-[24px]">
|
||||
<!-- 第一版没有模板 -->
|
||||
<div class="p-[18px]">
|
||||
<!-- TODO:第一版没有模板 -->
|
||||
<!-- <MsFormCreate v-model:api="fApi" :rule="currentApiTemplateRules" :option="options" /> -->
|
||||
<a-form ref="activeApiTabFormRef" :model="activeApiTab" layout="vertical">
|
||||
<a-form-item
|
||||
|
@ -127,7 +141,8 @@
|
|||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="mb-[8px] flex items-center">
|
||||
<!-- TODO:第一版先不做依赖 -->
|
||||
<!-- <div class="mb-[8px] flex items-center">
|
||||
<div class="text-[var(--color-text-2)]">
|
||||
{{ t('apiTestManagement.addDependency') }}
|
||||
</div>
|
||||
|
@ -168,7 +183,7 @@
|
|||
{{ t('apiTestManagement.addPostDependency') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
|
@ -183,10 +198,10 @@
|
|||
</template>
|
||||
|
||||
<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 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 { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||
// import MsFormCreate from '@/components/pure/ms-form-create/formCreate.vue';
|
||||
|
@ -197,7 +212,7 @@
|
|||
import apiMethodName from '@/views/api-test/components/apiMethodName.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 {
|
||||
addDefinition,
|
||||
debugDefinition,
|
||||
|
@ -211,7 +226,7 @@
|
|||
import useAppStore from '@/store/modules/app';
|
||||
import { filterTree } from '@/utils';
|
||||
|
||||
import { ExecuteBody, RequestTaskResult } from '@/models/apiTest/common';
|
||||
import { ExecuteBody, ProtocolItem, RequestTaskResult } from '@/models/apiTest/common';
|
||||
import {
|
||||
ApiDefinitionCreateParams,
|
||||
ApiDefinitionDetail,
|
||||
|
@ -229,10 +244,12 @@
|
|||
|
||||
import { defaultResponseItem } from '@/views/api-test/components/config';
|
||||
import type { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
import { parseRequestBodyFiles } from '@/views/api-test/components/utils';
|
||||
// 懒加载requestComposition组件
|
||||
const requestComposition = defineAsyncComponent(
|
||||
() => import('@/views/api-test/components/requestComposition/index.vue')
|
||||
);
|
||||
const preview = defineAsyncComponent(() => import('./preview.vue'));
|
||||
|
||||
const props = defineProps<{
|
||||
activeModule: string;
|
||||
|
@ -241,12 +258,27 @@
|
|||
protocol: string;
|
||||
}>();
|
||||
const emit = defineEmits(['addDone']);
|
||||
const definitionActiveKey = ref('definition');
|
||||
const setActiveApi: ((params: RequestParam) => void) | undefined = inject('setActiveApi');
|
||||
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
|
||||
|
||||
const appStore = useAppStore();
|
||||
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[]>([
|
||||
{
|
||||
id: 'all',
|
||||
|
@ -401,7 +433,7 @@
|
|||
isNew: !defaultProps?.id, // 新开的tab标记为前端新增的调试,因为此时都已经有id了;但是如果是查看打开的会有携带id
|
||||
...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>>();
|
||||
|
@ -413,8 +445,10 @@
|
|||
}
|
||||
|
||||
const loading = ref(false);
|
||||
async function openApiTab(apiInfo: ModuleTreeNode | ApiDefinitionDetail, isCopy = false) {
|
||||
const isLoadedTabIndex = apiTabs.value.findIndex((e) => e.id === apiInfo.id);
|
||||
async function openApiTab(apiInfo: ModuleTreeNode | ApiDefinitionDetail | string, isCopy = false) {
|
||||
const isLoadedTabIndex = apiTabs.value.findIndex(
|
||||
(e) => e.id === (typeof apiInfo === 'string' ? apiInfo : apiInfo.id)
|
||||
);
|
||||
if (isLoadedTabIndex > -1 && !isCopy) {
|
||||
// 如果点击的请求在tab中已经存在,则直接切换到该tab
|
||||
activeApiTab.value = apiTabs.value[isLoadedTabIndex] as RequestParam;
|
||||
|
@ -422,8 +456,13 @@
|
|||
}
|
||||
try {
|
||||
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;
|
||||
definitionActiveKey.value = isCopy ? 'definition' : 'preview';
|
||||
let parseRequestBodyResult;
|
||||
if (res.protocol === 'HTTP') {
|
||||
parseRequestBodyResult = parseRequestBodyFiles(res.request.body); // 解析请求体中的文件,将详情中的文件 id 集合收集,更新时以判断文件是否删除以及是否新上传的文件
|
||||
}
|
||||
addApiTab({
|
||||
label: name,
|
||||
...res.request,
|
||||
|
@ -434,6 +473,8 @@
|
|||
name, // request里面还有个name但是是null
|
||||
isNew: isCopy,
|
||||
unSaved: isCopy,
|
||||
isCopy,
|
||||
...parseRequestBodyResult,
|
||||
});
|
||||
nextTick(() => {
|
||||
// 等待内容渲染出来再隐藏loading
|
||||
|
@ -465,25 +506,25 @@
|
|||
const showAddDependencyDrawer = ref(false);
|
||||
const addDependencyMode = ref<'pre' | 'post'>('pre');
|
||||
|
||||
function handleDddDependency(value: string | number | Record<string, any> | undefined) {
|
||||
switch (value) {
|
||||
case 'pre':
|
||||
addDependencyMode.value = 'pre';
|
||||
showAddDependencyDrawer.value = true;
|
||||
break;
|
||||
case 'post':
|
||||
addDependencyMode.value = 'post';
|
||||
showAddDependencyDrawer.value = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
// function handleDddDependency(value: string | number | Record<string, any> | undefined) {
|
||||
// switch (value) {
|
||||
// case 'pre':
|
||||
// addDependencyMode.value = 'pre';
|
||||
// showAddDependencyDrawer.value = true;
|
||||
// break;
|
||||
// case 'post':
|
||||
// addDependencyMode.value = 'post';
|
||||
// showAddDependencyDrawer.value = true;
|
||||
// break;
|
||||
// default:
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
|
||||
function clearAllDependency() {
|
||||
activeApiTab.value.preDependency = [];
|
||||
activeApiTab.value.postDependency = [];
|
||||
}
|
||||
// function clearAllDependency() {
|
||||
// activeApiTab.value.preDependency = [];
|
||||
// activeApiTab.value.postDependency = [];
|
||||
// }
|
||||
|
||||
const splitBoxRef = ref<InstanceType<typeof MsSplitBox>>();
|
||||
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 apiRef = ref<InstanceType<typeof api>>();
|
||||
|
||||
function newTab(apiInfo?: ModuleTreeNode) {
|
||||
function newTab(apiInfo?: ModuleTreeNode | string) {
|
||||
if (apiInfo) {
|
||||
apiRef.value?.openApiTab(apiInfo);
|
||||
} else {
|
||||
|
|
|
@ -54,6 +54,7 @@
|
|||
|
||||
<script lang="ts" setup>
|
||||
import { provide } from 'vue';
|
||||
import { useRoute } from 'vue-router';
|
||||
|
||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
|
@ -64,6 +65,8 @@
|
|||
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const activeModule = ref<string>('all');
|
||||
const folderTree = ref<ModuleTreeNode[]>([]);
|
||||
const folderTreePathMap = ref<Record<string, any>>({});
|
||||
|
@ -110,6 +113,13 @@
|
|||
managementRef.value?.refreshApiTable();
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (route.query.dId) {
|
||||
// 携带 dId 参数,自动打开接口定义详情 tab
|
||||
managementRef.value?.newTab(route.query.dId as string);
|
||||
}
|
||||
});
|
||||
|
||||
/** 向子孙组件提供方法和值 */
|
||||
provide('setActiveApi', setActiveApi);
|
||||
provide('refreshModuleTree', refreshModuleTree);
|
||||
|
|
|
@ -118,4 +118,17 @@ export default {
|
|||
'mockManagement.batchDisEnable': 'Batch disable',
|
||||
'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.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.batchDisEnable': '批量禁用',
|
||||
'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'));
|
||||
router.push({
|
||||
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);
|
||||
// 创建用例
|
||||
|
@ -120,8 +120,8 @@
|
|||
router.push({
|
||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE,
|
||||
query: {
|
||||
organizationId: route.query.organizationId,
|
||||
projectId: route.query.projectId,
|
||||
organizationId: route.query.orgId,
|
||||
projectId: route.query.pId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue