feat: 评论组件优化
This commit is contained in:
parent
098ebaed1c
commit
b44e170c90
|
@ -1,11 +1,6 @@
|
|||
<template>
|
||||
<div class="flex flex-col">
|
||||
<div class="h-[40px] w-[40px] gap-[8px] rounded-full">
|
||||
<img
|
||||
src="https://p6-passport.byteacctimg.com/img/user-avatar/9a6e39ea689600e70175649a8cd14913~200x200.awebp"
|
||||
alt="User avatar"
|
||||
/>
|
||||
</div>
|
||||
<MsAvatar avatar="default" />
|
||||
<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>
|
||||
|
@ -14,7 +9,7 @@
|
|||
dayjs(props.element.updateTime).format('YYYY-MM-DD HH:mm:ss')
|
||||
}}</div>
|
||||
<div class="ml-[24px] flex flex-row gap-[16px]">
|
||||
<div class="comment-btn" @click="expendChange">
|
||||
<div v-if="props.mode === 'parent'" 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>
|
||||
|
@ -23,11 +18,11 @@
|
|||
<MsIconfont type="icon-icon_reply" />
|
||||
<span>{{ t('comment.reply') }}</span>
|
||||
</div>
|
||||
<div class="comment-btn" @click="editClick">
|
||||
<div v-if="hasEditAuth" class="comment-btn" @click="editClick">
|
||||
<MsIconfont type="icon-icon_edit_outlined" />
|
||||
<span>{{ t('comment.edit') }}</span>
|
||||
</div>
|
||||
<div class="comment-btn" @click="deleteClick">
|
||||
<div v-if="hasEditAuth" class="comment-btn" @click="deleteClick">
|
||||
<MsIconfont type="icon-icon_delete-trash_outlined" />
|
||||
<span>{{ t('comment.delete') }}</span>
|
||||
</div>
|
||||
|
@ -41,27 +36,24 @@
|
|||
import { ref } from 'vue';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
import MsAvatar from '@/components/pure/ms-avatar/index.vue';
|
||||
import MsIconfont from '@/components/pure/ms-icon-font/index.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
export interface CommentItem {
|
||||
id: string; // 评论id
|
||||
bugId: string; // bug id
|
||||
createUser: string; // 创建人
|
||||
updateTime: number; // 更新时间
|
||||
content: string;
|
||||
replyUser?: string; // 回复人
|
||||
notifier?: string; // 通知人
|
||||
children?: CommentItem[];
|
||||
}
|
||||
// 仅评论: ’COMMENT‘; 评论并@: ’AT‘; 回复评论/回复并@: ’REPLAY‘;)
|
||||
export type commentEvent = 'COMMENT' | 'AT' | 'REPLAY';
|
||||
import { CommentItem } from './types';
|
||||
|
||||
const props = defineProps<{
|
||||
element: CommentItem;
|
||||
element: CommentItem; // 评论的具体内容
|
||||
mode: 'parent' | 'child'; // 父级评论还是子级评论
|
||||
currentUserId: string; // 当前用户id
|
||||
}>();
|
||||
|
||||
// 是否拥有编辑|删除权限
|
||||
const hasEditAuth = computed(() => {
|
||||
return props.element.commentUserInfo.id === props.currentUserId;
|
||||
});
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'reply'): void;
|
||||
(event: 'edit'): void;
|
||||
|
|
|
@ -0,0 +1,110 @@
|
|||
import CommentInput from './input.vue';
|
||||
import Item from './item.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
import useModal from '@/hooks/useModal';
|
||||
|
||||
import { CommentItem, CommentParams } from './types';
|
||||
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: () => [],
|
||||
},
|
||||
},
|
||||
emits: {
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
updateOrAdd: (value: CommentParams) => true, // 更新或者新增评论
|
||||
delete: (value: string, cb: (result: boolean) => void) => true, // 删除评论
|
||||
},
|
||||
setup(props, { emit }) {
|
||||
const { currentUserId } = toRefs(props);
|
||||
const commentList = ref<CommentItem[]>([]);
|
||||
const currentItem = reactive<{ id: string; parentId: string }>({ id: '', parentId: '' });
|
||||
const { t } = useI18n();
|
||||
const { openModal } = useModal();
|
||||
|
||||
const handlePublish = (content: string, item: CommentItem) => {
|
||||
const params: CommentParams = {
|
||||
...item,
|
||||
content,
|
||||
event: 'REPLAY',
|
||||
};
|
||||
emit('updateOrAdd', params);
|
||||
};
|
||||
|
||||
const handleDelete = (item: CommentItem) => {
|
||||
openModal({
|
||||
type: 'error',
|
||||
title: t('comment.deleteConfirm'),
|
||||
content: t('comment.deleteContent'),
|
||||
okText: t('common.confirmClose'),
|
||||
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'));
|
||||
}
|
||||
});
|
||||
},
|
||||
hideCancel: false,
|
||||
});
|
||||
};
|
||||
|
||||
const renderInput = (item: CommentItem) => {
|
||||
return <CommentInput onPublish={(content: string) => handlePublish(content, item)} {...item} />;
|
||||
};
|
||||
|
||||
const renderChildrenList = (list?: CommentItem[]) => {
|
||||
if (!list || list.length === 0) {
|
||||
return null;
|
||||
}
|
||||
return list.map((item) => {
|
||||
return (
|
||||
<div class="flex flex-col gap-[24px]">
|
||||
<Item
|
||||
onReply={() => {
|
||||
currentItem.id = item.id;
|
||||
currentItem.parentId = item.parentId || '';
|
||||
}}
|
||||
onEdit={() => {
|
||||
currentItem.id = item.id;
|
||||
currentItem.parentId = item.parentId || '';
|
||||
}}
|
||||
onDelete={() => handleDelete(item)}
|
||||
mode={'child'}
|
||||
currentUserId={currentUserId.value}
|
||||
element={item}
|
||||
/>
|
||||
{item.id === currentItem.id && renderInput(item)}
|
||||
</div>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
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>
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
return () => <div class="ms-comment">{renderParentList(commentList.value)}</div>;
|
||||
},
|
||||
});
|
|
@ -0,0 +1,12 @@
|
|||
import _Comment from './comment';
|
||||
import type { App } from 'vue';
|
||||
|
||||
const MsComment = Object.assign(_Comment, {
|
||||
install: (app: App) => {
|
||||
app.component(_Comment.name, _Comment);
|
||||
},
|
||||
});
|
||||
|
||||
export type CommentInstance = InstanceType<typeof _Comment>;
|
||||
|
||||
export default MsComment;
|
|
@ -0,0 +1,49 @@
|
|||
<template>
|
||||
<div class="w-full">
|
||||
<a-input v-if="!isActive" class="w-full" @click="isActive = true"></a-input>
|
||||
<div v-else class="flex flex-col">
|
||||
<MsRichText v-model="currentContent" class="w-full" />
|
||||
<div class="flex flex-row justify-end gap-[12px]">
|
||||
<a-button @click="cancelClick">{{ t('common.cancel') }}</a-button>
|
||||
<a-button type="primary" :disabled="!content" @click="publish">{{ t('common.publish') }}</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
|
||||
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
|
||||
|
||||
import { useI18n } from '@/hooks/useI18n';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
const props = defineProps<{ content: string }>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(event: 'publish', value: string): void;
|
||||
}>();
|
||||
|
||||
const isActive = ref(false);
|
||||
const currentContent = ref('');
|
||||
|
||||
watchEffect(() => {
|
||||
if (props.content) {
|
||||
currentContent.value = props.content;
|
||||
}
|
||||
});
|
||||
|
||||
const publish = () => {
|
||||
emit('publish', currentContent.value);
|
||||
isActive.value = false;
|
||||
currentContent.value = '';
|
||||
};
|
||||
const cancelClick = () => {
|
||||
isActive.value = false;
|
||||
currentContent.value = '';
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
|
@ -4,4 +4,6 @@ export default {
|
|||
'comment.edit': '编辑',
|
||||
'comment.reply': '回复',
|
||||
'comment.delete': '删除',
|
||||
'comment.deleteConfirm': '确认删除该评论吗?',
|
||||
'comment.deleteContent': '删除后,评论无法回复,请谨慎操作',
|
||||
};
|
||||
|
|
|
@ -0,0 +1,32 @@
|
|||
export interface CommentUserInfo {
|
||||
id: string;
|
||||
name: string;
|
||||
email: string;
|
||||
}
|
||||
|
||||
export interface CommentItem {
|
||||
id: string; // 评论id
|
||||
parentId?: string; // 父级评论id
|
||||
bugId: string; // bug id
|
||||
createUser: string; // 创建人
|
||||
updateTime: number; // 更新时间
|
||||
content: string;
|
||||
commentUserInfo: CommentUserInfo; // 评论人用户信息
|
||||
replyUser?: string; // 回复人
|
||||
notifier?: string; // 通知人
|
||||
children?: CommentItem[];
|
||||
}
|
||||
|
||||
// 仅评论: ’COMMENT‘; 评论并@: ’AT‘; 回复评论/回复并@: ’REPLAY‘;)
|
||||
export type commentEvent = 'COMMENT' | 'AT' | 'REPLAY';
|
||||
|
||||
export interface WriteCommentProps {
|
||||
id?: string; // 评论id
|
||||
parentId?: string; // 父级评论id
|
||||
event: commentEvent; // 评论事件
|
||||
bugId: string; // bug id
|
||||
}
|
||||
export interface CommentParams extends WriteCommentProps {
|
||||
content: string;
|
||||
replyUser?: string; // 回复人
|
||||
}
|
|
@ -39,7 +39,7 @@
|
|||
</a-select>
|
||||
</a-form-item>
|
||||
</div>
|
||||
<div class="flex-1 grow-0">
|
||||
<div class="grow-0">
|
||||
<a-form-item
|
||||
:field="`list[${idx}].operator`"
|
||||
hide-asterisk
|
||||
|
|
|
@ -76,4 +76,5 @@ export default {
|
|||
'common.recycle': 'Recycle Bin',
|
||||
'common.new': 'New',
|
||||
'common.newSuccess': 'Added successfully',
|
||||
'common.publish': 'Publish',
|
||||
};
|
||||
|
|
|
@ -76,4 +76,5 @@ export default {
|
|||
'common.recycle': '回收站',
|
||||
'common.new': '新增',
|
||||
'common.newSuccess': '新增成功',
|
||||
'common.publish': '发布',
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue