✨ Rewind renders most of the data
This commit is contained in:
4
app/components.d.ts
vendored
4
app/components.d.ts
vendored
@@ -42,6 +42,7 @@ declare module 'vue' {
|
|||||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||||
NModal: typeof import('naive-ui')['NModal']
|
NModal: typeof import('naive-ui')['NModal']
|
||||||
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||||
|
NNumberAnimation: typeof import('naive-ui')['NNumberAnimation']
|
||||||
NPopover: typeof import('naive-ui')['NPopover']
|
NPopover: typeof import('naive-ui')['NPopover']
|
||||||
NProgress: typeof import('naive-ui')['NProgress']
|
NProgress: typeof import('naive-ui')['NProgress']
|
||||||
NRadio: typeof import('naive-ui')['NRadio']
|
NRadio: typeof import('naive-ui')['NRadio']
|
||||||
@@ -50,6 +51,7 @@ declare module 'vue' {
|
|||||||
NSelect: typeof import('naive-ui')['NSelect']
|
NSelect: typeof import('naive-ui')['NSelect']
|
||||||
NSpace: typeof import('naive-ui')['NSpace']
|
NSpace: typeof import('naive-ui')['NSpace']
|
||||||
NSpin: typeof import('naive-ui')['NSpin']
|
NSpin: typeof import('naive-ui')['NSpin']
|
||||||
|
NStatistic: typeof import('naive-ui')['NStatistic']
|
||||||
NTab: typeof import('naive-ui')['NTab']
|
NTab: typeof import('naive-ui')['NTab']
|
||||||
NTabs: typeof import('naive-ui')['NTabs']
|
NTabs: typeof import('naive-ui')['NTabs']
|
||||||
NTag: typeof import('naive-ui')['NTag']
|
NTag: typeof import('naive-ui')['NTag']
|
||||||
@@ -91,6 +93,7 @@ declare global {
|
|||||||
const NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
const NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||||
const NModal: typeof import('naive-ui')['NModal']
|
const NModal: typeof import('naive-ui')['NModal']
|
||||||
const NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
const NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||||
|
const NNumberAnimation: typeof import('naive-ui')['NNumberAnimation']
|
||||||
const NPopover: typeof import('naive-ui')['NPopover']
|
const NPopover: typeof import('naive-ui')['NPopover']
|
||||||
const NProgress: typeof import('naive-ui')['NProgress']
|
const NProgress: typeof import('naive-ui')['NProgress']
|
||||||
const NRadio: typeof import('naive-ui')['NRadio']
|
const NRadio: typeof import('naive-ui')['NRadio']
|
||||||
@@ -99,6 +102,7 @@ declare global {
|
|||||||
const NSelect: typeof import('naive-ui')['NSelect']
|
const NSelect: typeof import('naive-ui')['NSelect']
|
||||||
const NSpace: typeof import('naive-ui')['NSpace']
|
const NSpace: typeof import('naive-ui')['NSpace']
|
||||||
const NSpin: typeof import('naive-ui')['NSpin']
|
const NSpin: typeof import('naive-ui')['NSpin']
|
||||||
|
const NStatistic: typeof import('naive-ui')['NStatistic']
|
||||||
const NTab: typeof import('naive-ui')['NTab']
|
const NTab: typeof import('naive-ui')['NTab']
|
||||||
const NTabs: typeof import('naive-ui')['NTabs']
|
const NTabs: typeof import('naive-ui')['NTabs']
|
||||||
const NTag: typeof import('naive-ui')['NTag']
|
const NTag: typeof import('naive-ui')['NTag']
|
||||||
|
|||||||
97
app/components/Post/PostItemContained.vue
Normal file
97
app/components/Post/PostItemContained.vue
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="loading" class="flex justify-center items-center py-4">
|
||||||
|
<n-spin size="large" />
|
||||||
|
</div>
|
||||||
|
<div v-else-if="error" class="text-red-500 text-center py-4">
|
||||||
|
{{ error }}
|
||||||
|
</div>
|
||||||
|
<post-item
|
||||||
|
v-else-if="post"
|
||||||
|
:item="post"
|
||||||
|
:compact="compact"
|
||||||
|
:flat="flat"
|
||||||
|
:slim="slim"
|
||||||
|
:show-referenced="showReferenced"
|
||||||
|
@react="handleReaction"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { ref, watch, onMounted } from "vue"
|
||||||
|
import { useSolarNetwork } from "~/composables/useSolarNetwork"
|
||||||
|
import type { SnPost } from "~/types/api"
|
||||||
|
import PostItem from "./PostItem.vue"
|
||||||
|
import { keysToCamel } from "~/utils/transformKeys"
|
||||||
|
|
||||||
|
const props = withDefaults(
|
||||||
|
defineProps<{
|
||||||
|
postId: string
|
||||||
|
showReferenced?: boolean
|
||||||
|
compact?: boolean
|
||||||
|
flat?: boolean
|
||||||
|
slim?: boolean
|
||||||
|
}>(),
|
||||||
|
{ showReferenced: true, compact: false, flat: false, slim: false }
|
||||||
|
)
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
react: [symbol: string, attitude: number, delta: number]
|
||||||
|
loaded: [post: SnPost]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const api = useSolarNetwork()
|
||||||
|
const post = ref<SnPost | null>(null)
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
|
||||||
|
const fetchPost = async () => {
|
||||||
|
if (!props.postId) {
|
||||||
|
error.value = "No post ID provided"
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
loading.value = true
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
const response = await api<SnPost>(`/sphere/posts/${props.postId}`, {
|
||||||
|
method: "GET",
|
||||||
|
onResponse({ response }) {
|
||||||
|
if (response._data) {
|
||||||
|
response._data = keysToCamel(response._data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
post.value = response
|
||||||
|
emit("loaded", response)
|
||||||
|
} catch (err) {
|
||||||
|
console.error("Error fetching post:", err)
|
||||||
|
error.value = err instanceof Error ? err.message : "Failed to fetch post"
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleReaction = (symbol: string, attitude: number, delta: number) => {
|
||||||
|
emit("react", symbol, attitude, delta)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Watch for postId changes and refetch
|
||||||
|
watch(
|
||||||
|
() => props.postId,
|
||||||
|
(newId) => {
|
||||||
|
if (newId) {
|
||||||
|
fetchPost()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ immediate: true }
|
||||||
|
)
|
||||||
|
|
||||||
|
// Initial fetch on mount
|
||||||
|
onMounted(() => {
|
||||||
|
if (props.postId) {
|
||||||
|
fetchPost()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
</script>
|
||||||
41
app/components/account/account-nameplate.vue
Normal file
41
app/components/account/account-nameplate.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<n-card>
|
||||||
|
<div class="flex flex-col justify-center gap-4">
|
||||||
|
<img
|
||||||
|
v-if="userBackground"
|
||||||
|
:src="userBackground"
|
||||||
|
style="aspect-ratio: 16/7"
|
||||||
|
class="rounded-xl"
|
||||||
|
/>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<n-avatar :src="userPicture" />
|
||||||
|
<div class="grow">
|
||||||
|
<div class="font-bold text-lg">{{ data.nick }}</div>
|
||||||
|
<div class="text-sm opacity-80">@{{ data.name }}</div>
|
||||||
|
</div>
|
||||||
|
<div><slot name="suffix" /></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { SnAccount } from "~/types/api"
|
||||||
|
|
||||||
|
const props = defineProps<{ data: SnAccount }>()
|
||||||
|
const _ = defineSlots<{ suffix(): unknown }>()
|
||||||
|
|
||||||
|
const apiBase = useSolarNetworkUrl()
|
||||||
|
|
||||||
|
const userPicture = computed(() => {
|
||||||
|
return props.data.profile.picture
|
||||||
|
? `${apiBase}/drive/files/${props.data.profile.picture.id}`
|
||||||
|
: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const userBackground = computed(() => {
|
||||||
|
return props.data.profile.background
|
||||||
|
? `${apiBase}/drive/files/${props.data.profile.background.id}`
|
||||||
|
: undefined
|
||||||
|
})
|
||||||
|
</script>
|
||||||
41
app/components/publisher/publisher-nameplate.vue
Normal file
41
app/components/publisher/publisher-nameplate.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<n-card>
|
||||||
|
<div class="flex flex-col justify-center gap-4">
|
||||||
|
<img
|
||||||
|
v-if="userBackground"
|
||||||
|
:src="userBackground"
|
||||||
|
style="aspect-ratio: 16/7"
|
||||||
|
class="rounded-xl"
|
||||||
|
/>
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<n-avatar :src="userPicture" />
|
||||||
|
<div class="grow">
|
||||||
|
<div class="font-bold text-lg">{{ data.nick }}</div>
|
||||||
|
<div class="text-sm opacity-80">@{{ data.name }}</div>
|
||||||
|
</div>
|
||||||
|
<div><slot name="suffix" /></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { SnPublisher } from "~/types/api"
|
||||||
|
|
||||||
|
const props = defineProps<{ data: SnPublisher }>()
|
||||||
|
const _ = defineSlots<{ suffix(): unknown }>()
|
||||||
|
|
||||||
|
const apiBase = useSolarNetworkUrl()
|
||||||
|
|
||||||
|
const userPicture = computed(() => {
|
||||||
|
return props.data.picture
|
||||||
|
? `${apiBase}/drive/files/${props.data.picture.id}`
|
||||||
|
: undefined
|
||||||
|
})
|
||||||
|
|
||||||
|
const userBackground = computed(() => {
|
||||||
|
return props.data.background
|
||||||
|
? `${apiBase}/drive/files/${props.data.background.id}`
|
||||||
|
: undefined
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -1,13 +1,19 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="py-6 px-5 min-h-screen">
|
<div class="py-6 px-5 min-h-screen">
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
<div v-if="pending" class="text-center py-12 h-layout flex flex-col justify-center">
|
<div
|
||||||
|
v-if="pending"
|
||||||
|
class="text-center py-12 h-layout flex flex-col justify-center"
|
||||||
|
>
|
||||||
<n-spin size="large" />
|
<n-spin size="large" />
|
||||||
<p class="mt-4 text-lg">Loading your rewind data...</p>
|
<p class="mt-4 text-lg">正在整理你的回顾数据……</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Error State -->
|
<!-- Error State -->
|
||||||
<div v-else-if="error" class="text-center py-12 h-layout flex flex-col justify-center">
|
<div
|
||||||
|
v-else-if="error"
|
||||||
|
class="text-center py-12 h-layout flex flex-col justify-center max-w-2xl mx-auto"
|
||||||
|
>
|
||||||
<n-alert
|
<n-alert
|
||||||
type="error"
|
type="error"
|
||||||
title="Error Loading Rewind"
|
title="Error Loading Rewind"
|
||||||
@@ -15,10 +21,12 @@
|
|||||||
:closable="false"
|
:closable="false"
|
||||||
>
|
>
|
||||||
{{
|
{{
|
||||||
error instanceof Error ? error.message : "Failed to load rewind data"
|
error instanceof Error
|
||||||
|
? error.message
|
||||||
|
: "看起来出了点问题,请稍后再试。"
|
||||||
}}
|
}}
|
||||||
</n-alert>
|
</n-alert>
|
||||||
<n-button @click="fetchRewindData">Try Again</n-button>
|
<n-button @click="fetchRewindData">重试</n-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
@@ -26,13 +34,15 @@
|
|||||||
<!-- Header Section -->
|
<!-- Header Section -->
|
||||||
<div class="text-center mb-8 h-layout flex flex-col justify-center">
|
<div class="text-center mb-8 h-layout flex flex-col justify-center">
|
||||||
<img :src="CloudyRewind" class="w-36 h-36 mx-auto" />
|
<img :src="CloudyRewind" class="w-36 h-36 mx-auto" />
|
||||||
<h1 class="text-4xl font-bold mb-1">
|
<h1 class="text-4xl font-bold mb-1">Solar Network 年度回顾</h1>
|
||||||
Solar Network Rewind
|
<n-tooltip placement="bottom">
|
||||||
</h1>
|
<template #trigger>
|
||||||
<p class="text-lg opacity-80 mb-3">
|
<div class="text-lg opacity-80">
|
||||||
Reliving your {{ rewindData.year }} on the Solar Network
|
回顾你的 {{ rewindData.year }} 年在 Solar Network 的精彩旅程
|
||||||
</p>
|
</div>
|
||||||
<p class="text-sm opacity-60">Cooming soon!</p>
|
</template>
|
||||||
|
数据范围 2024/12/26 - 2025/12/25
|
||||||
|
</n-tooltip>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Scroll-based Sections -->
|
<!-- Scroll-based Sections -->
|
||||||
@@ -43,222 +53,332 @@
|
|||||||
class="scroll-section min-h-screen flex items-center justify-center"
|
class="scroll-section min-h-screen flex items-center justify-center"
|
||||||
:class="{ 'animate-in': inView1 }"
|
:class="{ 'animate-in': inView1 }"
|
||||||
>
|
>
|
||||||
<n-card class="w-full max-w-4xl bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-lg">
|
<n-card class="w-full max-w-4xl">
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<n-icon size="28" :component="ShieldIcon" class="text-blue-600" />
|
<n-icon
|
||||||
<h2 class="text-2xl font-bold">Pass Achievements</h2>
|
size="28"
|
||||||
|
:component="CalendarDaysIcon"
|
||||||
|
class="text-blue-600"
|
||||||
|
/>
|
||||||
|
<h2 class="text-2xl font-bold">活动数据</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div class="text-center p-6 bg-blue-50 dark:bg-blue-900/20 rounded-xl">
|
<div class="flex flex-col gap-6">
|
||||||
<div class="text-4xl font-bold mb-2">
|
<n-statistic label="最长连续签到" tabular-nums>
|
||||||
{{ rewindData.data.pass.maxCheckInStrike }}
|
<n-number-animation
|
||||||
|
:to="rewindData.data.pass.maxCheckInStreak"
|
||||||
|
/>
|
||||||
|
<template #suffix>天</template>
|
||||||
|
</n-statistic>
|
||||||
|
|
||||||
|
<n-statistic label="签到完成度" tabular-nums>
|
||||||
|
<n-number-animation
|
||||||
|
:to="rewindData.data.pass.checkInCompleteness * 100"
|
||||||
|
:precision="2"
|
||||||
|
/>
|
||||||
|
<template #suffix>%</template>
|
||||||
|
</n-statistic>
|
||||||
|
|
||||||
|
<n-statistic label="最晚活动时间">
|
||||||
|
{{ rewindData.data.pass.latestActiveTime }}
|
||||||
|
</n-statistic>
|
||||||
|
|
||||||
|
<div class="flex flex-row gap-8 flex-wrap">
|
||||||
|
<n-statistic label="最活跃的日期">
|
||||||
|
{{ rewindData.data.pass.mostActiveDay }}
|
||||||
|
</n-statistic>
|
||||||
|
<n-statistic label="最活跃的日子">
|
||||||
|
{{ rewindData.data.pass.mostActiveWeekday }}
|
||||||
|
</n-statistic>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-sm opacity-80">
|
</div>
|
||||||
Longest Check-in Streak
|
|
||||||
|
<div class="md:text-right pr-4 max-md:order-first">
|
||||||
|
<div class="text-5xl mb-3">🔥</div>
|
||||||
|
<div class="text-2xl font-bold mb-1">
|
||||||
|
{{ getStreakMessage(rewindData.data.pass.maxCheckInStreak) }}
|
||||||
|
</div>
|
||||||
|
<div class="text-lg opacity-80">
|
||||||
|
{{
|
||||||
|
getStreakDescription(rewindData.data.pass.maxCheckInStreak)
|
||||||
|
}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-card>
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Section 2: Sphere Overview -->
|
<!-- Section 2: Creator Career Overview -->
|
||||||
<div
|
<div
|
||||||
ref="section2"
|
ref="section2"
|
||||||
class="scroll-section min-h-screen flex items-center justify-center"
|
class="scroll-section min-h-screen flex items-center justify-center"
|
||||||
:class="{ 'animate-in': inView2 }"
|
:class="{ 'animate-in': inView2 }"
|
||||||
>
|
>
|
||||||
<n-card class="w-full max-w-4xl bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-lg">
|
<n-card
|
||||||
|
class="w-full max-w-4xl bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-lg"
|
||||||
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<n-icon size="28" :component="GlobeIcon" class="text-green-600" />
|
<n-icon
|
||||||
<h2 class="text-2xl font-bold">Sphere Activity</h2>
|
size="28"
|
||||||
|
:component="PencilLineIcon"
|
||||||
|
class="text-green-600"
|
||||||
|
/>
|
||||||
|
<h2 class="text-2xl font-bold">创作生涯</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
|
||||||
<div class="text-center p-6 bg-green-50 dark:bg-green-900/20 rounded-xl">
|
<n-statistic label="总发帖数量" tabular-nums>
|
||||||
<div class="text-4xl font-bold mb-2">
|
<n-number-animation
|
||||||
{{ rewindData.data.sphere.totalCount }}
|
:to="rewindData.data.sphere.totalPostCount"
|
||||||
</div>
|
/>
|
||||||
<div class="text-sm opacity-80">
|
<template #suffix>篇</template>
|
||||||
Total Posts
|
</n-statistic>
|
||||||
</div>
|
<n-statistic label="总获顶数量" tabular-nums>
|
||||||
|
<n-number-animation
|
||||||
|
:to="rewindData.data.sphere.totalUpvoteCount"
|
||||||
|
/>
|
||||||
|
<template #suffix>个</template>
|
||||||
|
</n-statistic>
|
||||||
|
<n-statistic label="高产记录" tabular-nums>
|
||||||
|
<n-number-animation
|
||||||
|
:to="rewindData.data.sphere.mostProductiveDay.postCount"
|
||||||
|
/>
|
||||||
|
<template #suffix>
|
||||||
|
篇帖子于
|
||||||
|
{{
|
||||||
|
rewindData.data.sphere.mostProductiveDay.date
|
||||||
|
.split(" ")[0]
|
||||||
|
?.split("/")
|
||||||
|
.slice(0, 2)
|
||||||
|
.join("/")
|
||||||
|
}}
|
||||||
|
</template>
|
||||||
|
</n-statistic>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
|
<div>
|
||||||
|
<h3 class="font-bold mb-2 flex items-center gap-2">
|
||||||
|
<n-icon :component="PartyPopperIcon" size="16" />
|
||||||
|
最受欢迎的帖子
|
||||||
|
</h3>
|
||||||
|
<nuxt-link
|
||||||
|
:to="`/posts/${rewindData.data.sphere.mostPopularPost.id}`"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
<post-item-contained
|
||||||
|
:post-id="rewindData.data.sphere.mostPopularPost.id"
|
||||||
|
/>
|
||||||
|
</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="text-center p-6 bg-purple-50 dark:bg-purple-900/20 rounded-xl">
|
<div>
|
||||||
<div class="text-4xl font-bold mb-2">
|
<h3 class="font-bold mb-2 flex items-center gap-2">
|
||||||
{{ rewindData.data.sphere.upvoteCounts }}
|
<n-icon :component="HeartIcon" size="16" />
|
||||||
</div>
|
最喜欢你的观众
|
||||||
<div class="text-sm opacity-80">
|
</h3>
|
||||||
Upvotes Received
|
<account-nameplate
|
||||||
</div>
|
:data="rewindData.data.sphere.mostLovedAudience.account"
|
||||||
</div>
|
>
|
||||||
<div class="text-center p-6 bg-orange-50 dark:bg-orange-900/20 rounded-xl">
|
<template #suffix>
|
||||||
<div class="text-4xl font-bold mb-2">
|
<n-config-provider
|
||||||
{{ rewindData.data.sphere.mostProductiveDay.postCount }}
|
:theme-overrides="{
|
||||||
</div>
|
Statistic: {
|
||||||
<div class="text-sm opacity-80">
|
valueFontSize: '1.3rem',
|
||||||
Posts on Best Day
|
labelFontSize: '0.8rem'
|
||||||
</div>
|
}
|
||||||
<div class="text-xs opacity-60 mt-1">
|
}"
|
||||||
{{ formatDate(rewindData.data.sphere.mostProductiveDay.date) }}
|
>
|
||||||
</div>
|
<n-statistic label="贡献的顶数量" tabular-nums>
|
||||||
|
<n-number-animation
|
||||||
|
:to="
|
||||||
|
rewindData.data.sphere.mostLovedAudience
|
||||||
|
.upvoteCounts
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<template #suffix>个</template>
|
||||||
|
</n-statistic>
|
||||||
|
</n-config-provider>
|
||||||
|
</template>
|
||||||
|
</account-nameplate>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-card>
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Section 3: Most Called Chat -->
|
<!-- Section 3: Explore History -->
|
||||||
<div
|
<div
|
||||||
ref="section3"
|
ref="section3"
|
||||||
class="scroll-section min-h-screen flex items-center justify-center"
|
class="scroll-section min-h-screen flex items-center justify-center"
|
||||||
:class="{ 'animate-in': inView3 }"
|
:class="{ 'animate-in': inView3 }"
|
||||||
>
|
>
|
||||||
<n-card class="w-full max-w-4xl bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-lg">
|
<n-card
|
||||||
|
class="w-full max-w-4xl bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-lg"
|
||||||
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<n-icon size="28" :component="MessageCircleIcon" class="text-indigo-600" />
|
<n-icon
|
||||||
<h2 class="text-2xl font-bold">Favorite Chat Room</h2>
|
size="28"
|
||||||
|
:component="GlobeIcon"
|
||||||
|
class="text-indigo-600"
|
||||||
|
/>
|
||||||
|
<h2 class="text-2xl font-bold">探索历史</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div class="p-6 bg-indigo-50 dark:bg-indigo-900/20 rounded-xl">
|
<div>
|
||||||
<div class="flex items-center gap-4 mb-4">
|
<h3 class="font-bold mb-2 flex items-center gap-2">
|
||||||
<n-avatar
|
<n-icon :component="HeartIcon" size="16" />
|
||||||
round
|
你最喜欢的创作者
|
||||||
:size="48"
|
</h3>
|
||||||
:src="getChatAvatar(rewindData.data.sphere.mostCalledChat)"
|
<nuxt-link
|
||||||
/>
|
:to="`/publishers/${rewindData.data.sphere.mostLovedPublisher.publisher.id}`"
|
||||||
<div>
|
target="_blank"
|
||||||
<h3 class="text-xl font-semibold">
|
>
|
||||||
{{ rewindData.data.sphere.mostCalledChat.name }}
|
<publisher-nameplate
|
||||||
</h3>
|
:data="rewindData.data.sphere.mostLovedPublisher.publisher"
|
||||||
<p class="text-sm opacity-80">
|
>
|
||||||
Most visited chat room
|
<template #suffix>
|
||||||
</p>
|
<n-config-provider
|
||||||
</div>
|
:theme-overrides="{
|
||||||
</div>
|
Statistic: {
|
||||||
<p class="text-slate-700 dark:text-slate-300">
|
valueFontSize: '1.3rem',
|
||||||
{{ rewindData.data.sphere.mostCalledChat.description }}
|
labelFontSize: '0.8rem'
|
||||||
</p>
|
}
|
||||||
|
}"
|
||||||
|
>
|
||||||
|
<n-statistic label="给予的顶数" tabular-nums>
|
||||||
|
<n-number-animation
|
||||||
|
:to="
|
||||||
|
rewindData.data.sphere.mostLovedPublisher
|
||||||
|
.upvoteCounts
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
<template #suffix>个</template>
|
||||||
|
</n-statistic>
|
||||||
|
</n-config-provider>
|
||||||
|
</template>
|
||||||
|
</publisher-nameplate>
|
||||||
|
</nuxt-link>
|
||||||
</div>
|
</div>
|
||||||
<div class="p-6 bg-cyan-50 dark:bg-cyan-900/20 rounded-xl">
|
<div class="text-right flex flex-col justify-center px-5 gap-2">
|
||||||
<div class="text-center">
|
<div class="text-4xl">🤔</div>
|
||||||
<div class="text-3xl font-bold mb-2">
|
<p class="text-lg">
|
||||||
Most Popular Post
|
看起来你真的喜欢他/她呢 (´▽`) <br />
|
||||||
</div>
|
新的一年不妨试试探索更多优秀创作者吧!
|
||||||
<div class="text-lg font-medium">
|
</p>
|
||||||
{{ rewindData.data.sphere.mostPopularPost.title }}
|
<p class="text-xs opacity-80">
|
||||||
</div>
|
<del>绝对不是因为没有别的东西放在这里所以写一些废话</del>
|
||||||
<div class="text-sm opacity-80 mt-2">
|
</p>
|
||||||
{{ rewindData.data.sphere.mostPopularPost.upvotes }} upvotes •
|
|
||||||
{{ rewindData.data.sphere.mostPopularPost.viewsTotal }} views
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-card>
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Section 4: Most Called Accounts -->
|
<!-- Section 4: Chat Summary -->
|
||||||
<div
|
<div
|
||||||
ref="section4"
|
ref="section4"
|
||||||
class="scroll-section min-h-screen flex items-center justify-center"
|
class="scroll-section min-h-screen flex items-center justify-center"
|
||||||
:class="{ 'animate-in': inView4 }"
|
:class="{ 'animate-in': inView4 }"
|
||||||
>
|
>
|
||||||
<n-card class="w-full max-w-4xl bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-lg">
|
<n-card
|
||||||
|
class="w-full max-w-4xl bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-lg"
|
||||||
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<n-icon size="28" :component="UsersIcon" class="text-teal-600" />
|
<n-icon
|
||||||
<h2 class="text-2xl font-bold">Top Connections</h2>
|
size="28"
|
||||||
|
:component="MessageCircleIcon"
|
||||||
|
class="text-teal-600"
|
||||||
|
/>
|
||||||
|
<h2 class="text-2xl font-bold">社交经历</h2>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
<div
|
<div class="flex flex-col gap-6 mt-2">
|
||||||
v-for="(account, index) in rewindData.data.sphere.mostCalledAccounts"
|
<div>
|
||||||
:key="account.id"
|
<h3 class="font-bold mb-2 flex items-center gap-2">
|
||||||
class="p-6 bg-teal-50 dark:bg-teal-900/20 rounded-xl"
|
<n-icon :component="HeartIcon" size="16" />
|
||||||
>
|
最常出没的聊天室
|
||||||
<div class="flex items-center gap-4">
|
</h3>
|
||||||
<n-avatar
|
<n-card size="small">
|
||||||
round
|
<div class="flex items-center gap-4">
|
||||||
:size="48"
|
<n-avatar
|
||||||
:src="getAccountAvatar(account)"
|
:src="
|
||||||
/>
|
getChatRoomAvatar(
|
||||||
<div>
|
rewindData.data.sphere.mostCalledChat.chat
|
||||||
<h3 class="text-lg font-semibold">{{ account.nick }}</h3>
|
)
|
||||||
<p class="text-sm opacity-80">
|
"
|
||||||
@{{ account.name }}
|
>{{
|
||||||
</p>
|
rewindData.data.sphere.mostMessagedChat.chat.name?.substring(
|
||||||
<p class="text-xs opacity-60 mt-1">
|
0,
|
||||||
Rank #{{ index + 1 }}
|
1
|
||||||
</p>
|
)
|
||||||
</div>
|
}}</n-avatar
|
||||||
</div>
|
>
|
||||||
</div>
|
<div class="grow flex flex-col">
|
||||||
</div>
|
<div class="text-md font-bold">
|
||||||
</n-card>
|
{{
|
||||||
</div>
|
rewindData.data.sphere.mostMessagedChat.chat.name
|
||||||
|
}}
|
||||||
<!-- Section 5: Most Loved Publisher -->
|
</div>
|
||||||
<div
|
<p>
|
||||||
ref="section5"
|
<n-number-animation
|
||||||
class="scroll-section min-h-screen flex items-center justify-center"
|
:to="
|
||||||
:class="{ 'animate-in': inView5 }"
|
rewindData.data.sphere.mostMessagedChat
|
||||||
>
|
.messageCounts
|
||||||
<n-card class="w-full max-w-4xl bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-lg">
|
"
|
||||||
<template #header>
|
/>
|
||||||
<div class="flex items-center gap-3">
|
条消息
|
||||||
<n-icon size="28" :component="HeartIcon" class="text-pink-600" />
|
</p>
|
||||||
<h2 class="text-2xl font-bold">Most Loved Publisher</h2>
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
||||||
<div class="p-6 bg-pink-50 dark:bg-pink-900/20 rounded-xl">
|
|
||||||
<div class="flex items-center gap-4 mb-4">
|
|
||||||
<n-avatar
|
|
||||||
round
|
|
||||||
:size="64"
|
|
||||||
:src="getPublisherAvatar(rewindData.data.sphere.mostLovedPublisher)"
|
|
||||||
/>
|
|
||||||
<div>
|
|
||||||
<h3 class="text-2xl font-bold">
|
|
||||||
{{ rewindData.data.sphere.mostLovedPublisher.publisher.nick }}
|
|
||||||
</h3>
|
|
||||||
<p class="text-sm opacity-80">
|
|
||||||
@{{ rewindData.data.sphere.mostLovedPublisher.publisher.name }}
|
|
||||||
</p>
|
|
||||||
<div class="flex gap-2 mt-2">
|
|
||||||
<n-tag type="primary" size="small">
|
|
||||||
{{ rewindData.data.sphere.mostLovedPublisher.publisher.level }} Level
|
|
||||||
</n-tag>
|
|
||||||
<n-tag type="success" size="small">
|
|
||||||
{{ rewindData.data.sphere.mostLovedPublisher.upvoteCounts }} Upvotes
|
|
||||||
</n-tag>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</n-card>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3 class="font-bold mb-2 flex items-center gap-2">
|
||||||
|
<n-icon :component="MessageCircleHeartIcon" size="16" />
|
||||||
|
最常联系的人
|
||||||
|
</h3>
|
||||||
|
<n-card size="small">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<n-avatar
|
||||||
|
object-fit="cover"
|
||||||
|
:src="getChatMemberAvatar(rewindData.data.sphere.mostMessagedDirectChat.chat.members[0]!)"
|
||||||
|
/>
|
||||||
|
<div class="grow flex flex-col">
|
||||||
|
<div class="text-md font-bold">
|
||||||
|
{{
|
||||||
|
rewindData.data.sphere.mostMessagedDirectChat.chat
|
||||||
|
.members[0]!.account.name || "Direct Message"
|
||||||
|
}}
|
||||||
|
</div>
|
||||||
|
<p>
|
||||||
|
<n-number-animation
|
||||||
|
:to="
|
||||||
|
rewindData.data.sphere.mostMessagedDirectChat
|
||||||
|
.messageCounts
|
||||||
|
"
|
||||||
|
/>
|
||||||
|
条消息
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-slate-700 dark:text-slate-300">
|
|
||||||
{{ rewindData.data.sphere.mostLovedPublisher.publisher.bio }}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="p-6 bg-amber-50 dark:bg-amber-900/20 rounded-xl">
|
<div class="text-right flex flex-col justify-center px-5 gap-2">
|
||||||
<div class="text-center">
|
<div class="text-4xl">💬</div>
|
||||||
<div class="text-3xl font-bold mb-2">
|
<p class="text-lg">
|
||||||
Most Messaged Chat
|
一眼丁真,鉴定为 <br/>
|
||||||
</div>
|
<b>纯纯的话唠</b>
|
||||||
<div class="text-lg font-medium">
|
</p>
|
||||||
{{ rewindData.data.sphere.mostMessagedChat.name || 'Direct Message' }}
|
|
||||||
</div>
|
|
||||||
<p class="text-sm opacity-80 mt-2">
|
|
||||||
Your most active conversation
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</n-card>
|
</n-card>
|
||||||
@@ -270,10 +390,16 @@
|
|||||||
class="scroll-section min-h-screen flex items-center justify-center"
|
class="scroll-section min-h-screen flex items-center justify-center"
|
||||||
:class="{ 'animate-in': inView6 }"
|
:class="{ 'animate-in': inView6 }"
|
||||||
>
|
>
|
||||||
<n-card class="w-full max-w-4xl bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-lg">
|
<n-card
|
||||||
|
class="w-full max-w-4xl bg-white/80 dark:bg-slate-800/80 backdrop-blur-sm shadow-lg"
|
||||||
|
>
|
||||||
<template #header>
|
<template #header>
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<n-icon size="28" :component="StarIcon" class="text-yellow-600" />
|
<n-icon
|
||||||
|
size="28"
|
||||||
|
:component="StarIcon"
|
||||||
|
class="text-yellow-600"
|
||||||
|
/>
|
||||||
<h2 class="text-2xl font-bold">
|
<h2 class="text-2xl font-bold">
|
||||||
Your {{ rewindData.year }} Summary
|
Your {{ rewindData.year }} Summary
|
||||||
</h2>
|
</h2>
|
||||||
@@ -287,19 +413,30 @@
|
|||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span class="opacity-80">Total Posts</span>
|
<span class="opacity-80">Total Posts</span>
|
||||||
<span class="font-bold">{{ rewindData.data.sphere.totalCount }}</span>
|
<span class="font-bold">{{
|
||||||
|
rewindData.data.sphere.totalPostCount
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span class="opacity-80">Upvotes Received</span>
|
<span class="opacity-80">Upvotes Received</span>
|
||||||
<span class="font-bold">{{ rewindData.data.sphere.upvoteCounts }}</span>
|
<span class="font-bold">{{
|
||||||
|
rewindData.data.sphere.totalUpvoteCount
|
||||||
|
}}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span class="opacity-80">Longest Streak</span>
|
<span class="opacity-80">Longest Streak</span>
|
||||||
<span class="font-bold">{{ rewindData.data.pass.maxCheckInStrike }} days</span>
|
<span class="font-bold"
|
||||||
|
>{{ rewindData.data.pass.maxCheckInStreak }} days</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex justify-between items-center">
|
<div class="flex justify-between items-center">
|
||||||
<span class="opacity-80">Best Day</span>
|
<span class="opacity-80">Best Day</span>
|
||||||
<span class="font-bold">{{ rewindData.data.sphere.mostProductiveDay.postCount }} posts</span>
|
<span class="font-bold"
|
||||||
|
>{{
|
||||||
|
rewindData.data.sphere.mostProductiveDay.postCount
|
||||||
|
}}
|
||||||
|
posts</span
|
||||||
|
>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -309,15 +446,20 @@
|
|||||||
<div class="space-y-2">
|
<div class="space-y-2">
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
<span class="font-medium opacity-80">Favorite Chat:</span>
|
<span class="font-medium opacity-80">Favorite Chat:</span>
|
||||||
{{ rewindData.data.sphere.mostCalledChat.name }}
|
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
<span class="font-medium opacity-80">Top Connection:</span>
|
<span class="font-medium opacity-80"
|
||||||
|
>Top Connection:</span
|
||||||
|
>
|
||||||
{{ rewindData.data.sphere.mostCalledAccounts[0]?.nick }}
|
{{ rewindData.data.sphere.mostCalledAccounts[0]?.nick }}
|
||||||
</p>
|
</p>
|
||||||
<p class="text-sm">
|
<p class="text-sm">
|
||||||
<span class="font-medium opacity-80">Loved Publisher:</span>
|
<span class="font-medium opacity-80"
|
||||||
{{ rewindData.data.sphere.mostLovedPublisher.publisher.nick }}
|
>Loved Publisher:</span
|
||||||
|
>
|
||||||
|
{{
|
||||||
|
rewindData.data.sphere.mostLovedPublisher.publisher.nick
|
||||||
|
}}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -327,11 +469,16 @@
|
|||||||
<div class="p-6 bg-indigo-50 dark:bg-indigo-900/20 rounded-xl">
|
<div class="p-6 bg-indigo-50 dark:bg-indigo-900/20 rounded-xl">
|
||||||
<h3 class="text-xl font-bold mb-4">Share Your Year</h3>
|
<h3 class="text-xl font-bold mb-4">Share Your Year</h3>
|
||||||
<p class="opacity-80 mb-4">
|
<p class="opacity-80 mb-4">
|
||||||
Capture this moment and share your Solar Network journey with friends!
|
Capture this moment and share your Solar Network journey
|
||||||
|
with friends!
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<n-button type="primary" size="large" @click="downloadSummary">
|
<n-button
|
||||||
|
type="primary"
|
||||||
|
size="large"
|
||||||
|
@click="downloadSummary"
|
||||||
|
>
|
||||||
<template #icon>
|
<template #icon>
|
||||||
<n-icon :component="DownloadIcon" />
|
<n-icon :component="DownloadIcon" />
|
||||||
</template>
|
</template>
|
||||||
@@ -346,10 +493,13 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-6 bg-emerald-50 dark:bg-emerald-900/20 rounded-xl">
|
<div
|
||||||
|
class="p-6 bg-emerald-50 dark:bg-emerald-900/20 rounded-xl"
|
||||||
|
>
|
||||||
<h3 class="text-xl font-bold mb-4">What's Next?</h3>
|
<h3 class="text-xl font-bold mb-4">What's Next?</h3>
|
||||||
<p class="opacity-80">
|
<p class="opacity-80">
|
||||||
Keep creating, connecting, and exploring. Your 2026 rewind will be even more amazing!
|
Keep creating, connecting, and exploring. Your 2026 rewind
|
||||||
|
will be even more amazing!
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -365,23 +515,25 @@
|
|||||||
import {
|
import {
|
||||||
GlobeIcon,
|
GlobeIcon,
|
||||||
MessageCircleIcon,
|
MessageCircleIcon,
|
||||||
UsersIcon,
|
MessageCircleHeartIcon,
|
||||||
HeartIcon,
|
HeartIcon,
|
||||||
StarIcon,
|
StarIcon,
|
||||||
ShieldIcon,
|
CalendarDaysIcon,
|
||||||
|
PencilLineIcon,
|
||||||
DownloadIcon,
|
DownloadIcon,
|
||||||
ShareIcon
|
ShareIcon,
|
||||||
|
PartyPopperIcon
|
||||||
} from "lucide-vue-next"
|
} from "lucide-vue-next"
|
||||||
import { ref, onMounted, onUnmounted } from "vue"
|
import { ref, onMounted, onUnmounted } from "vue"
|
||||||
import { DateTime } from "luxon"
|
import { DateTime } from "luxon"
|
||||||
import type {
|
import type {
|
||||||
SnRewind,
|
SnRewind,
|
||||||
SnRewindMostCalledChat,
|
SnRewindMostCalledChat,
|
||||||
SnRewindMostCalledAccount,
|
SnRewindChat,
|
||||||
SnRewindMostLovedPublisher
|
SnRewindChatMember
|
||||||
} from "~/types/api"
|
} from "~/types/api"
|
||||||
|
|
||||||
import CloudyRewind from "~/assets/images/cloudy-lamb-rewind.png";
|
import CloudyRewind from "~/assets/images/cloudy-lamb-rewind.png"
|
||||||
|
|
||||||
const api = useSolarNetwork()
|
const api = useSolarNetwork()
|
||||||
|
|
||||||
@@ -425,12 +577,15 @@ const fetchRewindData = async () => {
|
|||||||
const setupScrollAnimations = () => {
|
const setupScrollAnimations = () => {
|
||||||
const options = {
|
const options = {
|
||||||
threshold: 0.1,
|
threshold: 0.1,
|
||||||
rootMargin: '0px 0px -50px 0px'
|
rootMargin: "0px 0px -50px 0px"
|
||||||
}
|
}
|
||||||
|
|
||||||
const createObserver = (element: HTMLElement, inViewRef: { value: boolean }) => {
|
const createObserver = (
|
||||||
|
element: HTMLElement,
|
||||||
|
inViewRef: { value: boolean }
|
||||||
|
) => {
|
||||||
const observer = new IntersectionObserver((entries) => {
|
const observer = new IntersectionObserver((entries) => {
|
||||||
entries.forEach(entry => {
|
entries.forEach((entry) => {
|
||||||
inViewRef.value = entry.isIntersecting
|
inViewRef.value = entry.isIntersecting
|
||||||
})
|
})
|
||||||
}, options)
|
}, options)
|
||||||
@@ -456,7 +611,7 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
observers.forEach(observer => observer.disconnect())
|
observers.forEach((observer) => observer.disconnect())
|
||||||
})
|
})
|
||||||
|
|
||||||
// Helper methods
|
// Helper methods
|
||||||
@@ -470,17 +625,17 @@ const getChatAvatar = (chat: SnRewindMostCalledChat) => {
|
|||||||
return "/api/placeholder/48/48"
|
return "/api/placeholder/48/48"
|
||||||
}
|
}
|
||||||
|
|
||||||
const getAccountAvatar = (account: SnRewindMostCalledAccount) => {
|
const getChatRoomAvatar = (item: SnRewindChat) => {
|
||||||
const apiBase = useSolarNetworkUrl()
|
const apiBase = useSolarNetworkUrl()
|
||||||
return account.profile?.picture
|
return item.picture
|
||||||
? `${apiBase}/drive/files/${account.profile.picture.id}`
|
? `${apiBase}/drive/files/${item.picture.id}`
|
||||||
: "/api/placeholder/48/48"
|
: "/api/placeholder/48/48"
|
||||||
}
|
}
|
||||||
|
|
||||||
const getPublisherAvatar = (publisher: SnRewindMostLovedPublisher) => {
|
const getChatMemberAvatar = (member: SnRewindChatMember) => {
|
||||||
const apiBase = useSolarNetworkUrl()
|
const apiBase = useSolarNetworkUrl()
|
||||||
return publisher.publisher?.picture
|
return member.account?.profile?.picture
|
||||||
? `${apiBase}/drive/files/${publisher.publisher.picture.id}`
|
? `${apiBase}/drive/files/${member.account.profile.picture.id}`
|
||||||
: "/api/placeholder/64/64"
|
: "/api/placeholder/64/64"
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -489,22 +644,6 @@ const downloadSummary = () => {
|
|||||||
// Create a simple text summary for download
|
// Create a simple text summary for download
|
||||||
const summary = `
|
const summary = `
|
||||||
Solar Network Rewind ${rewindData.value?.year}
|
Solar Network Rewind ${rewindData.value?.year}
|
||||||
|
|
||||||
Your Journey:
|
|
||||||
- Total Posts: ${rewindData.value?.data.sphere.totalCount}
|
|
||||||
- Upvotes Received: ${rewindData.value?.data.sphere.upvoteCounts}
|
|
||||||
- Longest Check-in Streak: ${rewindData.value?.data.pass.maxCheckInStrike} days
|
|
||||||
- Best Day: ${
|
|
||||||
rewindData.value?.data.sphere.mostProductiveDay.postCount
|
|
||||||
} posts on ${formatDate(rewindData.value?.data.sphere.mostProductiveDay.date ?? 'none')}
|
|
||||||
|
|
||||||
Highlights:
|
|
||||||
- Favorite Chat: ${rewindData.value?.data.sphere.mostCalledChat.name}
|
|
||||||
- Top Connection: ${rewindData.value?.data.sphere.mostCalledAccounts[0]?.nick}
|
|
||||||
- Loved Publisher: ${
|
|
||||||
rewindData.value?.data.sphere.mostLovedPublisher.publisher.nick
|
|
||||||
}
|
|
||||||
|
|
||||||
Generated on: ${new Date().toLocaleDateString()}
|
Generated on: ${new Date().toLocaleDateString()}
|
||||||
`
|
`
|
||||||
|
|
||||||
@@ -520,7 +659,7 @@ Generated on: ${new Date().toLocaleDateString()}
|
|||||||
}
|
}
|
||||||
|
|
||||||
const shareOnSocial = () => {
|
const shareOnSocial = () => {
|
||||||
const text = `Just checked out my Solar Network Rewind ${rewindData.value?.year}! 🚀\n\nI made ${rewindData.value?.data.sphere.totalCount} posts and got ${rewindData.value?.data.sphere.upvoteCounts} upvotes. What was your highlight of the year?`
|
const text = `Just checked out my Solar Network Rewind ${rewindData.value?.year}! 🚀\n\nI made ${rewindData.value?.data.sphere.totalPostCount} posts and got ${rewindData.value?.data.sphere.totalUpvoteCount} upvotes. What was your highlight of the year?`
|
||||||
|
|
||||||
if (navigator.share) {
|
if (navigator.share) {
|
||||||
navigator.share({
|
navigator.share({
|
||||||
@@ -535,8 +674,45 @@ const shareOnSocial = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Helper methods for streak messages
|
||||||
|
const getStreakMessage = (streak: number): string => {
|
||||||
|
if (streak >= 365) {
|
||||||
|
return "年度签到王"
|
||||||
|
} else if (streak >= 300) {
|
||||||
|
return "签到狂人"
|
||||||
|
} else if (streak >= 200) {
|
||||||
|
return "签到达人"
|
||||||
|
} else if (streak >= 100) {
|
||||||
|
return "签到高手"
|
||||||
|
} else if (streak >= 50) {
|
||||||
|
return "签到积极分子"
|
||||||
|
} else if (streak >= 20) {
|
||||||
|
return "签到新手"
|
||||||
|
} else {
|
||||||
|
return "继续签到"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const getStreakDescription = (streak: number): string => {
|
||||||
|
if (streak >= 365) {
|
||||||
|
return `连续签到 ${streak} 天,你就是 Solar Network 的签到传奇`
|
||||||
|
} else if (streak >= 300) {
|
||||||
|
return `连续签到 ${streak} 天,你的坚持让人佩服`
|
||||||
|
} else if (streak >= 200) {
|
||||||
|
return `连续签到 ${streak} 天,签到已经成为你的习惯`
|
||||||
|
} else if (streak >= 100) {
|
||||||
|
return `连续签到 ${streak} 天,你真的很用心在使用 Solar Network`
|
||||||
|
} else if (streak >= 50) {
|
||||||
|
return `连续签到 ${streak} 天,继续保持这个好习惯`
|
||||||
|
} else if (streak >= 20) {
|
||||||
|
return `连续签到 ${streak} 天,开始养成好习惯`
|
||||||
|
} else {
|
||||||
|
return `连续签到 ${streak} 天,每天签到让生活更有仪式感`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
useHead({
|
useHead({
|
||||||
title: "Your Solar Network Rewind",
|
title: "Solar Network Rewind 2025",
|
||||||
meta: [
|
meta: [
|
||||||
{
|
{
|
||||||
name: "description",
|
name: "description",
|
||||||
@@ -559,10 +735,22 @@ useHead({
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Staggered animation delays */
|
/* Staggered animation delays */
|
||||||
.scroll-section:nth-child(1).animate-in { transition-delay: 0.1s; }
|
.scroll-section:nth-child(1).animate-in {
|
||||||
.scroll-section:nth-child(2).animate-in { transition-delay: 0.2s; }
|
transition-delay: 0.1s;
|
||||||
.scroll-section:nth-child(3).animate-in { transition-delay: 0.3s; }
|
}
|
||||||
.scroll-section:nth-child(4).animate-in { transition-delay: 0.4s; }
|
.scroll-section:nth-child(2).animate-in {
|
||||||
.scroll-section:nth-child(5).animate-in { transition-delay: 0.5s; }
|
transition-delay: 0.2s;
|
||||||
.scroll-section:nth-child(6).animate-in { transition-delay: 0.6s; }
|
}
|
||||||
|
.scroll-section:nth-child(3).animate-in {
|
||||||
|
transition-delay: 0.3s;
|
||||||
|
}
|
||||||
|
.scroll-section:nth-child(4).animate-in {
|
||||||
|
transition-delay: 0.4s;
|
||||||
|
}
|
||||||
|
.scroll-section:nth-child(5).animate-in {
|
||||||
|
transition-delay: 0.5s;
|
||||||
|
}
|
||||||
|
.scroll-section:nth-child(6).animate-in {
|
||||||
|
transition-delay: 0.6s;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -1,125 +1,122 @@
|
|||||||
// Rewind data interfaces
|
// Rewind data interfaces
|
||||||
import type { SnCloudFile } from './post'
|
import type { SnCloudFile } from "./post"
|
||||||
|
import type { SnPublisher } from "./publisher"
|
||||||
|
import type { SnAccount } from "./user"
|
||||||
|
|
||||||
export interface SnRewindPassData {
|
export interface SnRewindActiveData {
|
||||||
maxCheckInStrike: number;
|
maxCheckInStreak: number
|
||||||
|
mostActiveDay: string
|
||||||
|
mostActiveWeekday: string
|
||||||
|
latestActiveTime: string
|
||||||
|
checkInCompleteness: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SnRewindMostCalledChat {
|
export interface SnRewindMostCalledChat {
|
||||||
id: string;
|
id: string
|
||||||
name: string;
|
name: string
|
||||||
type: number;
|
type: number
|
||||||
description: string;
|
description: string
|
||||||
picture: SnCloudFile | null;
|
picture: SnCloudFile | null
|
||||||
realmId: string | null;
|
realmId: string | null
|
||||||
accountId: string;
|
accountId: string
|
||||||
isPublic: boolean;
|
isPublic: boolean
|
||||||
isCommunity: boolean;
|
isCommunity: boolean
|
||||||
background: SnCloudFile | null;
|
background: SnCloudFile | null
|
||||||
createdAt: string;
|
createdAt: string
|
||||||
updatedAt: string;
|
updatedAt: string
|
||||||
deletedAt: string | null;
|
deletedAt: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SnRewindMostPopularPost {
|
export interface SnRewindMostPopularPost {
|
||||||
id: string;
|
id: string
|
||||||
title: string;
|
title: string
|
||||||
upvotes: number;
|
upvotes: number
|
||||||
viewsTotal: number;
|
viewsTotal: number
|
||||||
viewsUnique: number;
|
viewsUnique: number
|
||||||
createdAt: string;
|
createdAt: string
|
||||||
updatedAt: string;
|
updatedAt: string
|
||||||
publishedAt: string;
|
publishedAt: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SnRewindMostProductiveDay {
|
export interface SnRewindMostProductiveDay {
|
||||||
date: string;
|
date: string
|
||||||
postCount: number;
|
postCount: number
|
||||||
}
|
|
||||||
|
|
||||||
export interface SnRewindMostCalledAccount {
|
|
||||||
id: string;
|
|
||||||
name: string;
|
|
||||||
nick: string;
|
|
||||||
profile: {
|
|
||||||
id: string;
|
|
||||||
bio: string;
|
|
||||||
level: number;
|
|
||||||
picture: SnCloudFile | null;
|
|
||||||
background: SnCloudFile | null;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
};
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SnRewindMostLovedPublisher {
|
export interface SnRewindMostLovedPublisher {
|
||||||
publisher: {
|
publisher: SnPublisher
|
||||||
id: string;
|
upvoteCounts: number
|
||||||
name: string;
|
|
||||||
nick: string;
|
|
||||||
bio: string;
|
|
||||||
level: number;
|
|
||||||
picture: SnCloudFile | null;
|
|
||||||
background: SnCloudFile | null;
|
|
||||||
createdAt: string;
|
|
||||||
updatedAt: string;
|
|
||||||
};
|
|
||||||
upvoteCounts: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SnRewindMostMessagedChat {
|
export interface SnRewindChat {
|
||||||
id: string;
|
id: string
|
||||||
name: string | null;
|
name: string | null
|
||||||
type: number;
|
type: number
|
||||||
members: SnRewindChatMember[];
|
members: SnRewindChatMember[]
|
||||||
createdAt: string;
|
picture: SnCloudFile | null
|
||||||
updatedAt: string;
|
background: SnCloudFile | null
|
||||||
|
createdAt: string
|
||||||
|
updatedAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SnRewindCallSummary {
|
||||||
|
chat: SnRewindChat
|
||||||
|
duration: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SnRewindChatSummary {
|
||||||
|
chat: SnRewindChat
|
||||||
|
messageCounts: number
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SnRewindChatMember {
|
export interface SnRewindChatMember {
|
||||||
id: string;
|
id: string
|
||||||
nick: string | null;
|
nick: string | null
|
||||||
role: number;
|
role: number
|
||||||
isBot: boolean;
|
|
||||||
account: {
|
account: {
|
||||||
id: string;
|
id: string
|
||||||
name: string;
|
name: string
|
||||||
nick: string;
|
nick: string
|
||||||
profile: {
|
profile: {
|
||||||
id: string;
|
id: string
|
||||||
bio: string;
|
bio: string
|
||||||
level: number;
|
level: number
|
||||||
picture: SnCloudFile | null;
|
picture: SnCloudFile | null
|
||||||
background: SnCloudFile | null;
|
background: SnCloudFile | null
|
||||||
createdAt: string;
|
createdAt: string
|
||||||
updatedAt: string;
|
updatedAt: string
|
||||||
};
|
}
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SnRewindSphereData {
|
export interface SnRewindMostLovedAudience {
|
||||||
totalCount: number;
|
account: SnAccount
|
||||||
upvoteCounts: number;
|
upvoteCounts: number
|
||||||
mostCalledChat: SnRewindMostCalledChat;
|
}
|
||||||
mostPopularPost: SnRewindMostPopularPost;
|
|
||||||
mostProductiveDay: SnRewindMostProductiveDay;
|
export interface SnRewindSocialData {
|
||||||
mostCalledAccounts: SnRewindMostCalledAccount[];
|
totalPostCount: number
|
||||||
mostLovedPublisher: SnRewindMostLovedPublisher;
|
totalUpvoteCount: number
|
||||||
mostMessagedChat: SnRewindMostMessagedChat;
|
mostCalledChat: SnRewindCallSummary
|
||||||
|
mostMessagedDirectChat: SnRewindChatSummary
|
||||||
|
mostPopularPost: SnRewindMostPopularPost
|
||||||
|
mostProductiveDay: SnRewindMostProductiveDay
|
||||||
|
mostCalledAccounts: SnAccount[]
|
||||||
|
mostLovedPublisher: SnRewindMostLovedPublisher
|
||||||
|
mostLovedAudience: SnRewindMostLovedAudience
|
||||||
|
mostMessagedChat: SnRewindChatSummary
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SnRewind {
|
export interface SnRewind {
|
||||||
id: string;
|
id: string
|
||||||
year: number;
|
year: number
|
||||||
schemaVersion: number;
|
schemaVersion: number
|
||||||
data: {
|
data: {
|
||||||
pass: SnRewindPassData;
|
pass: SnRewindActiveData
|
||||||
sphere: SnRewindSphereData;
|
sphere: SnRewindSocialData
|
||||||
};
|
}
|
||||||
accountId: string;
|
accountId: string
|
||||||
createdAt: string;
|
createdAt: string
|
||||||
updatedAt: string;
|
updatedAt: string
|
||||||
deletedAt: string | null;
|
deletedAt: string | null
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user