feat: reply upvote, like, dislike
This commit is contained in:
parent
d1d6a77cb9
commit
69b964be7f
|
@ -1,4 +1,4 @@
|
|||
import { fetchGet, fetchPost } from '@/utils/request'
|
||||
import { fetchGet, fetchPost, fetchPut } from '@/utils/request'
|
||||
// 将对象转为请求参数的函数
|
||||
import objectToQueryParams from '@/utils/objectToQueryParams'
|
||||
import * as Reply from './types/reply'
|
||||
|
@ -7,24 +7,18 @@ import * as Reply from './types/reply'
|
|||
export async function getRepliesByPidApi(
|
||||
request: Reply.TopicReplyRequestData
|
||||
): Promise<Reply.TopicReplyResponseData> {
|
||||
try {
|
||||
const queryParams = objectToQueryParams(request, 'tid')
|
||||
const url = `/topics/${request.tid}/replies?${queryParams}`
|
||||
|
||||
const response = await fetchGet<Reply.TopicReplyResponseData>(url)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
throw new Error('Failed to fetch replies')
|
||||
}
|
||||
}
|
||||
|
||||
// 根据 tid 创建一个回复
|
||||
export async function postReplyByPidApi(
|
||||
request: Reply.TopicCreateReplyRequestData
|
||||
): Promise<Reply.TopicCreateReplyResponseData> {
|
||||
try {
|
||||
const url = `/topics/${request.tid}/reply`
|
||||
|
||||
const response = await fetchPost<Reply.TopicCreateReplyResponseData>(
|
||||
|
@ -33,8 +27,40 @@ export async function postReplyByPidApi(
|
|||
)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
console.log(error)
|
||||
throw new Error('Failed to create reply')
|
||||
}
|
||||
|
||||
// 推
|
||||
export async function updateReplyUpvoteApi(
|
||||
request: Reply.TopicUpvoteReplyRequestData
|
||||
): Promise<Reply.TopicUpvoteReplyResponseData> {
|
||||
const queryParams = objectToQueryParams(request, 'tid')
|
||||
const url = `/topics/${request.tid}/reply/upvote?${queryParams}`
|
||||
|
||||
const response = fetchPut<Reply.TopicUpvoteReplyResponseData>(url)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// 点赞
|
||||
export async function updateReplyLikeApi(
|
||||
request: Reply.TopicLikeReplyRequestData
|
||||
): Promise<Reply.TopicLikeReplyResponseData> {
|
||||
const queryParams = objectToQueryParams(request, 'tid')
|
||||
const url = `/topics/${request.tid}/reply/like?${queryParams}`
|
||||
|
||||
const response = fetchPut<Reply.TopicLikeReplyResponseData>(url)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// 点踩
|
||||
export async function updateReplyDislikeApi(
|
||||
request: Reply.TopicDislikeReplyRequestData
|
||||
): Promise<Reply.TopicDislikeReplyResponseData> {
|
||||
const queryParams = objectToQueryParams(request, 'tid')
|
||||
const url = `/topics/${request.tid}/reply/dislike?${queryParams}`
|
||||
|
||||
const response = fetchPut<Reply.TopicDislikeReplyResponseData>(url)
|
||||
|
||||
return response
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@ export interface TopicReply {
|
|||
to_user: TopicToUserInfo
|
||||
edited: number
|
||||
content: string
|
||||
upvote: number[]
|
||||
upvotes: number[]
|
||||
likes: number[]
|
||||
dislikes: number[]
|
||||
tags: string[]
|
||||
|
|
|
@ -3,23 +3,21 @@ import { defineStore } from 'pinia'
|
|||
// 话题
|
||||
import {
|
||||
getTopicByTidApi,
|
||||
getRelatedTopicsByTagsApi,
|
||||
getPopularTopicsByUserUidApi,
|
||||
updateTopicUpvoteApi,
|
||||
updateTopicLikeApi,
|
||||
updateTopicDislikeApi,
|
||||
} from '@/api'
|
||||
import type {
|
||||
TopicDetailResponseData,
|
||||
TopicAsideOtherTagRequestData,
|
||||
getRelatedTopicsByTagsApi,
|
||||
TopicAsideMasterRequestData,
|
||||
getPopularTopicsByUserUidApi,
|
||||
TopicAsideResponseData,
|
||||
} from '@/api'
|
||||
|
||||
// 点赞等动作
|
||||
import {
|
||||
updateTopicUpvoteApi,
|
||||
TopicUpvoteTopicRequestData,
|
||||
TopicUpvoteTopicResponseData,
|
||||
updateTopicLikeApi,
|
||||
TopicLikeTopicRequestData,
|
||||
TopicLikeTopicResponseData,
|
||||
updateTopicDislikeApi,
|
||||
TopicDislikeTopicRequestData,
|
||||
TopicDislikeTopicResponseData,
|
||||
} from '@/api'
|
||||
|
@ -28,10 +26,22 @@ import {
|
|||
import {
|
||||
getRepliesByPidApi,
|
||||
postReplyByPidApi,
|
||||
updateReplyUpvoteApi,
|
||||
updateReplyLikeApi,
|
||||
updateReplyDislikeApi,
|
||||
} from '@/api'
|
||||
|
||||
import type {
|
||||
TopicReplyRequestData,
|
||||
TopicReplyResponseData,
|
||||
TopicCreateReplyRequestData,
|
||||
TopicCreateReplyResponseData,
|
||||
TopicUpvoteReplyRequestData,
|
||||
TopicUpvoteReplyResponseData,
|
||||
TopicLikeReplyRequestData,
|
||||
TopicLikeReplyResponseData,
|
||||
TopicDislikeReplyRequestData,
|
||||
TopicDislikeReplyResponseData,
|
||||
} from '@/api'
|
||||
|
||||
// 评论
|
||||
|
@ -154,7 +164,7 @@ export const useKUNGalgameTopicStore = defineStore({
|
|||
},
|
||||
|
||||
// 获取回复
|
||||
async getReplies(tid: number): Promise<TopicDislikeTopicResponseData> {
|
||||
async getReplies(tid: number): Promise<TopicReplyResponseData> {
|
||||
// 这里的默认值用于初始化
|
||||
const requestData: TopicReplyRequestData = {
|
||||
tid: tid,
|
||||
|
@ -179,6 +189,52 @@ export const useKUNGalgameTopicStore = defineStore({
|
|||
return await postReplyByPidApi(requestData)
|
||||
},
|
||||
|
||||
// 推回复
|
||||
async updateReplyUpvote(
|
||||
tid: number,
|
||||
toUid: number,
|
||||
rid: number
|
||||
): Promise<TopicUpvoteReplyResponseData> {
|
||||
const requestData: TopicUpvoteReplyRequestData = {
|
||||
tid: tid,
|
||||
to_uid: toUid,
|
||||
rid: rid,
|
||||
}
|
||||
return await updateReplyUpvoteApi(requestData)
|
||||
},
|
||||
|
||||
// 点赞回复
|
||||
async updateReplyLike(
|
||||
tid: number,
|
||||
toUid: number,
|
||||
rid: number,
|
||||
isPush: boolean
|
||||
): Promise<TopicLikeReplyResponseData> {
|
||||
const requestData: TopicLikeReplyRequestData = {
|
||||
tid: tid,
|
||||
to_uid: toUid,
|
||||
rid: rid,
|
||||
isPush: isPush,
|
||||
}
|
||||
return await updateReplyLikeApi(requestData)
|
||||
},
|
||||
|
||||
// 点踩回复
|
||||
async updateReplyDislike(
|
||||
tid: number,
|
||||
toUid: number,
|
||||
rid: number,
|
||||
isPush: boolean
|
||||
): Promise<TopicDislikeReplyResponseData> {
|
||||
const requestData: TopicDislikeReplyRequestData = {
|
||||
tid: tid,
|
||||
to_uid: toUid,
|
||||
rid: rid,
|
||||
isPush: isPush,
|
||||
}
|
||||
return await updateReplyDislikeApi(requestData)
|
||||
},
|
||||
|
||||
// 获取评论
|
||||
async getComments(
|
||||
tid: number,
|
||||
|
|
|
@ -98,16 +98,17 @@ const handleClickComment = (rid: number) => {
|
|||
</div>
|
||||
|
||||
<!-- 其它人回复的底部 -->
|
||||
<!-- info 代表 footer 的点赞等信息,rUser 表示当前的回复用户信息 -->
|
||||
<!-- info 代表 footer 的点赞等信息,master 表示当前的回复用户信息 -->
|
||||
<!-- 这里传入了回复的插槽,因为只有回复可以被评论 -->
|
||||
<ReplyFooter
|
||||
:info="{
|
||||
rid: reply.rid,
|
||||
likes: reply.likes,
|
||||
dislikes: reply.dislikes,
|
||||
upvotes: reply.upvote,
|
||||
upvotes: reply.upvotes,
|
||||
to_floor: reply.floor,
|
||||
}"
|
||||
:rUser="reply.r_user"
|
||||
:master="reply.r_user"
|
||||
>
|
||||
<template #comment>
|
||||
<span @click="handleClickComment(reply.rid)" class="icon">
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
<!-- 话题的底部区域,推话题,回复,点赞等 -->
|
||||
<script setup lang="ts">
|
||||
import { nextTick } from 'vue'
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { useRoute } from 'vue-router'
|
||||
import { Icon } from '@iconify/vue'
|
||||
|
||||
import { useRouter } from 'vue-router'
|
||||
// 全局消息组件(底部)
|
||||
import { useKUNGalgameMessageStore } from '@/store/modules/message'
|
||||
// 全局消息组件(顶部)
|
||||
import message from '@/components/alert/Message'
|
||||
// throttle 函数
|
||||
import { throttle } from '@/utils/throttle'
|
||||
|
||||
import { TopicUserInfo } from '@/api'
|
||||
|
||||
// 导入编辑界面的 store
|
||||
import { useKUNGalgameEditStore } from '@/store/modules/edit'
|
||||
// 导入话题页面 store
|
||||
import { useKUNGalgameTopicStore } from '@/store/modules/topic'
|
||||
// 导入用户的 store
|
||||
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
// 当前的话题 tid
|
||||
|
@ -20,31 +32,206 @@ const { isEdit, replyDraft } = storeToRefs(topicStore)
|
|||
// 接受父组件的传值
|
||||
const props = defineProps<{
|
||||
info: {
|
||||
views?: number
|
||||
rid: number
|
||||
likes: number[]
|
||||
dislikes: number[]
|
||||
upvotes: number[]
|
||||
// 被回复人的 floor
|
||||
to_floor: number
|
||||
}
|
||||
rUser: TopicUserInfo
|
||||
master: TopicUserInfo
|
||||
}>()
|
||||
|
||||
/**
|
||||
* 这里只是简单起见,不显示重新编辑
|
||||
* 实际上如果用户自己修改了 localStorage 中保存的信息,这个验证就失效了
|
||||
* 但是修改了也没有用,验证逻辑位于后端
|
||||
*/
|
||||
// 当前登录用户的 uid
|
||||
const currUserUid = useKUNGalgameUserStore().uid
|
||||
// 话题发布者的 uid
|
||||
const masterUid = props.master.uid
|
||||
// 是否具有重新编辑的权限
|
||||
const isShowRewrite = currUserUid === masterUid
|
||||
|
||||
// footer 左侧的动作,点赞等
|
||||
const actions = reactive({
|
||||
upvotes: props.info.upvotes,
|
||||
likes: props.info.likes,
|
||||
dislikes: props.info.dislikes,
|
||||
})
|
||||
|
||||
// 是否已经点过赞,点过踩
|
||||
const isActive = reactive({
|
||||
isUpvote: false,
|
||||
isLiked: false,
|
||||
isDisliked: false,
|
||||
isShared: false,
|
||||
})
|
||||
|
||||
// throttle 回调函数
|
||||
const throttleCallback = () => {
|
||||
message(
|
||||
'You can only perform one operation within 1007 milliseconds',
|
||||
'您在 1007 毫秒内只能进行一次操作',
|
||||
'warn'
|
||||
)
|
||||
}
|
||||
|
||||
// throttle 函数,1007 毫秒仅会触发一次点赞
|
||||
const handleClickLikeThrottled = throttle(
|
||||
async () => {
|
||||
// 当前用户不可以给自己点赞
|
||||
if (currUserUid === masterUid) {
|
||||
message(
|
||||
'You cannot like your own reply',
|
||||
'您不可以给自己的回复点赞',
|
||||
'warn'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// 切换点赞激活状态
|
||||
isActive.isLiked = !isActive.isLiked
|
||||
|
||||
const res = await useKUNGalgameTopicStore().updateReplyLike(
|
||||
tid,
|
||||
masterUid,
|
||||
props.info.rid,
|
||||
isActive.isLiked
|
||||
)
|
||||
|
||||
if (res.code === 200) {
|
||||
// 更新点赞数
|
||||
actions.likes.length = isActive.isLiked
|
||||
? actions.likes.length + 1
|
||||
: actions.likes.length - 1
|
||||
} else {
|
||||
message('Reply like failed!', '点赞回复失败', 'error')
|
||||
}
|
||||
},
|
||||
1007,
|
||||
throttleCallback
|
||||
)
|
||||
|
||||
// throttle 函数,1007 毫秒仅会触发一次点踩
|
||||
const handleClickDislikeThrottled = throttle(
|
||||
async () => {
|
||||
if (currUserUid === masterUid) {
|
||||
message(
|
||||
'You cannot dislike your own reply',
|
||||
'您不可以给自己的回复点踩',
|
||||
'warn'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
isActive.isDisliked = !isActive.isDisliked
|
||||
const res = await useKUNGalgameTopicStore().updateReplyDislike(
|
||||
tid,
|
||||
masterUid,
|
||||
props.info.rid,
|
||||
isActive.isDisliked
|
||||
)
|
||||
|
||||
if (res.code === 200) {
|
||||
// 更新点赞数
|
||||
actions.dislikes.length = isActive.isDisliked
|
||||
? actions.dislikes.length + 1
|
||||
: actions.dislikes.length - 1
|
||||
} else {
|
||||
message('Reply dislike failed!', '点踩回复失败', 'error')
|
||||
}
|
||||
},
|
||||
1007,
|
||||
throttleCallback
|
||||
)
|
||||
|
||||
// 推话题
|
||||
const handleClickUpvote = async () => {
|
||||
// 当前用户不可以推自己的话题
|
||||
if (currUserUid === masterUid) {
|
||||
message('You cannot upvote your own reply', '您不可以推自己的回复', 'warn')
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: 该功能必须有错误处理
|
||||
// if (useKUNGalgameUserStore().moemoepoint < 1100) {
|
||||
// message(
|
||||
// 'Your Moe Moe Points are less than 1100, and you cannot use the upvote feature',
|
||||
// '您的萌萌点小于 1100,无法使用推功能',
|
||||
// 'warn'
|
||||
// )
|
||||
// return
|
||||
// }
|
||||
|
||||
// 调用弹窗确认
|
||||
const res = await useKUNGalgameMessageStore().alert(
|
||||
'AlertInfo.edit.upvote',
|
||||
true
|
||||
)
|
||||
|
||||
// 这里实现用户的点击确认取消逻辑
|
||||
if (res) {
|
||||
// 请求推话题的接口
|
||||
const res = await useKUNGalgameTopicStore().updateReplyUpvote(
|
||||
tid,
|
||||
masterUid,
|
||||
props.info.rid
|
||||
)
|
||||
|
||||
if (res.code === 200) {
|
||||
// 更新推数
|
||||
actions.upvotes.length++
|
||||
|
||||
message('Topic upvote successfully', '推话题成功', 'success')
|
||||
} else {
|
||||
message('Topic upvote failed!', '推话题失败', 'error')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 点赞
|
||||
const handleClickLike = () => {
|
||||
handleClickLikeThrottled()
|
||||
}
|
||||
|
||||
// 点踩
|
||||
const handleClickDislike = () => {
|
||||
handleClickDislikeThrottled()
|
||||
}
|
||||
|
||||
// 点击回复打开回复面板
|
||||
const handelReply = async () => {
|
||||
// 保存必要信息,以便发表回复
|
||||
replyDraft.value.tid = tid
|
||||
replyDraft.value.replyUserName = props.rUser.name
|
||||
replyDraft.value.to_uid = props.rUser.uid
|
||||
replyDraft.value.replyUserName = props.master.name
|
||||
replyDraft.value.to_uid = props.master.uid
|
||||
replyDraft.value.to_floor = props.info.to_floor
|
||||
|
||||
// 打开回复面板
|
||||
await nextTick()
|
||||
isEdit.value = true
|
||||
}
|
||||
|
||||
// 重新编辑
|
||||
const handleClickEdit = () => {}
|
||||
|
||||
// 初始化按钮的状态,是否已点赞等
|
||||
onMounted(() => {
|
||||
// 已经推过
|
||||
if (props.info.upvotes.includes(currUserUid)) {
|
||||
isActive.isUpvote = true
|
||||
}
|
||||
|
||||
// 已经点赞
|
||||
if (props.info.likes.includes(currUserUid)) {
|
||||
isActive.isLiked = true
|
||||
}
|
||||
|
||||
// 已经点踩
|
||||
if (props.info.dislikes.includes(currUserUid)) {
|
||||
isActive.isDisliked = true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -55,23 +242,36 @@ const handleClickEdit = () => {}
|
|||
<ul>
|
||||
<!-- 推话题 -->
|
||||
<li>
|
||||
<span class="icon"><Icon icon="bi:rocket" /></span>
|
||||
{{ info?.upvotes?.length }}
|
||||
</li>
|
||||
<!-- 查看数量 -->
|
||||
<li v-if="info.views">
|
||||
<span class="icon"><Icon icon="ic:outline-remove-red-eye" /></span>
|
||||
{{ info.views }}
|
||||
<span
|
||||
class="icon"
|
||||
:class="isActive.isUpvote ? 'active' : ''"
|
||||
@click="handleClickUpvote"
|
||||
>
|
||||
<Icon icon="bi:rocket" />
|
||||
</span>
|
||||
{{ actions.upvotes.length }}
|
||||
</li>
|
||||
<!-- 点赞 -->
|
||||
<li>
|
||||
<span class="icon"><Icon icon="line-md:thumbs-up-twotone" /></span>
|
||||
{{ info?.likes?.length }}
|
||||
<span
|
||||
class="icon"
|
||||
:class="isActive.isLiked ? 'active' : ''"
|
||||
@click="handleClickLike"
|
||||
>
|
||||
<Icon icon="line-md:thumbs-up-twotone" />
|
||||
</span>
|
||||
{{ actions.likes.length }}
|
||||
</li>
|
||||
<!-- 踩 -->
|
||||
<li>
|
||||
<span class="icon"><Icon icon="line-md:thumbs-down-twotone" /></span>
|
||||
{{ info?.dislikes?.length }}
|
||||
<span
|
||||
class="icon"
|
||||
:class="isActive.isDisliked ? 'active' : ''"
|
||||
@click="handleClickDislike"
|
||||
>
|
||||
<Icon icon="line-md:thumbs-down-twotone" />
|
||||
</span>
|
||||
{{ actions.dislikes.length }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -204,6 +404,11 @@ const handleClickEdit = () => {}
|
|||
}
|
||||
}
|
||||
|
||||
/* 激活后的样式 */
|
||||
.active {
|
||||
color: var(--kungalgame-blue-4);
|
||||
}
|
||||
|
||||
@media (max-width: 700px) {
|
||||
.footer {
|
||||
flex-direction: column;
|
||||
|
|
Loading…
Reference in a new issue