优化页面、修复BUG

This commit is contained in:
李琦
2026-07-04 16:59:57 +08:00
parent e4ab62ab7e
commit 734f82b2c6
8 changed files with 819 additions and 0 deletions

View File

@@ -0,0 +1,193 @@
<template>
<Teleport to="body">
<div
v-if="isOpen"
class="fixed inset-0 z-[100] flex items-center justify-center p-4"
@click.self="close"
>
<div class="absolute inset-0 bg-black/80 backdrop-blur-sm" @click="close" />
<div
class="relative z-10 w-full max-w-2xl max-h-[90vh] overflow-y-auto admin-card border border-white/10 shadow-2xl animate-reveal custom-scrollbar"
role="dialog"
aria-modal="true"
>
<button
type="button"
class="absolute top-4 right-4 z-10 text-white/40 hover:text-white transition-colors"
aria-label="关闭"
@click="close"
>
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
</button>
<div v-if="loading" class="p-12 text-center text-art-muted">加载中...</div>
<div v-else-if="error" class="p-12 text-center text-art-error">{{ error }}</div>
<template v-else-if="profile">
<!-- Header -->
<div class="p-6 md:p-8 border-b border-white/5">
<div class="flex items-start gap-4 md:gap-6">
<div class="shrink-0 w-16 h-16 md:w-20 md:h-20 rounded-full overflow-hidden border border-white/20 bg-white/5 flex items-center justify-center">
<img v-if="profile.avatar" :src="profile.avatar" :alt="profile.username" class="w-full h-full object-cover" />
<span v-else class="text-2xl font-semibold text-art-accent uppercase">{{ profile.username.charAt(0) }}</span>
</div>
<div class="min-w-0 flex-1 pt-1">
<h2 class="text-xl md:text-2xl font-serif text-white mb-1">{{ profile.username }}</h2>
<p v-if="profile.email" class="text-sm text-art-muted truncate">{{ profile.email }}</p>
<p v-if="profile.phone" class="text-sm text-art-muted mt-0.5">{{ profile.phone }}</p>
<p v-if="profile.bio" class="text-sm text-white/70 mt-3 leading-relaxed">{{ profile.bio }}</p>
</div>
</div>
<!-- WeChat -->
<div v-if="profile.wechat || profile.wechatQrcode" class="mt-5">
<div
class="relative inline-block"
@mouseenter="showQr = true"
@mouseleave="showQr = false"
>
<button
type="button"
class="text-sm text-art-accent hover:text-white transition-colors link-underline cursor-pointer"
>
点击查看微信二维码加好友
</button>
<Transition name="fade">
<div
v-if="showQr"
class="absolute left-0 top-full mt-2 z-20 p-4 bg-art-surface border border-white/10 rounded-lg shadow-xl min-w-[180px]"
>
<img
v-if="qrImageSrc"
:src="qrImageSrc"
alt="微信二维码"
class="w-40 h-40 mx-auto rounded"
/>
<p v-if="profile.wechat" class="text-xs text-art-muted text-center mt-2 font-mono">{{ profile.wechat }}</p>
</div>
</Transition>
</div>
</div>
</div>
<!-- Posts -->
<div class="p-6 md:p-8 grid grid-cols-1 md:grid-cols-2 gap-6">
<div>
<h3 class="text-sm font-medium text-white/80 mb-3 uppercase tracking-wider">最近发布</h3>
<ul v-if="recentPosts.length" class="space-y-2">
<li v-for="post in recentPosts" :key="post.id">
<router-link
:to="`/blog/${post.id}`"
class="block text-sm text-art-muted hover:text-art-accent transition-colors truncate"
@click="close"
>
{{ post.title }}
</router-link>
</li>
</ul>
<p v-else class="text-xs text-art-muted">暂无文章</p>
</div>
<div>
<h3 class="text-sm font-medium text-white/80 mb-3 uppercase tracking-wider">浏览最多</h3>
<ul v-if="popularPosts.length" class="space-y-2">
<li v-for="post in popularPosts" :key="post.id">
<router-link
:to="`/blog/${post.id}`"
class="block text-sm text-art-muted hover:text-art-accent transition-colors truncate"
@click="close"
>
{{ post.title }}
<span v-if="post.readCount" class="text-xs text-white/30 ml-1">({{ post.readCount }})</span>
</router-link>
</li>
</ul>
<p v-else class="text-xs text-art-muted">暂无文章</p>
</div>
</div>
</template>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { ref, watch, computed } from 'vue'
import { fetchUserProfile, fetchUserPosts, type UserPublicProfile, type Post } from '../services/api'
const props = defineProps<{
isOpen: boolean
userId: number | null
}>()
const emit = defineEmits<{ close: [] }>()
const loading = ref(false)
const error = ref('')
const profile = ref<UserPublicProfile | null>(null)
const recentPosts = ref<Post[]>([])
const popularPosts = ref<Post[]>([])
const showQr = ref(false)
const generatedQr = ref('')
const qrImageSrc = computed(() => {
if (profile.value?.wechatQrcode) return profile.value.wechatQrcode
if (generatedQr.value) return generatedQr.value
return ''
})
const close = () => emit('close')
const loadProfile = async (userId: number) => {
loading.value = true
error.value = ''
profile.value = null
recentPosts.value = []
popularPosts.value = []
generatedQr.value = ''
try {
const [profileData, recent, popular] = await Promise.all([
fetchUserProfile(userId),
fetchUserPosts(userId, 'recent', 5),
fetchUserPosts(userId, 'popular', 5),
])
profile.value = profileData
recentPosts.value = recent
popularPosts.value = popular
if (!profileData.wechatQrcode && profileData.wechat) {
generatedQr.value = `https://api.qrserver.com/v1/create-qr-code/?size=160x160&data=${encodeURIComponent(profileData.wechat)}`
}
} catch (e) {
error.value = '加载用户资料失败'
console.error(e)
} finally {
loading.value = false
}
}
watch(
[() => props.isOpen, () => props.userId],
([open, userId]) => {
if (open && userId) {
document.body.style.overflow = 'hidden'
void loadProfile(userId)
} else {
document.body.style.overflow = ''
showQr.value = false
}
},
{ immediate: true }
)
</script>
<style scoped>
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.15s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
</style>