feat(功能用例): 脑图加载用例&接口部分 bug 修复
This commit is contained in:
parent
98a580a427
commit
d9ca6aabaa
|
@ -9,7 +9,7 @@ import type { ErrorMessageMode } from '#/axios';
|
|||
|
||||
export default function checkStatus(status: number, msg: string, errorMessageMode: ErrorMessageMode = 'message'): void {
|
||||
const { t } = useI18n();
|
||||
const { logout, isLoginPage } = useUser();
|
||||
const { logout, isLoginPage, isWhiteListPage } = useUser();
|
||||
let errMessage = '';
|
||||
switch (status) {
|
||||
case 400:
|
||||
|
@ -17,7 +17,7 @@ export default function checkStatus(status: number, msg: string, errorMessageMod
|
|||
break;
|
||||
case 401: {
|
||||
errMessage = msg || t('api.errMsg401');
|
||||
if (!isLoginPage()) {
|
||||
if (!isLoginPage() && !isWhiteListPage()) {
|
||||
// 不是登录页再调用logout
|
||||
logout();
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import type { MinderJsonNode } from '@/components/pure/ms-minder-editor/props';
|
||||
import { CommentItem, CommentParams } from '@/components/business/ms-comment/types';
|
||||
|
||||
import MSR from '@/api/http/index';
|
||||
|
@ -187,7 +188,7 @@ export function saveCaseMinder(data: FeatureCaseMinder) {
|
|||
|
||||
// 获取脑图
|
||||
export function getCaseMinder(data: { projectId: string; moduleId: string }) {
|
||||
return MSR.post({ url: `${GetCaseMinderUrl}`, data });
|
||||
return MSR.post<MinderJsonNode[]>({ url: `${GetCaseMinderUrl}`, data });
|
||||
}
|
||||
|
||||
// 回收站
|
||||
|
|
|
@ -43,6 +43,12 @@
|
|||
<paramTable
|
||||
v-model:params="innerParams"
|
||||
:columns="columns"
|
||||
:default-param-item="{
|
||||
key: '',
|
||||
value: '',
|
||||
description: '',
|
||||
required: false,
|
||||
}"
|
||||
:scroll="{ x: 'auto' }"
|
||||
:height-used="heightUsed"
|
||||
:selectable="false"
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<template>
|
||||
<MsMinderEditor
|
||||
v-model:activeExtraKey="activeExtraKey"
|
||||
:tags="tags"
|
||||
v-model:extra-visible="extraVisible"
|
||||
v-model:loading="loading"
|
||||
:tags="[]"
|
||||
:import-json="importJson"
|
||||
:replaceable-tags="replaceableTags"
|
||||
:insert-node="insertNode"
|
||||
|
@ -15,8 +17,8 @@
|
|||
@save="handleMinderSave"
|
||||
>
|
||||
<template #extractTabContent>
|
||||
<div>
|
||||
<div v-if="activeExtraKey === 'baseInfo'" class="pl-[16px]">
|
||||
<div v-if="activeExtraKey === 'baseInfo'" class="h-full pl-[16px]">
|
||||
<div class="baseInfo-form">
|
||||
<a-skeleton v-if="baseInfoLoading" :loading="baseInfoLoading" :animation="true">
|
||||
<a-space direction="vertical" class="w-full" size="large">
|
||||
<a-skeleton-line :rows="rowLength" :line-height="30" :line-spacing="30" />
|
||||
|
@ -30,35 +32,6 @@
|
|||
>
|
||||
<a-input v-model:model-value="baseInfoForm.name" :placeholder="t('common.pleaseInput')"></a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="moduleId"
|
||||
asterisk-position="end"
|
||||
:label="t('caseManagement.featureCase.ModuleOwned')"
|
||||
:rules="[{ required: true, message: t('system.orgTemplate.moduleRuleTip') }]"
|
||||
>
|
||||
<a-tree-select
|
||||
v-model="baseInfoForm.moduleId"
|
||||
:allow-search="true"
|
||||
:data="caseTree"
|
||||
:field-names="{
|
||||
title: 'name',
|
||||
key: 'id',
|
||||
children: 'children',
|
||||
}"
|
||||
:draggable="false"
|
||||
:tree-props="{
|
||||
virtualListProps: {
|
||||
height: 200,
|
||||
},
|
||||
}"
|
||||
>
|
||||
<template #tree-slot-title="node">
|
||||
<a-tooltip :content="`${node.name}`" position="tl">
|
||||
<div class="one-line-text w-[300px] text-[var(--color-text-1)]">{{ node.name }}</div>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-tree-select>
|
||||
</a-form-item>
|
||||
<MsFormCreate
|
||||
v-if="formRules.length"
|
||||
ref="formCreateRef"
|
||||
|
@ -70,176 +43,171 @@
|
|||
<MsTagsInput v-model:model-value="baseInfoForm.tags" :max-tag-count="6" />
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
<div class="flex items-center gap-[12px]">
|
||||
<a-button type="primary" @click="handleSave">{{ t('common.save') }}</a-button>
|
||||
<a-button type="secondary">{{ t('common.cancel') }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else-if="activeExtraKey === 'attachment'" class="pl-[16px]">
|
||||
<MsAddAttachment
|
||||
v-model:file-list="fileList"
|
||||
multiple
|
||||
only-button
|
||||
@change="handleFileChange"
|
||||
@link-file="() => (showLinkFileDrawer = true)"
|
||||
/>
|
||||
<MsFileList
|
||||
v-if="fileList.length > 0"
|
||||
ref="fileListRef"
|
||||
v-model:file-list="fileList"
|
||||
mode="static"
|
||||
:init-file-save-tips="t('ms.upload.waiting_save')"
|
||||
:show-upload-type-desc="true"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<!-- 本地文件 -->
|
||||
<div v-if="item.local || item.status === 'init'" class="flex flex-nowrap">
|
||||
<MsButton
|
||||
v-if="item.status !== 'init' && item.file.type.includes('image/')"
|
||||
type="button"
|
||||
status="primary"
|
||||
class="!mr-[4px]"
|
||||
@click="handlePreview(item)"
|
||||
>
|
||||
{{ t('ms.upload.preview') }}
|
||||
</MsButton>
|
||||
<SaveAsFilePopover
|
||||
v-model:visible="transferVisible"
|
||||
:saving-file="activeTransferFileParams"
|
||||
:file-save-as-source-id="activeCase.id"
|
||||
:file-save-as-api="transferFileRequest"
|
||||
:file-module-options-api="getTransferFileTree"
|
||||
source-id-key="caseId"
|
||||
/>
|
||||
<MsButton
|
||||
v-if="item.status !== 'init'"
|
||||
type="button"
|
||||
status="primary"
|
||||
class="!mr-[4px]"
|
||||
@click="transferFile(item)"
|
||||
>
|
||||
{{ t('caseManagement.featureCase.storage') }}
|
||||
</MsButton>
|
||||
<MsButton
|
||||
v-if="item.status !== 'init'"
|
||||
type="button"
|
||||
status="primary"
|
||||
class="!mr-[4px]"
|
||||
@click="downloadFile(item)"
|
||||
>
|
||||
{{ t('caseManagement.featureCase.download') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<!-- 关联文件 -->
|
||||
<div v-else class="flex flex-nowrap">
|
||||
<MsButton
|
||||
v-if="item.file.type.includes('/image')"
|
||||
type="button"
|
||||
status="primary"
|
||||
class="!mr-[4px]"
|
||||
@click="handlePreview(item)"
|
||||
>
|
||||
{{ t('ms.upload.preview') }}
|
||||
</MsButton>
|
||||
<MsButton
|
||||
v-if="activeCase.id"
|
||||
type="button"
|
||||
status="primary"
|
||||
class="!mr-[4px]"
|
||||
@click="downloadFile(item)"
|
||||
>
|
||||
{{ t('caseManagement.featureCase.download') }}
|
||||
</MsButton>
|
||||
<MsButton
|
||||
v-if="activeCase.id && item.isUpdateFlag"
|
||||
type="button"
|
||||
status="primary"
|
||||
@click="handleUpdateFile(item)"
|
||||
>
|
||||
{{ t('common.update') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
</template>
|
||||
<template #title="{ item }">
|
||||
<span v-if="item.isUpdateFlag" class="ml-4 flex items-center font-normal text-[rgb(var(--warning-6))]">
|
||||
<icon-exclamation-circle-fill />
|
||||
<span>{{ t('caseManagement.featureCase.fileIsUpdated') }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</MsFileList>
|
||||
<div class="flex items-center gap-[12px] bg-white py-[16px]">
|
||||
<a-button type="primary" @click="handleSave">{{ t('common.save') }}</a-button>
|
||||
<a-button type="secondary">{{ t('common.cancel') }}</a-button>
|
||||
</div>
|
||||
<div v-else-if="activeExtraKey === 'comments'" class="pl-[16px]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
{{
|
||||
t('ms.minders.commentTotal', {
|
||||
num: activeComment === 'caseComment' ? commentList.length : reviewCommentList.length,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
<div v-else-if="activeExtraKey === 'attachment'" class="pl-[16px]">
|
||||
<MsAddAttachment
|
||||
v-model:file-list="fileList"
|
||||
multiple
|
||||
only-button
|
||||
@change="handleFileChange"
|
||||
@link-file="() => (showLinkFileDrawer = true)"
|
||||
/>
|
||||
<MsFileList
|
||||
v-if="fileList.length > 0"
|
||||
ref="fileListRef"
|
||||
v-model:file-list="fileList"
|
||||
mode="static"
|
||||
:init-file-save-tips="t('ms.upload.waiting_save')"
|
||||
:show-upload-type-desc="true"
|
||||
>
|
||||
<template #actions="{ item }">
|
||||
<!-- 本地文件 -->
|
||||
<div v-if="item.local || item.status === 'init'" class="flex flex-nowrap">
|
||||
<MsButton
|
||||
v-if="item.status !== 'init' && item.file.type.includes('image/')"
|
||||
type="button"
|
||||
status="primary"
|
||||
class="!mr-[4px]"
|
||||
@click="handlePreview(item)"
|
||||
>
|
||||
{{ t('ms.upload.preview') }}
|
||||
</MsButton>
|
||||
<SaveAsFilePopover
|
||||
v-model:visible="transferVisible"
|
||||
:saving-file="activeTransferFileParams"
|
||||
:file-save-as-source-id="activeCase.id"
|
||||
:file-save-as-api="transferFileRequest"
|
||||
:file-module-options-api="getTransferFileTree"
|
||||
source-id-key="caseId"
|
||||
/>
|
||||
<MsButton
|
||||
v-if="item.status !== 'init'"
|
||||
type="button"
|
||||
status="primary"
|
||||
class="!mr-[4px]"
|
||||
@click="transferFile(item)"
|
||||
>
|
||||
{{ t('caseManagement.featureCase.storage') }}
|
||||
</MsButton>
|
||||
<MsButton
|
||||
v-if="item.status !== 'init'"
|
||||
type="button"
|
||||
status="primary"
|
||||
class="!mr-[4px]"
|
||||
@click="downloadFile(item)"
|
||||
>
|
||||
{{ t('caseManagement.featureCase.download') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<!-- 关联文件 -->
|
||||
<div v-else class="flex flex-nowrap">
|
||||
<MsButton
|
||||
v-if="item.file.type.includes('/image')"
|
||||
type="button"
|
||||
status="primary"
|
||||
class="!mr-[4px]"
|
||||
@click="handlePreview(item)"
|
||||
>
|
||||
{{ t('ms.upload.preview') }}
|
||||
</MsButton>
|
||||
<MsButton
|
||||
v-if="activeCase.id"
|
||||
type="button"
|
||||
status="primary"
|
||||
class="!mr-[4px]"
|
||||
@click="downloadFile(item)"
|
||||
>
|
||||
{{ t('caseManagement.featureCase.download') }}
|
||||
</MsButton>
|
||||
<MsButton
|
||||
v-if="activeCase.id && item.isUpdateFlag"
|
||||
type="button"
|
||||
status="primary"
|
||||
@click="handleUpdateFile(item)"
|
||||
>
|
||||
{{ t('common.update') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<a-select
|
||||
v-model:model-value="activeComment"
|
||||
:options="commentTypeOptions"
|
||||
class="w-[120px]"
|
||||
@change="getAllCommentList"
|
||||
></a-select>
|
||||
</div>
|
||||
<ReviewCommentList
|
||||
v-if="activeComment === 'reviewComment' || activeComment === 'executiveComment'"
|
||||
:review-comment-list="reviewCommentList"
|
||||
:active-comment="activeComment"
|
||||
/>
|
||||
<template v-else>
|
||||
<MsComment
|
||||
:upload-image="handleUploadImage"
|
||||
:comment-list="commentList"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
@delete="handleDelete"
|
||||
@update-or-add="handleUpdateOrAdd"
|
||||
/>
|
||||
<MsEmpty v-if="commentList.length === 0" />
|
||||
</template>
|
||||
<inputComment
|
||||
ref="commentInputRef"
|
||||
v-model:content="content"
|
||||
v-model:notice-user-ids="noticeUserIds"
|
||||
v-permission="['FUNCTIONAL_CASE:READ+COMMENT']"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
:is-active="isActive"
|
||||
mode="textarea"
|
||||
is-show-avatar
|
||||
is-use-bottom
|
||||
:upload-image="handleUploadImage"
|
||||
@publish="publishHandler"
|
||||
@cancel="cancelPublish"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="pl-[16px]">
|
||||
<a-button
|
||||
v-if="hasAnyPermission(['FUNCTIONAL_CASE:READ+UPDATE'])"
|
||||
class="mr-3"
|
||||
type="primary"
|
||||
@click="linkBug"
|
||||
>
|
||||
{{ t('caseManagement.featureCase.linkDefect') }}
|
||||
</a-button>
|
||||
<a-button v-permission="['PROJECT_BUG:READ+ADD']" type="outline" @click="createBug"
|
||||
>{{ t('caseManagement.featureCase.createDefect') }}
|
||||
</a-button>
|
||||
<div class="bug-list">
|
||||
<div v-for="item of bugList" :key="item.id" class="bug-item">
|
||||
<div class="mb-[4px] flex items-center justify-between">
|
||||
<MsButton type="text" @click="goBug(item.id)">{{ item.num }}</MsButton>
|
||||
<MsButton type="text" @click="disassociateBug(item.id)">
|
||||
{{ t('ms.add.attachment.cancelAssociate') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<a-tooltip :content="item.name">
|
||||
<div class="one-line-text">{{ item.name }}</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<MsEmpty v-if="bugList.length === 0" />
|
||||
<template #title="{ item }">
|
||||
<span v-if="item.isUpdateFlag" class="ml-4 flex items-center font-normal text-[rgb(var(--warning-6))]">
|
||||
<icon-exclamation-circle-fill />
|
||||
<span>{{ t('caseManagement.featureCase.fileIsUpdated') }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</MsFileList>
|
||||
</div>
|
||||
<div v-else-if="activeExtraKey === 'comments'" class="pl-[16px]">
|
||||
<div class="flex items-center justify-between">
|
||||
<div class="text-[var(--color-text-4)]">
|
||||
{{
|
||||
t('ms.minders.commentTotal', {
|
||||
num: activeComment === 'caseComment' ? commentList.length : reviewCommentList.length,
|
||||
})
|
||||
}}
|
||||
</div>
|
||||
<a-select
|
||||
v-model:model-value="activeComment"
|
||||
:options="commentTypeOptions"
|
||||
class="w-[120px]"
|
||||
@change="getAllCommentList"
|
||||
></a-select>
|
||||
</div>
|
||||
<ReviewCommentList
|
||||
v-if="activeComment === 'reviewComment' || activeComment === 'executiveComment'"
|
||||
:review-comment-list="reviewCommentList"
|
||||
:active-comment="activeComment"
|
||||
/>
|
||||
<template v-else>
|
||||
<MsComment
|
||||
:upload-image="handleUploadImage"
|
||||
:comment-list="commentList"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
@delete="handleDelete"
|
||||
@update-or-add="handleUpdateOrAdd"
|
||||
/>
|
||||
<MsEmpty v-if="commentList.length === 0" />
|
||||
</template>
|
||||
<inputComment
|
||||
ref="commentInputRef"
|
||||
v-model:content="content"
|
||||
v-model:notice-user-ids="noticeUserIds"
|
||||
v-permission="['FUNCTIONAL_CASE:READ+COMMENT']"
|
||||
:preview-url="PreviewEditorImageUrl"
|
||||
:is-active="isActive"
|
||||
mode="textarea"
|
||||
is-show-avatar
|
||||
is-use-bottom
|
||||
:upload-image="handleUploadImage"
|
||||
@publish="publishHandler"
|
||||
@cancel="cancelPublish"
|
||||
/>
|
||||
</div>
|
||||
<div v-else class="pl-[16px]">
|
||||
<a-button v-if="hasAnyPermission(['FUNCTIONAL_CASE:READ+UPDATE'])" class="mr-3" type="primary" @click="linkBug">
|
||||
{{ t('caseManagement.featureCase.linkDefect') }}
|
||||
</a-button>
|
||||
<a-button v-permission="['PROJECT_BUG:READ+ADD']" type="outline" @click="createBug"
|
||||
>{{ t('caseManagement.featureCase.createDefect') }}
|
||||
</a-button>
|
||||
<div class="bug-list">
|
||||
<div v-for="item of bugList" :key="item.id" class="bug-item">
|
||||
<div class="mb-[4px] flex items-center justify-between">
|
||||
<MsButton type="text" @click="goBug(item.id)">{{ item.num }}</MsButton>
|
||||
<MsButton type="text" @click="disassociateBug(item.id)">
|
||||
{{ t('ms.add.attachment.cancelAssociate') }}
|
||||
</MsButton>
|
||||
</div>
|
||||
<a-tooltip :content="item.name">
|
||||
<div class="one-line-text">{{ item.name }}</div>
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<MsEmpty v-if="bugList.length === 0" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -300,6 +268,7 @@
|
|||
editorUploadFile,
|
||||
getAssociatedFileListUrl,
|
||||
getCaseDefaultFields,
|
||||
getCaseDetail,
|
||||
getCaseMinder,
|
||||
getCaseModuleTree,
|
||||
getCommentList,
|
||||
|
@ -317,14 +286,15 @@
|
|||
import useModal from '@/hooks/useModal';
|
||||
import useAppStore from '@/store/modules/app';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import { downloadByteFile, getGenerateId } from '@/utils';
|
||||
import { downloadByteFile, getGenerateId, mapTree, traverseTree } from '@/utils';
|
||||
import { hasAnyPermission } from '@/utils/permission';
|
||||
|
||||
import { AssociatedList, OptionsFieldId } from '@/models/caseManagement/featureCase';
|
||||
import { ModuleTreeNode, TableQueryParams } from '@/models/common';
|
||||
import { TableQueryParams } from '@/models/common';
|
||||
import { BugManagementRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
import { convertToFile } from '@/views/case-management/caseManagementFeature/components/utils';
|
||||
import { Api } from '@form-create/arco-design';
|
||||
|
||||
const AddDefectDrawer = defineAsyncComponent(
|
||||
() => import('@/views/case-management/caseManagementFeature/components/tabContent/tabBug/addDefectDrawer.vue')
|
||||
|
@ -336,6 +306,7 @@
|
|||
const props = defineProps<{
|
||||
moduleId: string;
|
||||
moduleName: string;
|
||||
modulesCount: Record<string, number>; // 模块数量
|
||||
}>();
|
||||
|
||||
const router = useRouter();
|
||||
|
@ -344,14 +315,67 @@
|
|||
const userStore = useUserStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const caseTag = t('common.case');
|
||||
const moduleTag = t('common.module');
|
||||
const topTags = [moduleTag, caseTag];
|
||||
const descTags = [t('ms.minders.stepDesc'), t('ms.minders.textDesc')];
|
||||
const importJson = ref<MinderJson>({
|
||||
root: {},
|
||||
template: 'default',
|
||||
treePath: [],
|
||||
});
|
||||
const caseTree = ref<MinderJsonNode[]>([]);
|
||||
const loading = ref(false);
|
||||
|
||||
async function initCaseTree() {
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await getCaseModuleTree({
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleId: props.moduleId === 'all' ? '' : props.moduleId,
|
||||
});
|
||||
caseTree.value = mapTree<MinderJsonNode>(res, (e) => ({
|
||||
...e,
|
||||
data: {
|
||||
id: e.id,
|
||||
text: e.name,
|
||||
resource: e.data?.id === 'fakeNode' ? [] : [moduleTag],
|
||||
expandState: e.level === 1 ? 'expand' : 'collapse',
|
||||
count: props.modulesCount[e.id],
|
||||
},
|
||||
children:
|
||||
props.modulesCount[e.id] > 0 && !e.children?.length
|
||||
? [
|
||||
{
|
||||
data: {
|
||||
id: 'fakeNode',
|
||||
text: 'fakeNode',
|
||||
resource: ['fakeNode'],
|
||||
},
|
||||
},
|
||||
]
|
||||
: e.children,
|
||||
}));
|
||||
importJson.value.root = {
|
||||
children: caseTree.value,
|
||||
data: {
|
||||
id: 'all',
|
||||
text: t('ms.minders.allModule'),
|
||||
resource: [moduleTag],
|
||||
},
|
||||
};
|
||||
window.minder.importJson(importJson.value);
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function initMinder() {
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await getCaseMinder({
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleId: props.moduleId === 'all' ? '' : props.moduleId,
|
||||
|
@ -366,55 +390,19 @@
|
|||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.moduleId) {
|
||||
if (props.moduleId === 'all') {
|
||||
initCaseTree();
|
||||
} else {
|
||||
initMinder();
|
||||
}
|
||||
});
|
||||
|
||||
const caseTag = t('common.case');
|
||||
const moduleTag = t('common.module');
|
||||
const topTags = [moduleTag, caseTag];
|
||||
const descTags = [t('ms.minders.stepDesc'), t('ms.minders.textDesc')];
|
||||
const tags = [...topTags, t('ms.minders.precondition'), ...descTags, t('ms.minders.stepExpect'), t('common.remark')];
|
||||
const visible = ref<boolean>(false);
|
||||
const activeCase = ref<any>({});
|
||||
const extractContentTabList = computed(() => {
|
||||
const fullTabList = [
|
||||
{
|
||||
label: t('common.baseInfo'),
|
||||
value: 'baseInfo',
|
||||
},
|
||||
{
|
||||
label: t('caseManagement.featureCase.attachment'),
|
||||
value: 'attachment',
|
||||
},
|
||||
{
|
||||
value: 'comments',
|
||||
label: t('caseManagement.featureCase.comments'),
|
||||
},
|
||||
{
|
||||
value: 'bug',
|
||||
label: t('caseManagement.featureCase.bug'),
|
||||
},
|
||||
];
|
||||
if (activeCase.value.id) {
|
||||
return fullTabList;
|
||||
}
|
||||
return fullTabList.filter((item) => item.value === 'baseInfo');
|
||||
});
|
||||
const activeExtraKey = ref<'baseInfo' | 'attachment' | 'comments' | 'bug'>('baseInfo');
|
||||
|
||||
function handleNodeClick(data: any) {
|
||||
if (data.resource && data.resource.includes(caseTag)) {
|
||||
visible.value = true;
|
||||
activeCase.value = data;
|
||||
}
|
||||
}
|
||||
|
||||
async function handleMinderSave(data: any) {
|
||||
try {
|
||||
await saveCaseMinder({
|
||||
|
@ -435,6 +423,10 @@
|
|||
* @param node 选中节点
|
||||
*/
|
||||
function replaceableTags(node: MinderJsonNode) {
|
||||
if (Object.keys(node.data || {}).length === 0 || node.data?.id === 'root') {
|
||||
// 没有数据的节点或默认模块节点不可替换
|
||||
return [];
|
||||
}
|
||||
if (node.data?.resource?.some((e) => topTags.includes(e))) {
|
||||
// 选中节点属于顶级节点,可替换为除自身外的顶级标签
|
||||
return !node.children || node.children.length === 0
|
||||
|
@ -444,7 +436,7 @@
|
|||
if (node.data?.resource?.some((e) => descTags.includes(e))) {
|
||||
// 选中节点属于描述节点,可替换为除自身外的描述标签
|
||||
if (
|
||||
node.data?.resource?.includes(t('ms.minders.stepDesc')) &&
|
||||
node.data.resource.includes(t('ms.minders.stepDesc')) &&
|
||||
(node.parent?.children?.filter((e) => e.data?.resource?.includes(t('ms.minders.stepDesc'))) || []).length > 1
|
||||
) {
|
||||
// 如果当前节点是步骤描述,则需要判断是否有其他步骤描述节点,如果有,则不可替换为文本描述
|
||||
|
@ -453,7 +445,7 @@
|
|||
return descTags.filter((tag) => !node.data?.resource?.includes(tag));
|
||||
}
|
||||
if (
|
||||
(!node.data?.resource || node.data?.resource?.length === 0) &&
|
||||
(!node.data?.resource || node.data.resource.length === 0) &&
|
||||
(!node.parent?.data?.resource ||
|
||||
node.parent?.data?.resource.length === 0 ||
|
||||
node.parent?.data?.resource?.some((e) => topTags.includes(e)))
|
||||
|
@ -469,6 +461,11 @@
|
|||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* 执行插入节点
|
||||
* @param command 插入命令
|
||||
* @param node 目标节点
|
||||
*/
|
||||
function execInert(command: string, node?: MinderJsonNodeData) {
|
||||
if (window.minder.queryCommandState(command) !== -1) {
|
||||
window.minder.execCommand(command, node);
|
||||
|
@ -716,7 +713,7 @@
|
|||
const rowLength = ref<number>(0);
|
||||
const formRules = ref<FormItem[]>([]);
|
||||
const formItem = ref<FormRuleItem[]>([]);
|
||||
const fApi = ref<any>(null);
|
||||
const fApi = ref<Api>();
|
||||
// 初始化模板默认字段
|
||||
async function initDefaultFields() {
|
||||
formRules.value = [];
|
||||
|
@ -734,7 +731,10 @@
|
|||
initValue = item.type === 'MEMBER' ? userStore.id : [userStore.id];
|
||||
}
|
||||
}
|
||||
|
||||
if (item.internal && item.type === 'SELECT') {
|
||||
// TODO:过滤用例等级字段,等级字段后续可自定义,需要调整
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
type: item.type,
|
||||
name: item.fieldId,
|
||||
|
@ -748,7 +748,7 @@
|
|||
},
|
||||
};
|
||||
});
|
||||
formRules.value = result;
|
||||
formRules.value = result.filter((e: any) => e);
|
||||
baseInfoLoading.value = false;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
|
@ -756,27 +756,116 @@
|
|||
}
|
||||
}
|
||||
|
||||
const caseTree = ref<ModuleTreeNode[]>([]);
|
||||
const extraVisible = ref<boolean>(false);
|
||||
const activeCase = ref<Record<string, any>>({});
|
||||
const extractContentTabList = computed(() => {
|
||||
const fullTabList = [
|
||||
{
|
||||
label: t('common.baseInfo'),
|
||||
value: 'baseInfo',
|
||||
},
|
||||
{
|
||||
label: t('caseManagement.featureCase.attachment'),
|
||||
value: 'attachment',
|
||||
},
|
||||
{
|
||||
value: 'comments',
|
||||
label: t('caseManagement.featureCase.comments'),
|
||||
},
|
||||
{
|
||||
value: 'bug',
|
||||
label: t('caseManagement.featureCase.bug'),
|
||||
},
|
||||
];
|
||||
if (activeCase.value.id) {
|
||||
return fullTabList;
|
||||
}
|
||||
return fullTabList.filter((item) => item.value === 'baseInfo');
|
||||
});
|
||||
const activeExtraKey = ref<'baseInfo' | 'attachment' | 'comments' | 'bug'>('baseInfo');
|
||||
|
||||
async function initSelectTree() {
|
||||
try {
|
||||
caseTree.value = await getCaseModuleTree({ projectId: appStore.currentProjectId });
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
async function handleNodeClick(node: MinderJsonNode) {
|
||||
const { data } = node;
|
||||
if (data?.resource && data.resource.includes(caseTag)) {
|
||||
extraVisible.value = true;
|
||||
try {
|
||||
baseInfoLoading.value = true;
|
||||
const res = await getCaseDetail(data.id);
|
||||
activeCase.value = res;
|
||||
baseInfoForm.value.name = res.name;
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
baseInfoLoading.value = false;
|
||||
}
|
||||
} else if (data?.resource?.includes(moduleTag) && data.count > 0 && data.isLoaded !== true) {
|
||||
try {
|
||||
loading.value = true;
|
||||
const res = await getCaseMinder({
|
||||
projectId: appStore.currentProjectId,
|
||||
moduleId: data.id,
|
||||
});
|
||||
const fakeNode = node.children?.find((e) => e.data?.id === undefined); // 移除占位的虚拟节点
|
||||
window.minder.removeNode(fakeNode);
|
||||
res.forEach((e) => {
|
||||
// 用例节点
|
||||
const child = window.minder.createNode(e.data, node);
|
||||
child.render();
|
||||
e.children?.forEach((item) => {
|
||||
// 前置/步骤/备注节点
|
||||
const grandChild = window.minder.createNode(item.data, child);
|
||||
grandChild.render();
|
||||
item.children?.forEach((subItem) => {
|
||||
// 预期结果节点
|
||||
const greatGrandChild = window.minder.createNode(subItem.data, grandChild);
|
||||
greatGrandChild.render();
|
||||
});
|
||||
child.renderTree();
|
||||
});
|
||||
child.expand();
|
||||
child.renderTree();
|
||||
});
|
||||
node.expand();
|
||||
node.renderTree();
|
||||
window.minder.layout();
|
||||
if (node.data) {
|
||||
node.data.isLoaded = true;
|
||||
}
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
} else {
|
||||
extraVisible.value = false;
|
||||
activeCase.value = {};
|
||||
}
|
||||
}
|
||||
|
||||
onBeforeMount(() => {
|
||||
initDefaultFields();
|
||||
initSelectTree();
|
||||
});
|
||||
|
||||
function handleSave() {
|
||||
if (activeExtraKey.value === 'baseInfo') {
|
||||
baseInfoFormRef.value?.validate((errors) => {
|
||||
if (!errors) {
|
||||
Message.success(t('common.saveSuccess'));
|
||||
fApi.value?.validate((valid) => {
|
||||
if (valid) {
|
||||
const data = {
|
||||
...baseInfoForm.value,
|
||||
customFields: formItem.value.map((item: any) => {
|
||||
return {
|
||||
fieldId: item.field,
|
||||
value: Array.isArray(item.value) ? JSON.stringify(item.value) : item.value,
|
||||
};
|
||||
}),
|
||||
};
|
||||
console.log(data);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1130,6 +1219,12 @@
|
|||
:deep(.commentWrapper) {
|
||||
right: 0;
|
||||
}
|
||||
.baseInfo-form {
|
||||
.ms-scroll-bar();
|
||||
|
||||
overflow-y: auto;
|
||||
height: calc(100% - 64px);
|
||||
}
|
||||
.bug-list {
|
||||
.ms-scroll-bar();
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<FeatureCaseMinder :module-id="props.moduleId" :module-name="props.moduleName" />
|
||||
<FeatureCaseMinder :module-id="props.moduleId" :module-name="props.moduleName" :modules-count="props.modulesCount" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
|
@ -9,6 +9,7 @@
|
|||
minderType: 'FeatureCase';
|
||||
moduleId: string;
|
||||
moduleName: string;
|
||||
modulesCount: Record<string, number>; // 模块数量
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
export default {
|
||||
'ms.minders.allModule': '全部模块',
|
||||
'ms.minders.precondition': '前置条件',
|
||||
'ms.minders.stepDesc': '步骤描述',
|
||||
'ms.minders.stepExpect': '预期结果',
|
||||
|
|
|
@ -358,8 +358,8 @@
|
|||
const id = new Date().getTime().toString();
|
||||
propsRes.value.data.push({
|
||||
id,
|
||||
...cloneDeep(props.defaultParamDataItem), // 深拷贝,避免有嵌套引用类型,数据隔离
|
||||
enable: true, // 是否勾选
|
||||
...cloneDeep(props.defaultParamDataItem), // 深拷贝,避免有嵌套引用类型,数据隔离
|
||||
} as any);
|
||||
emitChange('addTableLine', isInit);
|
||||
}
|
||||
|
|
|
@ -109,5 +109,6 @@ export default {
|
|||
delete: 'Delete',
|
||||
enterNode: 'Enter the current node',
|
||||
},
|
||||
loading: 'Mind map loading...',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -103,5 +103,6 @@ export default {
|
|||
delete: '删除',
|
||||
enterNode: '进入当前节点',
|
||||
},
|
||||
loading: '脑图加载中...',
|
||||
},
|
||||
};
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div class="ms-minder-editor-container">
|
||||
<a-spin :loading="loading" :tip="t('minder.loading')" class="ms-minder-editor-container">
|
||||
<div class="flex-1">
|
||||
<minderHeader
|
||||
:sequence-enable="props.sequenceEnable"
|
||||
|
@ -49,15 +49,17 @@
|
|||
@enter-node="handleEnterNode"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="props.extractContentTabList?.length" class="ms-minder-editor-extra">
|
||||
<div class="pl-[16px] pt-[16px]">
|
||||
<MsTab v-model:activeKey="activeExtraKey" :content-tab-list="props.extractContentTabList" mode="button" />
|
||||
<template v-if="props.extractContentTabList?.length">
|
||||
<div class="ms-minder-editor-extra" :class="[extraVisible ? 'ms-minder-editor-extra--visible' : '']">
|
||||
<div class="pl-[16px] pt-[16px]">
|
||||
<MsTab v-model:activeKey="activeExtraKey" :content-tab-list="props.extractContentTabList" mode="button" />
|
||||
</div>
|
||||
<div class="ms-minder-editor-extra-content">
|
||||
<slot name="extractTabContent"></slot>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ms-minder-editor-extra-content">
|
||||
<slot name="extractTabContent"></slot>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" name="minderEditor" setup>
|
||||
|
@ -65,12 +67,15 @@
|
|||
import minderHeader from './main/header.vue';
|
||||
import mainEditor from './main/mainEditor.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import {
|
||||
delProps,
|
||||
editMenuProps,
|
||||
headerProps,
|
||||
insertProps,
|
||||
mainEditorProps,
|
||||
MinderJsonNode,
|
||||
moleProps,
|
||||
priorityProps,
|
||||
tagProps,
|
||||
|
@ -97,6 +102,18 @@
|
|||
...viewMenuProps,
|
||||
});
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const loading = defineModel<boolean>('loading', {
|
||||
default: false,
|
||||
});
|
||||
const activeExtraKey = defineModel<string>('activeExtraKey', {
|
||||
default: '',
|
||||
});
|
||||
const extraVisible = defineModel<boolean>('extraVisible', {
|
||||
default: false,
|
||||
});
|
||||
|
||||
onMounted(async () => {
|
||||
window.minderProps = props;
|
||||
});
|
||||
|
@ -113,19 +130,15 @@
|
|||
emit('enterNode', data);
|
||||
}
|
||||
|
||||
const activeExtraKey = defineModel<string>('activeExtraKey', {
|
||||
default: '',
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
nextTick(() => {
|
||||
if (window.minder.on) {
|
||||
window.minder.on('mousedown', (e: any) => {
|
||||
if (e.originEvent.button === 0) {
|
||||
// 鼠标左键点击
|
||||
const selectedNode = window.minder.getSelectedNode();
|
||||
const selectedNode: MinderJsonNode = window.minder.getSelectedNode();
|
||||
if (Object.keys(window.minder).length > 0 && selectedNode) {
|
||||
emit('nodeClick', selectedNode.data);
|
||||
emit('nodeClick', selectedNode);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -136,13 +149,13 @@
|
|||
|
||||
<style lang="less" scoped>
|
||||
.ms-minder-editor-container {
|
||||
@apply relative flex h-full;
|
||||
@apply relative flex h-full w-full;
|
||||
.ms-minder-editor-extra {
|
||||
@apply flex flex-col border-l;
|
||||
@apply flex flex-col overflow-hidden border-l;
|
||||
|
||||
width: 35%;
|
||||
min-width: 360px;
|
||||
width: 0;
|
||||
border-color: var(--color-text-n8);
|
||||
transition: all 300ms ease-in-out;
|
||||
.ms-minder-editor-extra-content {
|
||||
@apply relative flex-1 overflow-y-auto;
|
||||
.ms-scroll-bar();
|
||||
|
@ -150,5 +163,9 @@
|
|||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
.ms-minder-editor-extra--visible {
|
||||
width: 35%;
|
||||
transition: all 300ms ease-in-out;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -13,13 +13,16 @@ export interface MinderJsonNodeData {
|
|||
id: string;
|
||||
text: string;
|
||||
resource?: string[];
|
||||
expandState?: string;
|
||||
expandState?: 'collapse' | 'expand';
|
||||
priority?: number;
|
||||
// 前端渲染字段
|
||||
[key: string]: any;
|
||||
}
|
||||
export interface MinderJsonNode {
|
||||
parent?: MinderJsonNode;
|
||||
data?: MinderJsonNodeData;
|
||||
children?: MinderJsonNode[];
|
||||
[key: string]: any; // minder 内置字段
|
||||
}
|
||||
|
||||
export interface MinderJson {
|
||||
|
|
|
@ -2,6 +2,7 @@ import { Message } from '@arco-design/web-vue';
|
|||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import router from '@/router';
|
||||
import { WHITE_LIST } from '@/router/constants';
|
||||
import { useAppStore, useUserStore } from '@/store';
|
||||
|
||||
export default function useUser() {
|
||||
|
@ -33,8 +34,14 @@ export default function useUser() {
|
|||
return window.location.hash.indexOf('login') > -1;
|
||||
};
|
||||
|
||||
const isWhiteListPage = () => {
|
||||
const currentRoute = router.currentRoute.value;
|
||||
return WHITE_LIST.some((e) => e.path.includes(currentRoute.path));
|
||||
};
|
||||
|
||||
return {
|
||||
logout,
|
||||
isLoginPage,
|
||||
isWhiteListPage,
|
||||
};
|
||||
}
|
||||
|
|
|
@ -257,6 +257,7 @@ export interface CsvVariable {
|
|||
};
|
||||
// 以下为前端字段
|
||||
settingVisible: boolean;
|
||||
copyId?: string; // 复制场景时,源场景的 csv 参数的 id
|
||||
}
|
||||
export interface CommonVariable {
|
||||
id: string | number;
|
||||
|
@ -412,6 +413,7 @@ export interface Scenario {
|
|||
stepFileParam: Record<string, ScenarioStepFileParams>;
|
||||
fileParam: ScenarioFileParams;
|
||||
follow?: boolean;
|
||||
copyFromScenarioId?: string | number;
|
||||
uploadFileIds: string[];
|
||||
linkFileIds: string[];
|
||||
// 前端渲染字段
|
||||
|
|
|
@ -230,6 +230,12 @@
|
|||
:disabled-param-value="props.disabled"
|
||||
:scroll="{ x: '100%' }"
|
||||
:columns="scriptColumns"
|
||||
:default-param-item="{
|
||||
key: '',
|
||||
value: '',
|
||||
description: '',
|
||||
required: false,
|
||||
}"
|
||||
:selectable="false"
|
||||
@change="() => emit('change')"
|
||||
/>
|
||||
|
|
|
@ -13,7 +13,6 @@ import {
|
|||
ResponseDefinition,
|
||||
} from '@/models/apiTest/common';
|
||||
import type { MockParams } from '@/models/apiTest/mock';
|
||||
import type { CsvVariable } from '@/models/apiTest/scenario';
|
||||
import {
|
||||
FullResponseAssertionType,
|
||||
RequestAssertionCondition,
|
||||
|
@ -414,37 +413,3 @@ export const matchRuleOptions = [
|
|||
];
|
||||
// mock 参数为文件类型的匹配规则选项
|
||||
export const mockFileMatchRules = ['EQUALS', 'NOT_EQUALS', 'IS_EMPTY', 'IS_NOT_EMPTY'];
|
||||
|
||||
// 场景-常规参数默认值
|
||||
export const defaultNormalParamItem = {
|
||||
key: '',
|
||||
paramType: 'CONSTANT',
|
||||
value: '',
|
||||
description: '',
|
||||
tags: [],
|
||||
enable: true,
|
||||
};
|
||||
// 场景-csv参数默认值
|
||||
export const defaultCsvParamItem: CsvVariable = {
|
||||
id: '',
|
||||
scenarioId: '',
|
||||
name: '',
|
||||
scope: 'SCENARIO',
|
||||
enable: false,
|
||||
encoding: 'UTF-8',
|
||||
random: false,
|
||||
variableNames: '',
|
||||
ignoreFirstLine: false,
|
||||
delimiter: ',',
|
||||
allowQuotedData: false,
|
||||
recycleOnEof: false,
|
||||
stopThreadOnEof: false,
|
||||
settingVisible: false,
|
||||
file: {
|
||||
fileId: '',
|
||||
fileName: '',
|
||||
local: false,
|
||||
fileAlias: '',
|
||||
delete: false,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -495,9 +495,9 @@
|
|||
<div v-else class="text-[var(--color-text-1)]">{{ '-' }}</div>
|
||||
</template>
|
||||
<!-- 单独启用/禁用列 -->
|
||||
<template #enable="{ record, rowIndex }">
|
||||
<template v-if="!props.selectable" #enable="{ record, rowIndex }">
|
||||
<a-switch
|
||||
v-model="record.enable"
|
||||
v-model:model-value="record.enable"
|
||||
:disabled="props.disabledExceptParam"
|
||||
size="small"
|
||||
type="line"
|
||||
|
@ -513,7 +513,7 @@
|
|||
<div class="flex w-full flex-row items-center" :class="{ 'justify-end': columnConfig.align === 'right' }">
|
||||
<a-switch
|
||||
v-if="columnConfig.hasDisable"
|
||||
v-model="record.enable"
|
||||
v-model:model-value="record.enable"
|
||||
:disabled="props.disabledExceptParam"
|
||||
size="small"
|
||||
type="line"
|
||||
|
@ -664,7 +664,7 @@
|
|||
const props = withDefaults(
|
||||
defineProps<{
|
||||
params?: Record<string, any>[];
|
||||
defaultParamItem?: Record<string, any>; // 默认参数项,用于添加新行时的默认值
|
||||
defaultParamItem: Record<string, any>; // 默认参数项,用于添加新行时的默认值
|
||||
columns: ParamTableColumn[];
|
||||
scroll?: {
|
||||
x?: number | string;
|
||||
|
@ -703,20 +703,6 @@
|
|||
showSetting: false,
|
||||
tableKey: undefined,
|
||||
isSimpleSetting: true,
|
||||
defaultParamItem: () => ({
|
||||
required: false,
|
||||
key: '',
|
||||
paramType: RequestParamsType.STRING,
|
||||
value: '',
|
||||
minLength: undefined,
|
||||
maxLength: undefined,
|
||||
contentType: RequestContentTypeEnum.TEXT,
|
||||
tag: [],
|
||||
description: '',
|
||||
encode: false,
|
||||
disable: false,
|
||||
mustContain: false,
|
||||
}),
|
||||
}
|
||||
);
|
||||
const emit = defineEmits<{
|
||||
|
@ -942,7 +928,7 @@
|
|||
if (
|
||||
(!props.disabledExceptParam || !props.disabledParamValue) &&
|
||||
hasNoIdItem &&
|
||||
!filterKeyValParams(arr, props.defaultParamItem).lastDataIsDefault &&
|
||||
!filterKeyValParams(arr, props.defaultParamItem, !props.selectable).lastDataIsDefault &&
|
||||
!props.isTreeTable
|
||||
) {
|
||||
addTableLine(arr.length - 1, false, true);
|
||||
|
|
|
@ -179,11 +179,13 @@ export function filterKeyValParams<T>(
|
|||
}
|
||||
// id、enable、valid属性不参与比较
|
||||
delete lastData.id;
|
||||
delete lastData.enable;
|
||||
delete lastData.valid;
|
||||
delete defaultParam.valid;
|
||||
delete defaultParam.id;
|
||||
delete defaultParam.enable;
|
||||
delete defaultParam.valid;
|
||||
if (!filterEnable) {
|
||||
delete lastData.enable;
|
||||
delete defaultParam.enable;
|
||||
}
|
||||
const lastDataIsDefault = isEqual(lastData, defaultParam) || lastData.key === '';
|
||||
let validParams: (T & Record<string, any>)[];
|
||||
if (lastDataIsDefault) {
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
:file-save-as-source-id="props.scenarioId"
|
||||
:file-save-as-api="transferFile"
|
||||
:file-module-options-api="getTransferOptions"
|
||||
@change="handleCsvVariablesChange"
|
||||
@change="(data) => handleCsvVariablesChange(data as CsvVariable[])"
|
||||
>
|
||||
<template #operationPre="{ record }">
|
||||
<a-trigger
|
||||
|
@ -123,7 +123,7 @@
|
|||
|
||||
import { CsvVariable } from '@/models/apiTest/scenario';
|
||||
|
||||
import { defaultCsvParamItem } from '@/views/api-test/components/config';
|
||||
import { defaultCsvParamItem } from '../config';
|
||||
import { filterKeyValParams } from '@/views/api-test/components/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
|
@ -209,8 +209,8 @@
|
|||
},
|
||||
];
|
||||
|
||||
function handleCsvVariablesChange(resultArr: any[], isInit?: boolean) {
|
||||
csvVariables.value = resultArr.map((e) => ({ ...e, enable: e.name && e.fileId }));
|
||||
function handleCsvVariablesChange(resultArr: CsvVariable[], isInit?: boolean) {
|
||||
csvVariables.value = resultArr.map((e) => ({ ...e, enable: e.name && e.file.fileId ? e.enable : false }));
|
||||
if (!isInit) {
|
||||
emit('change');
|
||||
}
|
||||
|
@ -309,6 +309,7 @@
|
|||
Message.warning(t('apiScenario.csvFileNotNull'));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
|
||||
import { CsvVariable } from '@/models/apiTest/scenario';
|
||||
|
||||
import { defaultCsvParamItem } from '@/views/api-test/components/config';
|
||||
import { defaultCsvParamItem } from '../config';
|
||||
import { filterKeyValParams } from '@/views/api-test/components/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Scenario, ScenarioStepConfig } from '@/models/apiTest/scenario';
|
||||
import { type CsvVariable, Scenario, ScenarioStepConfig } from '@/models/apiTest/scenario';
|
||||
import {
|
||||
ApiScenarioStatus,
|
||||
RequestAssertionCondition,
|
||||
|
@ -180,3 +180,38 @@ export const conditionOptions = [
|
|||
label: 'apiScenario.notNull',
|
||||
},
|
||||
];
|
||||
|
||||
// 场景-常规参数默认值
|
||||
export const defaultNormalParamItem = {
|
||||
key: '',
|
||||
paramType: 'CONSTANT',
|
||||
value: '',
|
||||
description: '',
|
||||
tags: [],
|
||||
enable: true,
|
||||
};
|
||||
|
||||
// 场景-csv参数默认值
|
||||
export const defaultCsvParamItem: CsvVariable = {
|
||||
id: '',
|
||||
scenarioId: '',
|
||||
name: '',
|
||||
scope: 'SCENARIO',
|
||||
enable: false,
|
||||
encoding: 'UTF-8',
|
||||
random: false,
|
||||
variableNames: '',
|
||||
ignoreFirstLine: false,
|
||||
delimiter: ',',
|
||||
allowQuotedData: false,
|
||||
recycleOnEof: false,
|
||||
stopThreadOnEof: false,
|
||||
settingVisible: false,
|
||||
file: {
|
||||
fileId: '',
|
||||
fileName: '',
|
||||
local: false,
|
||||
fileAlias: '',
|
||||
delete: false,
|
||||
},
|
||||
};
|
||||
|
|
|
@ -54,7 +54,7 @@
|
|||
|
||||
import { CommonVariable, CsvVariable } from '@/models/apiTest/scenario';
|
||||
|
||||
import { defaultNormalParamItem } from '@/views/api-test/components/config';
|
||||
import { defaultNormalParamItem } from './config';
|
||||
import { filterKeyValParams } from '@/views/api-test/components/utils';
|
||||
|
||||
const props = defineProps<{
|
||||
|
|
|
@ -890,11 +890,14 @@
|
|||
function handleQuoteCsvConfirm(keys: string[]) {
|
||||
if (activeStep.value) {
|
||||
const realStep = findNodeByKey<ScenarioStepItem>(steps.value, activeStep.value.uniqueId, 'uniqueId');
|
||||
if (replaceCsvId.value && realStep) {
|
||||
if (!!replaceCsvId.value && realStep !== null) {
|
||||
const index = realStep.csvIds.findIndex((item: string) => item === replaceCsvId.value);
|
||||
realStep.csvIds?.splice(index, 1, keys[0]);
|
||||
} else if (realStep) {
|
||||
realStep.csvIds?.push(...keys);
|
||||
if (!realStep.csvIds) {
|
||||
realStep.csvIds = [];
|
||||
}
|
||||
realStep.csvIds.splice(index, 1, keys[0]);
|
||||
} else if (realStep !== null) {
|
||||
realStep.csvIds = [...(realStep.csvIds || []), ...keys];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -146,8 +146,7 @@
|
|||
import { ScenarioExecuteStatus, ScenarioStepRefType, ScenarioStepType } from '@/enums/apiEnum';
|
||||
import { ApiTestRouteEnum } from '@/enums/routeEnum';
|
||||
|
||||
import { defaultCsvParamItem, defaultNormalParamItem } from '../components/config';
|
||||
import { defaultScenario } from './components/config';
|
||||
import { defaultCsvParamItem, defaultNormalParamItem, defaultScenario } from './components/config';
|
||||
import updateStepStatus, { getScenarioFileParams } from './components/utils';
|
||||
import {
|
||||
filterAssertions,
|
||||
|
@ -382,6 +381,11 @@
|
|||
if (defaultScenarioInfo) {
|
||||
const isCopy = action === 'copy';
|
||||
let copySteps: ScenarioStepItem[] = [];
|
||||
const copyCsvVariables = defaultScenarioInfo.scenarioConfig.variable.csvVariables.map((e) => ({
|
||||
...e,
|
||||
copyId: e.id,
|
||||
id: getGenerateId(),
|
||||
}));
|
||||
if (isCopy) {
|
||||
// 场景被复制,递归处理节点,增加copyFromStepId
|
||||
copySteps = mapTree(defaultScenarioInfo.steps, (node) => {
|
||||
|
@ -406,6 +410,9 @@
|
|||
// 非引用场景步骤
|
||||
node.id = getGenerateId(); // 重新生成 ID
|
||||
}
|
||||
if (node.csvIds && node.csvIds.length > 0) {
|
||||
node.csvIds = node.csvIds.map((e: string) => copyCsvVariables.find((c) => c.copyId === e)?.id || '');
|
||||
}
|
||||
node.uniqueId = node.id;
|
||||
return node;
|
||||
});
|
||||
|
@ -439,9 +446,17 @@
|
|||
...defaultScenarioInfo,
|
||||
steps: copySteps,
|
||||
id: isCopy ? getGenerateId() : defaultScenarioInfo.id || '',
|
||||
copyFromScenarioId: isCopy ? defaultScenarioInfo.id : '',
|
||||
label: isCopy ? copyName : defaultScenarioInfo.name,
|
||||
name: isCopy ? copyName : defaultScenarioInfo.name,
|
||||
isNew: isCopy,
|
||||
scenarioConfig: {
|
||||
...defaultScenarioInfo.scenarioConfig,
|
||||
variable: {
|
||||
commonVariables: defaultScenarioInfo.scenarioConfig.variable.commonVariables,
|
||||
csvVariables: copyCsvVariables,
|
||||
},
|
||||
},
|
||||
stepResponses: {},
|
||||
errorMessageInfo: {},
|
||||
});
|
||||
|
@ -531,7 +546,8 @@
|
|||
).validParams,
|
||||
csvVariables: filterKeyValParams(
|
||||
activeScenarioTab.value.scenarioConfig.variable.csvVariables,
|
||||
defaultCsvParamItem
|
||||
defaultCsvParamItem,
|
||||
true
|
||||
).validParams,
|
||||
},
|
||||
},
|
||||
|
@ -587,6 +603,17 @@
|
|||
preProcessorConfig: filterConditionsSqlValidParams(
|
||||
activeScenarioTab.value.scenarioConfig.preProcessorConfig
|
||||
),
|
||||
variable: {
|
||||
commonVariables: filterKeyValParams(
|
||||
activeScenarioTab.value.scenarioConfig.variable.commonVariables,
|
||||
defaultNormalParamItem
|
||||
).validParams,
|
||||
csvVariables: filterKeyValParams(
|
||||
activeScenarioTab.value.scenarioConfig.variable.csvVariables,
|
||||
defaultCsvParamItem,
|
||||
true
|
||||
).validParams,
|
||||
},
|
||||
},
|
||||
environmentId: appStore.getCurrentEnvId || '',
|
||||
steps: mapTree(activeScenarioTab.value.steps, (node) => {
|
||||
|
@ -621,7 +648,7 @@
|
|||
(e) => e.id === (typeof record === 'string' ? record : record.id)
|
||||
);
|
||||
if (isLoadedTabIndex > -1 && action !== 'copy') {
|
||||
// 如果点击的请求在tab中已经存在,则直接切换到该tab
|
||||
// 如果点击的场景在tab中已经存在,则直接切换到该tab
|
||||
activeScenarioTab.value = scenarioTabs.value[isLoadedTabIndex];
|
||||
// tab子组件里监听的是id变化,所以id相等的时候需要单独调执行
|
||||
if (action === 'execute') {
|
||||
|
|
|
@ -30,14 +30,14 @@
|
|||
</a-popover>
|
||||
</template>
|
||||
<template #right>
|
||||
<a-radio-group v-model:model-value="showType" type="button" size="small" class="list-show-type">
|
||||
<!-- <a-radio-group v-model:model-value="showType" type="button" size="small" class="list-show-type">
|
||||
<a-radio value="list" class="show-type-icon !m-[2px]">
|
||||
<MsIcon :size="14" type="icon-icon_view-list_outlined" />
|
||||
</a-radio>
|
||||
<!-- <a-radio value="xMind" class="show-type-icon !m-[2px]">
|
||||
<a-radio value="xMind" class="show-type-icon !m-[2px]">
|
||||
<MsIcon :size="14" type="icon-icon_mindnote_outlined" />
|
||||
</a-radio>-->
|
||||
</a-radio-group>
|
||||
</a-radio>
|
||||
</a-radio-group> -->
|
||||
</template>
|
||||
</MsAdvanceFilter>
|
||||
<ms-base-table
|
||||
|
@ -54,9 +54,9 @@
|
|||
@cell-click="handleCellClick"
|
||||
>
|
||||
<template #num="{ record }">
|
||||
<span type="text" class="one-line-text cursor-pointer px-0 text-[rgb(var(--primary-5))]">{{
|
||||
record.num
|
||||
}}</span>
|
||||
<span type="text" class="one-line-text cursor-pointer px-0 text-[rgb(var(--primary-5))]">
|
||||
{{ record.num }}
|
||||
</span>
|
||||
</template>
|
||||
<template #name="{ record }">
|
||||
<div class="one-line-text">{{ characterLimit(record.name) }}</div>
|
||||
|
@ -219,7 +219,12 @@
|
|||
</div>
|
||||
<div class="mt-[16px] h-[calc(100%-32px)] border-t border-[var(--color-text-n8)]">
|
||||
<!-- 脑图开始 -->
|
||||
<MsMinder minder-type="FeatureCase" :module-id="props.activeFolder" :module-name="props.moduleName" />
|
||||
<MsMinder
|
||||
minder-type="FeatureCase"
|
||||
:module-id="props.activeFolder"
|
||||
:modules-count="props.modulesCount"
|
||||
:module-name="props.moduleName"
|
||||
/>
|
||||
<MsDrawer v-model:visible="visible" :width="480" :mask="false">
|
||||
{{ nodeData.text }}
|
||||
</MsDrawer>
|
||||
|
|
Loading…
Reference in New Issue