fix(接口测试): 定义和场景的列表操作及详情新增编辑按钮&调整样式
This commit is contained in:
parent
655ea48f14
commit
6bccc40e9c
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="ms-detail-card">
|
<div class="ms-detail-card">
|
||||||
<div class="ms-detail-card-title flex items-center justify-between">
|
<div class="ms-detail-card-title flex items-center justify-between">
|
||||||
<div class="flex items-center gap-[4px]">
|
<div class="flex items-center gap-[8px]">
|
||||||
<a-tooltip :content="t(props.title)">
|
<a-tooltip :content="t(props.title)">
|
||||||
<div class="one-line-text flex-1 font-medium text-[var(--color-text-1)]">
|
<div class="one-line-text flex-1 font-medium text-[var(--color-text-1)]">
|
||||||
{{ t(props.title) }}
|
{{ t(props.title) }}
|
||||||
|
|
|
@ -1592,6 +1592,8 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
|
execute,
|
||||||
|
isPriorityLocalExec,
|
||||||
makeRequestParams,
|
makeRequestParams,
|
||||||
changeVerticalExpand,
|
changeVerticalExpand,
|
||||||
});
|
});
|
||||||
|
|
|
@ -234,7 +234,6 @@
|
||||||
* 响应状态码对应颜色
|
* 响应状态码对应颜色
|
||||||
*/
|
*/
|
||||||
const statusCodeColor = computed(() => {
|
const statusCodeColor = computed(() => {
|
||||||
debugger;
|
|
||||||
if (activeStepDetailCopy.value?.content) {
|
if (activeStepDetailCopy.value?.content) {
|
||||||
const code = Number(activeStepDetailCopy.value?.content?.responseResult.responseCode);
|
const code = Number(activeStepDetailCopy.value?.content?.responseResult.responseCode);
|
||||||
if (code >= 200 && code < 300) {
|
if (code >= 200 && code < 300) {
|
||||||
|
|
|
@ -129,6 +129,15 @@
|
||||||
<apiStatus v-else :status="record.status" size="small" />
|
<apiStatus v-else :status="record.status" size="small" />
|
||||||
</template>
|
</template>
|
||||||
<template #action="{ record }">
|
<template #action="{ record }">
|
||||||
|
<MsButton
|
||||||
|
v-permission="['PROJECT_API_DEFINITION:READ+UPDATE']"
|
||||||
|
type="text"
|
||||||
|
class="!mr-0"
|
||||||
|
@click="editDefinition(record)"
|
||||||
|
>
|
||||||
|
{{ t('common.edit') }}
|
||||||
|
</MsButton>
|
||||||
|
<a-divider v-permission="['PROJECT_API_DEFINITION:READ+UPDATE']" direction="vertical" :margin="8"></a-divider>
|
||||||
<MsButton
|
<MsButton
|
||||||
v-permission="['PROJECT_API_DEFINITION:READ+EXECUTE']"
|
v-permission="['PROJECT_API_DEFINITION:READ+EXECUTE']"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -343,6 +352,7 @@
|
||||||
(e: 'openCopyApiTab', record: ApiDefinitionDetail): void;
|
(e: 'openCopyApiTab', record: ApiDefinitionDetail): void;
|
||||||
(e: 'addApiTab'): void;
|
(e: 'addApiTab'): void;
|
||||||
(e: 'import'): void;
|
(e: 'import'): void;
|
||||||
|
(e: 'openEditApiTab', record: ApiDefinitionDetail, isCopy: boolean, isExecute: boolean, isEdit: boolean): void;
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
|
@ -445,7 +455,7 @@
|
||||||
slotName: 'action',
|
slotName: 'action',
|
||||||
dataIndex: 'operation',
|
dataIndex: 'operation',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: hasOperationPermission.value ? 150 : 50,
|
width: hasOperationPermission.value ? 200 : 50,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
||||||
|
@ -872,6 +882,10 @@
|
||||||
emit('openApiTab', record, true);
|
emit('openApiTab', record, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function editDefinition(record: ApiDefinitionDetail) {
|
||||||
|
emit('openEditApiTab', record, false, false, true);
|
||||||
|
}
|
||||||
|
|
||||||
// 拖拽排序
|
// 拖拽排序
|
||||||
async function handleTableDragSort(params: DragSortParams) {
|
async function handleTableDragSort(params: DragSortParams) {
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
@open-copy-api-tab="openApiTab($event, true)"
|
@open-copy-api-tab="openApiTab($event, true)"
|
||||||
@add-api-tab="addApiTab"
|
@add-api-tab="addApiTab"
|
||||||
@import="emit('import')"
|
@import="emit('import')"
|
||||||
|
@open-edit-api-tab="openApiTab"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="activeApiTab.id !== 'all'" class="flex-1 overflow-hidden">
|
<div v-if="activeApiTab.id !== 'all'" class="flex-1 overflow-hidden">
|
||||||
|
@ -20,6 +21,34 @@
|
||||||
class="ms-api-tab-nav"
|
class="ms-api-tab-nav"
|
||||||
@change="changeDefinitionActiveKey"
|
@change="changeDefinitionActiveKey"
|
||||||
>
|
>
|
||||||
|
<template v-if="activeApiTab.definitionActiveKey === 'preview'" #extra>
|
||||||
|
<div class="flex gap-[12px] pr-[16px]">
|
||||||
|
<a-button
|
||||||
|
v-permission="['PROJECT_API_DEFINITION:READ+EXECUTE']"
|
||||||
|
type="primary"
|
||||||
|
@click="toExecuteDefinition"
|
||||||
|
>
|
||||||
|
{{ t('apiTestManagement.execute') }}
|
||||||
|
</a-button>
|
||||||
|
<a-dropdown-button type="outline" @click="toEditDefinition">
|
||||||
|
{{ t('common.edit') }}
|
||||||
|
<template #icon>
|
||||||
|
<icon-down />
|
||||||
|
</template>
|
||||||
|
<template #content>
|
||||||
|
<a-doption
|
||||||
|
v-permission="['PROJECT_API_DEFINITION:READ+DELETE']"
|
||||||
|
value="delete"
|
||||||
|
class="error-6 text-[rgb(var(--danger-6))]"
|
||||||
|
@click="handleDelete"
|
||||||
|
>
|
||||||
|
<MsIcon type="icon-icon_delete-trash_outlined" class="text-[rgb(var(--danger-6))]" />
|
||||||
|
{{ t('common.delete') }}
|
||||||
|
</a-doption>
|
||||||
|
</template>
|
||||||
|
</a-dropdown-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
<a-tab-pane
|
<a-tab-pane
|
||||||
v-if="!activeApiTab.isNew"
|
v-if="!activeApiTab.isNew"
|
||||||
key="preview"
|
key="preview"
|
||||||
|
@ -36,6 +65,7 @@
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane key="definition" :title="t('apiTestManagement.definition')" class="ms-api-tab-pane">
|
<a-tab-pane key="definition" :title="t('apiTestManagement.definition')" class="ms-api-tab-pane">
|
||||||
<requestComposition
|
<requestComposition
|
||||||
|
ref="requestCompositionRef"
|
||||||
v-model:detail-loading="loading"
|
v-model:detail-loading="loading"
|
||||||
v-model:request="activeApiTab"
|
v-model:request="activeApiTab"
|
||||||
:module-tree="props.moduleTree"
|
:module-tree="props.moduleTree"
|
||||||
|
@ -77,7 +107,6 @@
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { cloneDeep } from 'lodash-es';
|
import { cloneDeep } from 'lodash-es';
|
||||||
|
|
||||||
// import MsButton from '@/components/pure/ms-button/index.vue';
|
|
||||||
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
import { TabItem } from '@/components/pure/ms-editable-tab/types';
|
||||||
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
import type { ActionsItem } from '@/components/pure/ms-table-more-action/types';
|
||||||
import caseTable from '../case/caseTable.vue';
|
import caseTable from '../case/caseTable.vue';
|
||||||
|
@ -88,6 +117,7 @@
|
||||||
import {
|
import {
|
||||||
addDefinition,
|
addDefinition,
|
||||||
debugDefinition,
|
debugDefinition,
|
||||||
|
deleteDefinition,
|
||||||
getDefinitionDetail,
|
getDefinitionDetail,
|
||||||
getTransferOptions,
|
getTransferOptions,
|
||||||
transferFile,
|
transferFile,
|
||||||
|
@ -95,6 +125,7 @@
|
||||||
uploadTempFile,
|
uploadTempFile,
|
||||||
} from '@/api/modules/api-test/management';
|
} from '@/api/modules/api-test/management';
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
import useModal from '@/hooks/useModal';
|
||||||
import useAppStore from '@/store/modules/app';
|
import useAppStore from '@/store/modules/app';
|
||||||
|
|
||||||
import { ProtocolItem } from '@/models/apiTest/common';
|
import { ProtocolItem } from '@/models/apiTest/common';
|
||||||
|
@ -126,6 +157,7 @@
|
||||||
}>();
|
}>();
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
|
(e: 'deleteApi', id: string): void;
|
||||||
(e: 'import'): void;
|
(e: 'import'): void;
|
||||||
}>();
|
}>();
|
||||||
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
|
const refreshModuleTree: (() => Promise<any>) | undefined = inject('refreshModuleTree');
|
||||||
|
@ -134,6 +166,7 @@
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
const { openModal } = useModal();
|
||||||
|
|
||||||
const apiTabs = defineModel<RequestParam[]>('apiTabs', {
|
const apiTabs = defineModel<RequestParam[]>('apiTabs', {
|
||||||
required: true,
|
required: true,
|
||||||
|
@ -254,7 +287,12 @@
|
||||||
);
|
);
|
||||||
|
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
async function openApiTab(apiInfo: ModuleTreeNode | ApiDefinitionDetail | string, isCopy = false, isExecute = false) {
|
async function openApiTab(
|
||||||
|
apiInfo: ModuleTreeNode | ApiDefinitionDetail | string,
|
||||||
|
isCopy = false,
|
||||||
|
isExecute = false,
|
||||||
|
isEdit = false
|
||||||
|
) {
|
||||||
const isLoadedTabIndex = apiTabs.value.findIndex(
|
const isLoadedTabIndex = apiTabs.value.findIndex(
|
||||||
(e) => e.id === (typeof apiInfo === 'string' ? apiInfo : apiInfo.id)
|
(e) => e.id === (typeof apiInfo === 'string' ? apiInfo : apiInfo.id)
|
||||||
);
|
);
|
||||||
|
@ -262,6 +300,7 @@
|
||||||
// 如果点击的请求在tab中已经存在,则直接切换到该tab
|
// 如果点击的请求在tab中已经存在,则直接切换到该tab
|
||||||
activeApiTab.value = {
|
activeApiTab.value = {
|
||||||
...(apiTabs.value[isLoadedTabIndex] as RequestParam),
|
...(apiTabs.value[isLoadedTabIndex] as RequestParam),
|
||||||
|
definitionActiveKey: isCopy || isExecute || isEdit ? 'definition' : 'preview',
|
||||||
isExecute,
|
isExecute,
|
||||||
mode: isExecute ? 'debug' : 'definition',
|
mode: isExecute ? 'debug' : 'definition',
|
||||||
};
|
};
|
||||||
|
@ -289,7 +328,7 @@
|
||||||
id: isCopy ? new Date().getTime() : res.id,
|
id: isCopy ? new Date().getTime() : res.id,
|
||||||
isExecute,
|
isExecute,
|
||||||
mode: isExecute ? 'debug' : 'definition',
|
mode: isExecute ? 'debug' : 'definition',
|
||||||
definitionActiveKey: isCopy || isExecute ? 'definition' : 'preview',
|
definitionActiveKey: isCopy || isExecute || isEdit ? 'definition' : 'preview',
|
||||||
...parseRequestBodyResult,
|
...parseRequestBodyResult,
|
||||||
});
|
});
|
||||||
nextTick(() => {
|
nextTick(() => {
|
||||||
|
@ -320,6 +359,45 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 跳转到接口定义tab
|
||||||
|
function toEditDefinition() {
|
||||||
|
activeApiTab.value.definitionActiveKey = 'definition';
|
||||||
|
activeApiTab.value.mode = 'definition';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 跳转到接口定义tab,且执行
|
||||||
|
const requestCompositionRef = ref<InstanceType<typeof requestComposition>>();
|
||||||
|
function toExecuteDefinition() {
|
||||||
|
activeApiTab.value.definitionActiveKey = 'definition';
|
||||||
|
activeApiTab.value.isExecute = true;
|
||||||
|
activeApiTab.value.mode = 'debug';
|
||||||
|
requestCompositionRef.value?.execute(requestCompositionRef.value?.isPriorityLocalExec ? 'localExec' : 'serverExec');
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDelete() {
|
||||||
|
openModal({
|
||||||
|
type: 'error',
|
||||||
|
title: t('apiTestManagement.deleteApiTipTitle', { name: activeApiTab.value.name }),
|
||||||
|
content: t('apiTestManagement.deleteApiTip'),
|
||||||
|
okText: t('common.confirmDelete'),
|
||||||
|
cancelText: t('common.cancel'),
|
||||||
|
okButtonProps: {
|
||||||
|
status: 'danger',
|
||||||
|
},
|
||||||
|
maskClosable: false,
|
||||||
|
onBeforeOk: async () => {
|
||||||
|
try {
|
||||||
|
await deleteDefinition(activeApiTab.value.id as string);
|
||||||
|
emit('deleteApi', activeApiTab.value.id as string);
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.log(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hideCancel: false,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
openApiTab,
|
openApiTab,
|
||||||
addApiTab,
|
addApiTab,
|
||||||
|
@ -328,9 +406,15 @@
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
.error-6 {
|
||||||
|
color: rgb(var(--danger-6));
|
||||||
|
&:hover {
|
||||||
|
color: rgb(var(--danger-6));
|
||||||
|
}
|
||||||
|
}
|
||||||
:deep(.ms-api-tab-nav) {
|
:deep(.ms-api-tab-nav) {
|
||||||
@apply h-full;
|
@apply h-full;
|
||||||
.arco-tabs-nav-tab {
|
.arco-tabs-nav {
|
||||||
border-bottom: 1px solid var(--color-text-n8);
|
border-bottom: 1px solid var(--color-text-n8);
|
||||||
}
|
}
|
||||||
.arco-tabs-content {
|
.arco-tabs-content {
|
||||||
|
|
|
@ -7,32 +7,17 @@
|
||||||
:simple-show-count="4"
|
:simple-show-count="4"
|
||||||
>
|
>
|
||||||
<template #titleAppend>
|
<template #titleAppend>
|
||||||
<apiStatus :status="previewDetail.status" size="small" />
|
|
||||||
</template>
|
|
||||||
<template #titleRight>
|
|
||||||
<a-button
|
|
||||||
v-permission="['PROJECT_API_DEFINITION:READ+UPDATE']"
|
|
||||||
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
|
<MsIcon
|
||||||
|
v-permission="['PROJECT_API_DEFINITION:READ+UPDATE']"
|
||||||
|
:loading="followLoading"
|
||||||
:type="previewDetail.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
:type="previewDetail.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
||||||
:class="`${previewDetail.follow ? 'text-[rgb(var(--warning-6))]' : 'text-[var(--color-text-4)]'}`"
|
:class="`${previewDetail.follow ? 'text-[rgb(var(--warning-6))]' : 'text-[var(--color-text-4)]'}`"
|
||||||
:size="14"
|
class="cursor-pointer"
|
||||||
|
:size="16"
|
||||||
|
@click="toggleFollowReview"
|
||||||
/>
|
/>
|
||||||
{{ t(previewDetail.follow ? 'common.forked' : 'common.fork') }}
|
<MsIcon type="icon-icon_share1" class="cursor-pointer text-[var(--color-text-4)]" :size="16" @click="share" />
|
||||||
</div>
|
<apiStatus :status="previewDetail.status" size="small" />
|
||||||
</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>
|
||||||
<template #type="{ value }">
|
<template #type="{ value }">
|
||||||
<apiMethodName :method="value as RequestMethods" tag-size="small" is-tag />
|
<apiMethodName :method="value as RequestMethods" tag-size="small" is-tag />
|
||||||
|
@ -171,6 +156,7 @@
|
||||||
followLoading.value = true;
|
followLoading.value = true;
|
||||||
await toggleFollowDefinition(previewDetail.value.id);
|
await toggleFollowDefinition(previewDetail.value.id);
|
||||||
Message.success(previewDetail.value.follow ? t('common.unFollowSuccess') : t('common.followSuccess'));
|
Message.success(previewDetail.value.follow ? t('common.unFollowSuccess') : t('common.followSuccess'));
|
||||||
|
previewDetail.value.follow = !previewDetail.value.follow;
|
||||||
emit('updateFollow');
|
emit('updateFollow');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
|
|
@ -11,39 +11,44 @@
|
||||||
@stop-debug="stopDebug"
|
@stop-debug="stopDebug"
|
||||||
@execute="handleExecute"
|
@execute="handleExecute"
|
||||||
/>
|
/>
|
||||||
<a-dropdown position="br" :hide-on-select="false" @select="handleSelect">
|
<a-dropdown-button v-if="!props.isDrawer" type="outline" @click="editCase">
|
||||||
<a-button v-if="!props.isDrawer" type="outline">{{ t('common.operation') }}</a-button>
|
|
||||||
<template #content>
|
|
||||||
<a-doption v-permission="['PROJECT_API_DEFINITION_CASE:READ+UPDATE']" value="edit">
|
|
||||||
<MsIcon type="icon-icon_edit_outlined" />
|
|
||||||
{{ t('common.edit') }}
|
{{ t('common.edit') }}
|
||||||
</a-doption>
|
<template #icon>
|
||||||
<a-doption value="share">
|
<icon-down />
|
||||||
<MsIcon type="icon-icon_share1" />
|
</template>
|
||||||
{{ t('common.share') }}
|
<template #content>
|
||||||
</a-doption>
|
|
||||||
<a-doption v-permission="['PROJECT_API_DEFINITION_CASE:READ+UPDATE']" value="fork">
|
|
||||||
<MsIcon
|
|
||||||
:type="caseDetail.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
|
||||||
:class="`${caseDetail.follow ? 'text-[rgb(var(--warning-6))]' : ''}`"
|
|
||||||
/>
|
|
||||||
{{ t('common.fork') }}
|
|
||||||
</a-doption>
|
|
||||||
<a-divider v-permission="['PROJECT_API_DEFINITION_CASE:READ+DELETE']" margin="4px" />
|
|
||||||
<a-doption
|
<a-doption
|
||||||
v-permission="['PROJECT_API_DEFINITION_CASE:READ+DELETE']"
|
v-permission="['PROJECT_API_DEFINITION_CASE:READ+DELETE']"
|
||||||
value="delete"
|
value="delete"
|
||||||
class="error-6 text-[rgb(var(--danger-6))]"
|
class="error-6 text-[rgb(var(--danger-6))]"
|
||||||
|
@click="handleDelete"
|
||||||
>
|
>
|
||||||
<MsIcon type="icon-icon_delete-trash_outlined" class="text-[rgb(var(--danger-6))]" />
|
<MsIcon type="icon-icon_delete-trash_outlined" class="text-[rgb(var(--danger-6))]" />
|
||||||
{{ t('common.delete') }}
|
{{ t('common.delete') }}
|
||||||
</a-doption>
|
</a-doption>
|
||||||
</template>
|
</template>
|
||||||
</a-dropdown>
|
</a-dropdown-button>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<a-tab-pane key="detail" :title="t('case.detail')" class="px-[18px] py-[16px]">
|
<a-tab-pane key="detail" :title="t('case.detail')" class="px-[18px] py-[16px]">
|
||||||
<MsDetailCard :title="`【${caseDetail.num}】${caseDetail.name}`" :description="description" class="mb-[8px]">
|
<MsDetailCard :title="`【${caseDetail.num}】${caseDetail.name}`" :description="description" class="mb-[8px]">
|
||||||
|
<template v-if="!props.isDrawer" #titleAppend>
|
||||||
|
<MsIcon
|
||||||
|
v-permission="['PROJECT_API_DEFINITION_CASE:READ+UPDATE']"
|
||||||
|
:loading="followLoading"
|
||||||
|
:type="caseDetail.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
||||||
|
:class="`${caseDetail.follow ? 'text-[rgb(var(--warning-6))]' : 'text-[var(--color-text-4)]'}`"
|
||||||
|
class="cursor-pointer"
|
||||||
|
:size="16"
|
||||||
|
@click="follow"
|
||||||
|
/>
|
||||||
|
<MsIcon
|
||||||
|
type="icon-icon_share1"
|
||||||
|
class="cursor-pointer text-[var(--color-text-4)]"
|
||||||
|
:size="16"
|
||||||
|
@click="share"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
<template #type="{ value }">
|
<template #type="{ value }">
|
||||||
<apiMethodName :method="value as RequestMethods" tag-size="small" is-tag />
|
<apiMethodName :method="value as RequestMethods" tag-size="small" is-tag />
|
||||||
</template>
|
</template>
|
||||||
|
@ -159,6 +164,7 @@
|
||||||
followLoading.value = true;
|
followLoading.value = true;
|
||||||
await toggleFollowCase(caseDetail.value.id);
|
await toggleFollowCase(caseDetail.value.id);
|
||||||
Message.success(caseDetail.value.follow ? t('common.unFollowSuccess') : t('common.followSuccess'));
|
Message.success(caseDetail.value.follow ? t('common.unFollowSuccess') : t('common.followSuccess'));
|
||||||
|
caseDetail.value.follow = !caseDetail.value.follow;
|
||||||
emit('updateFollow');
|
emit('updateFollow');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
|
@ -206,25 +212,6 @@
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleSelect(val: string | number | Record<string, any> | undefined) {
|
|
||||||
switch (val) {
|
|
||||||
case 'edit':
|
|
||||||
editCase();
|
|
||||||
break;
|
|
||||||
case 'share':
|
|
||||||
share();
|
|
||||||
break;
|
|
||||||
case 'fork':
|
|
||||||
follow();
|
|
||||||
break;
|
|
||||||
case 'delete':
|
|
||||||
handleDelete();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const protocols = inject<Ref<ProtocolItem[]>>('protocols');
|
const protocols = inject<Ref<ProtocolItem[]>>('protocols');
|
||||||
|
|
||||||
const currentEnvConfigByInject = inject<Ref<EnvConfig>>('currentEnvConfig');
|
const currentEnvConfigByInject = inject<Ref<EnvConfig>>('currentEnvConfig');
|
||||||
|
|
|
@ -30,7 +30,7 @@
|
||||||
>
|
>
|
||||||
<MsIcon
|
<MsIcon
|
||||||
:type="props.detail.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
:type="props.detail.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
||||||
:class="[props.detail.follow ? 'text-[rgb(var(--warning-6))]' : '']"
|
:class="[props.detail.follow ? '!text-[rgb(var(--warning-6))]' : '']"
|
||||||
/>
|
/>
|
||||||
{{ t('common.fork') }}
|
{{ t('common.fork') }}
|
||||||
</MsButton>
|
</MsButton>
|
||||||
|
|
|
@ -167,6 +167,19 @@
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #operation="{ record }">
|
<template #operation="{ record }">
|
||||||
|
<MsButton
|
||||||
|
v-permission="['PROJECT_API_DEFINITION_CASE:READ+UPDATE']"
|
||||||
|
type="text"
|
||||||
|
class="!mr-0"
|
||||||
|
@click="editCase(record)"
|
||||||
|
>
|
||||||
|
{{ t('common.edit') }}
|
||||||
|
</MsButton>
|
||||||
|
<a-divider
|
||||||
|
v-permission="['PROJECT_API_DEFINITION_CASE:READ+UPDATE']"
|
||||||
|
direction="vertical"
|
||||||
|
:margin="8"
|
||||||
|
></a-divider>
|
||||||
<MsButton
|
<MsButton
|
||||||
v-permission="['PROJECT_API_DEFINITION_CASE:READ+EXECUTE']"
|
v-permission="['PROJECT_API_DEFINITION_CASE:READ+EXECUTE']"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -335,7 +348,6 @@
|
||||||
batchExecuteCase,
|
batchExecuteCase,
|
||||||
deleteCase,
|
deleteCase,
|
||||||
dragSort,
|
dragSort,
|
||||||
executeCase,
|
|
||||||
getCaseDetail,
|
getCaseDetail,
|
||||||
getCasePage,
|
getCasePage,
|
||||||
updateCasePriority,
|
updateCasePriority,
|
||||||
|
@ -516,7 +528,7 @@
|
||||||
slotName: 'operation',
|
slotName: 'operation',
|
||||||
dataIndex: 'operation',
|
dataIndex: 'operation',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: hasOperationPermission.value ? 150 : 50,
|
width: hasOperationPermission.value ? 200 : 50,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(getCasePage, {
|
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(getCasePage, {
|
||||||
|
@ -811,16 +823,6 @@
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
async function onExecute(id: string) {
|
|
||||||
try {
|
|
||||||
await executeCase(id);
|
|
||||||
Message.success(t('case.detail.execute.success'));
|
|
||||||
} catch (error) {
|
|
||||||
// eslint-disable-next-line no-console
|
|
||||||
console.log(error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancelBatchEdit() {
|
function cancelBatchEdit() {
|
||||||
showBatchEditModal.value = false;
|
showBatchEditModal.value = false;
|
||||||
batchFormRef.value?.resetFields();
|
batchFormRef.value?.resetFields();
|
||||||
|
@ -943,6 +945,11 @@
|
||||||
loadCaseList();
|
loadCaseList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function editCase(record: ApiCaseDetail) {
|
||||||
|
await getCaseDetailInfo(record.id);
|
||||||
|
createAndEditCaseDrawerRef.value?.open(record.apiDefinitionId, caseDetail.value as RequestParam);
|
||||||
|
}
|
||||||
|
|
||||||
// 在api下的用例里打开用例详情抽屉,点击编辑,编辑后在此刷新数据
|
// 在api下的用例里打开用例详情抽屉,点击编辑,编辑后在此刷新数据
|
||||||
function loadCase(id: string) {
|
function loadCase(id: string) {
|
||||||
getCaseDetailInfo(id);
|
getCaseDetailInfo(id);
|
||||||
|
|
|
@ -40,6 +40,7 @@
|
||||||
:protocol="props.protocol"
|
:protocol="props.protocol"
|
||||||
:module-tree="props.moduleTree"
|
:module-tree="props.moduleTree"
|
||||||
@import="emit('import')"
|
@import="emit('import')"
|
||||||
|
@delete-api="(id) => handleDeleteApiFromModuleTree(id)"
|
||||||
/>
|
/>
|
||||||
<apiCase
|
<apiCase
|
||||||
v-show="(activeApiTab.id === 'all' && currentTab === 'case') || activeApiTab.type === 'case'"
|
v-show="(activeApiTab.id === 'all' && currentTab === 'case') || activeApiTab.type === 'case'"
|
||||||
|
|
|
@ -1,40 +1,110 @@
|
||||||
<template>
|
<template>
|
||||||
<MsDescription :descriptions="descriptions"> </MsDescription>
|
<a-form ref="createFormRef" :model="scenario" layout="vertical">
|
||||||
|
<a-form-item
|
||||||
|
field="name"
|
||||||
|
:label="t('apiScenario.name')"
|
||||||
|
class="mb-[16px]"
|
||||||
|
:rules="[{ required: true, message: t('apiScenario.nameRequired') }]"
|
||||||
|
>
|
||||||
|
<a-input
|
||||||
|
v-model:model-value="scenario.name"
|
||||||
|
:max-length="255"
|
||||||
|
:placeholder="t('apiScenario.namePlaceholder')"
|
||||||
|
allow-clear
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="t('apiScenario.belongModule')" class="mb-[16px]">
|
||||||
|
<a-tree-select
|
||||||
|
v-model:modelValue="scenario.moduleId"
|
||||||
|
:data="props.moduleTree"
|
||||||
|
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
||||||
|
:tree-props="{
|
||||||
|
virtualListProps: {
|
||||||
|
height: 200,
|
||||||
|
threshold: 200,
|
||||||
|
},
|
||||||
|
}"
|
||||||
|
allow-search
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="t('apiScenario.scenarioLevel')">
|
||||||
|
<a-select v-model:model-value="scenario.priority" :placeholder="t('common.pleaseSelect')">
|
||||||
|
<template #label>
|
||||||
|
<span class="text-[var(--color-text-2)]"> <caseLevel :case-level="scenario.priority" /></span>
|
||||||
|
</template>
|
||||||
|
<a-option v-for="item of casePriorityOptions" :key="item.value" :value="item.value">
|
||||||
|
<caseLevel :case-level="item.label as CaseLevel" />
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="t('apiScenario.status')" class="mb-[16px]">
|
||||||
|
<a-select
|
||||||
|
v-model:model-value="scenario.status"
|
||||||
|
:placeholder="t('common.pleaseSelect')"
|
||||||
|
class="param-input w-full"
|
||||||
|
>
|
||||||
|
<template #label>
|
||||||
|
<apiStatus :status="scenario.status" />
|
||||||
|
</template>
|
||||||
|
<a-option v-for="item of Object.values(ApiScenarioStatus)" :key="item" :value="item">
|
||||||
|
<apiStatus :status="item" />
|
||||||
|
</a-option>
|
||||||
|
</a-select>
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="t('common.tag')" class="mb-[16px]">
|
||||||
|
<MsTagsInput v-model:model-value="scenario.tags" />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item :label="t('common.desc')" class="mb-[16px]">
|
||||||
|
<a-textarea
|
||||||
|
v-model:model-value="scenario.description"
|
||||||
|
:max-length="500"
|
||||||
|
:placeholder="t('apiScenario.descPlaceholder')"
|
||||||
|
/>
|
||||||
|
</a-form-item>
|
||||||
|
<template v-if="props.isEdit">
|
||||||
|
<a-form-item field="createUser" :label="t('apiScenario.table.columns.createUser')" class="mb-[16px]">
|
||||||
|
<a-input :model-value="(scenario as ScenarioDetail).createUser" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="createTime" :label="t('apiScenario.table.columns.createTime')" class="mb-[16px]">
|
||||||
|
<a-input :model-value="dayjs((scenario as ScenarioDetail).createTime).format('YYYY-MM-DD HH:mm:ss')" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
<a-form-item field="updateTime" :label="t('apiScenario.table.columns.updateTime')" class="mb-[16px]">
|
||||||
|
<a-input :model-value="dayjs((scenario as ScenarioDetail).updateTime).format('YYYY-MM-DD HH:mm:ss')" disabled />
|
||||||
|
</a-form-item>
|
||||||
|
</template>
|
||||||
|
</a-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { FormInstance } from '@arco-design/web-vue';
|
||||||
import dayjs from 'dayjs';
|
import dayjs from 'dayjs';
|
||||||
|
|
||||||
import MsDescription, { Description } from '@/components/pure/ms-description/index.vue';
|
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
||||||
|
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
||||||
|
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
||||||
|
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
import { ScenarioDetail } from '@/models/apiTest/scenario';
|
import { Scenario, ScenarioDetail } from '@/models/apiTest/scenario';
|
||||||
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
|
import { ApiScenarioStatus } from '@/enums/apiEnum';
|
||||||
|
|
||||||
|
import { casePriorityOptions } from '@/views/api-test/components/config';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
scenario: ScenarioDetail;
|
moduleTree: ModuleTreeNode[]; // 模块树
|
||||||
|
isEdit?: boolean;
|
||||||
}>();
|
}>();
|
||||||
|
const scenario = defineModel<ScenarioDetail | Scenario>('scenario', {
|
||||||
|
required: true,
|
||||||
|
});
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const descriptions = computed<Description[]>(() => [
|
const createFormRef = ref<FormInstance>();
|
||||||
{
|
|
||||||
label: t('apiScenario.belongModule'),
|
|
||||||
value: props.scenario.modulePath,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('apiScenario.table.columns.createUser'),
|
|
||||||
value: props.scenario.createUser,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('apiScenario.table.columns.createTime'),
|
|
||||||
value: dayjs(props.scenario.createTime).format('YYYY-MM-DD HH:mm:ss'),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t('apiScenario.table.columns.updateTime'),
|
|
||||||
value: dayjs(props.scenario.updateTime).format('YYYY-MM-DD HH:mm:ss'),
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style lang="less" scoped></style>
|
defineExpose({
|
||||||
|
createFormRef,
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
|
@ -127,6 +127,15 @@
|
||||||
/>
|
/>
|
||||||
</template>
|
</template>
|
||||||
<template #operation="{ record }">
|
<template #operation="{ record }">
|
||||||
|
<MsButton
|
||||||
|
v-permission="['PROJECT_API_SCENARIO:READ+UPDATE']"
|
||||||
|
type="text"
|
||||||
|
class="!mr-0"
|
||||||
|
@click="openScenarioTab(record)"
|
||||||
|
>
|
||||||
|
{{ t('common.edit') }}
|
||||||
|
</MsButton>
|
||||||
|
<a-divider v-permission="['PROJECT_API_SCENARIO:READ+UPDATE']" direction="vertical" :margin="8"></a-divider>
|
||||||
<MsButton
|
<MsButton
|
||||||
v-permission="['PROJECT_API_SCENARIO:READ+EXECUTE']"
|
v-permission="['PROJECT_API_SCENARIO:READ+EXECUTE']"
|
||||||
type="text"
|
type="text"
|
||||||
|
@ -522,7 +531,7 @@
|
||||||
slotName: 'operation',
|
slotName: 'operation',
|
||||||
dataIndex: 'operation',
|
dataIndex: 'operation',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
width: 180,
|
width: 200,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
|
||||||
|
|
|
@ -61,62 +61,7 @@
|
||||||
<div class="p-[16px]">
|
<div class="p-[16px]">
|
||||||
<!-- TODO:第一版没有模板 -->
|
<!-- TODO:第一版没有模板 -->
|
||||||
<!-- <MsFormCreate v-model:api="fApi" :rule="currentApiTemplateRules" :option="options" /> -->
|
<!-- <MsFormCreate v-model:api="fApi" :rule="currentApiTemplateRules" :option="options" /> -->
|
||||||
<a-form ref="createFormRef" :model="scenario" layout="vertical">
|
<baseInfo ref="baseInfoRef" :scenario="scenario as Scenario" :module-tree="props.moduleTree" />
|
||||||
<a-form-item
|
|
||||||
field="name"
|
|
||||||
:label="t('apiScenario.name')"
|
|
||||||
class="mb-[16px]"
|
|
||||||
:rules="[{ required: true, message: t('apiScenario.nameRequired') }]"
|
|
||||||
>
|
|
||||||
<a-input
|
|
||||||
v-model:model-value="scenario.name"
|
|
||||||
:max-length="255"
|
|
||||||
:placeholder="t('apiScenario.namePlaceholder')"
|
|
||||||
allow-clear
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item :label="t('apiScenario.belongModule')" class="mb-[16px]">
|
|
||||||
<a-tree-select
|
|
||||||
v-model:modelValue="scenario.moduleId"
|
|
||||||
:data="props.moduleTree"
|
|
||||||
:field-names="{ title: 'name', key: 'id', children: 'children' }"
|
|
||||||
:tree-props="{
|
|
||||||
virtualListProps: {
|
|
||||||
height: 200,
|
|
||||||
threshold: 200,
|
|
||||||
},
|
|
||||||
}"
|
|
||||||
allow-search
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item :label="t('apiScenario.scenarioLevel')">
|
|
||||||
<a-select v-model:model-value="scenario.priority" :placeholder="t('common.pleaseSelect')">
|
|
||||||
<template #label>
|
|
||||||
<span class="text-[var(--color-text-2)]"> <caseLevel :case-level="scenario.priority" /></span>
|
|
||||||
</template>
|
|
||||||
<a-option v-for="item of casePriorityOptions" :key="item.value" :value="item.value">
|
|
||||||
<caseLevel :case-level="item.label as CaseLevel" />
|
|
||||||
</a-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item :label="t('apiScenario.status')" class="mb-[16px]">
|
|
||||||
<a-select
|
|
||||||
v-model:model-value="scenario.status"
|
|
||||||
:placeholder="t('common.pleaseSelect')"
|
|
||||||
class="param-input w-full"
|
|
||||||
>
|
|
||||||
<template #label>
|
|
||||||
<apiStatus :status="scenario.status" />
|
|
||||||
</template>
|
|
||||||
<a-option v-for="item of Object.values(ApiScenarioStatus)" :key="item" :value="item">
|
|
||||||
<apiStatus :status="item" />
|
|
||||||
</a-option>
|
|
||||||
</a-select>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item :label="t('common.tag')" class="mb-[16px]">
|
|
||||||
<MsTagsInput v-model:model-value="scenario.tags" />
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
<!-- TODO:第一版先不做依赖 -->
|
<!-- TODO:第一版先不做依赖 -->
|
||||||
<!-- <div class="mb-[8px] flex items-center">
|
<!-- <div class="mb-[8px] flex items-center">
|
||||||
<div class="text-[var(--color-text-2)]">
|
<div class="text-[var(--color-text-2)]">
|
||||||
|
@ -166,21 +111,14 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { FormInstance } from '@arco-design/web-vue';
|
|
||||||
|
|
||||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||||
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
|
import baseInfo from '../components/baseInfo.vue';
|
||||||
import caseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
|
|
||||||
import type { CaseLevel } from '@/components/business/ms-case-associate/types';
|
|
||||||
import apiStatus from '@/views/api-test/components/apiStatus.vue';
|
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
import { ApiScenarioDebugRequest, Scenario } from '@/models/apiTest/scenario';
|
import { ApiScenarioDebugRequest, Scenario } from '@/models/apiTest/scenario';
|
||||||
import { ModuleTreeNode } from '@/models/common';
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
import { ApiScenarioStatus, ScenarioCreateComposition } from '@/enums/apiEnum';
|
import { ScenarioCreateComposition } from '@/enums/apiEnum';
|
||||||
|
|
||||||
import { casePriorityOptions } from '@/views/api-test/components/config';
|
|
||||||
|
|
||||||
// 组成部分异步导入
|
// 组成部分异步导入
|
||||||
const step = defineAsyncComponent(() => import('../components/step/index.vue'));
|
const step = defineAsyncComponent(() => import('../components/step/index.vue'));
|
||||||
|
@ -204,10 +142,10 @@
|
||||||
});
|
});
|
||||||
|
|
||||||
const splitBoxRef = ref<InstanceType<typeof MsSplitBox>>();
|
const splitBoxRef = ref<InstanceType<typeof MsSplitBox>>();
|
||||||
const createFormRef = ref<FormInstance>();
|
const baseInfoRef = ref<InstanceType<typeof baseInfo>>();
|
||||||
|
|
||||||
function validScenarioForm(cb: () => Promise<void>) {
|
function validScenarioForm(cb: () => Promise<void>) {
|
||||||
createFormRef.value?.validate(async (errors) => {
|
baseInfoRef.value?.createFormRef?.validate(async (errors) => {
|
||||||
if (errors) {
|
if (errors) {
|
||||||
splitBoxRef.value?.expand();
|
splitBoxRef.value?.expand();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -3,31 +3,16 @@
|
||||||
<div class="px-[24px] pt-[16px]">
|
<div class="px-[24px] pt-[16px]">
|
||||||
<MsDetailCard :title="`【${scenario.num}】${scenario.name}`" :description="description" class="!py-[8px]">
|
<MsDetailCard :title="`【${scenario.num}】${scenario.name}`" :description="description" class="!py-[8px]">
|
||||||
<template #titleAppend>
|
<template #titleAppend>
|
||||||
<apiStatus :status="scenario.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
|
<MsIcon
|
||||||
|
:loading="followLoading"
|
||||||
:type="scenario.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
:type="scenario.follow ? 'icon-icon_collect_filled' : 'icon-icon_collection_outlined'"
|
||||||
:class="`${scenario.follow ? 'text-[rgb(var(--warning-6))]' : 'text-[var(--color-text-4)]'}`"
|
:class="`${scenario.follow ? 'text-[rgb(var(--warning-6))]' : 'text-[var(--color-text-4)]'}`"
|
||||||
:size="14"
|
class="cursor-pointer"
|
||||||
|
:size="16"
|
||||||
|
@click="toggleFollowReview"
|
||||||
/>
|
/>
|
||||||
{{ t(scenario.follow ? 'common.forked' : 'common.fork') }}
|
<MsIcon type="icon-icon_share1" class="cursor-pointer text-[var(--color-text-4)]" :size="16" @click="share" />
|
||||||
</div>
|
<apiStatus :status="scenario.status" size="small" />
|
||||||
</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>
|
||||||
<template #priority="{ value }">
|
<template #priority="{ value }">
|
||||||
<caseLevel :case-level="value as CaseLevel" />
|
<caseLevel :case-level="value as CaseLevel" />
|
||||||
|
@ -39,9 +24,15 @@
|
||||||
<a-tab-pane
|
<a-tab-pane
|
||||||
:key="ScenarioDetailComposition.BASE_INFO"
|
:key="ScenarioDetailComposition.BASE_INFO"
|
||||||
:title="t('apiScenario.baseInfo')"
|
:title="t('apiScenario.baseInfo')"
|
||||||
class="scenario-detail-tab-pane"
|
class="scenario-detail-tab-pane base-info-pane"
|
||||||
>
|
>
|
||||||
<baseInfo :scenario="scenario as ScenarioDetail" />
|
<baseInfo
|
||||||
|
ref="baseInfoRef"
|
||||||
|
is-edit
|
||||||
|
:scenario="scenario as ScenarioDetail"
|
||||||
|
:module-tree="props.moduleTree"
|
||||||
|
class="w-[30%]"
|
||||||
|
/>
|
||||||
</a-tab-pane>
|
</a-tab-pane>
|
||||||
<a-tab-pane
|
<a-tab-pane
|
||||||
:key="ScenarioDetailComposition.STEP"
|
:key="ScenarioDetailComposition.STEP"
|
||||||
|
@ -140,6 +131,7 @@
|
||||||
import { followScenario } from '@/api/modules/api-test/scenario';
|
import { followScenario } from '@/api/modules/api-test/scenario';
|
||||||
|
|
||||||
import { ApiScenarioDebugRequest, Scenario, ScenarioDetail } from '@/models/apiTest/scenario';
|
import { ApiScenarioDebugRequest, Scenario, ScenarioDetail } from '@/models/apiTest/scenario';
|
||||||
|
import { ModuleTreeNode } from '@/models/common';
|
||||||
import { ScenarioDetailComposition } from '@/enums/apiEnum';
|
import { ScenarioDetailComposition } from '@/enums/apiEnum';
|
||||||
|
|
||||||
// 组成部分异步导入
|
// 组成部分异步导入
|
||||||
|
@ -152,6 +144,9 @@
|
||||||
// const quote = defineAsyncComponent(() => import('../components/quote.vue'));
|
// const quote = defineAsyncComponent(() => import('../components/quote.vue'));
|
||||||
const setting = defineAsyncComponent(() => import('../components/setting.vue'));
|
const setting = defineAsyncComponent(() => import('../components/setting.vue'));
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
moduleTree: ModuleTreeNode[]; // 模块树
|
||||||
|
}>();
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: 'batchDebug', data: Pick<ApiScenarioDebugRequest, 'steps' | 'stepDetails' | 'reportId'>): void;
|
(e: 'batchDebug', data: Pick<ApiScenarioDebugRequest, 'steps' | 'stepDetails' | 'reportId'>): void;
|
||||||
(e: 'updateFollow'): void;
|
(e: 'updateFollow'): void;
|
||||||
|
@ -208,6 +203,21 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeKey = ref<ScenarioDetailComposition>(ScenarioDetailComposition.STEP);
|
const activeKey = ref<ScenarioDetailComposition>(ScenarioDetailComposition.STEP);
|
||||||
|
|
||||||
|
const baseInfoRef = ref<InstanceType<typeof baseInfo>>();
|
||||||
|
function validScenarioForm(cb: () => Promise<void>) {
|
||||||
|
baseInfoRef.value?.createFormRef?.validate(async (errors) => {
|
||||||
|
if (errors) {
|
||||||
|
activeKey.value = ScenarioDetailComposition.BASE_INFO;
|
||||||
|
} else {
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({
|
||||||
|
validScenarioForm,
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="less" scoped>
|
<style lang="less" scoped>
|
||||||
|
@ -227,5 +237,9 @@
|
||||||
.scenario-detail-tab-pane {
|
.scenario-detail-tab-pane {
|
||||||
padding: 8px 16px;
|
padding: 8px 16px;
|
||||||
}
|
}
|
||||||
|
.base-info-pane {
|
||||||
|
@apply h-full overflow-auto;
|
||||||
|
.ms-scroll-bar();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -17,15 +17,15 @@
|
||||||
</MsEditableTab>
|
</MsEditableTab>
|
||||||
<div v-show="activeScenarioTab.id !== 'all'" class="flex items-center gap-[8px]">
|
<div v-show="activeScenarioTab.id !== 'all'" class="flex items-center gap-[8px]">
|
||||||
<environmentSelect v-model:current-env-config="currentEnvConfig" />
|
<environmentSelect v-model:current-env-config="currentEnvConfig" />
|
||||||
<a-button type="primary" :loading="saveLoading" @click="saveScenario">
|
|
||||||
{{ t('common.save') }}
|
|
||||||
</a-button>
|
|
||||||
<executeButton
|
<executeButton
|
||||||
ref="executeButtonRef"
|
ref="executeButtonRef"
|
||||||
:execute-loading="activeScenarioTab.executeLoading"
|
:execute-loading="activeScenarioTab.executeLoading"
|
||||||
@execute="handleExecute"
|
@execute="handleExecute"
|
||||||
@stop-debug="handleStopExecute"
|
@stop-debug="handleStopExecute"
|
||||||
/>
|
/>
|
||||||
|
<a-button type="primary" :loading="saveLoading" @click="saveScenario">
|
||||||
|
{{ t('common.save') }}
|
||||||
|
</a-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a-divider class="!my-0" />
|
<a-divider class="!my-0" />
|
||||||
|
@ -75,7 +75,12 @@
|
||||||
></create>
|
></create>
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="pageWrap">
|
<div v-else class="pageWrap">
|
||||||
<detail v-model:scenario="activeScenarioTab" @batch-debug="realExecute($event, false)"></detail>
|
<detail
|
||||||
|
ref="detailRef"
|
||||||
|
v-model:scenario="activeScenarioTab"
|
||||||
|
:module-tree="folderTree"
|
||||||
|
@batch-debug="realExecute($event, false)"
|
||||||
|
></detail>
|
||||||
</div>
|
</div>
|
||||||
</MsCard>
|
</MsCard>
|
||||||
</template>
|
</template>
|
||||||
|
@ -440,6 +445,7 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
const createRef = ref<InstanceType<typeof create>>();
|
const createRef = ref<InstanceType<typeof create>>();
|
||||||
|
const detailRef = ref<InstanceType<typeof detail>>();
|
||||||
const saveLoading = ref(false);
|
const saveLoading = ref(false);
|
||||||
|
|
||||||
async function realSaveScenario() {
|
async function realSaveScenario() {
|
||||||
|
@ -500,7 +506,7 @@
|
||||||
if (activeScenarioTab.value.isNew) {
|
if (activeScenarioTab.value.isNew) {
|
||||||
createRef.value?.validScenarioForm(realSaveScenario);
|
createRef.value?.validScenarioForm(realSaveScenario);
|
||||||
} else {
|
} else {
|
||||||
realSaveScenario();
|
detailRef.value?.validScenarioForm(realSaveScenario);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -51,6 +51,7 @@ export default {
|
||||||
// 批量操作文案
|
// 批量操作文案
|
||||||
'api_scenario.batch_operation.success': 'Success {opt} to {name}',
|
'api_scenario.batch_operation.success': 'Success {opt} to {name}',
|
||||||
'api_scenario.table.batchMoveConfirm': 'Ready to {opt} {count} scenarios',
|
'api_scenario.table.batchMoveConfirm': 'Ready to {opt} {count} scenarios',
|
||||||
|
'apiScenario.descPlaceholder': 'Please describe the scenario',
|
||||||
// 执行历史
|
// 执行历史
|
||||||
'apiScenario.executeHistory.searchPlaceholder': 'Search by ID or name',
|
'apiScenario.executeHistory.searchPlaceholder': 'Search by ID or name',
|
||||||
'apiScenario.executeHistory.num': 'Number',
|
'apiScenario.executeHistory.num': 'Number',
|
||||||
|
|
|
@ -69,6 +69,7 @@ export default {
|
||||||
'apiScenario.belongModule': '所属模块',
|
'apiScenario.belongModule': '所属模块',
|
||||||
'apiScenario.level': '场景等级',
|
'apiScenario.level': '场景等级',
|
||||||
'apiScenario.status': '场景状态',
|
'apiScenario.status': '场景状态',
|
||||||
|
'apiScenario.descPlaceholder': '请对该场景进行描述',
|
||||||
'apiScenario.addStep': '添加步骤',
|
'apiScenario.addStep': '添加步骤',
|
||||||
'apiScenario.requestScenario': '请求/场景',
|
'apiScenario.requestScenario': '请求/场景',
|
||||||
'apiScenario.importSystemApi': '导入系统请求',
|
'apiScenario.importSystemApi': '导入系统请求',
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
</template>
|
</template>
|
||||||
</MsSplitBox>
|
</MsSplitBox>
|
||||||
</div>
|
</div>
|
||||||
<detail v-else v-model:scenario="activeApiTab"></detail>
|
<!-- <detail v-else v-model:scenario="activeApiTab"></detail> -->
|
||||||
</MsCard>
|
</MsCard>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
@ -57,7 +57,7 @@
|
||||||
import MsCard from '@/components/pure/ms-card/index.vue';
|
import MsCard from '@/components/pure/ms-card/index.vue';
|
||||||
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
import MsEditableTab from '@/components/pure/ms-editable-tab/index.vue';
|
||||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||||
import detail from './detail/index.vue';
|
// import detail from './detail/index.vue';
|
||||||
import RecycleTable from '@/views/api-test/scenario/recycle/recycleTable.vue';
|
import RecycleTable from '@/views/api-test/scenario/recycle/recycleTable.vue';
|
||||||
import recycleTree from '@/views/api-test/scenario/recycle/recycleTree.vue';
|
import recycleTree from '@/views/api-test/scenario/recycle/recycleTree.vue';
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue