feat(接口管理): 接口定义-详情

This commit is contained in:
baiqi 2024-03-11 18:37:17 +08:00 committed by 刘瑞斌
parent 690b819470
commit da550b75f7
39 changed files with 1590 additions and 166 deletions

View File

@ -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,

View File

@ -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",

View File

@ -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
*/ */

View File

@ -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

View File

@ -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 {

View File

@ -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>

View File

@ -0,0 +1,4 @@
export default {
'msDetailCard.more': 'Expand more',
'msDetailCard.collapse': 'Collapse',
};

View File

@ -0,0 +1,4 @@
export default {
'msDetailCard.more': '展开更多',
'msDetailCard.collapse': '收起',
};

View File

@ -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; // inputTypetext
[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 showSettingtrue tableKey?: TableKeyEnum; // key showSettingtrue
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) {
// expressionexpression
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>

View File

@ -0,0 +1 @@
export default {};

View File

@ -0,0 +1,4 @@
export default {
'msFormTable.paramRequired': '必填',
'msFormTable.paramNotRequired': '非必填',
};

View File

@ -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 }">

View File

@ -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',
} }
); );

View File

@ -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', // 评审中

View File

@ -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',

View File

@ -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 {

View File

@ -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',
}; };

View File

@ -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': '分享',
}; };

View File

@ -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;
} }
// 执行请求配置 // 执行请求配置

View File

@ -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);

View File

@ -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();

View File

@ -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"

View File

@ -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;
} }
} }

View File

@ -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"
> >

View File

@ -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) {

View File

@ -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',

View File

@ -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;

View File

@ -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 {

View File

@ -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() {

View File

@ -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,
})) || [],
};
}

View File

@ -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,

View File

@ -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();

View File

@ -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, // tabidid isNew: !defaultProps?.id, // tabidid
...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) {
// tabtab // tabtab
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, // requestnamenull name, // requestnamenull
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>();

View File

@ -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.detailprops.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>

View File

@ -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 {

View File

@ -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);

View File

@ -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',
}; };

View File

@ -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': '请求数据',
}; };

View File

@ -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,
}, },
}); });
} }