feat: ranking page user

This commit is contained in:
KUN1007 2023-10-18 16:24:23 +08:00
parent f14f548ba0
commit 27dbbd98ec
10 changed files with 439 additions and 141 deletions

View file

@ -12,3 +12,14 @@ export async function getRankingTopicsApi(
const response = await fetchGet<Ranking.RankingGetTopicsResponseData>(url)
return response
}
// 获取 ranking 热门用户
export async function getRankingUsersApi(
request: Ranking.RankingGetUserRequestData
): Promise<Ranking.RankingGetUsersResponseData> {
const queryParams = objectToQueryParams(request)
const url = `/ranking/users?${queryParams}`
const response = await fetchGet<Ranking.RankingGetUsersResponseData>(url)
return response
}

View file

@ -1,8 +1,26 @@
type SortOrder = 'asc' | 'desc'
type TopicSortFieldRanking =
| 'popularity'
| 'views'
| 'upvotes_count'
| 'likes_count'
| 'replies_count'
| 'comments'
type UserSortFieldRanking =
| 'moemoepoint'
| 'upvote'
| 'like'
| 'topic_count'
| 'reply_count'
| 'comment_count'
export interface RankingGetTopicsRequestData {
page: number
limit: number
sortField: string
sortOrder: 'asc' | 'desc'
sortField: TopicSortFieldRanking
sortOrder: SortOrder
}
export interface RankingTopics {
@ -11,8 +29,22 @@ export interface RankingTopics {
field: string
}
export interface RankingGetUserRequestData {}
export interface RankingGetUserRequestData {
page: number
limit: number
sortField: UserSortFieldRanking
sortOrder: SortOrder
}
export interface RankingUsers {
uid: number
name: string
avatar: string
field: string
}
export type RankingGetTopicsResponseData = KUNGalgameResponseData<
RankingTopics[]
>
export type RankingGetUsersResponseData = KUNGalgameResponseData<RankingUsers[]>

View file

@ -212,7 +212,7 @@ $navNumber: v-bind(navItemNum);
}
&:nth-child(3):hover ~ .box {
background-color: var(--kungalgame-yellow-3);
background-color: var(--kungalgame-yellow-2);
left: calc(100% / $navNumber * 2);
}

View file

@ -5,9 +5,10 @@ import type {
RankingGetTopicsRequestData,
RankingGetTopicsResponseData,
RankingGetUserRequestData,
RankingGetUsersResponseData,
} from '@/api'
import { getRankingTopicsApi } from '@/api'
import { getRankingTopicsApi, getRankingUsersApi } from '@/api'
interface RankingStore {
topic: RankingGetTopicsRequestData
@ -20,12 +21,17 @@ export const useKUNGalgameRankingStore = defineStore({
persist: false,
state: (): RankingStore => ({
topic: {
page: 0,
limit: 0,
page: 1,
limit: 30,
sortField: 'popularity',
sortOrder: 'desc',
},
user: {},
user: {
page: 1,
limit: 30,
sortField: 'moemoepoint',
sortOrder: 'desc',
},
}),
getters: {},
actions: {
@ -39,5 +45,16 @@ export const useKUNGalgameRankingStore = defineStore({
return await getRankingTopicsApi(requestData)
},
async getUsers(): Promise<RankingGetUsersResponseData> {
const requestData: RankingGetUserRequestData = {
page: this.user.page,
limit: this.user.limit,
sortField: this.user.sortField,
sortOrder: this.user.sortOrder,
}
return await getRankingUsersApi(requestData)
},
},
})

View file

@ -17,6 +17,7 @@ html {
--kungalgame-green-4: #2da44e;
--kungalgame-yellow-2: #fcd554;
--kungalgame-yellow-3: #d4a72c;
--kungalgame-red-0: #ffebe9;

View file

@ -1,63 +1,116 @@
<script setup lang="ts">
import { computed } from 'vue'
import { Icon } from '@iconify/vue'
import { userIconMap } from './navSortItem'
import type { RankingUsers } from '@/api'
const props = defineProps<{
field: string
users: RankingUsers[]
}>()
const users = computed(() => props.users)
//
const parseTopicNumber = (field: string | string[]) => {
return Array.isArray(field) ? field.length : Math.ceil(parseInt(field))
}
</script>
<template>
<TransitionGroup name="list">
<!-- 单个用户 -->
<div class="single-kungalgamer">
<!-- 头像和名字 -->
<div class="avatar-name">
<img src="@/assets/images/KUN.jpg" alt="KUN" />
<span>鲲最可爱</span>
<div class="single-user" v-for="user in users" :key="user.uid">
<RouterLink :to="`/kungalgamer/${user.uid}/info`">
<!-- 用户的名字 -->
<div class="info">
<span class="avatar">
<img :src="user.avatar" :alt="user.name" />
</span>
<span class="name">{{ user.name }}</span>
</div>
<!-- 其它信息 -->
<!-- 用户的其它信息 -->
<div class="detail">
<!-- 萌萌点 -->
<span>MOE: 1007</span>
<!-- 点赞数 -->
<span><Icon icon="line-md:thumbs-up-twotone" />1007</span>
<!-- 话题数 -->
<span><Icon icon="line-md:text-box-multiple-twotone" />1007</span>
<!-- 回复数 -->
<span><Icon icon="ri:reply-line" />1007</span>
<!-- 评论数 -->
<span><Icon icon="fa-regular:comment-dots" />1007</span>
<Icon :icon="userIconMap[props.field]" />
<span>{{ parseTopicNumber(user.field) }}</span>
</div>
</RouterLink>
</div>
</TransitionGroup>
</template>
<style lang="scss" scoped>
/* 单个用户 */
.single-kungalgamer {
height: 37px;
/* 单个话题 */
.single-user {
a {
flex-shrink: 0;
border-bottom: 1px solid var(--kungalgame-pink-4);
height: 37px;
margin: 7px;
display: flex;
justify-content: space-between;
align-items: center;
background-color: var(--kungalgame-trans-pink-0);
border: 1px solid var(--kungalgame-trans-pink-0);
border-radius: 5px;
color: var(--kungalgame-font-color-3);
padding: 0 10px;
cursor: pointer;
&:hover {
transition: all 0.5s;
background-color: var(--kungalgame-trans-white-9);
border: 1px solid var(--kungalgame-pink-4);
color: var(--kungalgame-pink-4);
}
/* 用户的头像和名字 */
.avatar-name {
}
}
/* 话题的名字 */
.info {
display: flex;
align-items: center;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
.avatar {
height: 100%;
display: flex;
justify-content: center;
align-items: center;
}
/* 用户的头像 */
.avatar-name img {
img {
height: 30px;
width: 30px;
border-radius: 50%;
}
/* 用户的名字 */
.avatar-name span {
margin-left: 5px;
}
.detail {
display: flex;
span {
display: flex;
align-items: center;
.name {
margin-left: 10px;
}
}
.detail {
display: flex;
align-items: center;
color: var(--kungalgame-pink-4);
span {
color: var(--kungalgame-font-color-3);
margin-left: 10px;
}
}
.list-move, /* 对移动中的元素应用的过渡 */
.list-enter-active,
.list-leave-active {
transition: all 0.5s ease;
}
.list-enter-from,
.list-leave-to {
opacity: 0;
}
/*
以便能够正确地计算移动的动画 */
.list-leave-active {
position: absolute;
}
</style>

View file

@ -1,41 +1,104 @@
<script setup lang="ts">
import KUNgalgamer from './KUNGalgamer.vue'
import { onMounted, ref, watch } from 'vue'
import { Icon } from '@iconify/vue'
import KUNGalgamer from './KUNGalgamer.vue'
import { useKUNGalgameRankingStore } from '@/store/modules/ranking'
import { storeToRefs } from 'pinia'
import type { RankingUsers } from '@/api'
import { userSortItem, userIconMap } from './navSortItem'
const { user } = storeToRefs(useKUNGalgameRankingStore())
const users = ref<RankingUsers[]>([])
//
const isAscending = ref(false)
//
const getUsers = async () => {
const responseData = await useKUNGalgameRankingStore().getUsers()
return responseData.data
}
//
watch(
() => user,
async () => {
users.value = await getUsers()
},
{ deep: true }
)
//
onMounted(async () => {
users.value = await getUsers()
})
//
const handleClickSortOrder = () => {
isAscending.value = !isAscending.value
if (isAscending.value) {
user.value.sortOrder = 'asc'
} else {
user.value.sortOrder = 'desc'
}
}
</script>
<template>
<!-- 用户排行 -->
<div class="kungalgamer-ranking">
<!-- 用户排行标题 -->
<div class="kungalgamer-title">用户</div>
<!-- 用户排行的交互 -->
<div class="kungalgamer-nav">
<!-- 萌萌点 -->
<div class="moemoepoint">按萌萌点排序</div>
<!-- 获赞数 -->
<div class="praise">按获赞数排序</div>
<!-- 发布主题数 -->
<div class="topic">按发帖数排序</div>
<!-- 回复数 -->
<div class="reply">按回话题数排序</div>
<!-- 评论数 -->
<div class="comment">按评论数排序</div>
<!-- 话题排行 -->
<div class="topic">
<!-- 话题排行标题 -->
<div class="title">用户</div>
<!-- 话题排行的交互 -->
<div class="nav">
<!-- 升序降序 -->
<div class="order" @click="handleClickSortOrder">
<Transition name="order" mode="out-in">
<div v-if="isAscending">
<span>升序</span>
<Icon class="icon" icon="line-md:arrow-small-up" />
</div>
<!-- 单个用户的容器 -->
<div class="kungalgamer-container">
<KUNgalgamer />
<div v-else-if="!isAscending">
<span>降序</span>
<Icon class="icon" icon="line-md:arrow-small-down" />
</div>
</Transition>
</div>
<!-- 排序 -->
<div class="sort">
<Icon class="icon" :icon="userIconMap[user.sortField]" />
<span>筛选</span>
<!-- 排序子菜单 -->
<div class="submenu">
<div
class="item"
v-for="kun in userSortItem"
:key="kun.index"
@click="user.sortField = kun.sortField"
>
<span><Icon class="icon" :icon="kun.icon" /></span>
<span>{{ kun.name }}</span>
</div>
</div>
</div>
</div>
<!-- 单个话题的容器 -->
<div class="container">
<KUNGalgamer :field="user.sortField" :users="users" />
</div>
</div>
</template>
<style lang="scss" scoped>
/* 用户排行 */
.kungalgamer-ranking {
/* 话题排行 */
.topic {
width: 50%;
border-right: 1px solid var(--kungalgame-blue-4);
height: calc(100% - 50px - 20px - 40px);
}
/* 用户排行标题 */
.kungalgamer-title {
/* 字体设置 */
/* 话题排行标题 */
.title {
font-size: 30px;
color: var(--kungalgame-pink-4);
height: 50px;
@ -44,56 +107,114 @@ import KUNgalgamer from './KUNGalgamer.vue'
align-items: center;
margin-bottom: 20px;
}
/* 用户排行的交互 */
.kungalgamer-nav {
/* 话题排行的交互 */
.nav {
height: 40px;
display: flex;
justify-content: space-around;
margin-right: 10px;
cursor: pointer;
}
.kungalgamer-nav > div:hover {
background-color: var(--kungalgame-trans-pink-2);
transition: 0.2s;
}
/* 排行选择项 */
.moemoepoint,
.praise,
.topic,
.reply,
.comment {
height: 30px;
width: 100%;
align-items: center;
margin-left: 10px;
border: 1px solid var(--kungalgame-pink-4);
border-radius: 5px;
}
.order {
width: 100%;
height: 100%;
cursor: pointer;
transition: all 0.2s;
display: flex;
justify-content: center;
align-items: center;
.icon {
font-size: 22px;
}
& > div {
width: 100%;
display: flex;
justify-content: space-around;
align-items: center;
}
.praise,
.topic,
.reply,
.comment {
border-left: none;
}
/* 单个用户的容器 */
.kungalgamer-container {
height: 650px;
border: 1px solid var(--kungalgame-pink-4);
.sort {
position: relative;
width: 100%;
height: 100%;
display: flex;
justify-content: space-around;
align-items: center;
cursor: pointer;
border-left: 1px solid var(--kungalgame-pink-4);
}
.sort:hover .submenu {
display: flex;
}
.icon {
color: var(--kungalgame-pink-4);
}
.submenu {
top: 40px;
position: absolute;
width: 100%;
display: none;
flex-direction: column;
border-radius: 5px;
border: 1px solid var(--kungalgame-pink-0);
box-shadow: var(--shadow);
background-color: var(--kungalgame-trans-white-5);
backdrop-filter: blur(5px);
.item {
transition: all 0.2s;
height: 40px;
display: flex;
justify-content: space-around;
align-items: center;
&:hover {
background-color: var(--kungalgame-trans-pink-1);
}
}
}
/* 单个话题的容器 */
.container {
height: 100%;
border-top: none;
display: flex;
flex-direction: column;
margin-right: 10px;
margin-left: 10px;
overflow-y: scroll;
&::-webkit-scrollbar {
display: inline;
width: 4px;
height: 0;
}
&::-webkit-scrollbar-thumb {
background: var(--kungalgame-blue-4);
background: var(--kungalgame-pink-4);
border-radius: 2px;
}
/* 兼容火狐 */
scrollbar-width: thin;
scrollbar-color: var(--kungalgame-blue-4) var(--kungalgame-blue-1); /* Firefox 64+ */
scrollbar-color: var(--kungalgame-pink-4) var(--kungalgame-pink-0);
}
.order-enter-active,
.order-leave-active {
transition: all 0.25s ease-out;
}
.order-enter-from {
opacity: 0;
transform: translateY(-30px);
}
.order-leave-to {
opacity: 0;
transform: translateY(30px);
}
</style>

View file

@ -1,7 +1,8 @@
<script setup lang="ts">
import { Icon } from '@iconify/vue'
import type { RankingTopics } from '@/api'
import { computed } from 'vue'
import { Icon } from '@iconify/vue'
import { topicIconMap } from './navSortItem'
import type { RankingTopics } from '@/api'
const props = defineProps<{
field: string
@ -10,15 +11,6 @@ const props = defineProps<{
const topics = computed(() => props.topics)
const iconMap: Record<string, string> = {
popularity: 'bi:fire',
upvotes_count: 'bi:rocket',
views: 'ic:outline-remove-red-eye',
likes_count: 'line-md:thumbs-up-twotone',
replies_count: 'ri:reply-line',
comments: 'fa-regular:comment-dots',
}
//
const parseTopicNumber = (field: string | string[]) => {
return Array.isArray(field) ? field.length : Math.ceil(parseInt(field))
@ -38,7 +30,7 @@ const parseTopicNumber = (field: string | string[]) => {
<div class="detail">
<!-- 浏览数 -->
<Icon :icon="iconMap[props.field]" />
<Icon :icon="topicIconMap[props.field]" />
<span>{{ parseTopicNumber(topic.field) }}</span>
</div>
</RouterLink>

View file

@ -7,7 +7,7 @@ import { useKUNGalgameRankingStore } from '@/store/modules/ranking'
import { storeToRefs } from 'pinia'
import type { RankingTopics } from '@/api'
import { topicNavSortItem } from './navSortItem'
import { topicSortItem, topicIconMap } from './navSortItem'
const { topic } = storeToRefs(useKUNGalgameRankingStore())
const topics = ref<RankingTopics[]>([])
@ -20,15 +20,6 @@ const getTopics = async () => {
return responseData.data
}
const iconMap: Record<string, string> = {
popularity: 'bi:fire',
upvotes_count: 'bi:rocket',
views: 'ic:outline-remove-red-eye',
likes_count: 'line-md:thumbs-up-twotone',
replies_count: 'ri:reply-line',
comments: 'fa-regular:comment-dots',
}
//
watch(
() => topic,
@ -77,13 +68,13 @@ const handleClickSortOrder = () => {
<!-- 排序 -->
<div class="sort">
<Icon class="icon" :icon="iconMap[topic.sortField]" />
<Icon class="icon" :icon="topicIconMap[topic.sortField]" />
<span>筛选</span>
<!-- 排序子菜单 -->
<div class="submenu">
<div
class="item"
v-for="kun in topicNavSortItem"
v-for="kun in topicSortItem"
:key="kun.index"
@click="topic.sortField = kun.sortField"
>

View file

@ -1,11 +1,34 @@
type TopicSortFieldRanking =
| 'popularity'
| 'views'
| 'upvotes_count'
| 'likes_count'
| 'replies_count'
| 'comments'
type UserSortFieldRanking =
| 'moemoepoint'
| 'upvote'
| 'like'
| 'topic_count'
| 'reply_count'
| 'comment_count'
interface Topic {
index: number
icon: string
name: string
sortField: string
sortField: TopicSortFieldRanking
}
export const topicNavSortItem: Topic[] = [
interface User {
index: number
icon: string
name: string
sortField: UserSortFieldRanking
}
export const topicSortItem: Topic[] = [
{
index: 1,
icon: 'bi:fire',
@ -43,3 +66,60 @@ export const topicNavSortItem: Topic[] = [
sortField: 'comments',
},
]
export const userSortItem: User[] = [
{
index: 1,
icon: 'line-md:star-alt-twotone',
name: 'moemoepoint',
sortField: 'moemoepoint',
},
{
index: 2,
icon: 'bi:rocket',
name: 'upvote',
sortField: 'upvote',
},
{
index: 3,
icon: 'line-md:thumbs-up-twotone',
name: 'like',
sortField: 'like',
},
{
index: 4,
icon: 'line-md:text-box',
name: 'topicCount',
sortField: 'topic_count',
},
{
index: 5,
icon: 'ri:reply-line',
name: 'replyCount',
sortField: 'reply_count',
},
{
index: 6,
icon: 'fa-regular:comment-dots',
name: 'commentCount',
sortField: 'comment_count',
},
]
export const userIconMap: Record<string, string> = {
moemoepoint: 'line-md:star-alt-twotone',
upvote: 'bi:rocket',
like: 'line-md:thumbs-up-twotone',
topic_count: 'line-md:text-box',
reply_count: 'ri:reply-line',
comment_count: 'fa-regular:comment-dots',
}
export const topicIconMap: Record<string, string> = {
popularity: 'bi:fire',
upvotes_count: 'bi:rocket',
views: 'ic:outline-remove-red-eye',
likes_count: 'line-md:thumbs-up-twotone',
replies_count: 'ri:reply-line',
comments: 'fa-regular:comment-dots',
}