feat: 评论组件修改

This commit is contained in:
RubyLiu 2024-02-19 17:33:09 +08:00 committed by rubylliu
parent efddde8138
commit d6404ac984
10 changed files with 106 additions and 46 deletions

View File

@ -97,7 +97,7 @@ export function followBug(id: string, isFollow: boolean) {
// 创建评论
export function createOrUpdateComment(data: CommentParams) {
if (data.id || data.event !== 'REPLAY') {
if (data.fetchType === 'UPDATE') {
return MSR.post({ url: bugURL.postUpdateCommentUrl, data });
}
return MSR.post({ url: bugURL.postCreateCommentUrl, data });

View File

@ -3,7 +3,7 @@
<div class="p-1"> <MsAvatar avatar="word" /></div>
<div class="flex w-full flex-col">
<div class="font-medium text-[var(--color-text-1)]">{{ props.element.createUser }}</div>
<div v-if="!isEdit" v-dompurify-html="props.element.content" class="mt-[4px] text-[var(--color-text-2)]"></div>
<div v-dompurify-html="props.element.content" class="mt-[4px] text-[var(--color-text-2)]"></div>
<div class="mb-4 mt-[16px] flex flex-row items-center">
<div class="text-[var(--color-text-4)]">{{
@ -19,15 +19,28 @@
<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 hover:bg-[rgb(var(--color-bg-3))]" @click="replyClick">
<div
class="comment-btn hover:bg-[var(--color-bg-3)]"
:class="{ 'bg-[var(--color-text-n8)]': status === 'reply' }"
@click="replyClick"
>
<MsIconfont type="icon-icon_reply" />
<span>{{ t('ms.comment.reply') }}</span>
</div>
<div v-if="hasEditAuth" class="comment-btn hover:bg-[rgb(var(--color-bg-3))]" @click="editClick">
<div
v-if="hasEditAuth"
class="comment-btn hover:bg-[var(--color-bg-3)]"
:class="{ 'bg-[var(--color-text-n8)]': status === 'edit' }"
@click="editClick"
>
<MsIconfont type="icon-icon_edit_outlined" />
<span>{{ t('ms.comment.edit') }}</span>
</div>
<div class="comment-btn hover:bg-[rgb(var(--danger-1))]" @click="deleteClick">
<div
class="comment-btn hover:bg-[rgb(var(--danger-1))]"
:class="{ 'bg-[rgb(var(--danger-2))]': status === 'delete' }"
@click="deleteClick"
>
<MsIconfont type="icon-icon_delete-trash_outlined" />
<span>{{ t('ms.comment.delete') }}</span>
</div>
@ -38,7 +51,7 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { defineModel, ref } from 'vue';
import dayjs from 'dayjs';
import MsAvatar from '@/components/pure/ms-avatar/index.vue';
@ -67,6 +80,8 @@
return props.element.createUser === userStore.id;
});
const status = defineModel<'normal' | 'edit' | 'reply' | 'delete'>('status', { default: 'normal' });
const emit = defineEmits<{
(event: 'reply'): void;
(event: 'edit'): void;
@ -74,22 +89,23 @@
}>();
const expendComment = ref(false);
const isEdit = ref(false);
const expendChange = () => {
expendComment.value = !expendComment.value;
};
const replyClick = () => {
emit('reply');
status.value = 'reply';
};
const editClick = () => {
isEdit.value = true;
emit('edit');
status.value = 'edit';
};
const deleteClick = () => {
emit('delete');
status.value = 'delete';
};
</script>

View File

@ -5,7 +5,7 @@ import CommentInput from './input.vue';
import { useI18n } from '@/hooks/useI18n';
import { CommentItem, CommentParams } from './types';
import { CommentItem, CommentParams, CommentType } from './types';
import message from '@arco-design/web-vue/es/message';
export default defineComponent({
@ -27,25 +27,45 @@ export default defineComponent({
},
setup(props, { emit }) {
const { commentList, disabled } = toRefs(props);
const currentItem = reactive<{ id: string; parentId: string; status: string }>({
const currentItem = reactive<{ id: string; commentType: CommentType; commentStatus: string }>({
id: '',
parentId: '',
status: 'add',
commentType: 'ADD',
// 控制回复编辑删除按钮的状态
commentStatus: 'normal',
});
// 被@的用户id
const noticeUserIds = ref<string[]>([]);
const { t } = useI18n();
const resetCurrentItem = () => {
currentItem.id = '';
currentItem.parentId = '';
currentItem.commentType = 'ADD';
currentItem.commentStatus = 'normal';
noticeUserIds.value = [];
};
const handlePublish = (content: string, item: CommentItem) => {
// 这个组件里的都是回复和编辑不涉及新增,所以是 COMMENT 或 REPLAY
let parentId = '';
if (currentItem.commentType === 'REPLY') {
parentId = item.id;
} else if (currentItem.commentType === 'EDIT') {
parentId = item.parentId || '';
}
const params: CommentParams = {
...item,
id: currentItem.id,
bugId: item.bugId,
content,
event: 'REPLAY',
status: currentItem.status,
event: noticeUserIds.value.length > 0 ? 'REPLAY' : 'COMMENT',
commentType: currentItem.commentType,
fetchType: currentItem.commentType === 'EDIT' ? 'UPDATE' : 'ADD',
notifier: noticeUserIds.value.join(';'),
replyUser: item.createUser,
parentId,
};
if (currentItem.commentType === 'EDIT') {
params.id = item.id;
}
emit('updateOrAdd', params, (result: boolean) => {
if (result) {
message.success(t('common.publishSuccessfully'));
@ -62,32 +82,31 @@ export default defineComponent({
const handleReply = (item: CommentItem) => {
if (item.childComments && Array.isArray(item.childComments)) {
// 父级评论
// 点击的是父级评论的回复
currentItem.id = item.id;
currentItem.parentId = '';
} else {
// 子级评论
currentItem.id = item.parentId || '';
currentItem.parentId = item.id;
}
currentItem.status = 'replay';
currentItem.commentType = 'REPLY';
};
const handelEdit = (item: CommentItem) => {
currentItem.id = item.id;
currentItem.parentId = item.parentId || '';
currentItem.status = 'edit';
currentItem.commentType = 'EDIT';
};
const noticeUserIds = ref<string[]>([]);
const renderInput = (item: CommentItem) => {
return (
<CommentInput
isShowAvatar={false}
isUseBottom={false}
onPublish={(content: string) => handlePublish(content, item)}
defaultValue={item.content || ''}
defaultValue={currentItem.commentType === 'EDIT' ? item.content : ''}
noticeUserIds={noticeUserIds.value}
onUpdate:noticeUserIds={(ids: string[]) => {
noticeUserIds.value = ids;
}}
onCancel={() => resetCurrentItem()}
{...item}
/>
@ -102,10 +121,14 @@ export default defineComponent({
return (
<div class="flex flex-col">
<Item
mode={'child'}
onReply={() => handleReply(item)}
onEdit={() => handelEdit(item)}
onDelete={() => handleDelete(item)}
mode={'child'}
status={item.id === currentItem.id ? currentItem.commentStatus : 'normal'}
onUpdate:status={(v: string) => {
currentItem.commentStatus = v;
}}
element={item}
/>
{item.id === currentItem.id && renderInput(item)}
@ -123,9 +146,15 @@ export default defineComponent({
onReply={() => handleReply(item)}
onEdit={() => handelEdit(item)}
onDelete={() => handleDelete(item)}
status={item.id === currentItem.id ? currentItem.commentStatus : 'normal'}
onUpdate:status={(v: string) => {
currentItem.commentStatus = v;
}}
element={item}
>
<div class="rounded border border-[var(--color-text-7)] p-[16px]"></div>
<div class="rounded border border-[var(--color-text-7)] p-[16px]">
{renderChildrenList(item.childComments)}
</div>
</Item>
{item.id === currentItem.id && renderInput(item)}
</>

View File

@ -8,7 +8,7 @@
>
<div v-if="props.isShowAvatar" class="mr-3 inline-block"> <MsAvatar avatar="word"></MsAvatar></div>
<div class="w-full items-center">
<a-input v-if="!isActive" class="w-full" @click="isActive = true"></a-input>
<a-input v-if="!isActive" class="w-full hover:border-[rgb(var(--primary-5))]" @click="isActive = true"></a-input>
<div v-else class="flex flex-col justify-between">
<MsRichText v-model:raw="currentContent" v-model:commentIds="commentIds" class="w-full" />
<div class="mt-4 flex flex-row justify-end gap-[12px]">
@ -21,8 +21,7 @@
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { useVModel } from '@vueuse/core';
import { defineModel, ref } from 'vue';
import MsAvatar from '@/components/pure/ms-avatar/index.vue';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
@ -33,25 +32,20 @@
const { t } = useI18n();
// const currentContent = defineModel<string>('content', { required: true });
const props = defineProps<{
isShowAvatar: boolean; //
isUseBottom: boolean; //
defaultValue?: string; //
noticeUserIds?: string[]; // id
}>();
const currentContent = ref(props.defaultValue || '');
const currentContent = defineModel<string>('defaultValue', { default: '' });
const commentIds = defineModel<string[]>('noticeUserIds', { default: [] });
const emit = defineEmits<{
(event: 'publish', value: string): void;
(event: 'cancel'): void;
}>();
const isActive = ref(false);
const commentIds = useVModel(props, 'noticeUserIds', emit);
const publish = () => {
emit('publish', currentContent.value);
isActive.value = false;

View File

@ -21,6 +21,9 @@ export interface CommentItem {
// 仅评论: COMMENT; 评论并@: AT; 回复评论/回复并@: REPLAY;)
export type CommentEvent = 'COMMENT' | 'AT' | 'REPLAY';
// 评论请求的时候是编辑还是新增
export type FetchType = 'ADD' | 'UPDATE';
export type CommentType = 'REPLY' | 'EDIT' | 'ADD';
export interface WriteCommentProps {
id?: string; // 评论id
@ -32,7 +35,7 @@ export interface WriteCommentProps {
export interface CommentParams extends WriteCommentProps {
content: string;
replyUser?: string; // 回复人
notifiers?: string; // 通知人
notifier?: string; // 通知人
status?: string; // 编辑还是新增
fetchType?: FetchType; // 发送后端请求类型 编辑还是新增
commentType?: CommentType; // 评论类型
}

View File

@ -12,8 +12,7 @@
* import rehypeStringify from 'rehype-stringify';
* return unified().use(rehypeParse).use(rehypeFormat).use(rehypeStringify).processSync(content.value);
*/
import { useDebounceFn, useLocalStorage } from '@vueuse/core';
import { useVModel } from '@vueuse/core';
import { useDebounceFn, useLocalStorage, useVModel } from '@vueuse/core';
import type { MsFileItem } from '@/components/pure/ms-upload/types';
import AttachmentSelectorModal from './attachmentSelectorModal.vue';
@ -95,6 +94,7 @@
maxHeight?: string;
filedIds?: string[];
commentIds?: string[];
wrapperClass?: string;
}>(),
{
raw: '',
@ -507,6 +507,9 @@
.rich-wrapper {
border: 1px solid var(--color-text-n8);
border-radius: var(--border-radius-small);
&:hover {
border-color: rgb(var(--primary-5));
}
@apply relative overflow-hidden;
:deep(.halo-rich-text-editor .ProseMirror) {
padding: 16px 24px !important;

View File

@ -58,8 +58,8 @@
const handleUpdate = async (item: CommentParams, cb: (result: boolean) => void) => {
try {
await createOrUpdateComment(item);
if (item.bugId) {
initData(item.bugId);
if (props.bugId) {
initData(props.bugId);
}
cb(true);
} catch (error) {

View File

@ -410,6 +410,9 @@
const handleCreate = () => {
router.push({
name: RouteEnum.BUG_MANAGEMENT_DETAIL,
params: {
mode: 'add',
},
});
};
const handleSync = () => {

View File

@ -26,6 +26,7 @@
:rules="[{ required: true, message: t('login.form.userName.errMsg') }]"
:validate-trigger="['change', 'blur']"
hide-label
maxlength="64"
>
<a-input v-model="userInfo.username" :max-length="255" :placeholder="t('login.form.userName.placeholder')" />
</a-form-item>
@ -40,6 +41,7 @@
v-model="userInfo.password"
:placeholder="t('login.form.password.placeholder')"
allow-clear
maxlength="64"
/>
</a-form-item>
<div class="mb-6 mt-[12px]">

View File

@ -11,7 +11,7 @@
>
<div>
<div class="flex flex-row justify-between">
<a-button type="primary" @click="handleAddMember">
<a-button v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ+UPDATE']" type="primary" @click="handleAddMember">
{{ t('system.organization.addMember') }}
</a-button>
<a-input-search
@ -33,6 +33,7 @@
</template>
<template #operation="{ record }">
<MsRemoveButton
v-permission="['SYSTEM_ORGANIZATIN_PROJECT:READ+DELETE']"
:title="t('system.organization.removeName', { name: characterLimit(record.name) })"
:sub-title-tip="props.organizationId ? t('system.organization.removeTip') : t('system.project.removeTip')"
@ok="handleRemove(record)"
@ -67,6 +68,7 @@
} from '@/api/modules/setting/organizationAndProject';
import { useI18n } from '@/hooks/useI18n';
import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
export interface projectDrawerProps {
visible: boolean;
@ -87,6 +89,14 @@
const keyword = ref('');
const hasOperationPermission = computed(() =>
hasAnyPermission([
'SYSTEM_ORGANIZATION_PROJECT:READ+RECOVER',
'SYSTEM_ORGANIZATION_PROJECT:READ+UPDATE',
'SYSTEM_ORGANIZATION_PROJECT:READ+DELETE',
])
);
const projectColumn: MsTableColumn = [
{
title: 'system.organization.userName',
@ -105,7 +115,7 @@
title: 'system.organization.phone',
dataIndex: 'phone',
},
{ title: 'system.organization.operation', slotName: 'operation', width: 60 },
{ title: hasOperationPermission.value ? 'system.organization.operation' : '', slotName: 'operation', width: 60 },
];
const { propsRes, propsEvent, loadList, setLoadListParams, setKeyword } = useTable(