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 * as Reply from './types/reply'
@ -7,34 +7,60 @@ 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 queryParams = objectToQueryParams(request, 'tid')
const url = `/topics/${request.tid}/replies?${queryParams}`
const response = await fetchGet<Reply.TopicReplyResponseData>(url)
const response = await fetchGet<Reply.TopicReplyResponseData>(url)
return response
} catch (error) {
console.log(error)
throw new Error('Failed to fetch replies')
}
return response
}
// 根据 tid 创建一个回复
export async function postReplyByPidApi(
request: Reply.TopicCreateReplyRequestData
): Promise<Reply.TopicCreateReplyResponseData> {
try {
const url = `/topics/${request.tid}/reply`
const url = `/topics/${request.tid}/reply`
const response = await fetchPost<Reply.TopicCreateReplyResponseData>(
url,
request
)
const response = await fetchPost<Reply.TopicCreateReplyResponseData>(
url,
request
)
return response
} catch (error) {
console.log(error)
throw new Error('Failed to create reply')
}
return response
}
// 推
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
edited: number
content: string
upvote: number[]
upvotes: number[]
likes: number[]
dislikes: number[]
tags: string[]

View file

@ -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,

View file

@ -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">

View file

@ -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;