feat: reply upvote, like, dislike

This commit is contained in:
KUN1007 2023-10-06 18:43:14 +08:00
parent d1d6a77cb9
commit 69b964be7f
5 changed files with 341 additions and 53 deletions

View file

@ -1,4 +1,4 @@
import { fetchGet, fetchPost } from '@/utils/request' import { fetchGet, fetchPost, fetchPut } from '@/utils/request'
// 将对象转为请求参数的函数 // 将对象转为请求参数的函数
import objectToQueryParams from '@/utils/objectToQueryParams' import objectToQueryParams from '@/utils/objectToQueryParams'
import * as Reply from './types/reply' import * as Reply from './types/reply'
@ -7,34 +7,60 @@ import * as Reply from './types/reply'
export async function getRepliesByPidApi( export async function getRepliesByPidApi(
request: Reply.TopicReplyRequestData request: Reply.TopicReplyRequestData
): Promise<Reply.TopicReplyResponseData> { ): Promise<Reply.TopicReplyResponseData> {
try { const queryParams = objectToQueryParams(request, 'tid')
const queryParams = objectToQueryParams(request, 'tid') const url = `/topics/${request.tid}/replies?${queryParams}`
const url = `/topics/${request.tid}/replies?${queryParams}`
const response = await fetchGet<Reply.TopicReplyResponseData>(url) const response = await fetchGet<Reply.TopicReplyResponseData>(url)
return response return response
} catch (error) {
console.log(error)
throw new Error('Failed to fetch replies')
}
} }
// 根据 tid 创建一个回复 // 根据 tid 创建一个回复
export async function postReplyByPidApi( export async function postReplyByPidApi(
request: Reply.TopicCreateReplyRequestData request: Reply.TopicCreateReplyRequestData
): Promise<Reply.TopicCreateReplyResponseData> { ): Promise<Reply.TopicCreateReplyResponseData> {
try { const url = `/topics/${request.tid}/reply`
const url = `/topics/${request.tid}/reply`
const response = await fetchPost<Reply.TopicCreateReplyResponseData>( const response = await fetchPost<Reply.TopicCreateReplyResponseData>(
url, url,
request request
) )
return response 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
} }

View file

@ -21,7 +21,7 @@ export interface TopicReply {
to_user: TopicToUserInfo to_user: TopicToUserInfo
edited: number edited: number
content: string content: string
upvote: number[] upvotes: number[]
likes: number[] likes: number[]
dislikes: number[] dislikes: number[]
tags: string[] tags: string[]

View file

@ -3,23 +3,21 @@ import { defineStore } from 'pinia'
// 话题 // 话题
import { import {
getTopicByTidApi, getTopicByTidApi,
getRelatedTopicsByTagsApi,
getPopularTopicsByUserUidApi,
updateTopicUpvoteApi,
updateTopicLikeApi,
updateTopicDislikeApi,
} from '@/api'
import type {
TopicDetailResponseData, TopicDetailResponseData,
TopicAsideOtherTagRequestData, TopicAsideOtherTagRequestData,
getRelatedTopicsByTagsApi,
TopicAsideMasterRequestData, TopicAsideMasterRequestData,
getPopularTopicsByUserUidApi,
TopicAsideResponseData, TopicAsideResponseData,
} from '@/api'
// 点赞等动作
import {
updateTopicUpvoteApi,
TopicUpvoteTopicRequestData, TopicUpvoteTopicRequestData,
TopicUpvoteTopicResponseData, TopicUpvoteTopicResponseData,
updateTopicLikeApi,
TopicLikeTopicRequestData, TopicLikeTopicRequestData,
TopicLikeTopicResponseData, TopicLikeTopicResponseData,
updateTopicDislikeApi,
TopicDislikeTopicRequestData, TopicDislikeTopicRequestData,
TopicDislikeTopicResponseData, TopicDislikeTopicResponseData,
} from '@/api' } from '@/api'
@ -28,10 +26,22 @@ import {
import { import {
getRepliesByPidApi, getRepliesByPidApi,
postReplyByPidApi, postReplyByPidApi,
updateReplyUpvoteApi,
updateReplyLikeApi,
updateReplyDislikeApi,
} from '@/api'
import type {
TopicReplyRequestData, TopicReplyRequestData,
TopicReplyResponseData, TopicReplyResponseData,
TopicCreateReplyRequestData, TopicCreateReplyRequestData,
TopicCreateReplyResponseData, TopicCreateReplyResponseData,
TopicUpvoteReplyRequestData,
TopicUpvoteReplyResponseData,
TopicLikeReplyRequestData,
TopicLikeReplyResponseData,
TopicDislikeReplyRequestData,
TopicDislikeReplyResponseData,
} from '@/api' } 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 = { const requestData: TopicReplyRequestData = {
tid: tid, tid: tid,
@ -179,6 +189,52 @@ export const useKUNGalgameTopicStore = defineStore({
return await postReplyByPidApi(requestData) 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( async getComments(
tid: number, tid: number,

View file

@ -98,16 +98,17 @@ const handleClickComment = (rid: number) => {
</div> </div>
<!-- 其它人回复的底部 --> <!-- 其它人回复的底部 -->
<!-- info 代表 footer 的点赞等信息rUser 表示当前的回复用户信息 --> <!-- info 代表 footer 的点赞等信息master 表示当前的回复用户信息 -->
<!-- 这里传入了回复的插槽因为只有回复可以被评论 --> <!-- 这里传入了回复的插槽因为只有回复可以被评论 -->
<ReplyFooter <ReplyFooter
:info="{ :info="{
rid: reply.rid,
likes: reply.likes, likes: reply.likes,
dislikes: reply.dislikes, dislikes: reply.dislikes,
upvotes: reply.upvote, upvotes: reply.upvotes,
to_floor: reply.floor, to_floor: reply.floor,
}" }"
:rUser="reply.r_user" :master="reply.r_user"
> >
<template #comment> <template #comment>
<span @click="handleClickComment(reply.rid)" class="icon"> <span @click="handleClickComment(reply.rid)" class="icon">

View file

@ -1,13 +1,25 @@
<!-- 话题的底部区域推话题回复点赞等 --> <!-- 话题的底部区域推话题回复点赞等 -->
<script setup lang="ts"> <script setup lang="ts">
import { nextTick } from 'vue' import { onMounted, reactive, ref } from 'vue'
import { useRoute } from 'vue-router' import { useRoute } from 'vue-router'
import { Icon } from '@iconify/vue' 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' import { TopicUserInfo } from '@/api'
// store
import { useKUNGalgameEditStore } from '@/store/modules/edit'
// store // store
import { useKUNGalgameTopicStore } from '@/store/modules/topic' import { useKUNGalgameTopicStore } from '@/store/modules/topic'
// store
import { useKUNGalgameUserStore } from '@/store/modules/kungalgamer'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
// tid // tid
@ -20,31 +32,206 @@ const { isEdit, replyDraft } = storeToRefs(topicStore)
// //
const props = defineProps<{ const props = defineProps<{
info: { info: {
views?: number rid: number
likes: number[] likes: number[]
dislikes: number[] dislikes: number[]
upvotes: number[] upvotes: number[]
// floor // floor
to_floor: number 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 () => { const handelReply = async () => {
// 便 // 便
replyDraft.value.tid = tid replyDraft.value.tid = tid
replyDraft.value.replyUserName = props.rUser.name replyDraft.value.replyUserName = props.master.name
replyDraft.value.to_uid = props.rUser.uid replyDraft.value.to_uid = props.master.uid
replyDraft.value.to_floor = props.info.to_floor replyDraft.value.to_floor = props.info.to_floor
//
await nextTick()
isEdit.value = true isEdit.value = true
} }
// //
const handleClickEdit = () => {} 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> </script>
<template> <template>
@ -55,23 +242,36 @@ const handleClickEdit = () => {}
<ul> <ul>
<!-- 推话题 --> <!-- 推话题 -->
<li> <li>
<span class="icon"><Icon icon="bi:rocket" /></span> <span
{{ info?.upvotes?.length }} class="icon"
</li> :class="isActive.isUpvote ? 'active' : ''"
<!-- 查看数量 --> @click="handleClickUpvote"
<li v-if="info.views"> >
<span class="icon"><Icon icon="ic:outline-remove-red-eye" /></span> <Icon icon="bi:rocket" />
{{ info.views }} </span>
{{ actions.upvotes.length }}
</li> </li>
<!-- 点赞 --> <!-- 点赞 -->
<li> <li>
<span class="icon"><Icon icon="line-md:thumbs-up-twotone" /></span> <span
{{ info?.likes?.length }} class="icon"
:class="isActive.isLiked ? 'active' : ''"
@click="handleClickLike"
>
<Icon icon="line-md:thumbs-up-twotone" />
</span>
{{ actions.likes.length }}
</li> </li>
<!-- --> <!-- -->
<li> <li>
<span class="icon"><Icon icon="line-md:thumbs-down-twotone" /></span> <span
{{ info?.dislikes?.length }} class="icon"
:class="isActive.isDisliked ? 'active' : ''"
@click="handleClickDislike"
>
<Icon icon="line-md:thumbs-down-twotone" />
</span>
{{ actions.dislikes.length }}
</li> </li>
</ul> </ul>
</div> </div>
@ -204,6 +404,11 @@ const handleClickEdit = () => {}
} }
} }
/* 激活后的样式 */
.active {
color: var(--kungalgame-blue-4);
}
@media (max-width: 700px) { @media (max-width: 700px) {
.footer { .footer {
flex-direction: column; flex-direction: column;