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