feat(功能用例): 脑图加载用例&接口部分 bug 修复

This commit is contained in:
baiqi 2024-05-20 20:35:03 +08:00 committed by Craftsman
parent 98a580a427
commit d9ca6aabaa
24 changed files with 524 additions and 359 deletions

View File

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

View File

@ -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 });
} }
// 回收站 // 回收站

View File

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

View File

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

View File

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

View File

@ -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': '预期结果',

View File

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

View File

@ -109,5 +109,6 @@ export default {
delete: 'Delete', delete: 'Delete',
enterNode: 'Enter the current node', enterNode: 'Enter the current node',
}, },
loading: 'Mind map loading...',
}, },
}; };

View File

@ -103,5 +103,6 @@ export default {
delete: '删除', delete: '删除',
enterNode: '进入当前节点', enterNode: '进入当前节点',
}, },
loading: '脑图加载中...',
}, },
}; };

View File

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

View File

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

View File

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

View File

@ -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[];
// 前端渲染字段 // 前端渲染字段

View File

@ -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')"
/> />

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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') {
// tabtab // tabtab
activeScenarioTab.value = scenarioTabs.value[isLoadedTabIndex]; activeScenarioTab.value = scenarioTabs.value[isLoadedTabIndex];
// tabid,id // tabid,id
if (action === 'execute') { if (action === 'execute') {

View File

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