feat(achievement): 个人主页与排行榜添加成就入口
个人主页显示完成度与最近获得的成就,可跳转奖杯馆; 全服 Top100 和班级排名每行加一个跳转按钮。 成就摘要单独请求,不并进 init 里那个按位置取值的 promises 数组, 且失败时静默降级——它不该拖垮整个个人主页。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -198,17 +198,30 @@ const columns: DataTableColumn<Rank>[] = [
|
||||
"streamline-emojis:smiling-face-with-sunglasses",
|
||||
),
|
||||
key: "username",
|
||||
width: 200,
|
||||
width: 240,
|
||||
render: (row) =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
text: true,
|
||||
type: "info",
|
||||
onClick: () => router.push("/user?name=" + row.user.username),
|
||||
},
|
||||
() => row.user.username,
|
||||
),
|
||||
h("div", { style: "display:flex;align-items:center;gap:6px" }, [
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
text: true,
|
||||
type: "info",
|
||||
onClick: () => router.push("/user?name=" + row.user.username),
|
||||
},
|
||||
() => row.user.username,
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
text: true,
|
||||
size: "tiny",
|
||||
title: "查看成就",
|
||||
onClick: () =>
|
||||
router.push("/achievement?name=" + row.user.username),
|
||||
},
|
||||
() => "🏆",
|
||||
),
|
||||
]),
|
||||
},
|
||||
{
|
||||
title: renderTableTitle(
|
||||
@@ -378,32 +391,44 @@ const myClassColumns: DataTableColumn<UserRank>[] = [
|
||||
{
|
||||
title: "用户名",
|
||||
key: "username",
|
||||
width: 200,
|
||||
width: 240,
|
||||
render: (row) =>
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
text: true,
|
||||
type: "info",
|
||||
onClick: () => router.push("/user?name=" + row.username),
|
||||
},
|
||||
() =>
|
||||
row.rank === myRank.value
|
||||
? h(
|
||||
NFlex,
|
||||
{ align: "flex-end" },
|
||||
{
|
||||
default: () => [
|
||||
h("span", {}, row.username),
|
||||
h(Icon, {
|
||||
width: 20,
|
||||
icon: "fluent-emoji:person-raising-hand",
|
||||
}),
|
||||
],
|
||||
},
|
||||
)
|
||||
: row.username,
|
||||
),
|
||||
h("div", { style: "display:flex;align-items:center;gap:6px" }, [
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
text: true,
|
||||
type: "info",
|
||||
onClick: () => router.push("/user?name=" + row.username),
|
||||
},
|
||||
() =>
|
||||
row.rank === myRank.value
|
||||
? h(
|
||||
NFlex,
|
||||
{ align: "flex-end" },
|
||||
{
|
||||
default: () => [
|
||||
h("span", {}, row.username),
|
||||
h(Icon, {
|
||||
width: 20,
|
||||
icon: "fluent-emoji:person-raising-hand",
|
||||
}),
|
||||
],
|
||||
},
|
||||
)
|
||||
: row.username,
|
||||
),
|
||||
h(
|
||||
NButton,
|
||||
{
|
||||
text: true,
|
||||
size: "tiny",
|
||||
title: "查看成就",
|
||||
onClick: () => router.push("/achievement?name=" + row.username),
|
||||
},
|
||||
() => "🏆",
|
||||
),
|
||||
]),
|
||||
},
|
||||
{
|
||||
title: "已解决",
|
||||
|
||||
@@ -4,7 +4,12 @@ import { NH2, NH3 } from "naive-ui"
|
||||
import { getProfile } from "shared/api"
|
||||
import { useBreakpoints } from "shared/composables/breakpoints"
|
||||
import { durationToDays, parseTime } from "utils/functions"
|
||||
import type { Profile, UserBadge as UserBadgeType } from "utils/types"
|
||||
import type {
|
||||
AchievementSummary,
|
||||
Profile,
|
||||
UserBadge as UserBadgeType,
|
||||
} from "utils/types"
|
||||
import { getAchievementSummary } from "oj/achievement/api"
|
||||
import { getMetrics, getUserBadges } from "../api"
|
||||
import GroupedUserBadge from "shared/components/GroupedUserBadge.vue"
|
||||
import { useUserStore } from "shared/store/user"
|
||||
@@ -19,6 +24,7 @@ const latestSubmissionAt = ref("")
|
||||
const toLatestAt = ref("")
|
||||
const learnDuration = ref("")
|
||||
const userBadges = ref<GroupedBadge[]>([])
|
||||
const achievementSummary = ref<AchievementSummary | null>(null)
|
||||
const [loading, toggle] = useToggle()
|
||||
const [show, toggleShow] = useToggle(false)
|
||||
|
||||
@@ -142,6 +148,19 @@ async function init() {
|
||||
}
|
||||
}
|
||||
|
||||
// 单独取,不塞进上面的 promises 数组:那里是按位置取 results[0]/[1] 的,
|
||||
// 插一项进去会打乱既有索引。成就摘要取不到也不该影响整个个人主页
|
||||
async function loadAchievementSummary() {
|
||||
try {
|
||||
const res = await getAchievementSummary(
|
||||
(route.query.name as string) || undefined,
|
||||
)
|
||||
achievementSummary.value = res.data
|
||||
} catch {
|
||||
achievementSummary.value = null
|
||||
}
|
||||
}
|
||||
|
||||
const metrics = computed(() => {
|
||||
if (loading.value) return []
|
||||
return [
|
||||
@@ -180,7 +199,10 @@ const metrics = computed(() => {
|
||||
]
|
||||
})
|
||||
|
||||
onMounted(init)
|
||||
onMounted(() => {
|
||||
init()
|
||||
loadAchievementSummary()
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<n-flex
|
||||
@@ -244,6 +266,48 @@ onMounted(init)
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<!-- 成就摘要 -->
|
||||
<n-card
|
||||
v-if="!loading && profile && achievementSummary"
|
||||
class="wrapper"
|
||||
hoverable
|
||||
>
|
||||
<n-flex align="center" justify="space-between">
|
||||
<n-flex align="center" :size="12">
|
||||
<span class="achievement-title">
|
||||
成就 {{ achievementSummary.unlocked }} /
|
||||
{{ achievementSummary.total }}
|
||||
</span>
|
||||
<n-tag size="small" type="info">
|
||||
{{ achievementSummary.percent }}%
|
||||
</n-tag>
|
||||
</n-flex>
|
||||
<n-button
|
||||
text
|
||||
type="primary"
|
||||
@click="
|
||||
router.push({
|
||||
path: '/achievement',
|
||||
query: route.query.name ? { name: route.query.name } : {},
|
||||
})
|
||||
"
|
||||
>
|
||||
查看全部
|
||||
</n-button>
|
||||
</n-flex>
|
||||
<n-flex align="center" :size="10" class="achievement-recent">
|
||||
<n-tooltip v-for="a in achievementSummary.recent" :key="a.id">
|
||||
<template #trigger>
|
||||
<span class="achievement-icon">{{ a.icon }}</span>
|
||||
</template>
|
||||
{{ a.name }}
|
||||
</n-tooltip>
|
||||
<n-text v-if="!achievementSummary.recent.length" depth="3">
|
||||
还没有获得成就
|
||||
</n-text>
|
||||
</n-flex>
|
||||
</n-card>
|
||||
|
||||
<!-- 徽章展示卡片 -->
|
||||
<n-card
|
||||
v-if="!loading && profile && userBadges.length > 0"
|
||||
@@ -321,4 +385,14 @@ h2 {
|
||||
word-wrap: break-word;
|
||||
max-width: 100%;
|
||||
}
|
||||
.achievement-title {
|
||||
font-weight: 600;
|
||||
}
|
||||
.achievement-recent {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.achievement-icon {
|
||||
font-size: 24px;
|
||||
cursor: default;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user