242 lines
5.5 KiB
Vue
242 lines
5.5 KiB
Vue
<template>
|
||
<div v-if="items" class="tui-comment-list">
|
||
<div class="comment" v-for="comment in items" :key="comment.id">
|
||
<!-- 主评论 -->
|
||
<div class="user">
|
||
<!-- 主评论头像 -->
|
||
<template v-if="comment.user">
|
||
<a
|
||
:href="`/user/space/${comment.userId}`"
|
||
target="_blank"
|
||
v-popup:config="popupUserStore.config"
|
||
@mouseenter="popupUserStore.user.value = comment.user"
|
||
>
|
||
<img
|
||
class="avatar"
|
||
:class="(<any>ImageType)[comment.user.profile.avatarType]"
|
||
:src="UserAPI.getAvatarURL(comment.user.profile)"
|
||
alt="主评论头像"
|
||
/>
|
||
</a>
|
||
<a
|
||
class="name pink selectable"
|
||
:href="`/user/space/${comment.userId}`"
|
||
v-text="comment.user.name"
|
||
v-popup:config="popupUserStore.config"
|
||
@mouseenter="popupUserStore.user.value = comment.user"
|
||
></a>
|
||
</template>
|
||
<!-- 游客的 -->
|
||
<template v-else>
|
||
<img class="avatar ir-pixelated" :src="defAvatarURL" alt="头像" />
|
||
<div class="name gray selectable clip-text" v-text="comment.nick"></div>
|
||
</template>
|
||
</div>
|
||
<div class="content">
|
||
<!-- 主评论内容 -->
|
||
<markdown-view class="value" :content="comment.content" />
|
||
<div class="reply-to">
|
||
<a class="button" href="javascript:" @click="replyTo(comment)">回复</a>
|
||
<span
|
||
class="light-gray"
|
||
v-text="Time.toPassedDate(comment.createdAt)"
|
||
v-popup:text="Time.toDateTime(comment.createdAt)"
|
||
></span>
|
||
</div>
|
||
<!-- 子评论 -->
|
||
<comment-reply-list :comment="comment" @reply="replyTo" />
|
||
<t-pagination
|
||
class="pages"
|
||
v-if="6 < comment.repliesLength"
|
||
:total="comment.repliesLength"
|
||
:pageSize="6"
|
||
:showPageSize="false"
|
||
v-model:current="comment.repliesCurrent"
|
||
@change="() => doFetchReply(comment)"
|
||
>
|
||
<template #total-content>
|
||
<div class="total" v-text="`共 ${comment.repliesLength} 条回复`"></div>
|
||
</template>
|
||
</t-pagination>
|
||
<!-- 回复表单,此表单跟随(页面上)激活的回复对象 -->
|
||
<comment-reply-form
|
||
v-if="canReply && comment.id && activeReply === comment.id"
|
||
:commentId="comment.id"
|
||
:replyId="replyId"
|
||
:replyToNick="replyToNick"
|
||
@submit="onSubmitReply"
|
||
@cancel="activeReply = -1"
|
||
/>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script lang="ts" setup>
|
||
import {
|
||
CommentAPI,
|
||
CommentReply,
|
||
CommentReplyView,
|
||
CommentView,
|
||
CommonAPI,
|
||
ImageType,
|
||
MarkdownView,
|
||
SettingKey,
|
||
SettingMapper,
|
||
Time,
|
||
UserAPI
|
||
} from "timi-web";
|
||
import { CommentReplyForm, CommentReplyList } from "~/components";
|
||
import { popupUserStore } from "timi-tdesign-pc";
|
||
|
||
defineOptions({
|
||
name: "CommentList"
|
||
});
|
||
|
||
const props = withDefaults(defineProps<{
|
||
items: CommentView[],
|
||
canReply?: boolean,
|
||
}>(), {
|
||
canReply: true
|
||
});
|
||
const { items, canReply } = toRefs(props);
|
||
|
||
// 默认头像
|
||
const defAvatarURL = ref();
|
||
onMounted(async () => {
|
||
const res = JSON.parse(SettingMapper.getValue(SettingKey.PUBLIC_RESOURCES) as string);
|
||
defAvatarURL.value = CommonAPI.getAttachmentReadAPI(res.user.avatar);
|
||
});
|
||
|
||
// 激活的回复评论下标
|
||
const activeReply = ref();
|
||
const replyId = ref();
|
||
// 被回复昵称
|
||
const replyToNick = ref();
|
||
|
||
/**
|
||
* 获取回复
|
||
*
|
||
* @param comment 所属评论
|
||
*/
|
||
async function doFetchReply(comment: CommentView) {
|
||
// 组件下标以 1 开始,分页参数以 0 开始
|
||
comment.repliesPage.index = comment.repliesCurrent - 1;
|
||
const result = await CommentAPI.pageReply(comment.repliesPage);
|
||
comment.replies.length = 0;
|
||
comment.replies.push(...result.list);
|
||
}
|
||
|
||
/**
|
||
* 触发回复表单(表单会跟随评论)
|
||
*
|
||
* @param comment 跟随的评论
|
||
* @param replyTo 被回复对象
|
||
*/
|
||
async function replyTo(comment: CommentView, replyTo?: CommentReplyView) {
|
||
activeReply.value = comment.id;
|
||
if (replyTo) {
|
||
// 被回复对象
|
||
replyToNick.value = replyTo.sender?.name || replyTo.senderNick;
|
||
replyId.value = replyTo.id;
|
||
} else {
|
||
replyToNick.value = comment.user?.name || comment.nick;
|
||
}
|
||
}
|
||
|
||
function onSubmitReply(reply: CommentReply) {
|
||
activeReply.value = -1;
|
||
|
||
const comment = items.value.find(comment => comment.id === reply.commentId)!;
|
||
comment.repliesLength++;
|
||
comment.repliesCurrent = Math.ceil(comment.repliesLength / 6); // 上取整最后一页
|
||
doFetchReply(comment);
|
||
}
|
||
</script>
|
||
|
||
<style lang="less" scoped>
|
||
.tui-comment-list {
|
||
font-size: 13px;
|
||
line-height: 1.5;
|
||
|
||
.comment {
|
||
display: flex;
|
||
line-height: 1.5;
|
||
border-bottom: 2px solid #CDDEF0;
|
||
|
||
.user {
|
||
width: 120px;
|
||
padding: 16px 0;
|
||
text-align: center;
|
||
background: #E4EFFA;
|
||
transition: .5s var(--tui-bezier);
|
||
|
||
.avatar {
|
||
width: 64px;
|
||
border: 1px solid #FBC7D4;
|
||
margin: 0 auto 10px auto;
|
||
padding: 3px;
|
||
background: #FFF;
|
||
transition: .5s var(--tui-bezier);
|
||
}
|
||
}
|
||
|
||
.content {
|
||
width: calc(100% - 120px);
|
||
transition: .5s var(--tui-bezier);
|
||
|
||
.value {
|
||
width: calc(100% - 2rem);
|
||
padding: .5rem;
|
||
min-height: 92px;
|
||
}
|
||
|
||
.pages {
|
||
padding: 4px 4px 4px 1rem;
|
||
border-top: 1px solid #E4EFFA;
|
||
|
||
.total {
|
||
flex: 1;
|
||
}
|
||
}
|
||
|
||
.reply-to {
|
||
display: flex;
|
||
padding-right: .5rem;
|
||
justify-content: end;
|
||
|
||
> a {
|
||
margin-right: .5rem;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@media screen and (max-width: 650px) {
|
||
.tui-comment-list {
|
||
|
||
.comment {
|
||
|
||
.user {
|
||
width: 60px;
|
||
padding: 8px 0;
|
||
transition: .5s var(--tui-bezier);
|
||
|
||
.avatar {
|
||
width: 32px;
|
||
margin: 0 auto 4px auto;
|
||
padding: 2px;
|
||
transition: .5s var(--tui-bezier);
|
||
}
|
||
}
|
||
|
||
.content {
|
||
width: calc(100% - 60px);
|
||
transition: .5s var(--tui-bezier);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|