feat(缺陷管理): 评论接口对接
This commit is contained in:
parent
4bc05648fc
commit
a81a8cba2b
|
@ -1,7 +1,9 @@
|
|||
import { CommentParams } from '@/components/business/ms-comment/types';
|
||||
|
||||
import MSR from '@/api/http/index';
|
||||
import * as bugURL from '@/api/requrls/bug-management';
|
||||
|
||||
import { BugEditFormObject, BugExportParams, BugListItem } from '@/models/bug-management';
|
||||
import { BugEditFormObject, BugExportParams, BugListItem, CreateOrUpdateComment } from '@/models/bug-management';
|
||||
import { AssociatedList } from '@/models/caseManagement/featureCase';
|
||||
import { CommonList, TableQueryParams, TemplateOption } from '@/models/common';
|
||||
|
||||
|
@ -89,3 +91,19 @@ export function followBug(id: string, isFollow: boolean) {
|
|||
}
|
||||
return MSR.get({ url: `${bugURL.getFollowBugUrl}${id}` });
|
||||
}
|
||||
|
||||
// 创建评论
|
||||
export function createOrUpdateComment(data: CommentParams) {
|
||||
if (data.id) {
|
||||
return MSR.post({ url: bugURL.postUpdateCommentUrl, data });
|
||||
}
|
||||
return MSR.post({ url: bugURL.postCreateCommentUrl, data });
|
||||
}
|
||||
// 获取评论列表
|
||||
export function getCommentList(bugId: string) {
|
||||
return MSR.get({ url: `${bugURL.getCommentListUrl}${bugId}` });
|
||||
}
|
||||
// 删除评论
|
||||
export function deleteComment(commentId: string) {
|
||||
return MSR.get({ url: `${bugURL.getDeleteCommentUrl}${commentId}` });
|
||||
}
|
||||
|
|
|
@ -14,3 +14,7 @@ export const postAssociatedFileListUrl = '/bug/relate/case/page';
|
|||
export const getBugDetailUrl = '/bug/detail/';
|
||||
export const getFollowBugUrl = '/bug/follow/';
|
||||
export const getUnFollowBugUrl = '/bug/unfollow/';
|
||||
export const postUpdateCommentUrl = '/bug/comment/update';
|
||||
export const postCreateCommentUrl = '/bug/comment/add';
|
||||
export const getCommentListUrl = '/bug/comment/get/';
|
||||
export const getDeleteCommentUrl = '/bug/comment/delete/';
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div class="flex flex-col">
|
||||
<MsAvatar avatar="default" />
|
||||
<div class="flex flex-row gap-[8px]">
|
||||
<MsAvatar avatar="word" />
|
||||
<div class="flex flex-col">
|
||||
<div class="text-[var(--color-text-1)]">{{ props.element.createUser }}</div>
|
||||
<div v-dompurify-html="props.element.content" class="mt-[4px]"></div>
|
||||
|
@ -9,22 +9,26 @@
|
|||
dayjs(props.element.updateTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}}</div>
|
||||
<div class="ml-[24px] flex flex-row gap-[16px]">
|
||||
<div v-if="props.mode === 'parent'" class="comment-btn" @click="expendChange">
|
||||
<div
|
||||
v-if="props.mode === 'parent' && element.childComments?.length"
|
||||
class="comment-btn"
|
||||
@click="expendChange"
|
||||
>
|
||||
<MsIconfont type="icon-icon_comment_outlined" />
|
||||
<span>{{ !expendComment ? t('comment.expendComment') : t('comment.collapseComment') }}</span>
|
||||
<span class="text-[var(--color-text-4)]">({{ element.children?.length }})</span>
|
||||
<span>{{ !expendComment ? t('ms.comment.expendComment') : t('ms.comment.collapseComment') }}</span>
|
||||
<span class="text-[var(--color-text-4)]">({{ element.childComments?.length }})</span>
|
||||
</div>
|
||||
<div class="comment-btn" @click="replyClick">
|
||||
<MsIconfont type="icon-icon_reply" />
|
||||
<span>{{ t('comment.reply') }}</span>
|
||||
<span>{{ t('ms.comment.reply') }}</span>
|
||||
</div>
|
||||
<div v-if="hasEditAuth" class="comment-btn" @click="editClick">
|
||||
<MsIconfont type="icon-icon_edit_outlined" />
|
||||
<span>{{ t('comment.edit') }}</span>
|
||||
<span>{{ t('ms.comment.edit') }}</span>
|
||||
</div>
|
||||
<div v-if="hasEditAuth" class="comment-btn" @click="deleteClick">
|
||||
<MsIconfont type="icon-icon_delete-trash_outlined" />
|
||||
<span>{{ t('comment.delete') }}</span>
|
||||
<span>{{ t('ms.comment.delete') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -40,18 +44,21 @@
|
|||
import MsIconfont from '@/components/pure/ms-icon-font/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useUserStore from '@/store/modules/user/index';
|
||||
|
||||
import { CommentItem } from './types';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{
|
||||
element: CommentItem; // 评论的具体内容
|
||||
mode: 'parent' | 'child'; // 父级评论还是子级评论
|
||||
currentUserId: string; // 当前用户id
|
||||
}>();
|
||||
|
||||
// 是否拥有编辑|删除权限
|
||||
const hasEditAuth = computed(() => {
|
||||
return props.element.commentUserInfo.id === props.currentUserId;
|
||||
return props.element.commentUserInfo.id === userStore.id;
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
|
@ -78,8 +85,6 @@
|
|||
const deleteClick = () => {
|
||||
emit('delete');
|
||||
};
|
||||
|
||||
const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
// eslint-disable-next-line no-shadow
|
||||
|
||||
import Item from './comment-item.vue';
|
||||
import CommentInput from './input.vue';
|
||||
|
||||
|
@ -10,11 +12,6 @@ import message from '@arco-design/web-vue/es/message';
|
|||
export default defineComponent({
|
||||
name: 'MsComment',
|
||||
props: {
|
||||
currentUserId: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
|
||||
commentList: {
|
||||
type: Array as PropType<CommentItem[]>,
|
||||
default: () => [],
|
||||
|
@ -22,12 +19,11 @@ export default defineComponent({
|
|||
},
|
||||
emits: {
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
updateOrAdd: (value: CommentParams) => true, // 更新或者新增评论
|
||||
delete: (value: string, cb: (result: boolean) => void) => true, // 删除评论
|
||||
updateOrAdd: (value: CommentParams, cb: (result: boolean) => void) => true, // 更新或者新增评论
|
||||
delete: (value: string) => true, // 删除评论
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const { currentUserId } = toRefs(props);
|
||||
const commentList = ref<CommentItem[]>([]);
|
||||
const { commentList } = toRefs(props);
|
||||
const currentItem = reactive<{ id: string; parentId: string }>({ id: '', parentId: '' });
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
|
@ -38,27 +34,27 @@ export default defineComponent({
|
|||
content,
|
||||
event: 'REPLAY',
|
||||
};
|
||||
emit('updateOrAdd', params);
|
||||
emit('updateOrAdd', params, (result: boolean) => {
|
||||
if (result) {
|
||||
message.success(t('common.publishSuccess'));
|
||||
} else {
|
||||
message.error(t('common.publishFail'));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const handleDelete = (item: CommentItem) => {
|
||||
openModal({
|
||||
type: 'error',
|
||||
title: t('comment.deleteConfirm'),
|
||||
content: t('comment.deleteContent'),
|
||||
okText: t('common.confirmClose'),
|
||||
title: t('ms.comment.deleteConfirm'),
|
||||
content: t('ms.comment.deleteContent'),
|
||||
okText: t('common.confirmDelete'),
|
||||
cancelText: t('common.cancel'),
|
||||
okButtonProps: {
|
||||
status: 'danger',
|
||||
},
|
||||
onBeforeOk: async () => {
|
||||
emit('delete', item.id, (result: boolean) => {
|
||||
if (result) {
|
||||
message.success(t('common.deleteSuccess'));
|
||||
} else {
|
||||
message.error(t('common.deleteFail'));
|
||||
}
|
||||
});
|
||||
emit('delete', item.id);
|
||||
},
|
||||
hideCancel: false,
|
||||
});
|
||||
|
@ -81,7 +77,7 @@ export default defineComponent({
|
|||
}
|
||||
return list.map((item) => {
|
||||
return (
|
||||
<div class="flex flex-col gap-[24px]">
|
||||
<div class="flex flex-col">
|
||||
<Item
|
||||
onReply={() => {
|
||||
currentItem.id = item.id;
|
||||
|
@ -93,7 +89,6 @@ export default defineComponent({
|
|||
}}
|
||||
onDelete={() => handleDelete(item)}
|
||||
mode={'child'}
|
||||
currentUserId={currentUserId.value}
|
||||
element={item}
|
||||
/>
|
||||
{item.id === currentItem.id && renderInput(item)}
|
||||
|
@ -105,13 +100,15 @@ export default defineComponent({
|
|||
const renderParentList = (list: CommentItem[]) => {
|
||||
return list.map((item) => {
|
||||
return (
|
||||
<Item mode={'parent'} onDelete={() => handleDelete(item)} currentUserId={currentUserId.value} element={item}>
|
||||
<div class="rounded border border-[var(--color-text-7)] p-[16px]">{renderChildrenList(item.children)}</div>
|
||||
<Item mode={'parent'} onDelete={() => handleDelete(item)} element={item}>
|
||||
<div class="rounded border border-[var(--color-text-7)] p-[16px]">
|
||||
{renderChildrenList(item.childComments)}
|
||||
</div>
|
||||
</Item>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return () => <div class="ms-comment">{renderParentList(commentList.value)}</div>;
|
||||
return () => <div class="ms-comment gap[24px] flex flex-col">{renderParentList(commentList.value)}</div>;
|
||||
},
|
||||
});
|
||||
|
|
|
@ -9,4 +9,5 @@ const MsComment = Object.assign(_Comment, {
|
|||
|
||||
export type CommentInstance = InstanceType<typeof _Comment>;
|
||||
|
||||
export { default as CommentInput } from './input.vue';
|
||||
export default MsComment;
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
export default {
|
||||
'ms.comment.expendComment': 'Expand comment',
|
||||
'ms.comment.collapseComment': 'Collapse comment',
|
||||
'ms.comment.edit': 'Edit',
|
||||
'ms.comment.reply': 'Reply',
|
||||
'ms.comment.delete': 'Delete',
|
||||
'ms.comment.deleteConfirm': 'Are you sure you want to delete this comment?',
|
||||
'ms.comment.deleteContent': 'After deletion, the comment cannot be replied to. Please proceed with caution.',
|
||||
'ms.comment.enterPlaceHolderTip': 'Please enter a comment and press Enter to finish.',
|
||||
};
|
|
@ -1,8 +0,0 @@
|
|||
export default {
|
||||
'comment.expendComment': 'Expand comment',
|
||||
'comment.collapseComment': 'Collapse comment',
|
||||
'comment.reply': 'Reply',
|
||||
'comment.delete': 'Delete',
|
||||
'comment.edit': 'Edit',
|
||||
'comment.enterPlaceHolderTip': 'Please enter a comment and press enter to end',
|
||||
};
|
|
@ -0,0 +1,10 @@
|
|||
export default {
|
||||
'ms.comment.expendComment': '展开评论',
|
||||
'ms.comment.collapseComment': '收起评论',
|
||||
'ms.comment.edit': '编辑',
|
||||
'ms.comment.reply': '回复',
|
||||
'ms.comment.delete': '删除',
|
||||
'ms.comment.deleteConfirm': '确认删除该评论吗?',
|
||||
'ms.comment.deleteContent': '删除后,评论无法回复,请谨慎操作',
|
||||
'ms.comment.enterPlaceHolderTip': '请输入评论,回车结束',
|
||||
};
|
|
@ -1,10 +0,0 @@
|
|||
export default {
|
||||
'comment.expendComment': '展开评论',
|
||||
'comment.collapseComment': '收起评论',
|
||||
'comment.edit': '编辑',
|
||||
'comment.reply': '回复',
|
||||
'comment.delete': '删除',
|
||||
'comment.deleteConfirm': '确认删除该评论吗?',
|
||||
'comment.deleteContent': '删除后,评论无法回复,请谨慎操作',
|
||||
'comment.enterPlaceHolderTip': '请输入评论,回车结束',
|
||||
};
|
|
@ -14,7 +14,7 @@ export interface CommentItem {
|
|||
commentUserInfo: CommentUserInfo; // 评论人用户信息
|
||||
replyUser?: string; // 回复人
|
||||
notifier?: string; // 通知人
|
||||
children?: CommentItem[];
|
||||
childComments?: CommentItem[];
|
||||
}
|
||||
|
||||
// 仅评论: ’COMMENT‘; 评论并@: ’AT‘; 回复评论/回复并@: ’REPLAY‘;)
|
||||
|
@ -24,9 +24,11 @@ export interface WriteCommentProps {
|
|||
id?: string; // 评论id
|
||||
parentId?: string; // 父级评论id
|
||||
event: commentEvent; // 评论事件
|
||||
bugId: string; // bug id
|
||||
bugId?: string; // bug id
|
||||
caseId?: string; // 用例id
|
||||
}
|
||||
export interface CommentParams extends WriteCommentProps {
|
||||
content: string;
|
||||
replyUser?: string; // 回复人
|
||||
notifiers?: string; // 通知人
|
||||
}
|
||||
|
|
|
@ -52,4 +52,14 @@ export interface BugBatchUpdateFiledForm {
|
|||
append: boolean;
|
||||
inputValue: string;
|
||||
}
|
||||
|
||||
export interface CreateOrUpdateComment {
|
||||
id?: string;
|
||||
bugId: string;
|
||||
notifier: string;
|
||||
replyUser: string;
|
||||
parentId: string;
|
||||
content: string;
|
||||
event: string; // 任务事件(仅评论: ’COMMENT‘; 评论并@: ’AT‘; 回复评论/回复并@: ’REPLAY‘;)
|
||||
}
|
||||
export default {};
|
||||
|
|
|
@ -78,15 +78,25 @@
|
|||
<template #first>
|
||||
<div class="leftWrapper h-full">
|
||||
<div class="header h-[50px]">
|
||||
<a-tabs v-model:active-key="activeTab">
|
||||
<a-tabs v-model:active-key="activeTab" lazy-load>
|
||||
<a-tab-pane key="detail">
|
||||
<template #title>
|
||||
{{ t('bugManagement.detail.detail') }}
|
||||
</template>
|
||||
<BugDetailTab :detail-info="detailInfo" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="case">
|
||||
<template #title>
|
||||
{{ t('bugManagement.detail.case') }}
|
||||
<a-badge class="relative top-1 ml-1" :count="1000" :max-count="99" />
|
||||
</template>
|
||||
<BugCaseTab :detail-info="detailInfo" />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="comment">
|
||||
<MsComment />
|
||||
<template #title>
|
||||
{{ t('bugManagement.detail.comment') }}
|
||||
</template>
|
||||
<CommentTab ref="commentRef" bug-id="1070838426116099" />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</div>
|
||||
|
@ -126,6 +136,13 @@
|
|||
</template>
|
||||
</MsSplitBox>
|
||||
</div>
|
||||
<CommentInput
|
||||
v-if="activeTab === 'comment'"
|
||||
:content="commentContent"
|
||||
is-show-avatar
|
||||
is-use-bottom
|
||||
@publish="publishHandler"
|
||||
/>
|
||||
</template>
|
||||
</MsDetailDrawer>
|
||||
</template>
|
||||
|
@ -143,19 +160,20 @@
|
|||
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
|
||||
import MsSplitBox from '@/components/pure/ms-split-box/index.vue';
|
||||
import type { MsPaginationI } from '@/components/pure/ms-table/type';
|
||||
import MsComment from '@/components/business/ms-comment';
|
||||
import { CommentInput } from '@/components/business/ms-comment';
|
||||
import { CommentParams } from '@/components/business/ms-comment/types';
|
||||
import MsDetailDrawer from '@/components/business/ms-detail-drawer/index.vue';
|
||||
import { MsUserSelector } from '@/components/business/ms-user-selector';
|
||||
import BugCaseTab from './bugCaseTab.vue';
|
||||
import BugDetailTab from './bugDetailTab.vue';
|
||||
import CommentTab from './commentTab.vue';
|
||||
|
||||
import { deleteSingleBug, followBug, getBugDetail } from '@/api/modules/bug-management/index';
|
||||
import { createOrUpdateComment, deleteSingleBug, followBug, getBugDetail } from '@/api/modules/bug-management/index';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
import { useAppStore } from '@/store';
|
||||
import useFeatureCaseStore from '@/store/modules/case/featureCase';
|
||||
import useUserStore from '@/store/modules/user';
|
||||
import { characterLimit, findNodeByKey } from '@/utils';
|
||||
import { characterLimit } from '@/utils';
|
||||
|
||||
import type { CaseManagementTable, CustomAttributes, TabItemType } from '@/models/caseManagement/featureCase';
|
||||
import { RouteEnum } from '@/enums/routeEnum';
|
||||
|
@ -165,7 +183,6 @@
|
|||
const wrapperRef = ref();
|
||||
const { isFullscreen, toggle } = useFullscreen(wrapperRef);
|
||||
const featureCaseStore = useFeatureCaseStore();
|
||||
const userStore = useUserStore();
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
|
||||
|
@ -180,8 +197,9 @@
|
|||
|
||||
const emit = defineEmits(['update:visible']);
|
||||
|
||||
const userId = computed(() => userStore.userInfo.id);
|
||||
const appStore = useAppStore();
|
||||
const commentContent = ref('');
|
||||
const commentRef = ref();
|
||||
|
||||
const currentProjectId = computed(() => appStore.currentProjectId);
|
||||
|
||||
|
@ -200,13 +218,9 @@
|
|||
function loadedBug(detail: CaseManagementTable) {
|
||||
detailInfo.value = { ...detail };
|
||||
customFields.value = detailInfo.value.customFields;
|
||||
detailInfo.value.id = '1070838426116099';
|
||||
}
|
||||
|
||||
const moduleName = computed(() => {
|
||||
return findNodeByKey<Record<string, any>>(featureCaseStore.caseTree, detailInfo.value?.moduleId as string, 'id')
|
||||
?.name;
|
||||
});
|
||||
|
||||
const editLoading = ref<boolean>(false);
|
||||
|
||||
function updateSuccess() {
|
||||
|
@ -331,6 +345,32 @@
|
|||
}) as FormItem[];
|
||||
}
|
||||
|
||||
async function publishHandler(currentContent: string) {
|
||||
const regex = /data-id="([^"]*)"/g;
|
||||
const matchesNotifier = currentContent.match(regex);
|
||||
let notifiers = '';
|
||||
if (matchesNotifier?.length) {
|
||||
notifiers = matchesNotifier.map((match) => match.replace('data-id="', '').replace('"', '')).join(';');
|
||||
}
|
||||
try {
|
||||
const params = {
|
||||
// TODO 本地测试
|
||||
bugId: detailInfo.value.id || '1070838426116099',
|
||||
notifier: notifiers,
|
||||
replyUser: '',
|
||||
parentId: '',
|
||||
content: currentContent,
|
||||
event: notifiers ? 'AT' : 'COMMENT', // 任务事件(仅评论: ’COMMENT‘; 评论并@: ’AT‘; 回复评论/回复并@: ’REPLAY‘;)
|
||||
};
|
||||
await createOrUpdateComment(params as CommentParams);
|
||||
Message.success(t('common.publishSuccessfully'));
|
||||
commentRef.value?.initData(detailInfo.value.id || '1070838426116099');
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(error);
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
() => customFields.value,
|
||||
() => {
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
<template>
|
||||
<MsComment :comment-list="commentList" @delete="handleDelete" @update-or-add="handleUpdate" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import MsComment from '@/components/business/ms-comment';
|
||||
import { CommentItem, CommentParams } from '@/components/business/ms-comment/types';
|
||||
|
||||
import { createOrUpdateComment, deleteComment, getCommentList } from '@/api/modules/bug-management/index';
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
import message from '@arco-design/web-vue/es/message';
|
||||
|
||||
const props = defineProps<{
|
||||
bugId: string;
|
||||
}>();
|
||||
const { t } = useI18n();
|
||||
|
||||
const commentList = ref<CommentItem[]>([]);
|
||||
|
||||
const initData = async (bugId: string) => {
|
||||
try {
|
||||
commentList.value = (await getCommentList(bugId)) || [];
|
||||
} catch (error) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleDelete = async (bugid: string) => {
|
||||
try {
|
||||
await deleteComment(bugid);
|
||||
message.success(t('common.deleteSuccess'));
|
||||
initData(bugid);
|
||||
} catch (error) {
|
||||
message.error(t('common.deleteFail'));
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdate = async (item: CommentParams, cb: (result: boolean) => void) => {
|
||||
try {
|
||||
await createOrUpdateComment(item);
|
||||
if (item.bugId) {
|
||||
initData(item.bugId);
|
||||
}
|
||||
cb(true);
|
||||
} catch (error) {
|
||||
cb(false);
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
if (props.bugId) {
|
||||
initData(props.bugId);
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({
|
||||
initData,
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
/* Your component styles here */
|
||||
</style>
|
|
@ -11,6 +11,7 @@
|
|||
>
|
||||
<template #headerRight>
|
||||
<a-select
|
||||
v-if="!isEdit"
|
||||
v-model="form.templateId"
|
||||
class="w-[240px]"
|
||||
:options="templateOption"
|
||||
|
|
|
@ -64,6 +64,9 @@ export default {
|
|||
basicInfo: '基本信息',
|
||||
handleUser: '处理人',
|
||||
tag: '标签',
|
||||
detail: '详情',
|
||||
case: '用例',
|
||||
comment: '评论',
|
||||
},
|
||||
batchUpdate: {
|
||||
attribute: '选择属性',
|
||||
|
|
Loading…
Reference in New Issue