feat: 评论组件修改
This commit is contained in:
parent
efddde8138
commit
d6404ac984
|
@ -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 });
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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)}
|
||||
</>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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; // 评论类型
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -410,6 +410,9 @@
|
|||
const handleCreate = () => {
|
||||
router.push({
|
||||
name: RouteEnum.BUG_MANAGEMENT_DETAIL,
|
||||
params: {
|
||||
mode: 'add',
|
||||
},
|
||||
});
|
||||
};
|
||||
const handleSync = () => {
|
||||
|
|
|
@ -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]">
|
||||
|
|
|
@ -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(
|
||||
|
|
Loading…
Reference in New Issue