Files
rating/pages/mine/mine.vue
2026-06-22 18:07:44 +08:00

484 lines
12 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="mine-page">
<!-- Custom Navbar -->
<view
class="nav-bar"
:style="{ paddingTop: navBarTop + 'px', height: navBarHeight + 'px' }"
>
<!-- Optional title -->
</view>
<scroll-view
scroll-y
class="content-scroll"
:style="{ paddingTop: navBarTop + navBarHeight + 'px' }"
>
<view class="content-wrap">
<!-- User Card -->
<view class="user-card">
<view class="avatar-container" @tap="handleUserClick">
<view class="avatar-ring">
<image :src="userInfo.avatarUrl" class="avatar" mode="aspectFill" />
</view>
</view>
<text class="nickname" @tap="handleUserClick">{{ userInfo.nickName }}</text>
<view class="tags-container" v-if="isLoggedIn && userStats.label && userStats.label.length">
<!-- <text class="sparkle-icon"></text>
<text class="tags-text">{{ userStats.label.join(' | ') }}</text> -->
</view>
<view class="tags-container" v-else-if="!isLoggedIn">
<text class="tags-text">点击登录解锁更多功能</text>
</view>
<view class="stats-container">
<view class="stat-item">
<text class="stat-num">{{ isLoggedIn ? userStats.participant : '-' }}</text>
<text class="stat-label">参与话题</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-num">{{ isLoggedIn ? userStats.pkCount : '-' }}</text>
<text class="stat-label">参与PK</text>
</view>
<view class="stat-divider"></view>
<view class="stat-item">
<text class="stat-num">{{ isLoggedIn ? userStats.liked : '-' }}</text>
<text class="stat-label">获赞</text>
</view>
</view>
<view class="action-buttons">
<button class="btn-primary" @tap="navTo('profile')">编辑资料</button>
<button v-if="isLoggedIn" class="btn-secondary" open-type="share">
<text class="share-icon-btn"></text> 分享主页
</button>
<button v-else class="btn-secondary" @tap="openLoginPopup('请先登录后再分享主页')">
<text class="share-icon-btn"></text> 分享主页
</button>
</view>
</view>
<!-- Menu Group 1 -->
<view class="menu-card">
<view class="menu-item" @tap="navTo('/pages/mine/topics')">
<view class="icon-left"><text class="icon-bookmark"></text></view>
<text class="menu-text">我参与的话题</text>
<text class="menu-value" v-if="isLoggedIn">{{ userStats.participant }}</text>
</view>
<view class="menu-item" @tap="navTo('/pages/mine/pk')">
<view class="icon-left"><text class="icon-bookmark"></text></view>
<text class="menu-text">我参与的PK</text>
<text class="menu-value" v-if="isLoggedIn">{{ userStats.pkCount }}</text>
</view>
<!-- <view class="menu-item" @tap="navTo('comments')">
<view class="icon-left"><text class="icon-document"></text></view>
<text class="menu-text">我的评论</text>
<text class="menu-value" v-if="isLoggedIn">{{ userStats.comment }}</text>
</view> -->
</view>
<!-- Menu Group 2 -->
<view class="menu-card">
<view class="menu-item" @tap="navTo('feedback')">
<view class="icon-left"><text class="icon-feedback"></text></view>
<text class="menu-text">意见反馈</text>
<text class="arrow"></text>
</view>
</view>
<view style="height: 120rpx"></view>
</view>
</scroll-view>
<!-- Login Popup -->
<LoginPopup ref="loginPopupRef" @logind="handleLogind" />
</view>
</template>
<script setup>
import { ref, computed } from "vue";
import { useUserStore } from "@/stores/user";
import { onShareAppMessage, onShareTimeline, onLoad, onShow } from "@dcloudio/uni-app";
import { getShareToken } from "@/utils/common";
import { getShareReward } from "@/api/system";
import { fetchUserNums } from "@/api/mine";
import LoginPopup from "@/components/LoginPopup/LoginPopup.vue";
const userStore = useUserStore();
const loginPopupRef = ref(null);
// Navigation Bar
const navBarTop = ref(20);
const navBarHeight = ref(44);
// User Info
const defaultAvatarUrl = "https://api.dicebear.com/7.x/avataaars/svg?seed=fallback";
const userInfo = computed(() => ({
nickName: userStore.userInfo.nickName || "点击登录",
avatarUrl: userStore.userInfo.avatarUrl || defaultAvatarUrl,
isVip: userStore.userInfo.isVip || false,
}));
const isLoggedIn = computed(() => !!userStore.token || !!userStore.userInfo.nickName);
// User Stats
const userStats = ref({
participant: 0,
comment: 0,
liked: 0,
pkCount: 0,
label: []
});
const loadUserStats = async () => {
if (!isLoggedIn.value) {
userStats.value = {
participant: 0,
comment: 0,
liked: 0,
pkCount: 0,
label: []
};
return;
}
try {
const res = await fetchUserNums();
const data = res?.data || res || {};
userStats.value = {
participant: data.participant || 0,
comment: data.comment || 0,
liked: data.liked || 0,
pkCount: data.pkCount || 0,
label: data.label || []
};
} catch (error) {
console.error("获取用户数据失败:", error);
}
};
onLoad(() => {
const sysInfo = uni.getSystemInfoSync();
navBarTop.value = sysInfo.statusBarHeight;
navBarHeight.value = 44;
});
onShow(() => {
loadUserStats();
});
onShareAppMessage(async () => {
const shareToken = await getShareToken("mine");
getShareReward({ scene: "mine" });
const nickName = isLoggedIn.value ? userInfo.value.nickName : "我";
return {
title: `${nickName}也在玩夯拉评分来看看TA的主页`,
path: "/pages/mine/mine?shareToken=" + shareToken,
imageUrl: "https://file.lihailezzc.com/resource/8dd026d76ef7a63d123b7fd698fb989b.png",
};
});
onShareTimeline(async () => {
const shareToken = await getShareToken("mine");
getShareReward({ scene: "mine_timeline" });
const nickName = isLoggedIn.value ? userInfo.value.nickName : "我";
const participant = userStats.value.participant || 0;
return {
title: `${nickName}已参与 ${participant} 个话题,来夯拉评分一起围观`,
query: `shareToken=${shareToken}`,
};
});
const handleUserClick = () => {
if (!isLoggedIn.value) {
openLoginPopup("请先登录");
} else {
uni.navigateTo({
url: "/pages/mine/profile",
});
}
};
const handleLogind = async () => {
loadUserStats();
};
const openLoginPopup = (message = "请先登录") => {
loginPopupRef.value.open();
if (message) {
uni.showToast({ title: message, icon: "none" });
}
};
const navTo = (page) => {
if (!isLoggedIn.value) {
openLoginPopup("请先登录");
return;
}
if (page === "profile") {
uni.navigateTo({ url: "/pages/mine/profile" });
return;
}
if (page === "feedback") {
uni.navigateTo({ url: "/pages/feedback/index" });
return;
}
if (page === "/pages/mine/topics") {
uni.navigateTo({ url: page });
return;
}
if (page === "/pages/mine/pk") {
uni.navigateTo({ url: page });
return;
}
uni.showToast({ title: "功能开发中", icon: "none" });
};
</script>
<style lang="scss" scoped>
.mine-page {
min-height: 100vh;
background-color: #f8f9fc;
box-sizing: border-box;
}
.nav-bar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 100;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
}
.content-scroll {
height: 100vh;
}
.content-wrap {
padding: 20rpx 32rpx 40rpx;
}
/* User Card */
.user-card {
background: #fff;
border-radius: 40rpx;
padding: 60rpx 40rpx 40rpx;
display: flex;
flex-direction: column;
align-items: center;
margin-bottom: 32rpx;
box-shadow: 0 16rpx 40rpx rgba(77, 68, 241, 0.04);
}
.avatar-container {
margin-bottom: 24rpx;
}
.avatar-ring {
width: 176rpx;
height: 176rpx;
border-radius: 50%;
padding: 6rpx;
background: linear-gradient(135deg, #7B46F1, #4d44f1, #00d2ff);
display: flex;
align-items: center;
justify-content: center;
}
.avatar {
width: 100%;
height: 100%;
border-radius: 50%;
border: 4rpx solid #fff;
background-color: #fff;
}
.nickname {
font-size: 40rpx;
font-weight: 800;
color: #111;
margin-bottom: 12rpx;
}
.tags-container {
display: flex;
align-items: center;
margin-bottom: 48rpx;
}
.sparkle-icon {
font-size: 28rpx;
color: #7B46F1;
margin-right: 8rpx;
}
.tags-text {
font-size: 24rpx;
color: #666;
}
.stats-container {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
margin-bottom: 48rpx;
}
.stat-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
}
.stat-num {
font-size: 40rpx;
font-weight: 800;
color: #111;
margin-bottom: 8rpx;
}
.stat-label {
font-size: 24rpx;
color: #888;
}
.stat-divider {
width: 2rpx;
height: 40rpx;
background-color: #f0f0f0;
}
.action-buttons {
width: 100%;
display: flex;
flex-direction: column;
gap: 20rpx;
}
.btn-primary {
width: 100%;
height: 88rpx;
background: linear-gradient(135deg, #5b54df, #453bc7);
color: #fff;
font-size: 30rpx;
font-weight: 600;
border-radius: 100rpx;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 8rpx 20rpx rgba(77, 68, 241, 0.2);
}
.btn-primary::after {
border: none;
}
.btn-secondary {
width: 100%;
height: 88rpx;
background-color: #f3f2ff;
color: #453bc7;
font-size: 30rpx;
font-weight: 600;
border-radius: 100rpx;
display: flex;
align-items: center;
justify-content: center;
}
.btn-secondary::after {
border: none;
}
.share-icon-btn {
width: 36rpx;
height: 36rpx;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23453bc7' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='18' cy='5' r='3'%3E%3C/circle%3E%3Ccircle cx='6' cy='12' r='3'%3E%3C/circle%3E%3Ccircle cx='18' cy='19' r='3'%3E%3C/circle%3E%3Cline x1='8.59' y1='13.51' x2='15.42' y2='17.49'%3E%3C/line%3E%3Cline x1='15.41' y1='6.51' x2='8.59' y2='10.49'%3E%3C/line%3E%3C/svg%3E");
background-size: cover;
margin-right: 12rpx;
}
/* Menu Card */
.menu-card {
background: #fff;
border-radius: 32rpx;
padding: 16rpx 0;
margin-bottom: 24rpx;
box-shadow: 0 8rpx 24rpx rgba(0, 0, 0, 0.02);
}
.menu-item {
display: flex;
align-items: center;
padding: 32rpx 40rpx;
position: relative;
}
.menu-item:active {
background: #fcfcfc;
}
.icon-left {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
margin-right: 24rpx;
}
.icon-bookmark {
width: 40rpx;
height: 40rpx;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%234d44f1' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M19 21l-7-5-7 5V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z'%3E%3C/path%3E%3C/svg%3E");
background-size: cover;
}
.icon-document {
width: 40rpx;
height: 40rpx;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%237B46F1' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z'%3E%3C/path%3E%3Cpolyline points='14 2 14 8 20 8'%3E%3C/polyline%3E%3Cline x1='16' y1='13' x2='8' y2='13'%3E%3C/line%3E%3Cline x1='16' y1='17' x2='8' y2='17'%3E%3C/line%3E%3Cpolyline points='10 9 9 9 8 9'%3E%3C/polyline%3E%3C/svg%3E");
background-size: cover;
}
.icon-feedback {
width: 40rpx;
height: 40rpx;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23453bc7' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z'%3E%3C/path%3E%3Cline x1='9' y1='10' x2='15' y2='10'%3E%3C/line%3E%3C/svg%3E");
background-size: cover;
}
.menu-text {
flex: 1;
font-size: 30rpx;
color: #333;
font-weight: 500;
}
.menu-value {
font-size: 32rpx;
color: #999;
font-family: 'DIN Alternate', -apple-system, sans-serif;
}
.arrow {
color: #ccc;
font-size: 36rpx;
margin-left: 12rpx;
}
</style>