feat: ranking page fetch
This commit is contained in:
parent
69f4db0bfe
commit
e5e3135aef
|
@ -14,50 +14,29 @@ const homeURLs = {
|
|||
export async function getHomeTopicApi(
|
||||
requestData: Home.HomeTopicRequestData
|
||||
): Promise<Home.HomeTopicResponseData> {
|
||||
try {
|
||||
const queryParams = objectToQueryParams(requestData)
|
||||
|
||||
// 调用 fetchPost 函数
|
||||
const response = await fetchGet<Home.HomeTopicResponseData>(
|
||||
`${homeURLs.home}?${queryParams}`
|
||||
)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
// 处理错误
|
||||
console.error(error)
|
||||
throw new Error('Failed to fetch home topic')
|
||||
}
|
||||
}
|
||||
|
||||
// 首页今日热门话题
|
||||
export async function getHomeNavHotTopicApi(): Promise<Home.HomeHotTopicResponseData> {
|
||||
try {
|
||||
// 调用 fetchPost 函数
|
||||
const response = await fetchGet<Home.HomeHotTopicResponseData>(
|
||||
homeURLs.navHot
|
||||
)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
// 处理错误
|
||||
console.error(error)
|
||||
throw new Error('Failed to fetch home nav hot topic')
|
||||
}
|
||||
}
|
||||
|
||||
// 首页今日最新话题
|
||||
export async function getHomeNavNewTopicApi(): Promise<Home.HomeNewTopicResponseData> {
|
||||
try {
|
||||
// 调用 fetchPost 函数
|
||||
const response = await fetchGet<Home.HomeNewTopicResponseData>(
|
||||
homeURLs.navNew
|
||||
)
|
||||
|
||||
return response
|
||||
} catch (error) {
|
||||
// 处理错误
|
||||
console.error(error)
|
||||
throw new Error('Failed to fetch home nav new topic')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,12 +29,13 @@ export interface HomeTopic {
|
|||
tid: number
|
||||
title: string
|
||||
views: number
|
||||
likes: number[]
|
||||
replies: number[]
|
||||
upvotesCount: number
|
||||
likesCount: number
|
||||
repliesCount: number
|
||||
comments: number
|
||||
|
||||
time: number
|
||||
content: string
|
||||
upvotes: number[]
|
||||
tags: string[]
|
||||
category: string[]
|
||||
popularity: number
|
||||
|
|
|
@ -8,6 +8,7 @@ export * from './edit/types/edit'
|
|||
export * from './home/types/home'
|
||||
export * from './user/types/user'
|
||||
export * from './login/types/login'
|
||||
export * from './ranking/types/ranking'
|
||||
export * from './topic/types'
|
||||
export * from './update-log/types/updateLog'
|
||||
|
||||
|
@ -17,5 +18,6 @@ export * from './edit'
|
|||
export * from './home'
|
||||
export * from './user'
|
||||
export * from './login'
|
||||
export * from './ranking'
|
||||
export * from './topic'
|
||||
export * from './update-log'
|
||||
|
|
14
src/api/ranking/index.ts
Normal file
14
src/api/ranking/index.ts
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { fetchGet } from '@/utils/request'
|
||||
import objectToQueryParams from '@/utils/objectToQueryParams'
|
||||
import * as Ranking from './types/ranking'
|
||||
|
||||
// 获取 ranking 热门话题
|
||||
export async function getRankingTopicsApi(
|
||||
request: Ranking.RankingGetTopicsRequestData
|
||||
): Promise<Ranking.RankingGetTopicsResponseData> {
|
||||
const queryParams = objectToQueryParams(request)
|
||||
const url = `/ranking/topics?${queryParams}`
|
||||
|
||||
const response = await fetchGet<Ranking.RankingGetTopicsResponseData>(url)
|
||||
return response
|
||||
}
|
18
src/api/ranking/types/ranking.ts
Normal file
18
src/api/ranking/types/ranking.ts
Normal file
|
@ -0,0 +1,18 @@
|
|||
export interface RankingGetTopicsRequestData {
|
||||
page: number
|
||||
limit: number
|
||||
sortField: string
|
||||
sortOrder: string
|
||||
}
|
||||
|
||||
export interface RankingTopics {
|
||||
tid: number
|
||||
title: string
|
||||
field: string
|
||||
}
|
||||
|
||||
export interface RankingGetUserRequestData {}
|
||||
|
||||
export type RankingGetTopicsResponseData = KUNGalgameResponseData<
|
||||
RankingTopics[]
|
||||
>
|
|
@ -25,7 +25,7 @@ export interface TopicDetail {
|
|||
tags: string[]
|
||||
edited: number
|
||||
user: TopicUserInfo
|
||||
rid: number[]
|
||||
replies: number[]
|
||||
status: number
|
||||
share: number[]
|
||||
category: string[]
|
||||
|
|
|
@ -12,11 +12,11 @@ export interface UserInfo {
|
|||
dislike: number
|
||||
daily_topic_count: number
|
||||
|
||||
topic: number[]
|
||||
reply: number[]
|
||||
comment: number[]
|
||||
like_topic: number[]
|
||||
upvote_topic: number[]
|
||||
topicCount: number[]
|
||||
replyCount: number[]
|
||||
commentCount: number[]
|
||||
likeTopic: number[]
|
||||
upvoteTopic: number[]
|
||||
}
|
||||
|
||||
// 用户更新头像
|
||||
|
|
43
src/store/modules/ranking.ts
Normal file
43
src/store/modules/ranking.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
// 评论的临时数据,用于组件间传输
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
import type {
|
||||
RankingGetTopicsRequestData,
|
||||
RankingGetTopicsResponseData,
|
||||
RankingGetUserRequestData,
|
||||
} from '@/api'
|
||||
|
||||
import { getRankingTopicsApi } from '@/api'
|
||||
|
||||
interface RankingStore {
|
||||
topic: RankingGetTopicsRequestData
|
||||
user: RankingGetUserRequestData
|
||||
}
|
||||
|
||||
export const useKUNGalgameRankingStore = defineStore({
|
||||
id: 'KUNGalgameRanking',
|
||||
// 不持久
|
||||
persist: false,
|
||||
state: (): RankingStore => ({
|
||||
topic: {
|
||||
page: 0,
|
||||
limit: 0,
|
||||
sortField: 'popularity',
|
||||
sortOrder: 'desc',
|
||||
},
|
||||
user: {},
|
||||
}),
|
||||
getters: {},
|
||||
actions: {
|
||||
async getTopics(): Promise<RankingGetTopicsResponseData> {
|
||||
const requestData: RankingGetTopicsRequestData = {
|
||||
page: this.topic.page,
|
||||
limit: this.topic.limit,
|
||||
sortField: this.topic.sortField,
|
||||
sortOrder: this.topic.sortOrder,
|
||||
}
|
||||
|
||||
return await getRankingTopicsApi(requestData)
|
||||
},
|
||||
},
|
||||
})
|
|
@ -7,7 +7,7 @@ export const isValidURL = (url: string) => {
|
|||
|
||||
// 正则表达式匹配合法邮箱
|
||||
export const isValidEmail = (email: string) => {
|
||||
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
const regex = /^[^\s@]{1,64}@[^\s@]{1,255}\.[^\s@]{1,24}$/
|
||||
return regex.test(email)
|
||||
}
|
||||
|
||||
|
|
|
@ -23,12 +23,12 @@ const {
|
|||
tid,
|
||||
title,
|
||||
views,
|
||||
likes,
|
||||
replies,
|
||||
likesCount,
|
||||
repliesCount,
|
||||
comments,
|
||||
time,
|
||||
content,
|
||||
upvotes,
|
||||
upvotesCount,
|
||||
tags,
|
||||
category,
|
||||
popularity,
|
||||
|
@ -37,7 +37,7 @@ const {
|
|||
const plainText = getPlainText(content)
|
||||
|
||||
const getRepliesCount = computed(() => {
|
||||
return replies.length + comments
|
||||
return repliesCount + comments
|
||||
})
|
||||
</script>
|
||||
|
||||
|
@ -57,9 +57,9 @@ const getRepliesCount = computed(() => {
|
|||
<Icon icon="ic:outline-remove-red-eye" /><span>{{ views }}</span>
|
||||
</li>
|
||||
<li>
|
||||
<Icon icon="line-md:thumbs-up-twotone" /><span>{{
|
||||
likes.length
|
||||
}}</span>
|
||||
<Icon icon="line-md:thumbs-up-twotone" /><span>
|
||||
{{ likesCount }}
|
||||
</span>
|
||||
</li>
|
||||
<li>
|
||||
<Icon icon="ri:reply-line" /><span>{{ getRepliesCount }}</span>
|
||||
|
@ -68,12 +68,14 @@ const getRepliesCount = computed(() => {
|
|||
|
||||
<!-- 话题的发布日期 -->
|
||||
<div class="time">
|
||||
<span>{{
|
||||
<span>
|
||||
{{
|
||||
formatTimeDifference(
|
||||
time,
|
||||
settingsStore.showKUNGalgameLanguage.value
|
||||
)
|
||||
}}</span>
|
||||
}}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,26 +1,44 @@
|
|||
<script setup lang="ts">
|
||||
import { Icon } from '@iconify/vue'
|
||||
import type { RankingTopics } from '@/api'
|
||||
import { computed } from 'vue'
|
||||
|
||||
const props = defineProps<{
|
||||
field: string
|
||||
topics: RankingTopics[]
|
||||
}>()
|
||||
|
||||
const topics = computed(() => props.topics)
|
||||
|
||||
const icons: Record<string, string> = {
|
||||
popularity: 'bi:fire',
|
||||
upvotes: 'bi:rocket',
|
||||
views: 'ic:outline-remove-red-eye',
|
||||
likes: 'line-md:thumbs-up-twotone',
|
||||
replies: 'ri:reply-line',
|
||||
comments: 'fa-regular:comment-dots',
|
||||
}
|
||||
|
||||
// 将传过来的数据转为数值
|
||||
const parseTopicNumber = (field: string | string[]) => {
|
||||
return Array.isArray(field) ? field.length : Math.ceil(parseInt(field))
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 单个话题 -->
|
||||
<div class="single-topic">
|
||||
<div class="single-topic" v-for="(topic, index) in topics" :key="index">
|
||||
<!-- 话题的名字 -->
|
||||
<div class="topic-name">
|
||||
啊这可海星啊这可海星啊这可海星啊这可海星啊这可海星啊这可海星啊这可海星啊这可海星
|
||||
{{ topic.title }}
|
||||
</div>
|
||||
<!-- 话题的其它信息 -->
|
||||
<div class="detail">
|
||||
<!-- 浏览数 -->
|
||||
<span><Icon icon="ic:outline-remove-red-eye" />1007</span>
|
||||
<!-- 点赞数 -->
|
||||
<span><Icon icon="line-md:thumbs-up-twotone" />1007</span>
|
||||
<!-- 回复数 -->
|
||||
<span><Icon icon="ri:reply-line" />1007</span>
|
||||
<!-- 评论数 -->
|
||||
<span><Icon icon="fa-regular:comment-dots" />1007</span>
|
||||
<!-- 推话题数 -->
|
||||
<span><Icon icon="bi:rocket" />1007</span>
|
||||
<span>
|
||||
<Icon :icon="icons[props.field]" />
|
||||
{{ parseTopicNumber(topic.field) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
|
|
@ -1,39 +1,77 @@
|
|||
<script setup lang="ts">
|
||||
import { onMounted, ref, watch } from 'vue'
|
||||
import { Icon } from '@iconify/vue'
|
||||
import Topic from './Topic.vue'
|
||||
|
||||
import { useKUNGalgameRankingStore } from '@/store/modules/ranking'
|
||||
import { storeToRefs } from 'pinia'
|
||||
|
||||
import type { RankingTopics } from '@/api'
|
||||
import { topicNavSortItem } from './navSortItem'
|
||||
|
||||
const { topic, user } = storeToRefs(useKUNGalgameRankingStore())
|
||||
const topics = ref<RankingTopics[]>([])
|
||||
|
||||
// 获取话题
|
||||
const getTopics = async () => {
|
||||
const responseData = await useKUNGalgameRankingStore().getTopics()
|
||||
return responseData.data
|
||||
}
|
||||
|
||||
// 监听话题数据获取新话题
|
||||
watch(
|
||||
() => topic,
|
||||
async () => {
|
||||
topics.value = await getTopics()
|
||||
},
|
||||
{ deep: true }
|
||||
)
|
||||
|
||||
// 挂载时获取话题
|
||||
onMounted(async () => {
|
||||
topics.value = await getTopics()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<!-- 话题排行 -->
|
||||
<div class="topic-ranking">
|
||||
<div class="topic">
|
||||
<!-- 话题排行标题 -->
|
||||
<div class="topic-title">最萌的话题</div>
|
||||
<div class="title">最萌的话题</div>
|
||||
<!-- 话题排行的交互 -->
|
||||
<div class="topic-nav">
|
||||
<!-- 浏览数 -->
|
||||
<div class="view">按浏览数排序</div>
|
||||
<!-- 点赞数 -->
|
||||
<div class="like">按点赞数排序</div>
|
||||
<!-- 按回复数排序 -->
|
||||
<div class="reply">按回复数排序</div>
|
||||
<!-- 按回复数排序 -->
|
||||
<div class="comment">按评论数排序</div>
|
||||
<!-- 按推话题数排序 -->
|
||||
<div class="top">按推数排序</div>
|
||||
<div class="nav">
|
||||
<!-- 升序降序 -->
|
||||
<div class="order">排序</div>
|
||||
|
||||
<div class="field">
|
||||
<!-- 排序子菜单 -->
|
||||
<div class="sort-submenu">
|
||||
<div
|
||||
class="sort-item"
|
||||
v-for="kun in topicNavSortItem"
|
||||
:key="kun.index"
|
||||
@click="topic.sortField = kun.sortField"
|
||||
>
|
||||
<span><Icon class="icon-item" :icon="kun.icon" /></span>
|
||||
<span>按时间排序</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 单个话题的容器 -->
|
||||
<div class="topic-container">
|
||||
<Topic />
|
||||
<div class="container">
|
||||
<Topic :field="topic.sortField" :topics="topics" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
/* 话题排行 */
|
||||
.topic-ranking {
|
||||
.topic {
|
||||
width: 50%;
|
||||
}
|
||||
/* 话题排行标题 */
|
||||
.topic-title {
|
||||
.title {
|
||||
font-size: 30px;
|
||||
color: var(--kungalgame-blue-4);
|
||||
height: 50px;
|
||||
|
@ -43,56 +81,31 @@ import Topic from './Topic.vue'
|
|||
margin-bottom: 20px;
|
||||
}
|
||||
/* 话题排行的交互 */
|
||||
.topic-nav {
|
||||
.nav {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
margin-left: 10px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.topic-nav > div:hover {
|
||||
background-color: var(--kungalgame-trans-blue-2);
|
||||
transition: 0.2s;
|
||||
}
|
||||
/* 单个交互项目 */
|
||||
.view,
|
||||
.like,
|
||||
.comment,
|
||||
.reply,
|
||||
.top {
|
||||
height: 30px;
|
||||
width: 100%;
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.like,
|
||||
.reply,
|
||||
.comment,
|
||||
.top {
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
/* 单个话题的容器 */
|
||||
.topic-container {
|
||||
height: 650px;
|
||||
border: 1px solid var(--kungalgame-blue-4);
|
||||
.container {
|
||||
height: 100%;
|
||||
border-top: none;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 10px;
|
||||
overflow-y: scroll;
|
||||
/* 兼容火狐 */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--kungalgame-blue-4) var(--kungalgame-blue-1); /* Firefox 64+ */
|
||||
}
|
||||
/* 滚动条的样式 */
|
||||
.topic-container::-webkit-scrollbar {
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: inline;
|
||||
width: 4px;
|
||||
height: 0;
|
||||
}
|
||||
.topic-container::-webkit-scrollbar-thumb {
|
||||
&::-webkit-scrollbar-thumb {
|
||||
background: var(--kungalgame-blue-4);
|
||||
border-radius: 2px;
|
||||
}
|
||||
/* 兼容火狐 */
|
||||
scrollbar-width: thin;
|
||||
scrollbar-color: var(--kungalgame-blue-4) var(--kungalgame-blue-1);
|
||||
}
|
||||
</style>
|
||||
|
|
45
src/views/ranking/components/navSortItem.ts
Normal file
45
src/views/ranking/components/navSortItem.ts
Normal file
|
@ -0,0 +1,45 @@
|
|||
interface Topic {
|
||||
index: number
|
||||
icon: string
|
||||
name: string
|
||||
sortField: string
|
||||
}
|
||||
|
||||
export const topicNavSortItem: Topic[] = [
|
||||
{
|
||||
index: 1,
|
||||
icon: 'bi:fire',
|
||||
name: 'popularity',
|
||||
sortField: 'popularity',
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
icon: 'bi:rocket',
|
||||
name: 'upvote',
|
||||
sortField: 'upvotes',
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
icon: 'ic:outline-remove-red-eye',
|
||||
name: 'views',
|
||||
sortField: 'views',
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
icon: 'line-md:thumbs-up-twotone',
|
||||
name: 'likes',
|
||||
sortField: 'likes',
|
||||
},
|
||||
{
|
||||
index: 5,
|
||||
icon: 'ri:reply-line',
|
||||
name: 'replies',
|
||||
sortField: 'replies',
|
||||
},
|
||||
{
|
||||
index: 6,
|
||||
icon: 'fa-regular:comment-dots',
|
||||
name: 'comments',
|
||||
sortField: 'comments',
|
||||
},
|
||||
]
|
|
@ -37,7 +37,7 @@ const {
|
|||
tags,
|
||||
edited,
|
||||
user,
|
||||
// rid,
|
||||
// replies,
|
||||
status,
|
||||
share,
|
||||
category,
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
这个区域包含所有人回复给楼主的话题,其中每个人的话题将会被拆分成为单独的组件
|
||||
-->
|
||||
<script setup lang="ts">
|
||||
import { computed, onMounted, onUnmounted, onUpdated, ref } from 'vue'
|
||||
import { computed, ref } from 'vue'
|
||||
import { Icon } from '@iconify/vue'
|
||||
// 内容区组件
|
||||
import Content from '../Content.vue'
|
||||
|
|
Loading…
Reference in a new issue