From 8cfa90ca160259462f9a9ed51ff923521f21cbc2 Mon Sep 17 00:00:00 2001 From: yuetsh <517252939@qq.com> Date: Wed, 5 Aug 2026 13:52:58 -0600 Subject: [PATCH] =?UTF-8?q?feat(achievement):=20=E5=A5=96=E7=AB=A0?= =?UTF-8?q?=E6=A0=87=E5=87=BA=E6=9D=A5=E8=87=AA=E5=93=AA=E4=B8=AA=E9=A2=98?= =?UTF-8?q?=E5=8D=95=20+=20=E8=BF=9B=E5=BA=A6=E6=9D=A1=E5=8A=A8=E7=94=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 奖章卡片只有名字和描述,看不出是哪个题单发的,补上题单名并链到详情页。 进度是异步加载的,卡片挂载时百分比已是终值,naive 自带的过渡触发不了, 先按 0 渲染一帧再填真值,同时把时长从 .3s/.2s 拉到 .9s, 圆环数字换成 n-number-animation 同步滚动。 --- src/oj/achievement/index.vue | 78 ++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 3 deletions(-) diff --git a/src/oj/achievement/index.vue b/src/oj/achievement/index.vue index 18d7c9c..2451931 100644 --- a/src/oj/achievement/index.vue +++ b/src/oj/achievement/index.vue @@ -19,6 +19,11 @@ interface UserBadge { description: string icon: string } + // 奖章来自哪个题单,接口在 UserBadgeSerializer 里带出来 + problemset: { + id: number + title: string + } | null } const route = useRoute() @@ -43,6 +48,17 @@ const rarities = computed(() => (a, b) => RARITY_RANK[a.rarity] - RARITY_RANK[b.rarity], ), ) + +// 数据是异步来的,卡片挂上去时百分比就是终值,CSS 过渡不会触发。 +// 先按 0 渲染一帧,下一帧再填真值,圆环和进度条才有"长出来"的动画 +const grown = ref(false) +const percent = computed(() => + grown.value ? (summary.value?.percent ?? 0) : 0, +) +function rarityPercent(r: { unlocked: number; total: number }) { + if (!grown.value || !r.total) return 0 + return (r.unlocked / r.total) * 100 +} const badges = ref([]) const { isDesktop } = useBreakpoints() const tab = ref("all") @@ -58,6 +74,7 @@ const filtered = computed(() => { async function load() { loading.value = true + grown.value = false try { const [list, sum, badgeRes] = await Promise.all([ getAchievements(name.value), @@ -71,6 +88,9 @@ async function load() { } finally { loading.value = false } + // 等卡片以 0 渲染完这一帧,再切到真值 + await nextTick() + requestAnimationFrame(() => requestAnimationFrame(() => (grown.value = true))) } onMounted(load) @@ -84,10 +104,17 @@ watch(name, load) - {{ summary.percent }}% + + % + 已获得 {{ summary.unlocked }} / {{ summary.total }} @@ -107,8 +134,9 @@ watch(name, load) + + 来自题单 + + {{ b.problemset.title }} + + @@ -190,4 +229,37 @@ watch(name, load) .tabs { margin: 16px 0; } +.source { + display: block; + margin-top: 6px; + font-size: 13px; +} +.source a { + color: inherit; + text-decoration: underline; + text-underline-offset: 2px; +} +.source a:hover { + color: var(--n-text-color); +} + +/* naive 自带过渡,但圆环 stroke-dasharray 只有 .3s、线条 max-width 只有 .2s, + 配合 0 → 真值那一下太快看不出来,这里拉长 */ +.grow :deep(.n-progress-graph-circle-fill) { + transition: + opacity 0.3s var(--n-bezier), + stroke 0.3s var(--n-bezier), + stroke-dasharray 0.9s cubic-bezier(0.25, 1, 0.5, 1); +} +.grow :deep(.n-progress-graph-line-fill) { + transition: + background-color 0.3s var(--n-bezier), + max-width 0.9s cubic-bezier(0.25, 1, 0.5, 1); +} +@media (prefers-reduced-motion: reduce) { + .grow :deep(.n-progress-graph-circle-fill), + .grow :deep(.n-progress-graph-line-fill) { + transition: none; + } +}