feat: like topic

This commit is contained in:
KUN1007 2023-09-30 21:49:37 +08:00
parent efef4bc6f9
commit cc7fcb678a
12 changed files with 241 additions and 48 deletions

View file

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

View file

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

View file

@ -1,3 +1,13 @@
/**
*
*/
// 点赞
export interface TopicLikeTopicRequestData {
tid: number
to_uid: number
isPush: boolean
}
// 点赞话题响应数据的格式
export type TopicLikeTopicResponseData = KUNGalgameResponseData<{}>

View file

@ -16,6 +16,9 @@ export default (
type: MessageType,
duration?: number
) => {
// 初始化的时候先将上一次创建的内容销毁
render(null, document.body)
const messageNode = h(Message, {
messageCN,
messageEN,

View file

@ -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;
}
}
/* 编辑器体的样式 */

View file

@ -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> {
// 这里的默认值用于初始化

View file

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

View file

@ -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
View 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
}

View file

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

View file

@ -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">
{{ $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">
<Icon icon="line-md:pencil-twotone-alt" />
</span>
</li>
</ul>
<div @click="handelReply" class="reply">
{{ $tm('topic.content.reply') }}
</div>
<!-- 分享 -->
<span class="icon"><Icon icon="majesticons:share-line" /></span>
<!-- 只看 -->
<span class="icon"><Icon icon="ph:user-focus-duotone" /></span>
<!-- 编辑 -->
<span v-if="isShowRewrite" @click="handleClickEdit" class="icon">
<Icon icon="line-md:pencil-twotone-alt" />
</span>
</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 {
span {
display: flex;
margin-right: 17px;
span {
display: flex;
}
}
}

View file

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