Files
CosScene/clients/pages/mine/index.vue
T
2026-05-09 16:40:29 +08:00

404 lines
8.9 KiB
Vue

<script setup>
import { ref, computed } from "vue";
import { useUserStore } from "@/store/user";
import { resolveImageUrl } from "@/utils/image";
import { getMyStats } from "@/api/user";
import { getUnreadCount } from "@/api/notification";
const userStore = useUserStore();
const isLoggedIn = computed(() => userStore.isLoggedIn);
const user = computed(() => userStore.userInfo);
const stats = ref({ spot_count: 0, approved_count: 0, favorite_count: 0, rating_received: 0 });
const unreadCount = ref(0);
const goLogin = () => {
uni.navigateTo({ url: "/pages/login/index" });
};
const handleLogout = () => {
uni.showModal({
title: "提示",
content: "确定要退出登录吗?",
success: (res) => {
if (res.confirm) {
userStore.logout();
}
},
});
};
const menuItems = [
{ label: "编辑资料", icon: "gear-filled", action: "profile" },
{ label: "我的收藏", icon: "heart-filled", action: "favorites" },
{ label: "我的投稿", icon: "location-filled", action: "my-spots" },
{ label: "约拍/活动", icon: "calendar-filled", action: "activity-hub" },
{ label: "会员中心", icon: "vip-filled", action: "membership" },
{ label: "积分概览", icon: "star-filled", action: "points" },
{ label: "消息通知", icon: "chat-filled", action: "notifications" },
{ label: "设置", icon: "gear", action: "settings" },
];
const handleMenu = (action) => {
switch (action) {
case "profile":
uni.navigateTo({ url: "/pages/mine/profile" });
break;
case "favorites":
uni.navigateTo({ url: "/pages/mine/favorites" });
break;
case "my-spots":
uni.navigateTo({ url: "/pages/mine/my-spots" });
break;
case "points":
uni.navigateTo({ url: "/pages/mine/points" });
break;
case "activity-hub":
uni.switchTab({ url: "/pages/activity/index" });
break;
case "membership":
uni.navigateTo({ url: "/pages/mine/membership" });
break;
case "notifications":
uni.switchTab({ url: "/pages/mine/notifications" });
break;
case "settings":
uni.navigateTo({ url: "/pages/mine/settings" });
break;
}
};
import { onShow } from "@dcloudio/uni-app";
onShow(async () => {
if (isLoggedIn.value) {
userStore.fetchUserInfo();
try {
const s = await getMyStats();
stats.value = s;
} catch (e) { /* ignore */ }
try {
const r = await getUnreadCount();
unreadCount.value = r.count || 0;
} catch (e) { /* ignore */ }
}
});
</script>
<template>
<view class="mine-page">
<view v-if="isLoggedIn" class="user-card">
<view class="avatar-area">
<image
v-if="user?.avatar_url"
class="avatar"
:src="resolveImageUrl(user.avatar_url)"
mode="aspectFill"
/>
<view v-else class="avatar avatar-default">
<uni-icons type="person" size="28" color="#ffffff" class="avatar-icon" />
</view>
</view>
<view class="user-info">
<text class="nickname">{{ user?.nickname || "加载中..." }}</text>
<text v-if="user?.bio" class="user-bio">{{ user.bio }}</text>
<view class="user-meta">
<view v-if="user?.city" class="meta-item"><uni-icons type="location" size="14" color="rgba(255,255,255,0.8)" /> {{ user.city }}</view>
<view v-if="user?.identity" class="identity-badge">
<text class="identity-text">{{ user.identity }}</text>
</view>
</view>
</view>
</view>
<view v-else class="login-card">
<view class="login-avatar">
<uni-icons type="person" size="28" color="#6366f1" class="login-avatar-icon" />
</view>
<text class="login-hint">请先登录</text>
<button class="login-btn" @tap="goLogin">立即登录</button>
</view>
<view v-if="isLoggedIn" class="stats-card">
<view class="stat-item" @tap="handleMenu('my-spots')">
<text class="stat-num">{{ stats.spot_count }}</text>
<text class="stat-label">投稿</text>
</view>
<view class="stat-item" @tap="handleMenu('favorites')">
<text class="stat-num">{{ stats.favorite_count }}</text>
<text class="stat-label">收藏</text>
</view>
<view class="stat-item">
<text class="stat-num">{{ stats.rating_received }}</text>
<text class="stat-label">获赞</text>
</view>
<view class="stat-item">
<text class="stat-num">{{ stats.approved_count }}</text>
<text class="stat-label">通过</text>
</view>
</view>
<view class="menu-card">
<view
v-for="(item, idx) in menuItems"
:key="idx"
class="menu-item"
@tap="handleMenu(item.action)"
>
<view class="menu-left">
<uni-icons :type="item.icon" size="22" color="#6366f1" class="menu-icon" />
<text class="menu-label">{{ item.label }}</text>
</view>
<view class="menu-right">
<view v-if="item.action === 'notifications' && unreadCount > 0" class="badge">
<text class="badge-text">{{ unreadCount > 99 ? '99+' : unreadCount }}</text>
</view>
<uni-icons type="right" size="16" color="#cbd5e1" class="menu-arrow" />
</view>
</view>
</view>
<view v-if="isLoggedIn" class="logout-area">
<button class="logout-btn" @tap="handleLogout">退出登录</button>
</view>
</view>
</template>
<style scoped>
.mine-page {
min-height: 100vh;
background: #f5f6fa;
padding: 0 32rpx;
padding-top: 32rpx;
}
.user-card {
background: linear-gradient(135deg, #6366f1, #818cf8);
border-radius: 24rpx;
padding: 40rpx 32rpx;
display: flex;
align-items: center;
margin-bottom: 32rpx;
}
.avatar-area {
margin-right: 24rpx;
}
.avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
border: 4rpx solid rgba(255, 255, 255, 0.3);
}
.avatar-default {
background: rgba(255, 255, 255, 0.25);
display: flex;
align-items: center;
justify-content: center;
}
.avatar-icon {
font-size: 56rpx;
}
.user-info {
flex: 1;
}
.nickname {
font-size: 36rpx;
font-weight: 700;
color: #ffffff;
display: block;
margin-bottom: 8rpx;
}
.user-bio {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.75);
display: block;
margin-bottom: 8rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 400rpx;
}
.user-meta {
display: flex;
align-items: center;
gap: 16rpx;
}
.meta-item {
font-size: 24rpx;
color: rgba(255, 255, 255, 0.8);
}
.identity-badge {
background: rgba(255, 255, 255, 0.2);
padding: 4rpx 16rpx;
border-radius: 8rpx;
}
.identity-text {
font-size: 22rpx;
color: #ffffff;
}
.login-card {
background: #ffffff;
border-radius: 24rpx;
padding: 60rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 32rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
}
.login-avatar {
width: 120rpx;
height: 120rpx;
border-radius: 60rpx;
background: #e0e7ff;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 20rpx;
}
.login-avatar-icon {
font-size: 56rpx;
}
.login-hint {
font-size: 30rpx;
color: #64748b;
margin-bottom: 32rpx;
}
.login-btn {
width: 320rpx;
height: 80rpx;
line-height: 80rpx;
background: #6366f1;
color: #ffffff;
font-size: 30rpx;
font-weight: 600;
border-radius: 40rpx;
border: none;
}
.login-btn::after {
border: none;
}
.stats-card {
display: flex;
background: #ffffff;
border-radius: 16rpx;
padding: 28rpx 0;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
}
.stat-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
}
.stat-num {
font-size: 36rpx;
font-weight: 700;
color: #1e293b;
}
.stat-label {
font-size: 22rpx;
color: #94a3b8;
}
.menu-right {
display: flex;
align-items: center;
gap: 8rpx;
}
.badge {
background: #ef4444;
min-width: 32rpx;
height: 32rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
padding: 0 8rpx;
}
.badge-text {
font-size: 20rpx;
color: #fff;
font-weight: 600;
}
.menu-card {
background: #ffffff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
margin-bottom: 32rpx;
}
.menu-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 32rpx;
border-bottom: 1rpx solid #f1f5f9;
}
.menu-item:last-child {
border-bottom: none;
}
.menu-left {
display: flex;
align-items: center;
}
.menu-icon {
font-size: 36rpx;
margin-right: 20rpx;
}
.menu-label {
font-size: 30rpx;
color: #1e293b;
}
.menu-arrow {
font-size: 36rpx;
color: #cbd5e1;
}
.logout-area {
padding: 16rpx 0 48rpx;
}
.logout-btn {
width: 100%;
height: 84rpx;
line-height: 84rpx;
background: #ffffff;
color: #ef4444;
font-size: 30rpx;
border-radius: 16rpx;
border: none;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
}
.logout-btn::after {
border: none;
}
</style>