mirror of
https://gitee.com/fit2cloud-feizhiyun/MeterSphere.git
synced 2024-12-01 19:49:10 +08:00
feat(接口管理): 接口定义-详情-变更历史&引用关系列表&部分 bug 解决
This commit is contained in:
parent
8065a5ac90
commit
ad2759e459
26
frontend/public/images/noResponse.svg
Normal file
26
frontend/public/images/noResponse.svg
Normal file
@ -0,0 +1,26 @@
|
||||
<svg width="78" height="60" viewBox="0 0 78 60" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M4 0.5H74C75.933 0.5 77.5 2.067 77.5 4V56C77.5 57.933 75.933 59.5 74 59.5H4C2.067 59.5 0.5 57.933 0.5 56V4C0.5 2.067 2.067 0.5 4 0.5Z" fill="#F9F9FE" stroke="white"/>
|
||||
<path d="M1 4C1 2.34315 2.34315 1 4 1H74C75.6569 1 77 2.34315 77 4V7H1V4Z" fill="#EDEDF1"/>
|
||||
<ellipse cx="9.2623" cy="3.41245" rx="1.4625" ry="1.4625" fill="white"/>
|
||||
<ellipse cx="14.1373" cy="3.41245" rx="1.4625" ry="1.4625" fill="white"/>
|
||||
<circle cx="4.3873" cy="3.41245" r="1.4625" fill="white"/>
|
||||
<rect x="2.67129" y="8.67141" width="72.6572" height="48.6571" rx="1.9" fill="white" stroke="#EDEDF1" stroke-width="0.2"/>
|
||||
<rect x="5" y="10" width="50" height="4" rx="0.5" fill="#EBF1FF"/>
|
||||
<rect x="65.8877" y="10" width="8" height="4" rx="0.5" fill="#EDEDF1"/>
|
||||
<path d="M5 15.5C5 15.2239 5.22386 15 5.5 15H72.5C72.7761 15 73 15.2239 73 15.5V54.5C73 54.7761 72.7761 55 72.5 55H5.5C5.22386 55 5 54.7761 5 54.5V15.5Z" fill="#F9F9FE"/>
|
||||
<rect x="6" y="17" width="31" height="36" rx="1" fill="white"/>
|
||||
<rect x="37" y="17" width="35" height="36" rx="1" fill="white"/>
|
||||
<rect x="7.80078" y="19.9126" width="2.925" height="1.4625" rx="0.5" fill="#AEAEB2"/>
|
||||
<rect x="39" y="20" width="32" height="1" rx="0.5" fill="#E5F9EF"/>
|
||||
<rect x="7.80078" y="39.8572" width="2.925" height="1.4625" rx="0.5" fill="#AEAEB2"/>
|
||||
<rect x="7.80078" y="29.6624" width="6.825" height="1.4625" rx="0.5" fill="#D4D4D8"/>
|
||||
<rect x="31.6875" y="29.6625" width="3.4125" height="1.4625" rx="0.5" fill="#D4D4D8"/>
|
||||
<rect x="7.80078" y="22.8374" width="27.3" height="1.4625" rx="0.5" fill="#EDEDF1"/>
|
||||
<rect x="39" y="24" width="32" height="23" rx="0.5" fill="#F9F9FE"/>
|
||||
<rect x="7.80078" y="42.7822" width="27.3" height="1.4625" rx="0.5" fill="#EDEDF1"/>
|
||||
<rect x="7.80078" y="32.5874" width="27.3" height="1.4625" rx="0.5" fill="#EDEDF1"/>
|
||||
<rect x="7.80078" y="25.7625" width="27.3" height="1.4625" rx="0.5" fill="#EDEDF1"/>
|
||||
<rect x="7.80078" y="45.7073" width="27.3" height="1.4625" rx="0.5" fill="#EDEDF1"/>
|
||||
<rect x="57" y="10" width="8" height="4" rx="0.5" fill="#A762BF"/>
|
||||
<path d="M59.232 11.98L59.39 11.424L59.532 11.448L59.418 11.842H59.768C59.808 11.686 59.84 11.524 59.864 11.352L60.01 11.37C59.984 11.538 59.952 11.694 59.916 11.842H60.852V11.98H59.878C59.854 12.06 59.83 12.136 59.802 12.208H60.62V12.332C60.566 12.504 60.458 12.66 60.294 12.8C60.456 12.896 60.654 12.984 60.886 13.064L60.812 13.198C60.556 13.104 60.344 13.002 60.176 12.892C60.012 13.008 59.808 13.112 59.566 13.206L59.49 13.072C59.714 12.99 59.9 12.9 60.05 12.802C59.894 12.682 59.784 12.552 59.718 12.412C59.572 12.728 59.384 12.978 59.156 13.164L59.082 13.032C59.374 12.776 59.588 12.426 59.728 11.98H59.232ZM59.826 12.34C59.894 12.476 60.008 12.602 60.168 12.718C60.316 12.602 60.418 12.476 60.474 12.34H59.826ZM60.346 11.368C60.49 11.478 60.608 11.588 60.704 11.698L60.602 11.8C60.518 11.694 60.4 11.582 60.25 11.462L60.346 11.368ZM61.252 11.398C61.39 11.502 61.506 11.604 61.6 11.708L61.496 11.81C61.414 11.712 61.3 11.606 61.154 11.492L61.252 11.398ZM62.422 13.164C62.312 13.164 62.192 13.162 62.064 13.16C61.934 13.158 61.828 13.146 61.748 13.124C61.668 13.1 61.596 13.05 61.534 12.978C61.506 12.944 61.48 12.928 61.456 12.928C61.408 12.928 61.328 13.018 61.218 13.202L61.11 13.106C61.216 12.938 61.31 12.836 61.392 12.8V12.192H61.108V12.06H61.526V12.808C61.542 12.82 61.558 12.834 61.576 12.854C61.624 12.912 61.676 12.954 61.732 12.98C61.796 13.008 61.886 13.024 62.006 13.028C62.112 13.03 62.244 13.032 62.402 13.032C62.494 13.032 62.59 13.03 62.688 13.028C62.784 13.026 62.858 13.024 62.91 13.02L62.876 13.164H62.422ZM61.712 12.154H62.196C62.208 12.068 62.216 11.97 62.22 11.858H61.78V11.726H62.384C62.446 11.61 62.5 11.486 62.544 11.354L62.68 11.402C62.636 11.524 62.586 11.632 62.53 11.726H62.79V11.858H62.362C62.358 11.97 62.352 12.07 62.34 12.154H62.858V12.29H62.314C62.258 12.532 62.082 12.732 61.786 12.89L61.694 12.774C61.946 12.65 62.104 12.49 62.164 12.29H61.712V12.154ZM61.986 11.37C62.052 11.458 62.114 11.556 62.174 11.662L62.052 11.722C61.992 11.614 61.926 11.516 61.856 11.432L61.986 11.37ZM62.406 12.408C62.572 12.524 62.722 12.65 62.856 12.786L62.756 12.894C62.616 12.746 62.472 12.616 62.32 12.504L62.406 12.408Z" fill="white"/>
|
||||
</svg>
|
After Width: | Height: | Size: 4.2 KiB |
@ -22,6 +22,9 @@ import {
|
||||
GetModuleTreeUrl,
|
||||
ImportDefinitionUrl,
|
||||
MoveModuleUrl,
|
||||
OperationHistoryUrl,
|
||||
RecoverOperationHistoryUrl,
|
||||
SaveOperationHistoryUrl,
|
||||
SortDefinitionUrl,
|
||||
SwitchDefinitionScheduleUrl,
|
||||
ToggleFollowDefinitionUrl,
|
||||
@ -50,9 +53,12 @@ import {
|
||||
ApiDefinitionUpdateParams,
|
||||
CheckScheduleParams,
|
||||
CreateImportApiDefinitionScheduleParams,
|
||||
DefinitionHistoryItem,
|
||||
DefinitionHistoryPageParams,
|
||||
EnvModule,
|
||||
ImportApiDefinitionParams,
|
||||
mockParams,
|
||||
RecoverDefinitionParams,
|
||||
UpdateScheduleParams,
|
||||
} from '@/models/apiTest/management';
|
||||
import {
|
||||
@ -209,6 +215,21 @@ export function toggleFollowDefinition(id: string | number) {
|
||||
return MSR.get({ url: ToggleFollowDefinitionUrl, params: id });
|
||||
}
|
||||
|
||||
// 接口定义-变更历史
|
||||
export function operationHistory(data: DefinitionHistoryPageParams) {
|
||||
return MSR.post<CommonList<DefinitionHistoryItem>>({ url: OperationHistoryUrl, data });
|
||||
}
|
||||
|
||||
// 接口定义-保存变更历史为指定版本
|
||||
export function saveOperationHistory(data: ExecuteRequestParams) {
|
||||
return MSR.post({ url: SaveOperationHistoryUrl, data });
|
||||
}
|
||||
|
||||
// 接口定义-恢复至指定变更历史
|
||||
export function recoverOperationHistory(data: RecoverDefinitionParams) {
|
||||
return MSR.post({ url: RecoverOperationHistoryUrl, data });
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock
|
||||
*/
|
||||
|
@ -31,6 +31,10 @@ 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'; // 接口定义-关注/取消关注
|
||||
export const OperationHistoryUrl = '/api/definition/operation-history'; // 接口定义-变更历史
|
||||
export const SaveOperationHistoryUrl = '/api/definition/operation-history/save'; // 接口定义-另存变更历史为指定版本
|
||||
export const RecoverOperationHistoryUrl = '/api/definition/operation-history/recover'; // 接口定义-变更历史恢复
|
||||
|
||||
/**
|
||||
* Mock
|
||||
*/
|
||||
|
@ -97,9 +97,9 @@
|
||||
</div>
|
||||
<div class="file-list">
|
||||
<div v-for="file of alreadyDeleteFiles" :key="file.value" class="file-list-item">
|
||||
<a-tooltip :content="file.name" :mouse-enter-delay="300">
|
||||
<a-tooltip :content="file.label" :mouse-enter-delay="300">
|
||||
<MsTag size="small" max-width="100%">
|
||||
{{ file.name }}
|
||||
{{ file.label }}
|
||||
</MsTag>
|
||||
</a-tooltip>
|
||||
<a-tooltip :content="t('ms.add.attachment.remove')">
|
||||
@ -116,9 +116,9 @@
|
||||
</div>
|
||||
<div class="file-list">
|
||||
<div v-for="file of otherFiles" :key="file.value" class="file-list-item">
|
||||
<a-tooltip :content="file.name" :mouse-enter-delay="300">
|
||||
<a-tooltip :content="file.label" :mouse-enter-delay="300">
|
||||
<MsTag size="small" max-width="100%">
|
||||
{{ file.name }}
|
||||
{{ file.label }}
|
||||
</MsTag>
|
||||
</a-tooltip>
|
||||
<div v-if="file.local === true" class="flex items-center">
|
||||
@ -175,7 +175,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { Message, TagData } from '@arco-design/web-vue';
|
||||
import { TagData } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
@ -199,7 +199,6 @@
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
mode?: 'button' | 'input';
|
||||
fileList: MsFileItem[]; // TODO:这里的文件含有组件内部定义的属性,应该继承MsFileItem类型并扩展声明组件定义的类型属性
|
||||
multiple?: boolean;
|
||||
inputClass?: string;
|
||||
inputSize?: 'small' | 'medium' | 'large' | 'mini';
|
||||
@ -222,7 +221,6 @@
|
||||
}
|
||||
);
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:fileList', fileList: MsFileItem[]): void;
|
||||
(e: 'upload', file: File): void;
|
||||
(e: 'change', _fileList: MsFileItem[], fileItem?: MsFileItem): void;
|
||||
(e: 'linkFile'): void;
|
||||
@ -232,6 +230,7 @@
|
||||
const { t } = useI18n();
|
||||
|
||||
const innerFileList = defineModel<MsFileItem[]>('fileList', {
|
||||
// TODO:这里的文件含有组件内部定义的属性,应该继承MsFileItem类型并扩展声明组件定义的类型属性
|
||||
required: true,
|
||||
});
|
||||
const inputFileName = ref('');
|
||||
@ -244,9 +243,12 @@
|
||||
});
|
||||
const buttonDropDownVisible = ref(false);
|
||||
|
||||
watchEffect(() => {
|
||||
console.log('innerFileList', innerFileList.value);
|
||||
});
|
||||
onBeforeMount(() => {
|
||||
// 回显文件
|
||||
const defaultFiles = props.fileList.filter((item) => item) || [];
|
||||
const defaultFiles = innerFileList.value.filter((item) => item) || [];
|
||||
if (defaultFiles.length > 0) {
|
||||
if (props.multiple) {
|
||||
inputFiles.value = defaultFiles.map((item) => ({
|
||||
@ -298,7 +300,7 @@
|
||||
watch(
|
||||
() => innerFileList.value,
|
||||
(arr) => {
|
||||
getListFunParams.value.combine.hiddenIds = innerFileList.value
|
||||
getListFunParams.value.combine.hiddenIds = arr
|
||||
.filter((item) => !item.local)
|
||||
.map((item) => item[props.fields.id] || item.uid);
|
||||
},
|
||||
|
@ -148,8 +148,8 @@
|
||||
path: route.path,
|
||||
query: {
|
||||
...route.query,
|
||||
organizationId: appStore.currentOrgId,
|
||||
projectId: appStore.currentProjectId,
|
||||
orgId: appStore.currentOrgId,
|
||||
pId: appStore.currentProjectId,
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
|
@ -295,8 +295,8 @@
|
||||
// 点击名称跳转
|
||||
function handleNameClick(item: MessageHistoryItem) {
|
||||
const routeQuery: Record<string, any> = {
|
||||
organizationId: item.organizationId,
|
||||
projectId: item.projectId,
|
||||
orgId: item.organizationId,
|
||||
pId: item.projectId,
|
||||
id: item.resourceId,
|
||||
};
|
||||
if (item.organizationId === 'SYSTEM') {
|
||||
@ -340,16 +340,15 @@
|
||||
.right-align {
|
||||
float: right;
|
||||
}
|
||||
|
||||
:deep(.arco-list) {
|
||||
display: flex;
|
||||
overflow-y: auto;
|
||||
width: 100%;
|
||||
font-size: 14px;
|
||||
border-radius: var(--border-radius-medium);
|
||||
color: var(--color-text-1);
|
||||
flex-direction: column;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
color: var(--color-text-1);
|
||||
font-size: 14px;
|
||||
line-height: 1.8715;
|
||||
border-radius: var(--border-radius-medium);
|
||||
}
|
||||
</style>
|
||||
|
@ -301,7 +301,6 @@
|
||||
dropNode: MsTreeNodeData; // 放入的节点
|
||||
dropPosition: number; // 放入的位置,-1 为放入节点前,1 为放入节点后,0 为放入节点内
|
||||
}) {
|
||||
console.log('dropNode', dropNode);
|
||||
loop(originalTreeData.value, dragNode.key, (item, index, arr) => {
|
||||
arr.splice(index, 1);
|
||||
});
|
||||
|
@ -54,6 +54,10 @@
|
||||
color: rgb(var(--danger-2)) !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.ms-button--text--disabled {
|
||||
color: rgb(var(--primary-3)) !important;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.ms-button-text {
|
||||
@apply p-0;
|
||||
|
||||
|
@ -157,13 +157,10 @@
|
||||
import { LOCALE_OPTIONS } from '@/locale';
|
||||
import useLocale from '@/locale/useLocale';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useLicenseStore from '@/store/modules/setting/license';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
|
||||
import { IconInfoCircle, IconQuestionCircle } from '@arco-design/web-vue/es/icon';
|
||||
|
||||
const licenseStore = useLicenseStore();
|
||||
|
||||
const props = defineProps<{
|
||||
isPreview?: boolean;
|
||||
logo?: string;
|
||||
@ -212,8 +209,8 @@
|
||||
path: route.path,
|
||||
query: {
|
||||
...route.query,
|
||||
organizationId: appStore.currentOrgId,
|
||||
projectId: appStore.currentProjectId,
|
||||
orgId: appStore.currentOrgId,
|
||||
pId: appStore.currentProjectId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -5,3 +5,83 @@ export const dropPositionMap: Record<string, any> = {
|
||||
'0': 'APPEND',
|
||||
'1': 'AFTER',
|
||||
};
|
||||
|
||||
// 操作类型
|
||||
export const operationTypeOptions = [
|
||||
{
|
||||
label: 'system.log.operateType.all',
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.add',
|
||||
value: 'ADD',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.delete',
|
||||
value: 'DELETE',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.update',
|
||||
value: 'UPDATE',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.debug',
|
||||
value: 'DEBUG',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.review',
|
||||
value: 'REVIEW',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.copy',
|
||||
value: 'COPY',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.execute',
|
||||
value: 'EXECUTE',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.share',
|
||||
value: 'SHARE',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.restore',
|
||||
value: 'RESTORE',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.import',
|
||||
value: 'IMPORT',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.export',
|
||||
value: 'EXPORT',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.login',
|
||||
value: 'LOGIN',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.select',
|
||||
value: 'SELECT',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.recover',
|
||||
value: 'RECOVER',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.logout',
|
||||
value: 'LOGOUT',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.associate',
|
||||
value: 'ASSOCIATE',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.disassociate',
|
||||
value: 'DISASSOCIATE',
|
||||
},
|
||||
{
|
||||
label: 'system.log.operateType.archived',
|
||||
value: 'ARCHIVED',
|
||||
},
|
||||
];
|
||||
|
@ -130,4 +130,5 @@ export default {
|
||||
'common.followSuccess': 'Followed',
|
||||
'common.unFollowSuccess': 'Unfollow successfully',
|
||||
'common.share': 'Share',
|
||||
'common.notRemind': `Don't remind again`,
|
||||
};
|
||||
|
@ -133,4 +133,5 @@ export default {
|
||||
'common.followSuccess': '关注成功',
|
||||
'common.unFollowSuccess': '取消关注成功',
|
||||
'common.share': '分享',
|
||||
'common.notRemind': '不再提醒',
|
||||
};
|
||||
|
@ -230,3 +230,30 @@ export interface CreateImportApiDefinitionScheduleParams extends ImportApiDefini
|
||||
value: string; // cron 表达式
|
||||
config?: string;
|
||||
}
|
||||
// 定义-变更历史列表项
|
||||
export interface DefinitionHistoryItem {
|
||||
id: number;
|
||||
projectId: string;
|
||||
createTime: number;
|
||||
createUser: string;
|
||||
sourceId: string;
|
||||
type: string;
|
||||
module: string;
|
||||
refId: number;
|
||||
createUserName: string;
|
||||
versionName: string;
|
||||
}
|
||||
// 变更历史列表查询参数
|
||||
export interface DefinitionHistoryPageParams extends TableQueryParams {
|
||||
projectId: string;
|
||||
sourceId: string;
|
||||
createUser: string;
|
||||
types: string[];
|
||||
modules: string[];
|
||||
}
|
||||
// 定义-恢复历史版本参数
|
||||
export interface RecoverDefinitionParams {
|
||||
id: string | number;
|
||||
sourceId: string | number;
|
||||
versionId?: string;
|
||||
}
|
||||
|
@ -138,12 +138,12 @@ const useUserStore = defineStore('user', {
|
||||
const appStore = useAppStore();
|
||||
setToken(res.sessionId, res.csrfToken);
|
||||
this.setInfo(res);
|
||||
const { organizationId, projectId } = getHashParameters();
|
||||
const { orgId, pId } = getHashParameters();
|
||||
// 如果访问页面的时候携带了组织 ID和项目 ID,则不设置
|
||||
if (!organizationId || forceSet) {
|
||||
if (!orgId || forceSet) {
|
||||
appStore.setCurrentOrgId(res.lastOrganizationId || '');
|
||||
}
|
||||
if (!projectId || forceSet) {
|
||||
if (!pId || forceSet) {
|
||||
appStore.setCurrentProjectId(res.lastProjectId || '');
|
||||
}
|
||||
return true;
|
||||
|
@ -174,6 +174,7 @@
|
||||
input-size="small"
|
||||
tag-size="small"
|
||||
@change="(files, file) => handleFileChange(files, record, rowIndex, file)"
|
||||
@delete-file="() => emitChange('deleteFile')"
|
||||
/>
|
||||
<MsParamsInput
|
||||
v-else
|
||||
|
@ -322,7 +322,7 @@
|
||||
...files,
|
||||
...resultArr,
|
||||
currentTableParams.value[currentTableParams.value.length - 1],
|
||||
];
|
||||
].filter(Boolean);
|
||||
}
|
||||
emit('change');
|
||||
}
|
||||
|
@ -250,6 +250,9 @@
|
||||
v-model:active-layout="activeLayout"
|
||||
v-model:active-tab="requestVModel.responseActiveTab"
|
||||
v-model:response-definition="requestVModel.responseDefinition"
|
||||
:is-http-protocol="isHttpProtocol"
|
||||
:is-priority-local-exec="isPriorityLocalExec"
|
||||
:request-url="requestVModel.url"
|
||||
:is-expanded="isExpanded"
|
||||
:hide-layout-switch="props.hideResponseLayoutSwitch"
|
||||
:request-task-result="requestVModel.response"
|
||||
@ -259,6 +262,7 @@
|
||||
@change-expand="changeExpand"
|
||||
@change-layout="handleActiveLayoutChange"
|
||||
@change="handleActiveDebugChange"
|
||||
@execute="execute"
|
||||
/>
|
||||
</template>
|
||||
</MsSplitBox>
|
||||
@ -1049,6 +1053,7 @@
|
||||
...props.otherParams,
|
||||
});
|
||||
requestVModel.value.id = res.id;
|
||||
requestVModel.value.num = res.num;
|
||||
requestVModel.value.isNew = false;
|
||||
Message.success(t('common.saveSuccess'));
|
||||
requestVModel.value.unSaved = false;
|
||||
|
@ -127,6 +127,10 @@
|
||||
v-model:active-tab="innerActiveTab"
|
||||
:request-result="props.requestTaskResult?.requestResults[0]"
|
||||
:console="props.requestTaskResult?.console"
|
||||
:is-http-protocol="props.isHttpProtocol"
|
||||
:is-priority-local-exec="props.isPriorityLocalExec"
|
||||
:request-url="props.requestUrl"
|
||||
@execute="emit('execute', props.isPriorityLocalExec ? 'localExec' : 'serverExec')"
|
||||
/>
|
||||
</a-spin>
|
||||
</div>
|
||||
@ -147,8 +151,11 @@
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
activeTab: ResponseComposition;
|
||||
activeLayout?: Direction;
|
||||
isExpanded: boolean;
|
||||
isPriorityLocalExec: boolean;
|
||||
requestUrl: string;
|
||||
isHttpProtocol: boolean;
|
||||
activeLayout?: Direction;
|
||||
responseDefinition?: ResponseItem[];
|
||||
requestTaskResult?: RequestTaskResult;
|
||||
hideLayoutSwitch?: boolean; // 隐藏布局切换
|
||||
@ -165,6 +172,7 @@
|
||||
(e: 'changeExpand', value: boolean): void;
|
||||
(e: 'changeLayout', value: Direction): void;
|
||||
(e: 'change'): void;
|
||||
(e: 'execute', executeType: 'localExec' | 'serverExec'): void;
|
||||
}>();
|
||||
|
||||
const { t } = useI18n();
|
||||
|
@ -1,67 +1,90 @@
|
||||
<template>
|
||||
<a-tabs v-model:active-key="activeTab" class="no-content border-b border-[var(--color-text-n8)]">
|
||||
<a-tab-pane v-for="item of responseCompositionTabList" :key="item.value" :title="item.label" />
|
||||
</a-tabs>
|
||||
<div class="response-container">
|
||||
<MsCodeEditor
|
||||
v-if="activeTab === ResponseComposition.BODY"
|
||||
ref="responseEditorRef"
|
||||
:model-value="props.requestResult?.responseResult.body"
|
||||
:language="responseLanguage"
|
||||
theme="vs"
|
||||
height="100%"
|
||||
:languages="[LanguageEnum.JSON, LanguageEnum.HTML, LanguageEnum.XML, LanguageEnum.PLAINTEXT]"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
show-language-change
|
||||
show-charset-change
|
||||
read-only
|
||||
>
|
||||
<template #rightTitle>
|
||||
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini" @click="copyScript">
|
||||
<template #icon>
|
||||
<MsIcon type="icon-icon_copy_outlined" class="text-var(--color-text-4)" size="12" />
|
||||
</template>
|
||||
</a-button>
|
||||
</template>
|
||||
</MsCodeEditor>
|
||||
<MsCodeEditor
|
||||
v-else-if="activeTab === ResponseComposition.CONSOLE"
|
||||
:model-value="props.console?.trim()"
|
||||
:language="LanguageEnum.PLAINTEXT"
|
||||
theme="MS-text"
|
||||
height="100%"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
:show-language-change="false"
|
||||
:show-charset-change="false"
|
||||
read-only
|
||||
>
|
||||
</MsCodeEditor>
|
||||
<div
|
||||
v-else-if="
|
||||
activeTab === ResponseComposition.HEADER ||
|
||||
activeTab === ResponseComposition.REAL_REQUEST ||
|
||||
activeTab === ResponseComposition.EXTRACT
|
||||
"
|
||||
class="h-full rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[12px]"
|
||||
>
|
||||
<pre class="response-header-pre">{{ getResponsePreContent(activeTab) }}</pre>
|
||||
<div v-show="props.requestResult?.responseResult.responseCode" class="h-full">
|
||||
<a-tabs v-model:active-key="activeTab" class="no-content border-b border-[var(--color-text-n8)]">
|
||||
<a-tab-pane v-for="item of responseCompositionTabList" :key="item.value" :title="item.label" />
|
||||
</a-tabs>
|
||||
<div class="response-container">
|
||||
<MsCodeEditor
|
||||
v-if="activeTab === ResponseComposition.BODY"
|
||||
ref="responseEditorRef"
|
||||
:model-value="props.requestResult?.responseResult.body"
|
||||
:language="responseLanguage"
|
||||
theme="vs"
|
||||
height="100%"
|
||||
:languages="[LanguageEnum.JSON, LanguageEnum.HTML, LanguageEnum.XML, LanguageEnum.PLAINTEXT]"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
show-language-change
|
||||
show-charset-change
|
||||
read-only
|
||||
>
|
||||
<template #rightTitle>
|
||||
<a-button type="outline" class="arco-btn-outline--secondary p-[0_8px]" size="mini" @click="copyScript">
|
||||
<template #icon>
|
||||
<MsIcon type="icon-icon_copy_outlined" class="text-var(--color-text-4)" size="12" />
|
||||
</template>
|
||||
</a-button>
|
||||
</template>
|
||||
</MsCodeEditor>
|
||||
<MsCodeEditor
|
||||
v-else-if="activeTab === ResponseComposition.CONSOLE"
|
||||
:model-value="props.console?.trim()"
|
||||
:language="LanguageEnum.PLAINTEXT"
|
||||
theme="MS-text"
|
||||
height="100%"
|
||||
:show-full-screen="false"
|
||||
:show-theme-change="false"
|
||||
:show-language-change="false"
|
||||
:show-charset-change="false"
|
||||
read-only
|
||||
>
|
||||
</MsCodeEditor>
|
||||
<div
|
||||
v-else-if="
|
||||
activeTab === ResponseComposition.HEADER ||
|
||||
activeTab === ResponseComposition.REAL_REQUEST ||
|
||||
activeTab === ResponseComposition.EXTRACT
|
||||
"
|
||||
class="h-full rounded-[var(--border-radius-small)] bg-[var(--color-text-n9)] p-[12px]"
|
||||
>
|
||||
<pre class="response-header-pre">{{ getResponsePreContent(activeTab) }}</pre>
|
||||
</div>
|
||||
<MsBaseTable v-else-if="activeTab === 'ASSERTION'" v-bind="propsRes" v-on="propsEvent">
|
||||
<template #status="{ record }">
|
||||
<MsTag :type="record.status === 1 ? 'success' : 'danger'" theme="light">
|
||||
{{ record.status === 1 ? t('common.success') : t('common.fail') }}
|
||||
</MsTag>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
</div>
|
||||
<MsBaseTable v-else-if="activeTab === 'ASSERTION'" v-bind="propsRes" v-on="propsEvent">
|
||||
<template #status="{ record }">
|
||||
<MsTag :type="record.status === 1 ? 'success' : 'danger'" theme="light">
|
||||
{{ record.status === 1 ? t('common.success') : t('common.fail') }}
|
||||
</MsTag>
|
||||
</template>
|
||||
</MsBaseTable>
|
||||
</div>
|
||||
<a-empty
|
||||
v-show="!props.requestResult?.responseResult.responseCode"
|
||||
class="flex h-[150px] items-center gap-[16px] p-[16px]"
|
||||
>
|
||||
<template #image>
|
||||
<img :src="noDataSvg" class="!h-[60px] w-[78px]" />
|
||||
</template>
|
||||
<div class="flex items-center gap-[8px]">
|
||||
<div>{{ t('apiTestManagement.click') }}</div>
|
||||
<MsButton
|
||||
class="!mr-0"
|
||||
type="text"
|
||||
:disabled="props.isHttpProtocol && !props.requestUrl"
|
||||
@click="emit('execute')"
|
||||
>
|
||||
{{ props.isPriorityLocalExec ? t('apiTestDebug.localExec') : t('apiTestDebug.serverExec') }}
|
||||
</MsButton>
|
||||
<div>{{ t('apiTestManagement.getResponse') }}</div>
|
||||
</div>
|
||||
</a-empty>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
import { LanguageEnum } from '@/components/pure/ms-code-editor/types';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
@ -78,9 +101,15 @@
|
||||
const props = defineProps<{
|
||||
requestResult?: RequestResult;
|
||||
console?: string;
|
||||
isPriorityLocalExec: boolean;
|
||||
requestUrl: string;
|
||||
isHttpProtocol: boolean;
|
||||
}>();
|
||||
const emit = defineEmits(['execute']);
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const noDataSvg = `${import.meta.env.BASE_URL}images/noResponse.svg`;
|
||||
const responseCompositionTabList = [
|
||||
{
|
||||
label: t('apiTestDebug.responseBody'),
|
||||
|
@ -34,8 +34,9 @@ export function parseRequestBodyFiles(
|
||||
const tempSaveUploadFileIds = new Set<string>(); // 临时存储 body 内已保存的上传文件 id 集合,用于对比 saveUploadFileIds 以判断有哪些文件被删除
|
||||
const tempSaveLinkFileIds = new Set<string>(); // 临时存储 body 内已保存的关联文件 id 集合,用于对比 saveLinkFileIds 以判断有哪些文件被取消关联
|
||||
// 获取上传文件和关联文件
|
||||
for (let i = 0; i < formDataBody.formValues.length; i++) {
|
||||
const item = formDataBody.formValues[i];
|
||||
const formValues = formDataBody?.formValues.filter((e) => e) || [];
|
||||
for (let i = 0; i < formValues.length; i++) {
|
||||
const item = formValues[i];
|
||||
if (item.paramType === RequestParamsType.FILE) {
|
||||
if (item.files) {
|
||||
for (let j = 0; j < item.files.length; j++) {
|
||||
@ -150,15 +151,15 @@ export function filterKeyValParams<T>(params: (T & Record<string, any>)[], defau
|
||||
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,
|
||||
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,
|
||||
headers: filterKeyValParams(e.headers || [], defaultKeyValueParamItem).validParams,
|
||||
})) || [],
|
||||
};
|
||||
}
|
||||
|
@ -77,13 +77,16 @@
|
||||
@more-action-select="handleFolderMoreSelect"
|
||||
@more-actions-close="moreActionsClose"
|
||||
@drop="handleDrop"
|
||||
@select="
|
||||
(keys, node) => {
|
||||
if (node.type === 'API') {
|
||||
emit('clickApiNode', node);
|
||||
}
|
||||
}
|
||||
"
|
||||
>
|
||||
<template #title="nodeData">
|
||||
<div
|
||||
v-if="nodeData.type === 'API'"
|
||||
class="inline-flex w-full cursor-pointer gap-[4px]"
|
||||
@click="emit('clickApiNode', nodeData)"
|
||||
>
|
||||
<div v-if="nodeData.type === 'API'" class="inline-flex w-full cursor-pointer gap-[4px]">
|
||||
<apiMethodName :method="nodeData.attachInfo?.method || nodeData.attachInfo?.protocol" />
|
||||
<div class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
||||
</div>
|
||||
@ -101,7 +104,8 @@
|
||||
:field-config="{ field: renameFolderTitle }"
|
||||
:all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')"
|
||||
:node-type="nodeData.type"
|
||||
:add-module-api="addDebugModule"
|
||||
:update-module-api="updateDebugModule"
|
||||
:update-api-node-api="updateDebug"
|
||||
@close="resetFocusNodeKey"
|
||||
@rename-finish="handleRenameFinish"
|
||||
>
|
||||
@ -113,8 +117,7 @@
|
||||
mode="add"
|
||||
:all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')"
|
||||
:parent-id="nodeData.id"
|
||||
:update-module-api="updateDebugModule"
|
||||
:update-api-node-api="updateDebug"
|
||||
:add-module-api="addDebugModule"
|
||||
@close="resetFocusNodeKey"
|
||||
@add-finish="() => initModules()"
|
||||
>
|
||||
|
@ -293,6 +293,7 @@
|
||||
...res.request,
|
||||
url: res.path,
|
||||
name: res.name, // request里面还有个name但是是null
|
||||
moduleId: res.moduleId, // request里面还有个moduleId但是是null
|
||||
...parseRequestBodyResult,
|
||||
});
|
||||
nextTick(() => {
|
||||
|
@ -425,7 +425,6 @@
|
||||
}
|
||||
return [props.activeModule];
|
||||
});
|
||||
const tableQueryParams = ref<any>();
|
||||
function loadApiList() {
|
||||
const params = {
|
||||
keyword: keyword.value,
|
||||
@ -440,11 +439,6 @@
|
||||
};
|
||||
setLoadListParams(params);
|
||||
loadList();
|
||||
tableQueryParams.value = {
|
||||
...params,
|
||||
current: propsRes.value.msPagination?.current,
|
||||
pageSize: propsRes.value.msPagination?.pageSize,
|
||||
};
|
||||
}
|
||||
|
||||
watch(
|
||||
|
@ -190,7 +190,7 @@
|
||||
</a-tab-pane>
|
||||
<a-tab-pane v-if="!activeApiTab.isNew" key="case" :title="t('apiTestManagement.case')" class="ms-api-tab-pane">
|
||||
</a-tab-pane>
|
||||
<a-tab-pane v-if="!activeApiTab.isNew" key="mock" title="MOCK" class="ms-api-tab-pane"> </a-tab-pane>
|
||||
<!-- <a-tab-pane v-if="!activeApiTab.isNew" key="mock" title="MOCK" class="ms-api-tab-pane"> </a-tab-pane> -->
|
||||
</a-tabs>
|
||||
</div>
|
||||
</div>
|
||||
@ -198,7 +198,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { FormInstance, Message, SelectOptionData } from '@arco-design/web-vue';
|
||||
import { FormInstance, Message } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
// import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
@ -249,7 +249,7 @@
|
||||
const requestComposition = defineAsyncComponent(
|
||||
() => import('@/views/api-test/components/requestComposition/index.vue')
|
||||
);
|
||||
const preview = defineAsyncComponent(() => import('./preview.vue'));
|
||||
const preview = defineAsyncComponent(() => import('./preview/index.vue'));
|
||||
|
||||
const props = defineProps<{
|
||||
activeModule: string;
|
||||
|
@ -1,862 +0,0 @@
|
||||
<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>
|
@ -0,0 +1,728 @@
|
||||
<template>
|
||||
<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>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
|
||||
import MsCodeEditor from '@/components/pure/ms-code-editor/index.vue';
|
||||
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 MsFormTable, { FormTableColumn } from '@/components/pure/ms-form-table/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import { ResponseItem } from '@/views/api-test/components/requestComposition/response/edit.vue';
|
||||
|
||||
import { getPluginScript } from '@/api/modules/api-test/common';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import { PluginConfig, ProtocolItem } from '@/models/apiTest/common';
|
||||
import { RequestBodyFormat, 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;
|
||||
protocols: ProtocolItem[];
|
||||
}>();
|
||||
|
||||
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 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();
|
||||
|
||||
border-radius: 0;
|
||||
:deep(.arco-collapse-item-icon-hover) {
|
||||
@apply !hidden;
|
||||
}
|
||||
:deep(.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);
|
||||
}
|
||||
}
|
||||
.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);
|
||||
}
|
||||
}
|
||||
}
|
||||
.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>
|
@ -0,0 +1,124 @@
|
||||
<template>
|
||||
<div class="history-container">
|
||||
<a-alert v-if="!getIsVisited()" :show-icon="false" class="mb-[16px]" type="warning" closable @close="addVisited">
|
||||
{{ t('apiTestManagement.historyListTip') }}
|
||||
<template #close-element>
|
||||
<span class="text-[14px]">{{ t('common.notRemind') }}</span>
|
||||
</template>
|
||||
</a-alert>
|
||||
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
|
||||
<!-- <template #action="{ record }">
|
||||
<div class="flex items-center">
|
||||
<MsButton type="text" @click="recover(record)">{{ t('apiTestManagement.recover') }}</MsButton>
|
||||
</div>
|
||||
</template> -->
|
||||
</ms-base-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
// import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
|
||||
import { operationHistory } from '@/api/modules/api-test/management';
|
||||
import { operationTypeOptions } from '@/config/common';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useVisit from '@/hooks/useVisit';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
const props = defineProps<{
|
||||
sourceId: string | number;
|
||||
}>();
|
||||
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
const visitedKey = 'messageManagementRobotListTip';
|
||||
const { addVisited, getIsVisited } = useVisit(visitedKey);
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'apiTestManagement.changeOrder',
|
||||
dataIndex: 'id',
|
||||
width: 200,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.type',
|
||||
dataIndex: 'type',
|
||||
slotName: 'type',
|
||||
titleSlotName: 'typeFilter',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'mockManagement.operationUser',
|
||||
dataIndex: 'createUserName',
|
||||
showTooltip: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.updateTime',
|
||||
dataIndex: 'updateTime',
|
||||
sortable: {
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
sorter: true,
|
||||
},
|
||||
width: 180,
|
||||
},
|
||||
// {
|
||||
// title: 'common.operation',
|
||||
// slotName: 'action',
|
||||
// dataIndex: 'operation',
|
||||
// width: 50,
|
||||
// },
|
||||
];
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(
|
||||
operationHistory,
|
||||
{
|
||||
columns,
|
||||
scroll: { x: '100%' },
|
||||
selectable: false,
|
||||
heightUsed: 374,
|
||||
},
|
||||
(item) => ({
|
||||
...item,
|
||||
type: t(operationTypeOptions.find((e) => e.value === item.type)?.label || ''),
|
||||
createTime: dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
updateTime: dayjs(item.updateTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
})
|
||||
);
|
||||
|
||||
function loadHistory() {
|
||||
setLoadListParams({
|
||||
projectId: appStore.currentProjectId,
|
||||
sourceId: props.sourceId,
|
||||
});
|
||||
loadList();
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
loadHistory();
|
||||
});
|
||||
|
||||
// async function recover(record: any) {
|
||||
// try {
|
||||
// await recoverOperationHistory({
|
||||
// id: record.id,
|
||||
// sourceId: props.sourceId,
|
||||
// });
|
||||
// } catch (error) {
|
||||
// // eslint-disable-next-line no-console
|
||||
// console.log(error);
|
||||
// }
|
||||
// }
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.history-container {
|
||||
@apply h-full overflow-y-auto;
|
||||
|
||||
.ms-scroll-bar();
|
||||
}
|
||||
</style>
|
@ -0,0 +1,188 @@
|
||||
<template>
|
||||
<div class="h-full w-full overflow-hidden">
|
||||
<div class="px-[18px] pt-[16px]">
|
||||
<MsDetailCard
|
||||
:title="`【${previewDetail.num}】${previewDetail.name}`"
|
||||
:description="description"
|
||||
:simple-show-count="4"
|
||||
>
|
||||
<template #titleAppend>
|
||||
<apiStatus :status="previewDetail.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="previewDetail.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
||||
:class="`${previewDetail.follow ? 'text-[rgb(var(--warning-6))]' : 'text-[var(--color-text-4)]'}`"
|
||||
:size="14"
|
||||
/>
|
||||
{{ t(previewDetail.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="px-[18px] py-[16px]">
|
||||
<detailTab :detail="previewDetail" :protocols="props.protocols" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="reference" :title="t('apiTestManagement.reference')" class="px-[18px] py-[16px]">
|
||||
<quote :source-id="previewDetail.id" />
|
||||
</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]">
|
||||
<history :source-id="previewDetail.id" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useClipboard } from '@vueuse/core';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { cloneDeep } from 'lodash-es';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MsDetailCard from '@/components/pure/ms-detail-card/index.vue';
|
||||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import detailTab from './detail.vue';
|
||||
import history from './history.vue';
|
||||
import quote from './quote.vue';
|
||||
import apiMethodName from '@/views/api-test/components/apiMethodName.vue';
|
||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||
import { RequestParam } from '@/views/api-test/components/requestComposition/index.vue';
|
||||
|
||||
import { toggleFollowDefinition } from '@/api/modules/api-test/management';
|
||||
import { findNodeByKey } from '@/utils';
|
||||
|
||||
import { ProtocolItem } from '@/models/apiTest/common';
|
||||
import { ModuleTreeNode } from '@/models/common';
|
||||
import { RequestMethods } from '@/enums/apiEnum';
|
||||
|
||||
import { getValidRequestTableParams } from '@/views/api-test/components/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
detail: RequestParam;
|
||||
moduleTree: ModuleTreeNode[];
|
||||
protocols: ProtocolItem[];
|
||||
}>();
|
||||
const emit = defineEmits(['updateFollow']);
|
||||
|
||||
const { copy, isSupported } = useClipboard();
|
||||
const { t } = useI18n();
|
||||
|
||||
const previewDetail = ref<RequestParam>(cloneDeep(props.detail));
|
||||
|
||||
watchEffect(() => {
|
||||
previewDetail.value = cloneDeep(props.detail); // props.detail是嵌套的引用类型,防止不必要的修改来源影响props.detail的数据
|
||||
const tableParam = getValidRequestTableParams(previewDetail.value); // 在编辑props.detail时,参数表格会多出一行默认数据,需要去除
|
||||
previewDetail.value = {
|
||||
...previewDetail.value,
|
||||
body: {
|
||||
...previewDetail.value.body,
|
||||
formDataBody: {
|
||||
formValues: tableParam.formDataBodyTableParams,
|
||||
},
|
||||
wwwFormBody: {
|
||||
formValues: tableParam.wwwFormBodyTableParams,
|
||||
},
|
||||
},
|
||||
headers: tableParam.headers,
|
||||
rest: tableParam.rest,
|
||||
query: tableParam.query,
|
||||
responseDefinition: tableParam.response,
|
||||
};
|
||||
});
|
||||
|
||||
const description = computed(() => [
|
||||
{
|
||||
key: 'type',
|
||||
locale: 'apiTestManagement.apiType',
|
||||
value: previewDetail.value.method,
|
||||
},
|
||||
{
|
||||
key: 'path',
|
||||
locale: 'apiTestManagement.path',
|
||||
value: previewDetail.value.path,
|
||||
},
|
||||
{
|
||||
key: 'tags',
|
||||
locale: 'common.tag',
|
||||
value: previewDetail.value.tags,
|
||||
},
|
||||
{
|
||||
key: 'description',
|
||||
locale: 'common.desc',
|
||||
value: previewDetail.value.description,
|
||||
width: '100%',
|
||||
},
|
||||
{
|
||||
key: 'belongModule',
|
||||
locale: 'apiTestManagement.belongModule',
|
||||
value: findNodeByKey<ModuleTreeNode>(props.moduleTree, previewDetail.value.moduleId, 'id')?.path,
|
||||
},
|
||||
{
|
||||
key: 'creator',
|
||||
locale: 'common.creator',
|
||||
value: previewDetail.value.createUserName,
|
||||
},
|
||||
{
|
||||
key: 'createTime',
|
||||
locale: 'apiTestManagement.createTime',
|
||||
value: dayjs(previewDetail.value.createTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
{
|
||||
key: 'updateTime',
|
||||
locale: 'apiTestManagement.updateTime',
|
||||
value: dayjs(previewDetail.value.updateTime).format('YYYY-MM-DD HH:mm:ss'),
|
||||
},
|
||||
]);
|
||||
|
||||
const followLoading = ref(false);
|
||||
async function toggleFollowReview() {
|
||||
try {
|
||||
followLoading.value = true;
|
||||
await toggleFollowDefinition(previewDetail.value.id);
|
||||
Message.success(previewDetail.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=${previewDetail.value.id}`);
|
||||
Message.success(t('apiTestManagement.shareUrlCopied'));
|
||||
} else {
|
||||
Message.error(t('common.copyNotSupport'));
|
||||
}
|
||||
}
|
||||
|
||||
const activeKey = ref('detail');
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped></style>
|
@ -0,0 +1,136 @@
|
||||
<template>
|
||||
<div class="history-container">
|
||||
<a-input-search
|
||||
v-model:model-value="keyword"
|
||||
:placeholder="t('apiTestManagement.quoteSearchPlaceholder')"
|
||||
allow-clear
|
||||
class="mr-[8px] w-[240px]"
|
||||
@search="loadQuoteList"
|
||||
@press-enter="loadQuoteList"
|
||||
/>
|
||||
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
|
||||
<template #id="{ record }">
|
||||
<MsButton type="text" @click="gotoResource(record)">{{ record.id }}</MsButton>
|
||||
</template>
|
||||
</ms-base-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
// import { useRouter } from 'vue-router';
|
||||
|
||||
import MsButton from '@/components/pure/ms-button/index.vue';
|
||||
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
|
||||
import { MsTableColumn } from '@/components/pure/ms-table/type';
|
||||
import useTable from '@/components/pure/ms-table/useTable';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
const props = defineProps<{
|
||||
sourceId: string | number;
|
||||
}>();
|
||||
|
||||
// const router = useRouter();
|
||||
const appStore = useAppStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const keyword = ref('');
|
||||
const quoteLocaleMap = {
|
||||
COPY: 'common.copy',
|
||||
QUOTE: 'apiTestManagement.quote',
|
||||
};
|
||||
|
||||
const columns: MsTableColumn = [
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id',
|
||||
slotName: 'id',
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.resourceName',
|
||||
dataIndex: 'resourceName',
|
||||
showTooltip: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.resourceType',
|
||||
dataIndex: 'resourceType',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.quoteType',
|
||||
dataIndex: 'quoteType',
|
||||
width: 100,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.belongOrg',
|
||||
dataIndex: 'belongOrg',
|
||||
showTooltip: true,
|
||||
width: 150,
|
||||
},
|
||||
{
|
||||
title: 'apiTestManagement.belongProject',
|
||||
dataIndex: 'belongProject',
|
||||
showTooltip: true,
|
||||
width: 150,
|
||||
},
|
||||
];
|
||||
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(
|
||||
() =>
|
||||
Promise.resolve({
|
||||
list: [
|
||||
{
|
||||
id: '1',
|
||||
resourceName: '资源名称',
|
||||
resourceType: '资源类型',
|
||||
quoteType: '引用类型',
|
||||
belongOrg: '所属组织',
|
||||
belongProject: '所属项目',
|
||||
},
|
||||
],
|
||||
total: 1,
|
||||
}),
|
||||
{
|
||||
columns,
|
||||
scroll: { x: '100%' },
|
||||
selectable: false,
|
||||
heightUsed: 374,
|
||||
},
|
||||
(item) => ({
|
||||
...item,
|
||||
type: t(quoteLocaleMap[item.type] || ''),
|
||||
})
|
||||
);
|
||||
|
||||
function loadQuoteList() {
|
||||
setLoadListParams({
|
||||
projectId: appStore.currentProjectId,
|
||||
sourceId: props.sourceId,
|
||||
keyword: keyword.value,
|
||||
});
|
||||
loadList();
|
||||
}
|
||||
|
||||
function gotoResource(record: any) {
|
||||
// router.push({
|
||||
// name: 'apiTestManagementApiPreview',
|
||||
// query: {
|
||||
// id: record.id,
|
||||
// },
|
||||
// });
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
loadQuoteList();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.history-container {
|
||||
@apply h-full overflow-y-auto;
|
||||
|
||||
.ms-scroll-bar();
|
||||
}
|
||||
</style>
|
@ -10,7 +10,7 @@
|
||||
/>
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="case" title="CASE" class="ms-api-tab-pane"> </a-tab-pane>
|
||||
<a-tab-pane key="mock" title="MOCK" class="ms-api-tab-pane">
|
||||
<!-- <a-tab-pane key="mock" title="MOCK" class="ms-api-tab-pane">
|
||||
<mock-table
|
||||
ref="mockRef"
|
||||
:module-tree="props.moduleTree"
|
||||
@ -18,7 +18,7 @@
|
||||
:offspring-ids="props.offspringIds"
|
||||
:protocol="protocol"
|
||||
/>
|
||||
</a-tab-pane>
|
||||
</a-tab-pane> -->
|
||||
<!-- <a-tab-pane key="doc" title="API Docs" class="ms-api-tab-pane"> </a-tab-pane> -->
|
||||
<template #extra>
|
||||
<div class="flex items-center gap-[8px] pr-[24px]">
|
||||
@ -47,8 +47,8 @@
|
||||
|
||||
import MsSelect from '@/components/business/ms-select';
|
||||
import api from './api/index.vue';
|
||||
import MockTable from '@/views/api-test/management/components/management/mock/mockTable.vue';
|
||||
|
||||
// import MockTable from '@/views/api-test/management/components/management/mock/mockTable.vue';
|
||||
import { getEnvironment, getEnvList } from '@/api/modules/api-test/common';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
|
||||
|
@ -350,15 +350,12 @@
|
||||
:deep(.param-input:not(.arco-input-focus, .arco-select-view-focus)) {
|
||||
&:not(:hover) {
|
||||
border-color: transparent !important;
|
||||
|
||||
.arco-input::placeholder {
|
||||
@apply invisible;
|
||||
}
|
||||
|
||||
.arco-select-view-icon {
|
||||
@apply invisible;
|
||||
}
|
||||
|
||||
.arco-select-view-value {
|
||||
color: var(--color-text-brand);
|
||||
}
|
||||
|
@ -103,11 +103,7 @@
|
||||
@drop="handleDrop"
|
||||
>
|
||||
<template #title="nodeData">
|
||||
<div
|
||||
v-if="nodeData.type === 'API'"
|
||||
class="inline-flex w-full cursor-pointer gap-[4px]"
|
||||
@click="emit('clickApiNode', nodeData)"
|
||||
>
|
||||
<div v-if="nodeData.type === 'API'" class="inline-flex w-full cursor-pointer gap-[4px]">
|
||||
<apiMethodName :method="nodeData.attachInfo?.method || nodeData.attachInfo?.protocol" />
|
||||
<div class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]">{{ nodeData.name }}</div>
|
||||
</div>
|
||||
@ -451,6 +447,8 @@
|
||||
return e;
|
||||
});
|
||||
emit('folderNodeSelect', _selectedKeys, offspringIds);
|
||||
} else if (node.type === 'API') {
|
||||
emit('clickApiNode', node);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -131,4 +131,17 @@ export default {
|
||||
'apiTestManagement.paramsType': 'Param type',
|
||||
'apiTestManagement.required': 'Required',
|
||||
'apiTestManagement.requestData': 'Request data',
|
||||
'apiTestManagement.apiNameRequired': 'Interface name cannot be empty',
|
||||
'apiTestManagement.historyListTip':
|
||||
'View and compare historical changes. According to the rules set by the administrator, the change history data will be automatically deleted.',
|
||||
'apiTestManagement.changeOrder': 'Change serial number',
|
||||
'apiTestManagement.type': 'Type',
|
||||
'apiTestManagement.recover': 'Recover',
|
||||
'apiTestManagement.quote': 'Quote',
|
||||
'apiTestManagement.resourceName': 'Resource name',
|
||||
'apiTestManagement.resourceType': 'Resource type',
|
||||
'apiTestManagement.quoteType': 'Quote type',
|
||||
'apiTestManagement.belongOrg': 'Organization',
|
||||
'apiTestManagement.belongProject': 'Project',
|
||||
'apiTestManagement.quoteSearchPlaceholder': 'Enter ID or name to search',
|
||||
};
|
||||
|
@ -125,4 +125,18 @@ export default {
|
||||
'apiTestManagement.paramsType': '参数类型',
|
||||
'apiTestManagement.required': '必填',
|
||||
'apiTestManagement.requestData': '请求数据',
|
||||
'apiTestManagement.apiNameRequired': '接口名称不能为空',
|
||||
'apiTestManagement.historyListTip': '查看、对比历史修改,根据管理员设置规则,变更历史数据将自动删除',
|
||||
'apiTestManagement.changeOrder': '变更序号',
|
||||
'apiTestManagement.type': '类型',
|
||||
'apiTestManagement.recover': '恢复',
|
||||
'apiTestManagement.quote': '引用',
|
||||
'apiTestManagement.resourceName': '资源名称',
|
||||
'apiTestManagement.resourceType': '资源类型',
|
||||
'apiTestManagement.quoteType': '引用类型',
|
||||
'apiTestManagement.belongOrg': '所属组织',
|
||||
'apiTestManagement.belongProject': '所属项目',
|
||||
'apiTestManagement.quoteSearchPlaceholder': '输入 ID 或名称搜索',
|
||||
'apiTestManagement.click': '点击',
|
||||
'apiTestManagement.getResponse': '获取响应内容',
|
||||
};
|
||||
|
@ -76,8 +76,8 @@
|
||||
name: getFirstRouteNameByPermission(router.getRoutes()),
|
||||
query: {
|
||||
...route.query,
|
||||
organizationId: appStore.currentOrgId,
|
||||
projectId: appStore.currentProjectId,
|
||||
orgId: appStore.currentOrgId,
|
||||
pId: appStore.currentProjectId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -352,7 +352,7 @@
|
||||
|
||||
function shareHandler() {
|
||||
const { origin } = window.location;
|
||||
const url = `${origin}/#${route.path}?id=${detailInfo.value.id}&projectId=${appStore.currentProjectId}&organizationId=${appStore.currentOrgId}`;
|
||||
const url = `${origin}/#${route.path}?id=${detailInfo.value.id}&pId=${appStore.currentProjectId}&orgId=${appStore.currentOrgId}`;
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(url).then(
|
||||
() => {
|
||||
|
@ -86,7 +86,7 @@
|
||||
Message.success(t('caseManagement.featureCase.editSuccess'));
|
||||
router.push({
|
||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE,
|
||||
query: { organizationId: route.query.orgId, projectId: route.query.pId },
|
||||
query: { orgId: route.query.orgId, pId: route.query.pId },
|
||||
});
|
||||
setState(true);
|
||||
// 创建用例
|
||||
@ -120,8 +120,8 @@
|
||||
router.push({
|
||||
name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE,
|
||||
query: {
|
||||
organizationId: route.query.orgId,
|
||||
projectId: route.query.pId,
|
||||
orgId: route.query.orgId,
|
||||
pId: route.query.pId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -414,7 +414,7 @@
|
||||
|
||||
function shareHandler() {
|
||||
const { origin } = window.location;
|
||||
const url = `${origin}/#${route.path}?id=${detailInfo.value.id}&projectId=${appStore.currentProjectId}&organizationId=${appStore.currentOrgId}`;
|
||||
const url = `${origin}/#${route.path}?id=${detailInfo.value.id}&pId=${appStore.currentProjectId}&orgId=${appStore.currentOrgId}`;
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(url).then(
|
||||
() => {
|
||||
|
@ -166,8 +166,8 @@
|
||||
name: redirectHasPermission ? (redirect as string) : currentRouteName,
|
||||
query: {
|
||||
...othersQuery,
|
||||
organizationId: appStore.currentOrgId,
|
||||
projectId: appStore.currentProjectId,
|
||||
orgId: appStore.currentOrgId,
|
||||
pId: appStore.currentProjectId,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
|
@ -552,15 +552,15 @@
|
||||
|
||||
function handleNameClick(record: LogItem) {
|
||||
const routeQuery: Record<string, any> = {
|
||||
organizationId: record.organizationId,
|
||||
projectId: record.projectId,
|
||||
orgId: record.organizationId,
|
||||
pId: record.projectId,
|
||||
id: record.sourceId,
|
||||
};
|
||||
if (record.organizationId === 'SYSTEM') {
|
||||
delete routeQuery.organizationId;
|
||||
delete routeQuery.orgId;
|
||||
}
|
||||
if (record.projectId === 'SYSTEM' || record.projectId === 'ORGANIZATION') {
|
||||
delete routeQuery.projectId;
|
||||
delete routeQuery.pId;
|
||||
}
|
||||
jumpRouteByMapKey(record.module, routeQuery, true);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user