优化页面、修复BUG
This commit is contained in:
193
client/src/components/UserProfileModal.vue
Normal file
193
client/src/components/UserProfileModal.vue
Normal 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>
|
||||
Reference in New Issue
Block a user