feat(功能用例): 脑图用例缺陷

This commit is contained in:
baiqi 2024-05-22 18:15:11 +08:00 committed by 刘瑞斌
parent 70fcf72a21
commit 43d6ed5916
5 changed files with 241 additions and 227 deletions

View File

@ -31,3 +31,14 @@
list-style: decimal !important; list-style: decimal !important;
} }
} }
.ms-comment-list {
@apply h-full;
.ms-comment-list-item {
@apply flex;
gap: 8px;
&:not(:last-child) {
margin-bottom: 16px;
}
}
}

View File

@ -61,7 +61,7 @@
<a-button type="secondary" :disabled="saveLoading">{{ t('common.cancel') }}</a-button> <a-button type="secondary" :disabled="saveLoading">{{ t('common.cancel') }}</a-button>
</div> </div>
</div> </div>
<div v-else-if="activeExtraKey === 'attachment'" class="pl-[16px]"> <div v-else-if="activeExtraKey === 'attachment'" class="h-full pl-[16px]">
<a-spin :loading="attachmentLoading" class="h-full w-full"> <a-spin :loading="attachmentLoading" class="h-full w-full">
<MsAddAttachment <MsAddAttachment
v-model:file-list="fileList" v-model:file-list="fileList"
@ -159,7 +159,7 @@
</MsFileList> </MsFileList>
</a-spin> </a-spin>
</div> </div>
<div v-else-if="activeExtraKey === 'comments'" class="pl-[16px]"> <div v-else-if="activeExtraKey === 'comments'" class="h-full pl-[16px]">
<div class="mb-[16px] flex items-center justify-between"> <div class="mb-[16px] flex items-center justify-between">
<div class="text-[var(--color-text-4)]"> <div class="text-[var(--color-text-4)]">
{{ {{
@ -175,21 +175,23 @@
@change="getAllCommentList" @change="getAllCommentList"
></a-select> ></a-select>
</div> </div>
<ReviewCommentList <div class="comment-container">
v-if="activeComment === 'reviewComment' || activeComment === 'executiveComment'" <ReviewCommentList
:review-comment-list="reviewCommentList" v-if="activeComment === 'reviewComment' || activeComment === 'executiveComment'"
:active-comment="activeComment" :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 v-else>
</template> <MsComment
:upload-image="handleUploadImage"
:comment-list="commentList"
:preview-url="PreviewEditorImageUrl"
@delete="handleDelete"
@update-or-add="handleUpdateOrAdd"
/>
<MsEmpty v-if="commentList.length === 0" />
</template>
</div>
<inputComment <inputComment
ref="commentInputRef" ref="commentInputRef"
v-model:content="content" v-model:content="content"
@ -205,27 +207,40 @@
@cancel="cancelPublish" @cancel="cancelPublish"
/> />
</div> </div>
<div v-else class="pl-[16px]"> <div v-else class="h-full pl-[16px]">
<a-button v-if="hasAnyPermission(['FUNCTIONAL_CASE:READ+UPDATE'])" class="mr-3" type="primary" @click="linkBug"> <a-button v-if="hasAnyPermission(['FUNCTIONAL_CASE:READ+UPDATE'])" class="mr-3" type="primary" @click="linkBug">
{{ t('caseManagement.featureCase.linkDefect') }} {{ t('caseManagement.featureCase.linkDefect') }}
</a-button> </a-button>
<a-button v-permission="['PROJECT_BUG:READ+ADD']" type="outline" @click="createBug" <a-button v-permission="['PROJECT_BUG:READ+ADD']" type="outline" @click="createBug">
>{{ t('caseManagement.featureCase.createDefect') }} {{ t('caseManagement.featureCase.createDefect') }}
</a-button> </a-button>
<div class="bug-list"> <MsList
<div v-for="item of bugList" :key="item.id" class="bug-item"> v-model:data="bugList"
<div class="mb-[4px] flex items-center justify-between"> mode="remote"
<MsButton type="text" @click="goBug(item.id)">{{ item.num }}</MsButton> item-key-field="id"
<MsButton type="text" @click="disassociateBug(item.id)"> :item-border="false"
{{ t('ms.add.attachment.cancelAssociate') }} class="my-[16px] h-[calc(100%-64px)] w-full rounded-[var(--border-radius-small)]"
</MsButton> :no-more-data="noMoreData"
raggable
:virtual-list-props="{
height: '100%',
}"
@reach-bottom="handleReachBottom"
>
<template #item="{ item }">
<div 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" position="tl">
<div class="one-line-text">{{ item.name }}</div>
</a-tooltip>
</div> </div>
<a-tooltip :content="item.name"> </template>
<div class="one-line-text">{{ item.name }}</div> </MsList>
</a-tooltip>
</div>
<MsEmpty v-if="bugList.length === 0" />
</div>
</div> </div>
</template> </template>
</MsMinderEditor> </MsMinderEditor>
@ -243,7 +258,7 @@
v-model:visible="showCreateBugDrawer" v-model:visible="showCreateBugDrawer"
:case-id="activeCase.id" :case-id="activeCase.id"
:extra-params="{ caseId: activeCase.id }" :extra-params="{ caseId: activeCase.id }"
@success="initBugList" @success="loadBugList"
/> />
<LinkDefectDrawer <LinkDefectDrawer
v-if="activeCase.id" v-if="activeCase.id"
@ -262,6 +277,7 @@
import MsEmpty from '@/components/pure/ms-empty/index.vue'; import MsEmpty from '@/components/pure/ms-empty/index.vue';
import MsFormCreate from '@/components/pure/ms-form-create/ms-form-create.vue'; import MsFormCreate from '@/components/pure/ms-form-create/ms-form-create.vue';
import { FormItem, FormRuleItem } from '@/components/pure/ms-form-create/types'; import { FormItem, FormRuleItem } from '@/components/pure/ms-form-create/types';
import MsList from '@/components/pure/ms-list/index.vue';
import MsMinderEditor from '@/components/pure/ms-minder-editor/minderEditor.vue'; import MsMinderEditor from '@/components/pure/ms-minder-editor/minderEditor.vue';
import type { MinderJson, MinderJsonNode, MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props'; import type { MinderJson, MinderJsonNode, MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue'; import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
@ -291,6 +307,7 @@
getCaseMinder, getCaseMinder,
getCaseModuleTree, getCaseModuleTree,
getCommentList, getCommentList,
getLinkedCaseBugList,
getReviewCommentList, getReviewCommentList,
getTestPlanExecuteCommentList, getTestPlanExecuteCommentList,
getTransferFileTree, getTransferFileTree,
@ -1031,70 +1048,6 @@
} }
} }
/**
* 处理脑图节点激活/点击
* @param node 被激活/点击的节点
*/
async function handleNodeClick(node: MinderJsonNode) {
const { data } = node;
if (data?.resource && data.resource.includes(caseTag)) {
extraVisible.value = true;
activeExtraKey.value = 'baseInfo';
initCaseDetail(data);
} 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); //
if (fakeNode) {
window.minder.removeNode(fakeNode);
}
if ((!res || res.length === 0) && node.children?.length) {
//
node.expand();
node.renderTree();
window.minder.layout();
return;
}
// TODO:
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.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();
}); });
@ -1285,55 +1238,37 @@
} }
const bugList = ref<any[]>([]); const bugList = ref<any[]>([]);
const noMoreData = ref<boolean>(false);
const pageNation = ref({
total: 0,
pageSize: 10,
current: 1,
});
async function initBugList() { async function loadBugList() {
bugList.value = [ try {
{ const res = await getLinkedCaseBugList({
name: 'sdasd', keyword: '',
id: '3d23d23d', projectId: appStore.currentProjectId,
num: 100001, caseId: activeCase.value.id,
}, current: pageNation.value.current || 1,
{ pageSize: pageNation.value.pageSize,
name: 'sdasd', });
id: '3d23d23d', res.list.forEach((item) => bugList.value.push(item));
num: 100002, pageNation.value.total = res.total;
}, } catch (error) {
{ // eslint-disable-next-line no-console
name: 'sdasd', console.log(error);
id: '3d23d23d', }
num: 100003, }
},
{ //
name: 'sdasd', function handleReachBottom() {
id: '3d23d23d', pageNation.value.current += 1;
num: 100004, if (pageNation.value.current > Math.ceil(pageNation.value.total / pageNation.value.pageSize)) {
}, return;
{ }
name: 'sdasd', loadBugList();
id: '3d23d23d',
num: 100005,
},
{
name: 'sdasd',
id: '3d23d23d',
num: 100006,
},
{
name: 'sdasd',
id: '3d23d23d',
num: 100007,
},
{
name: 'sdasd',
id: '3d23d23d',
num: 100008,
},
{
name: 'sdasd',
id: '3d23d23d',
num: 100009,
},
];
} }
const cancelLoading = ref<boolean>(false); const cancelLoading = ref<boolean>(false);
@ -1378,7 +1313,7 @@
drawerLoading.value = true; drawerLoading.value = true;
await associatedDebug(params); await associatedDebug(params);
Message.success(t('caseManagement.featureCase.associatedSuccess')); Message.success(t('caseManagement.featureCase.associatedSuccess'));
initBugList(); loadBugList();
showLinkBugDrawer.value = false; showLinkBugDrawer.value = false;
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
@ -1388,11 +1323,88 @@
} }
} }
function resetExtractInfo() {
activeCase.value = {};
pageNation.value.current = 1;
bugList.value = [];
commentList.value = [];
reviewCommentList.value = [];
fileList.value = [];
}
/**
* 处理脑图节点激活/点击
* @param node 被激活/点击的节点
*/
async function handleNodeClick(node: MinderJsonNode) {
const { data } = node;
if (data?.resource && data.resource.includes(caseTag)) {
extraVisible.value = true;
activeExtraKey.value = 'baseInfo';
resetExtractInfo();
initCaseDetail(data);
} 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); //
if (fakeNode) {
window.minder.removeNode(fakeNode);
}
if ((!res || res.length === 0) && node.children?.length) {
//
node.expand();
node.renderTree();
window.minder.layout();
return;
}
// TODO:
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.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;
resetExtractInfo();
}
}
watch( watch(
() => activeExtraKey.value, () => activeExtraKey.value,
(val) => { (val) => {
if (val === 'comments') { if (val === 'comments') {
getAllCommentList(); getAllCommentList();
} else if (val === 'bug') {
pageNation.value.current = 1;
loadBugList();
} }
} }
); );
@ -1408,29 +1420,27 @@
overflow-y: auto; overflow-y: auto;
height: calc(100% - 64px); height: calc(100% - 64px);
} }
.bug-list { .bug-item {
@apply cursor-pointer;
&:not(:last-child) {
margin-bottom: 8px;
}
padding: 8px;
border: 1px solid var(--color-text-n8);
border-radius: var(--border-radius-small);
background-color: white;
&:hover {
@apply relative;
background: var(--color-text-n9);
box-shadow: inset 0 0 0.5px 0.5px rgb(var(--primary-5));
}
}
.comment-container {
.ms-scroll-bar(); .ms-scroll-bar();
overflow-y: auto; overflow-y: auto;
margin-bottom: 16px; height: calc(100% - 130px);
height: calc(100% - 46px);
border-radius: var(--border-radius-small);
.bug-item {
@apply cursor-pointer;
&:not(:last-child) {
margin-bottom: 8px;
}
padding: 8px;
border: 1px solid var(--color-text-n8);
border-radius: var(--border-radius-small);
background-color: white;
&:hover {
@apply relative;
background: var(--color-text-n9);
box-shadow: inset 0 0 0.5px 0.5px rgb(var(--primary-5));
}
}
} }
</style> </style>

View File

@ -1,12 +1,17 @@
<template> <template>
<a-spin class="z-[100] !block" :loading="props.loading" :size="28"> <a-spin
class="z-[100] !block"
:class="props.autoHeight ? '' : 'h-full min-h-[500px]'"
:loading="props.loading"
:size="28"
>
<div <div
ref="fullRef" ref="fullRef"
:class="[ :class="[
'ms-card', 'ms-card',
'relative', 'relative',
'h-full',
props.isFullscreen || isFullScreen ? 'ms-card--fullScreen' : '', props.isFullscreen || isFullScreen ? 'ms-card--fullScreen' : '',
props.autoHeight ? '' : 'h-full min-h-[500px]',
props.noContentPadding ? 'ms-card--noContentPadding' : 'p-[16px]', props.noContentPadding ? 'ms-card--noContentPadding' : 'p-[16px]',
props.noBottomRadius ? 'ms-card--noBottomRadius' : '', props.noBottomRadius ? 'ms-card--noBottomRadius' : '',
!props.hideFooter && !props.simple ? 'pb-[24px]' : '', !props.hideFooter && !props.simple ? 'pb-[24px]' : '',

View File

@ -1,14 +1,13 @@
<template> <template>
<div class="flex flex-1 flex-col overflow-hidden"> <div class="flex flex-1 flex-col overflow-hidden">
<div class="review-history-list"> <div class="ms-comment-list">
<div v-for="item of props.reviewCommentList" :key="item.id" class="review-history-list-item"> <div v-for="item of props.reviewCommentList" :key="item.id" class="ms-comment-list-item">
<div class="flex items-center"> <MSAvatar :avatar="item.userLogo" />
<MSAvatar :avatar="item.userLogo" /> <div class="flex-1">
<div class="ml-[8px] flex items-center"> <div class="flex items-center gap-[8px]">
<a-tooltip :content="item.userName" :mouse-enter-delay="300"> <a-tooltip :content="item.userName" :mouse-enter-delay="300">
<div class="one-line-text max-w-[300px] font-medium text-[var(--color-text-1)]">{{ item.userName }}</div> <div class="one-line-text max-w-[300px] font-medium text-[var(--color-text-1)]">{{ item.userName }}</div>
</a-tooltip> </a-tooltip>
<a-divider direction="vertical" margin="8px"></a-divider>
<div v-if="item.status === 'PASS'" class="flex items-center"> <div v-if="item.status === 'PASS'" class="flex items-center">
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" /> <MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
{{ t('caseManagement.caseReview.pass') }} {{ t('caseManagement.caseReview.pass') }}
@ -38,39 +37,39 @@
{{ t('common.fail') }} {{ t('common.fail') }}
</div> </div>
</div> </div>
</div> <div class="markdown-body" v-html="item.contentText"></div>
<div class="markdown-body" style="margin-left: 48px" v-html="item.contentText"></div> <div class="mt-[8px] flex text-[12px] leading-[16px] text-[var(--color-text-4)]">
<div class="ml-[48px] mt-[8px] flex text-[var(--color-text-4)]"> {{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }} <div v-if="props.activeComment === 'reviewComment'">
<div v-if="props.activeComment === 'reviewComment'"> <a-tooltip :content="item.reviewName" :mouse-enter-delay="300">
<a-tooltip :content="item.reviewName" :mouse-enter-delay="300"> <span v-if="item.deleted" class="one-line-text ml-[16px] max-w-[300px] break-words break-all">
<span v-if="item.deleted" class="one-line-text ml-[16px] max-w-[300px] break-words break-all"> {{ characterLimit(item.reviewName) }}
{{ characterLimit(item.reviewName) }} </span>
</span>
<span <span
v-else v-else
class="one-line-text ml-[16px] max-w-[300px] cursor-pointer break-words break-all text-[rgb(var(--primary-5))]" class="one-line-text ml-[16px] max-w-[300px] cursor-pointer break-words break-all text-[rgb(var(--primary-5))]"
@click="review(item)" @click="review(item)"
> >
{{ characterLimit(item.reviewName) }} {{ characterLimit(item.reviewName) }}
</span> </span>
</a-tooltip> </a-tooltip>
</div> </div>
<div v-if="props.activeComment === 'executiveComment'"> <div v-if="props.activeComment === 'executiveComment'">
<a-tooltip :content="item.testPlanName" :mouse-enter-delay="300"> <a-tooltip :content="item.testPlanName" :mouse-enter-delay="300">
<span v-if="item.deleted" class="one-line-text ml-[16px] max-w-[300px] break-words break-all"> <span v-if="item.deleted" class="one-line-text ml-[16px] max-w-[300px] break-words break-all">
{{ characterLimit(item.testPlanName) }} {{ characterLimit(item.testPlanName) }}
</span> </span>
<span <span
v-else v-else
class="one-line-text ml-[16px] max-w-[300px] cursor-pointer break-words break-all text-[rgb(var(--primary-5))]" class="one-line-text ml-[16px] max-w-[300px] cursor-pointer break-words break-all text-[rgb(var(--primary-5))]"
@click="toPlan(item)" @click="toPlan(item)"
> >
{{ characterLimit(item.testPlanName) }} {{ characterLimit(item.testPlanName) }}
</span> </span>
</a-tooltip> </a-tooltip>
</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -194,15 +194,14 @@
:show-empty="false" :show-empty="false"
/> />
</div> </div>
<div v-else class="flex flex-1 flex-col overflow-hidden"> <div v-else class="flex flex-1 flex-col overflow-hidden pl-[16px] pt-[16px]">
<div class="review-history-list"> <div class="ms-comment-list">
<a-spin :loading="reviewHistoryListLoading" class="h-full w-full"> <a-spin :loading="reviewHistoryListLoading" class="h-full w-full">
<div v-for="item of reviewHistoryList" :key="item.id" class="review-history-list-item"> <div v-for="item of reviewHistoryList" :key="item.id" class="ms-comment-list-item">
<div class="flex items-center"> <MSAvatar :avatar="item.userLogo" />
<MSAvatar :avatar="item.userLogo" /> <div class="flex-1">
<div class="ml-[8px] flex items-center"> <div class="flex items-center gap-[8px]">
<div class="font-medium text-[var(--color-text-1)]">{{ item.userName }}</div> <div class="font-medium text-[var(--color-text-1)]">{{ item.userName }}</div>
<a-divider direction="vertical" margin="8px"></a-divider>
<div v-if="item.status === 'PASS'" class="flex items-center"> <div v-if="item.status === 'PASS'" class="flex items-center">
<MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" /> <MsIcon type="icon-icon_succeed_filled" class="mr-[4px] text-[rgb(var(--success-6))]" />
{{ t('caseManagement.caseReview.pass') }} {{ t('caseManagement.caseReview.pass') }}
@ -220,10 +219,10 @@
{{ t('caseManagement.caseReview.reReview') }} {{ t('caseManagement.caseReview.reReview') }}
</div> </div>
</div> </div>
</div> <div class="markdown-body ml-[48px]" v-html="item.contentText"></div>
<div class="markdown-body ml-[48px]" v-html="item.contentText"></div> <div class="mt-[8px] text-[12px] leading-[16px] text-[var(--color-text-4)]">
<div class="ml-[48px] mt-[8px] text-[var(--color-text-4)]"> {{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }}
{{ dayjs(item.createTime).format('YYYY-MM-DD HH:mm:ss') }} </div>
</div> </div>
</div> </div>
<MsEmpty v-if="reviewHistoryList.length === 0" /> <MsEmpty v-if="reviewHistoryList.length === 0" />
@ -640,16 +639,6 @@
padding: 16px; padding: 16px;
align-content: start; align-content: start;
.review-history-list {
@apply h-full;
padding: 16px 0 0 16px;
.review-history-list-item {
&:not(:last-child) {
margin-bottom: 16px;
}
}
}
} }
.content-footer { .content-footer {
padding: 16px; padding: 16px;