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;
}
}
.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>
</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">
<MsAddAttachment
v-model:file-list="fileList"
@ -159,7 +159,7 @@
</MsFileList>
</a-spin>
</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="text-[var(--color-text-4)]">
{{
@ -175,21 +175,23 @@
@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"
<div class="comment-container">
<ReviewCommentList
v-if="activeComment === 'reviewComment' || activeComment === 'executiveComment'"
:review-comment-list="reviewCommentList"
:active-comment="activeComment"
/>
<MsEmpty v-if="commentList.length === 0" />
</template>
<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>
</div>
<inputComment
ref="commentInputRef"
v-model:content="content"
@ -205,27 +207,40 @@
@cancel="cancelPublish"
/>
</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">
{{ t('caseManagement.featureCase.linkDefect') }}
</a-button>
<a-button v-permission="['PROJECT_BUG:READ+ADD']" type="outline" @click="createBug"
>{{ t('caseManagement.featureCase.createDefect') }}
<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>
<MsList
v-model:data="bugList"
mode="remote"
item-key-field="id"
:item-border="false"
class="my-[16px] h-[calc(100%-64px)] w-full rounded-[var(--border-radius-small)]"
: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>
<a-tooltip :content="item.name">
<div class="one-line-text">{{ item.name }}</div>
</a-tooltip>
</div>
<MsEmpty v-if="bugList.length === 0" />
</div>
</template>
</MsList>
</div>
</template>
</MsMinderEditor>
@ -243,7 +258,7 @@
v-model:visible="showCreateBugDrawer"
:case-id="activeCase.id"
:extra-params="{ caseId: activeCase.id }"
@success="initBugList"
@success="loadBugList"
/>
<LinkDefectDrawer
v-if="activeCase.id"
@ -262,6 +277,7 @@
import MsEmpty from '@/components/pure/ms-empty/index.vue';
import MsFormCreate from '@/components/pure/ms-form-create/ms-form-create.vue';
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 type { MinderJson, MinderJsonNode, MinderJsonNodeData } from '@/components/pure/ms-minder-editor/props';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
@ -291,6 +307,7 @@
getCaseMinder,
getCaseModuleTree,
getCommentList,
getLinkedCaseBugList,
getReviewCommentList,
getTestPlanExecuteCommentList,
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(() => {
initDefaultFields();
});
@ -1285,55 +1238,37 @@
}
const bugList = ref<any[]>([]);
const noMoreData = ref<boolean>(false);
const pageNation = ref({
total: 0,
pageSize: 10,
current: 1,
});
async function initBugList() {
bugList.value = [
{
name: 'sdasd',
id: '3d23d23d',
num: 100001,
},
{
name: 'sdasd',
id: '3d23d23d',
num: 100002,
},
{
name: 'sdasd',
id: '3d23d23d',
num: 100003,
},
{
name: 'sdasd',
id: '3d23d23d',
num: 100004,
},
{
name: 'sdasd',
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,
},
];
async function loadBugList() {
try {
const res = await getLinkedCaseBugList({
keyword: '',
projectId: appStore.currentProjectId,
caseId: activeCase.value.id,
current: pageNation.value.current || 1,
pageSize: pageNation.value.pageSize,
});
res.list.forEach((item) => bugList.value.push(item));
pageNation.value.total = res.total;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
//
function handleReachBottom() {
pageNation.value.current += 1;
if (pageNation.value.current > Math.ceil(pageNation.value.total / pageNation.value.pageSize)) {
return;
}
loadBugList();
}
const cancelLoading = ref<boolean>(false);
@ -1378,7 +1313,7 @@
drawerLoading.value = true;
await associatedDebug(params);
Message.success(t('caseManagement.featureCase.associatedSuccess'));
initBugList();
loadBugList();
showLinkBugDrawer.value = false;
} catch (error) {
// 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(
() => activeExtraKey.value,
(val) => {
if (val === 'comments') {
getAllCommentList();
} else if (val === 'bug') {
pageNation.value.current = 1;
loadBugList();
}
}
);
@ -1408,29 +1420,27 @@
overflow-y: auto;
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();
overflow-y: auto;
margin-bottom: 16px;
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));
}
}
height: calc(100% - 130px);
}
</style>

View File

@ -1,12 +1,17 @@
<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
ref="fullRef"
:class="[
'ms-card',
'relative',
'h-full',
props.isFullscreen || isFullScreen ? 'ms-card--fullScreen' : '',
props.autoHeight ? '' : 'h-full min-h-[500px]',
props.noContentPadding ? 'ms-card--noContentPadding' : 'p-[16px]',
props.noBottomRadius ? 'ms-card--noBottomRadius' : '',
!props.hideFooter && !props.simple ? 'pb-[24px]' : '',

View File

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

View File

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