feat: like topic
This commit is contained in:
parent
efef4bc6f9
commit
cc7fcb678a
|
@ -7,6 +7,7 @@ export * from './edit/types/edit'
|
|||
export * from './home/types/home'
|
||||
export * from './kungalgamer/types/kungalgamer'
|
||||
export * from './login/types/login'
|
||||
export * from './topic/types/action'
|
||||
export * from './topic/types/topic'
|
||||
export * from './topic/types/aside'
|
||||
export * from './update-log/types/updateLog'
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
import { fetchGet, fetchPost } from '@/utils/request'
|
||||
import { fetchGet, fetchPost, fetchPut } from '@/utils/request'
|
||||
// 将对象转为请求参数的函数
|
||||
import objectToQueryParams from '@/utils/objectToQueryParams'
|
||||
import * as Action from './types/action'
|
||||
import * as Aside from './types/aside'
|
||||
import * as Topic from './types/topic'
|
||||
|
||||
|
@ -54,6 +55,18 @@ export async function getTopicByTidApi(
|
|||
}
|
||||
}
|
||||
|
||||
// 点赞话题
|
||||
export async function updateTopicLikeApi(
|
||||
request: Action.TopicLikeTopicRequestData
|
||||
): Promise<Action.TopicLikeTopicResponseData> {
|
||||
const queryParams = objectToQueryParams(request, 'tid')
|
||||
const url = `/topics/${request.tid}/like?${queryParams}`
|
||||
|
||||
const response = fetchPut<Action.TopicLikeTopicResponseData>(url)
|
||||
|
||||
return response
|
||||
}
|
||||
|
||||
// 根据话题 tid 获取话题回复
|
||||
export async function getRepliesByPidApi(
|
||||
request: Topic.TopicReplyRequestData
|
||||
|
|
|
@ -1,3 +1,13 @@
|
|||
/**
|
||||
* 这是用户对话题的操作,推,点赞,点踩等
|
||||
*/
|
||||
|
||||
// 点赞
|
||||
export interface TopicLikeTopicRequestData {
|
||||
tid: number
|
||||
to_uid: number
|
||||
isPush: boolean
|
||||
}
|
||||
|
||||
// 点赞话题响应数据的格式
|
||||
export type TopicLikeTopicResponseData = KUNGalgameResponseData<{}>
|
||||
|
|
|
@ -16,6 +16,9 @@ export default (
|
|||
type: MessageType,
|
||||
duration?: number
|
||||
) => {
|
||||
// 初始化的时候先将上一次创建的内容销毁
|
||||
render(null, document.body)
|
||||
|
||||
const messageNode = h(Message, {
|
||||
messageCN,
|
||||
messageEN,
|
||||
|
|
|
@ -200,6 +200,10 @@ const handleTextChange = async () => {
|
|||
/* 头部下方阴影 */
|
||||
box-shadow: 0 2px 4px 0 var(--kungalgame-trans-blue-1);
|
||||
display: v-bind(isShowEditorToolbar);
|
||||
/* 不显示视频插入,这个功能 BUG 太多了 */
|
||||
.ql-video {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* 编辑器体的样式 */
|
||||
|
|
|
@ -10,6 +10,14 @@ import {
|
|||
getPopularTopicsByUserUidApi,
|
||||
TopicAsideResponseData,
|
||||
} from '@/api'
|
||||
|
||||
// 点赞等动作
|
||||
import {
|
||||
updateTopicLikeApi,
|
||||
TopicLikeTopicRequestData,
|
||||
TopicLikeTopicResponseData,
|
||||
} from '@/api'
|
||||
|
||||
// 回复
|
||||
import {
|
||||
getRepliesByPidApi,
|
||||
|
@ -19,6 +27,7 @@ import {
|
|||
TopicCreateReplyRequestData,
|
||||
TopicCreateReplyResponseData,
|
||||
} from '@/api'
|
||||
|
||||
// 评论
|
||||
import {
|
||||
getCommentsByReplyRidApi,
|
||||
|
@ -98,6 +107,20 @@ export const useKUNGalgameTopicStore = defineStore({
|
|||
return await getTopicByTidApi(tid)
|
||||
},
|
||||
|
||||
// 点赞话题
|
||||
async updateTopicLike(
|
||||
tid: number,
|
||||
toUid: number,
|
||||
isPush: boolean
|
||||
): Promise<TopicLikeTopicResponseData> {
|
||||
const requestData: TopicLikeTopicRequestData = {
|
||||
tid: tid,
|
||||
to_uid: toUid,
|
||||
isPush: isPush,
|
||||
}
|
||||
return await updateTopicLikeApi(requestData)
|
||||
},
|
||||
|
||||
// 获取回复
|
||||
async getReplies(tid: number): Promise<TopicReplyResponseData> {
|
||||
// 这里的默认值用于初始化
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
export function getPlainText(html: string): string {
|
||||
// 使用正则表达式匹配所有HTML标签并删除
|
||||
// 使用正则表达式匹配所有 HTML 标签并删除
|
||||
const plainText = html.replace(/<[^>]*>/g, '')
|
||||
|
||||
// 使用实体编码映射表将HTML实体编码还原
|
||||
// 使用实体编码映射表将 HTML 实体编码还原
|
||||
const entityMap: Record<string, string> = {
|
||||
lt: '<',
|
||||
gt: '>',
|
||||
|
|
|
@ -58,19 +58,19 @@ const loliFaceLeft = loli.face.left + 'px'
|
|||
const loliFaceTop = loli.face.top + 'px'
|
||||
|
||||
// 身体的图片资源链接
|
||||
let lass = getAssetsFile(loli.lass.layer_id)
|
||||
const lass = getAssetsFile(loli.lass.layer_id)
|
||||
|
||||
// 眼睛的图片资源链接
|
||||
let eye = getAssetsFile(loli.eye.layer_id)
|
||||
const eye = getAssetsFile(loli.eye.layer_id)
|
||||
|
||||
// 眉毛的图片资源链接
|
||||
let brow = getAssetsFile(loli.brow.layer_id)
|
||||
const brow = getAssetsFile(loli.brow.layer_id)
|
||||
|
||||
// 嘴巴的图片资源链接
|
||||
let mouth = getAssetsFile(loli.mouth.layer_id)
|
||||
const mouth = getAssetsFile(loli.mouth.layer_id)
|
||||
|
||||
// 腮红的图片资源链接
|
||||
let face = getAssetsFile(loli.face.layer_id)
|
||||
const face = getAssetsFile(loli.face.layer_id)
|
||||
|
||||
// 导出模块
|
||||
export {
|
||||
|
|
36
src/utils/throttle.ts
Normal file
36
src/utils/throttle.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* Throttle(节流)函数
|
||||
* 在一段时间内,无论触发多少次函数,都只执行一次
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {T} executeCallback - 要应用 throttle 函数的代码
|
||||
* @param {number} delay - throttle 的时间
|
||||
* @param {T | undefined} delayedCallback - 用户在 throttle 时间未到执行了 execute 函数产生的回调
|
||||
*/
|
||||
export function throttle<T extends (...args: any[]) => void>(
|
||||
executeCallback: T,
|
||||
delay: number,
|
||||
delayedCallback?: T | undefined
|
||||
) {
|
||||
let lastExecution = 0
|
||||
let timeout: NodeJS.Timeout | null = null
|
||||
|
||||
const throttled = (...args: Parameters<T>) => {
|
||||
const now = Date.now()
|
||||
|
||||
if (!lastExecution || now - lastExecution >= delay) {
|
||||
executeCallback(...args)
|
||||
lastExecution = now
|
||||
} else {
|
||||
if (!timeout && delayedCallback) {
|
||||
delayedCallback(...args)
|
||||
timeout = setTimeout(() => {
|
||||
timeout = null
|
||||
}, delay)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return throttled as (...args: Parameters<T>) => void
|
||||
}
|
|
@ -36,7 +36,7 @@ const {
|
|||
user,
|
||||
// rid,
|
||||
status,
|
||||
// share,
|
||||
share,
|
||||
category,
|
||||
} = topicData.topicData
|
||||
|
||||
|
@ -94,11 +94,19 @@ const loliStatus = computed(() => {
|
|||
<Rewrite v-if="edited" :time="edited" />
|
||||
</div>
|
||||
</div>
|
||||
<!-- 话题的点赞数等信息,楼主的 floor 就是 0 -->
|
||||
<!-- 话题的点赞数等信息,楼主的 floor 就是 0,被作用人就是该话题的发布人 -->
|
||||
<MasterFooter
|
||||
:info="{ tid, views, likes, dislikes, upvotes, to_floor: 0 }"
|
||||
:info="{
|
||||
tid,
|
||||
views,
|
||||
likes,
|
||||
dislikes,
|
||||
upvotes,
|
||||
share,
|
||||
to_floor: 0,
|
||||
}"
|
||||
:topic="{ tid, title, content, tags, category }"
|
||||
:r-user="user"
|
||||
:master="user"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
<!-- 话题的底部区域,推话题,回复,点赞等 -->
|
||||
<script setup lang="ts">
|
||||
import { onMounted, reactive, ref } from 'vue'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import { useRouter } from 'vue-router'
|
||||
// 全局消息组件(顶部)
|
||||
import message from '@/components/alert/Message'
|
||||
// throttle 函数
|
||||
import { throttle } from '@/utils/throttle'
|
||||
|
||||
import { TopicUserInfo } from '@/api'
|
||||
|
||||
|
@ -28,6 +33,7 @@ const props = defineProps<{
|
|||
likes: number[]
|
||||
dislikes: number[]
|
||||
upvotes: number[]
|
||||
share: number[]
|
||||
// 被回复人的 floor
|
||||
to_floor: number
|
||||
}
|
||||
|
@ -38,23 +44,87 @@ const props = defineProps<{
|
|||
tags: string[]
|
||||
category: string[]
|
||||
}
|
||||
rUser: TopicUserInfo
|
||||
master: TopicUserInfo
|
||||
}>()
|
||||
|
||||
// 是否具有重新编辑的权限
|
||||
/**
|
||||
* 这里只是简单起见,不显示重新编辑
|
||||
* 实际上如果用户自己修改了 localStorage 中保存的信息,这个验证就失效了
|
||||
* 但是修改了也没有用,验证逻辑位于后端
|
||||
*/
|
||||
const isShowRewrite = useKUNGalgameUserStore().uid === props.rUser.uid
|
||||
// 当前登录用户的 uid
|
||||
const currUserUid = useKUNGalgameUserStore().uid
|
||||
// 是否具有重新编辑的权限
|
||||
const isShowRewrite = currUserUid === props.master.uid
|
||||
|
||||
// footer 左侧的动作,点赞等
|
||||
const actions = reactive({
|
||||
upvotes: props.info.upvotes,
|
||||
likes: props.info.likes,
|
||||
dislikes: props.info.dislikes,
|
||||
share: props.info.share,
|
||||
})
|
||||
|
||||
// 是否已经点过赞,点过踩
|
||||
const isActive = reactive({
|
||||
isUpvote: false,
|
||||
isLiked: false,
|
||||
isDisliked: false,
|
||||
isShared: false,
|
||||
})
|
||||
|
||||
// throttle 函数,1007 毫秒仅会触发一次点赞
|
||||
const handleClickLikeThrottled = throttle(
|
||||
async () => {
|
||||
// 当前用户不可以给自己点赞
|
||||
if (currUserUid === props.master.uid) {
|
||||
message(
|
||||
'You cannot like your own topic',
|
||||
'您不可以给自己的话题点赞',
|
||||
'warn'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
// 切换点赞激活状态
|
||||
isActive.isLiked = !isActive.isLiked
|
||||
|
||||
const res = await useKUNGalgameTopicStore().updateTopicLike(
|
||||
props.info.tid,
|
||||
props.master.uid,
|
||||
isActive.isLiked
|
||||
)
|
||||
|
||||
if (res.code === 200) {
|
||||
// 更新点赞数
|
||||
actions.likes.length = isActive.isLiked
|
||||
? actions.likes.length + 1
|
||||
: actions.likes.length - 1
|
||||
} else {
|
||||
message('Like topic failed!', '点赞话题失败', 'error')
|
||||
}
|
||||
},
|
||||
1007,
|
||||
() => {
|
||||
message(
|
||||
'You can only perform one operation within 1007 milliseconds',
|
||||
'您在 1007 毫秒内只能进行一次操作',
|
||||
'warn'
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
// 在点击事件中使用节流版本的函数
|
||||
const handleClickLike = () => {
|
||||
handleClickLikeThrottled()
|
||||
}
|
||||
|
||||
// 点击回复打开回复面板
|
||||
const handelReply = async () => {
|
||||
// 保存必要信息,以便发表回复
|
||||
replyDraft.value.tid = props.info.tid
|
||||
// 被回复人就是发表人的 uid
|
||||
replyDraft.value.to_uid = props.rUser.uid
|
||||
replyDraft.value.to_uid = props.master.uid
|
||||
replyDraft.value.to_floor = props.info.to_floor
|
||||
|
||||
isEdit.value = true
|
||||
|
@ -75,6 +145,29 @@ const handleClickEdit = () => {
|
|||
// 跳转到编辑界面
|
||||
router.push({ name: 'Edit' })
|
||||
}
|
||||
|
||||
// 初始化按钮的状态,是否已点赞等
|
||||
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
|
||||
}
|
||||
|
||||
// 已经分享
|
||||
if (props.info.share.includes(currUserUid)) {
|
||||
isActive.isShared = true
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
@ -86,7 +179,7 @@ const handleClickEdit = () => {
|
|||
<!-- 推话题 -->
|
||||
<li>
|
||||
<span class="icon"><Icon icon="bi:rocket" /></span>
|
||||
{{ info.upvotes.length }}
|
||||
{{ actions.upvotes.length }}
|
||||
</li>
|
||||
<!-- 查看数量 -->
|
||||
<li>
|
||||
|
@ -95,39 +188,38 @@ const handleClickEdit = () => {
|
|||
</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 }}
|
||||
{{ actions.dislikes.length }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- 底部右侧部分(回复、评论、只看、编辑) -->
|
||||
<div class="right">
|
||||
<ul>
|
||||
<li @click="handelReply">
|
||||
<div class="reply">
|
||||
<div @click="handelReply" class="reply">
|
||||
{{ $tm('topic.content.reply') }}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
<!-- 分享 -->
|
||||
<li>
|
||||
<span class="icon"><Icon icon="majesticons:share-line" /></span>
|
||||
</li>
|
||||
|
||||
<!-- 只看 -->
|
||||
<li>
|
||||
<span class="icon"><Icon icon="ph:user-focus-duotone" /></span>
|
||||
</li>
|
||||
|
||||
<!-- 编辑 -->
|
||||
<li v-if="isShowRewrite">
|
||||
<span @click="handleClickEdit" class="icon">
|
||||
<span v-if="isShowRewrite" @click="handleClickEdit" class="icon">
|
||||
<Icon icon="line-md:pencil-twotone-alt" />
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -170,17 +262,20 @@ const handleClickEdit = () => {
|
|||
color: var(--kungalgame-font-color-2);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* 激活后的样式 */
|
||||
.active {
|
||||
color: var(--kungalgame-blue-4);
|
||||
}
|
||||
|
||||
/* 底部右侧部分(回复、评论、只看、编辑) */
|
||||
.right ul {
|
||||
.right {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 10px;
|
||||
li {
|
||||
margin-right: 17px;
|
||||
span {
|
||||
display: flex;
|
||||
}
|
||||
margin-right: 17px;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,7 @@ const handelClosePanel = () => {
|
|||
<template>
|
||||
<Teleport to="body" :disabled="isEdit">
|
||||
<Transition
|
||||
enter-active-class="animate__animated animate__bounceInUp animate__faster"
|
||||
enter-active-class="animate__animated animate__fadeInUp animate__faster"
|
||||
leave-active-class="animate__animated animate__fadeOutDown animate__faster"
|
||||
>
|
||||
<div class="root" v-if="isEdit">
|
||||
|
|
Loading…
Reference in a new issue