chore: 首次提交 - 关联 GitHub fnvtk/MBTI_wang

Made-with: Cursor
This commit is contained in:
卡若
2026-03-17 12:39:38 +08:00
commit eb510304c0
259 changed files with 60464 additions and 0 deletions

View File

@@ -0,0 +1,318 @@
// pages/profile/index.js - 我的页面
const app = getApp()
const { getTypeOnly } = require('../../utils/resultFormat')
const { request } = require('../../utils/request')
Page({
data: {
hasLogin: false,
userInfo: null,
balance: 0,
testCount: 0,
hasResults: false,
mbtiType: '',
discType: '',
pdpType: '',
aiType: '',
mbtiTime: '',
discTime: '',
pdpTime: '',
aiTime: '',
/** 最近记录的数据库 ID用于跳转时传参 */
mbtiResultId: null,
discResultId: null,
pdpResultId: null,
aiResultId: null,
loginLoading: false,
loginFailed: false,
nicknameDisplay: '',
displayNickname: '',
/** 默认头像(根据昵称):无头像时显示首字+背景色 */
avatarLetter: '登',
avatarBgColor: '#6366f1',
// 推广中心统计与配置(来自 /api/distribution/stats
promoDistributionEnabled: true,
promoCenterTitle: '推广中心',
promoTotalInvite: 0,
promoTotalEarned: '0.00',
promoWithdrawable: '0.00',
/** 是否有企业权限(绑定企业):有则显示「我的简历」 */
hasEnterprise: false
},
onLoad() {
// 仅在 onLoad 执行一次onShow 里的 runLoginThenLoad 会导致重复请求
this.runLoginThenLoad()
},
onShow() {
// 如果是从其他页面返回,且已经登录,则只刷新数据而不重新执行登录流程
if (this.data.hasLogin) {
this.loadData()
}
if (typeof this.getTabBar === 'function' && this.getTabBar()) {
this.getTabBar().setData({ selected: 2 })
}
},
/** 先确保登录完成(静默登录),再刷新页面数据 */
runLoginThenLoad() {
this.setData({ loginLoading: true, loginFailed: false })
app.ensureLogin().then((ok) => {
this.setData({ loginLoading: false, loginFailed: !ok })
this.loadData()
}).catch(() => {
this.setData({ loginLoading: false, loginFailed: true })
this.loadData()
})
},
/** 生成随机后缀:仅 26 英文字母(大小写)与数字,不含特殊字符,默认 4 位 */
_randomWechatUserSuffix(len) {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
let s = ''
for (let i = 0; i < (len || 4); i++) {
s += chars[Math.floor(Math.random() * chars.length)]
}
return s
},
/** 根据昵称生成默认头像的首字与背景色(同一昵称同色) */
_avatarFromNickname(name) {
const str = (name && String(name).trim()) || '登'
const letter = str.charAt(0).toUpperCase() || '登'
const palette = ['#6366f1', '#8b5cf6', '#ec4899', '#f43f5e', '#14b8a6', '#0ea5e9', '#3b82f6', '#eab308']
let hash = 0
for (let i = 0; i < str.length; i++) hash += str.charCodeAt(i)
const bgColor = palette[Math.abs(hash) % palette.length]
return { avatarLetter: letter, avatarBgColor: bgColor }
},
/** 获取或生成当前用户的「微信用户XXXX」后缀同一用户固定仅 26 字母+数字) */
_getOrCreateWechatUserSuffix(userInfo) {
const uid = (userInfo && (userInfo.id || userInfo.userId)) ? String(userInfo.id || userInfo.userId) : '_default'
const storageKey = 'wechat_user_suffix'
const stored = wx.getStorageSync(storageKey)
if (stored && typeof stored === 'object' && stored[uid]) return stored[uid]
const suffix = this._randomWechatUserSuffix(4)
const next = { ...(stored && typeof stored === 'object' ? stored : {}), [uid]: suffix }
wx.setStorageSync(storageKey, next)
return suffix
},
loadData() {
const userInfo = app.globalData.userInfo || wx.getStorageSync('userInfo')
const token = app.globalData.token || wx.getStorageSync('token')
// 1. 先同步渲染用户基础信息(昵称/头像)
const nickname = (userInfo && (userInfo.nickname || userInfo.nickName))
? String(userInfo.nickname || userInfo.nickName).trim() : ''
let nicknameDisplay = nickname
let displayNickname = ''
if (!nickname && userInfo) {
const suffix = this._getOrCreateWechatUserSuffix(userInfo)
displayNickname = '微信用户' + suffix
nicknameDisplay = displayNickname
}
const avatarFromName = userInfo
? this._avatarFromNickname(nicknameDisplay)
: this._avatarFromNickname('点击登录')
const hasEnterprise = !!(userInfo && (userInfo.hasEnterprise === true || (userInfo.enterpriseId && Number(userInfo.enterpriseId) > 0)))
this.setData({
hasLogin: !!token || !!userInfo,
userInfo: userInfo || null,
hasEnterprise,
nicknameDisplay,
displayNickname,
avatarLetter: avatarFromName.avatarLetter,
avatarBgColor: avatarFromName.avatarBgColor
})
// 2. 已登录则从服务端拉取最近记录;失败降级读 localStorage
if (token || userInfo) {
this._loadRecentFromAPI()
this._loadPromoStats()
}
},
/** 从 /api/test/recent 拉取各类型最新记录 */
_loadRecentFromAPI() {
const scope = app.globalData.appScope || 'personal'
request({
url: `/api/test/recent?scope=${scope}`,
method: 'GET',
success: (res) => {
// res 是 wx.request 原始响应:{ statusCode, data: { code, data, message } }
const payload = res && res.data
if (!payload || payload.code !== 200 || !payload.data) {
this._loadRecentFromStorage()
return
}
const { records = {}, totalCount = 0 } = payload.data
const r = records
// DISC resultText 后端已含「型」type badge 只显示字母,去掉「型」
const discType = r.disc ? r.disc.resultText.replace(/型$/, '') : ''
this.setData({
testCount: totalCount,
hasResults: !!(r.mbti || r.disc || r.pdp || r.ai),
mbtiType: r.mbti ? r.mbti.resultText : '',
discType,
pdpType: r.pdp ? r.pdp.resultText : '',
aiType: r.ai ? r.ai.resultText : '',
mbtiTime: r.mbti ? r.mbti.testTime : '',
discTime: r.disc ? r.disc.testTime : '',
pdpTime: r.pdp ? r.pdp.testTime : '',
aiTime: r.ai ? r.ai.testTime : '',
mbtiResultId: r.mbti ? r.mbti.id : null,
discResultId: r.disc ? r.disc.id : null,
pdpResultId: r.pdp ? r.pdp.id : null,
aiResultId: r.ai ? r.ai.id : null,
})
},
fail: () => {
this._loadRecentFromStorage()
}
})
},
/** 从 /api/distribution/stats 拉取推广中心统计 */
_loadPromoStats() {
request({
url: '/api/distribution/stats',
method: 'GET',
success: (res) => {
const payload = res && res.data
if (payload && payload.code === 200 && payload.data) {
const d = payload.data
this.setData({
promoDistributionEnabled: d.distributionEnabled !== false,
promoCenterTitle: d.promoCenterTitle || '推广中心',
promoTotalInvite: d.totalInvite || 0,
promoTotalEarned: d.totalEarned || '0.00',
promoWithdrawable: d.walletBalance || '0.00'
})
}
}
})
},
/** 降级:从 localStorage 读最近记录(兼容离线或 API 失败) */
_loadRecentFromStorage() {
const mbtiResult = wx.getStorageSync('mbtiResult')
const discResult = wx.getStorageSync('discResult')
const pdpResult = wx.getStorageSync('pdpResult')
const aiResult = wx.getStorageSync('aiResult')
let testCount = 0
if (mbtiResult) testCount++
if (discResult) testCount++
if (pdpResult) testCount++
if (aiResult) testCount++
const _fmt = (ts) => {
if (!ts) return ''
const d = new Date(typeof ts === 'number' && ts < 1e12 ? ts * 1000 : ts)
if (isNaN(d.getTime())) return ''
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}
this.setData({
testCount,
hasResults: testCount > 0,
mbtiType: mbtiResult ? getTypeOnly(mbtiResult, 'mbti') : '',
discType: discResult ? getTypeOnly(discResult, 'disc') : '',
pdpType: pdpResult ? getTypeOnly(pdpResult, 'pdp') : '',
aiType: aiResult ? (aiResult.mbtiType || aiResult.type || '') : '',
mbtiTime: _fmt(mbtiResult && (mbtiResult.createdAt || mbtiResult.timestamp || mbtiResult.testTime)),
discTime: _fmt(discResult && (discResult.createdAt || discResult.timestamp || discResult.testTime)),
pdpTime: _fmt(pdpResult && (pdpResult.createdAt || pdpResult.timestamp || pdpResult.testTime)),
aiTime: _fmt(aiResult && (aiResult.createdAt || aiResult.timestamp || aiResult.testTime)),
mbtiResultId: null,
discResultId: null,
pdpResultId: null,
aiResultId: null,
})
},
/** 点击登录:先静默登录拿到 token */
doLogin() {
this.setData({ loginLoading: true, loginFailed: false })
app.ensureLogin().then((ok) => {
this.setData({ loginLoading: false })
if (ok) {
this.loadData()
} else {
wx.showToast({ title: '登录失败,请检查网络或稍后重试', icon: 'none' })
this.setData({ loginFailed: true })
}
}).catch(() => {
this.setData({ loginLoading: false, loginFailed: true })
wx.showToast({ title: '登录失败', icon: 'none' })
})
},
goToIndex() { wx.switchTab({ url: '/pages/index/index' }) },
goToCamera() { wx.switchTab({ url: '/pages/index/camera' }) },
goToHistory() { wx.navigateTo({ url: '/pages/history/index' }) },
goToUserProfile() { wx.navigateTo({ url: '/pages/user-profile/index' }) },
goToPurchase() { wx.navigateTo({ url: '/pages/purchase/index?tab=personal' }) },
goToPurchasePersonal() { wx.navigateTo({ url: '/pages/purchase/index?tab=personal' }) },
goToPurchaseEnterprise() { wx.navigateTo({ url: '/pages/purchase/index?tab=enterprise' }) },
goToEnterprise() { wx.navigateTo({ url: '/pages/enterprise/index' }) },
goToPromo() { wx.navigateTo({ url: '/pages/promo/index' }) },
goToMyResume() { wx.navigateTo({ url: '/pages/enterprise/resume-history' }) },
goToSettings() {
wx.showToast({ title: '开发中', icon: 'none' })
},
shareApp() {
// 触发分享
},
viewMBTI() {
const id = this.data.mbtiResultId
wx.navigateTo({ url: id ? `/pages/result/mbti?id=${id}&type=mbti` : '/pages/result/mbti' })
},
viewDISC() {
const id = this.data.discResultId
wx.navigateTo({ url: id ? `/pages/result/disc?id=${id}&type=disc` : '/pages/result/disc' })
},
viewPDP() {
const id = this.data.pdpResultId
wx.navigateTo({ url: id ? `/pages/result/pdp?id=${id}&type=pdp` : '/pages/result/pdp' })
},
viewAI() {
const id = this.data.aiResultId
wx.navigateTo({ url: id ? `/pages/index/result?id=${id}&type=ai` : '/pages/index/result' })
},
logout() {
wx.showModal({
title: '确认退出',
content: '退出后测试记录仍会保留',
success: (res) => {
if (res.confirm) {
app.logout()
this.setData({ hasLogin: false, userInfo: null, loginFailed: false })
}
}
})
},
onShareAppMessage() {
const { getSharePathByScope } = require('../../utils/share')
return {
title: '神仙团队AI性格测试 - 发现你的MBTI类型',
path: getSharePathByScope('/pages/index/index')
}
},
onShareTimeline() {
const { buildShareQuery } = require('../../utils/share')
return {
title: '神仙团队AI性格测试 - 发现你的MBTI类型',
query: buildShareQuery()
}
}
})

View File

@@ -0,0 +1,6 @@
{
"navigationBarTitleText": "",
"navigationBarBackgroundColor": "#F9FAFB",
"navigationBarTextStyle": "black",
"usingComponents": {}
}

View File

@@ -0,0 +1,212 @@
<!--pages/profile/index.wxml-->
<view class="page">
<!-- 顶部栏 -->
<view class="topbar">
<text class="topbar-title">我的</text>
<view class="topbar-actions">
<!-- <view class="settings-btn" bindtap="goToSettings">
<text class="settings-icon">⚙️</text>
</view> -->
</view>
</view>
<!-- 未登录 -->
<view class="user-card" wx:if="{{!hasLogin}}" bindtap="doLogin">
<view class="avatar-ring">
<view class="avatar-letter-wrap" style="background:{{avatarBgColor}}">
<text class="avatar-letter">{{avatarLetter}}</text>
</view>
</view>
<view class="user-meta">
<text class="user-name" wx:if="{{loginLoading}}">登录中...</text>
<text class="user-name" wx:elif="{{loginFailed}}">登录失败,点击重试</text>
<text class="user-name" wx:else>点击登录</text>
<text class="user-sub">登录后查看你的测试结果</text>
</view>
<text class="chevron"></text>
</view>
<!-- 已登录:用户卡片(点击进入个人资料) -->
<view class="user-card" wx:if="{{hasLogin}}" bindtap="goToUserProfile">
<!-- 头像区 -->
<view class="avatar-wrap">
<view class="avatar-ring">
<image wx:if="{{userInfo && (userInfo.avatarUrl || userInfo.avatar)}}"
class="avatar-img"
src="{{userInfo.avatarUrl || userInfo.avatar}}"
mode="aspectFill"/>
<view wx:else class="avatar-letter-wrap" style="background:{{avatarBgColor}}">
<text class="avatar-letter">{{avatarLetter}}</text>
</view>
</view>
<view class="online-dot"></view>
</view>
<!-- 昵称 + 标签 -->
<view class="user-meta">
<text class="nickname-text">{{nicknameDisplay || '点击设置昵称'}}</text>
<!-- 类型标签 -->
<scroll-view scroll-x class="tags-scroll">
<view class="tags-row">
<view class="tag tag-purple" wx:if="{{mbtiType}}">
<text class="tag-text">{{mbtiType}}</text>
</view>
<view class="tag tag-blue" wx:if="{{discType}}">
<text class="tag-text">{{discType}}型</text>
</view>
<view class="tag tag-orange" wx:if="{{pdpType}}">
<text class="tag-text">{{pdpType}}</text>
</view>
<view class="tag tag-gray" wx:if="{{!mbtiType && !discType && !pdpType}}">
<text class="tag-text">暂无测试记录</text>
</view>
</view>
</scroll-view>
</view>
<text class="chevron"></text>
</view>
<!-- 最新测试卡片(横向滚动) -->
<view class="section" wx:if="{{hasLogin && hasResults}}">
<view class="section-header">
<text class="section-title">最新测试</text>
<view class="section-link" bindtap="goToHistory">
<text class="section-link-text">查看全部</text>
</view>
</view>
<scroll-view scroll-x class="cards-scroll" enhanced show-scrollbar="{{false}}">
<view class="cards-row">
<!-- MBTI -->
<view class="result-card card-purple" wx:if="{{mbtiType}}" bindtap="viewMBTI">
<view class="card-deco"></view>
<view class="card-icon-wrap card-icon-purple">
<text class="card-icon">🧠</text>
</view>
<text class="card-label">MBTI性格</text>
<text class="card-value">{{mbtiType}}</text>
<text class="card-time">{{mbtiTime}}</text>
</view>
<!-- DISC -->
<view class="result-card card-blue" wx:if="{{discType}}" bindtap="viewDISC">
<view class="card-deco"></view>
<view class="card-icon-wrap card-icon-blue">
<text class="card-icon">📊</text>
</view>
<text class="card-label">DISC测评</text>
<text class="card-value">{{discType}}型</text>
<text class="card-time">{{discTime}}</text>
</view>
<!-- PDP -->
<view class="result-card card-orange" wx:if="{{pdpType}}" bindtap="viewPDP">
<view class="card-deco"></view>
<view class="card-icon-wrap card-icon-orange">
<text class="card-icon">🦁</text>
</view>
<text class="card-label">PDP行为</text>
<text class="card-value">{{pdpType}}</text>
<text class="card-time">{{pdpTime}}</text>
</view>
<!-- AI 面相 -->
<view class="result-card card-rose" wx:if="{{aiType}}" bindtap="viewAI">
<view class="card-deco"></view>
<view class="card-icon-wrap card-icon-rose">
<text class="card-icon">👁️</text>
</view>
<text class="card-label">面相分析</text>
<text class="card-value">{{aiType}}</text>
<text class="card-time">{{aiTime}}</text>
</view>
</view>
</scroll-view>
</view>
<!-- 推广中心(根据管理端开关显示/隐藏,标题可配置) -->
<view class="section px-section" wx:if="{{hasLogin && promoDistributionEnabled}}">
<view class="promo-card" bindtap="goToPromo">
<view class="promo-header">
<view class="promo-title-wrap">
<text class="promo-icon">📈</text>
<text class="promo-title">{{promoCenterTitle}}</text>
</view>
</view>
<view class="promo-stats">
<view class="promo-stat-item">
<text class="promo-stat-label">邀请好友</text>
<text class="promo-stat-value">{{promoTotalInvite}}</text>
</view>
<view class="promo-stat-divider"></view>
<view class="promo-stat-item">
<text class="promo-stat-label">累计收益</text>
<text class="promo-stat-value">¥{{promoTotalEarned}}</text>
</view>
<view class="promo-stat-divider"></view>
<view class="promo-stat-item">
<text class="promo-stat-label">可提现</text>
<text class="promo-stat-value promo-highlight">¥{{promoWithdrawable}}</text>
</view>
</view>
</view>
</view>
<!-- 服务菜单 -->
<view class="section px-section">
<text class="section-title">深度服务</text>
<view class="menu-card">
<!-- 专业报告:跳到个人版开通页面 -->
<view class="menu-item" bindtap="goToPurchasePersonal">
<view class="menu-icon-wrap menu-icon-red">
<text class="menu-icon">📄</text>
</view>
<view class="menu-content">
<text class="menu-title">专业报告</text>
<text class="menu-sub">解锁完整的深度性格解析</text>
</view>
<text class="menu-chevron"></text>
</view>
<view class="menu-divider"></view>
<!-- 企业版服务:跳到企业版开通页面 -->
<view class="menu-item" bindtap="goToPurchaseEnterprise">
<view class="menu-icon-wrap menu-icon-indigo">
<text class="menu-icon">🏢</text>
</view>
<view class="menu-content">
<text class="menu-title">企业版服务</text>
<text class="menu-sub">团队测评与人才管理方案</text>
</view>
<text class="menu-chevron"></text>
</view>
<view class="menu-divider"></view>
<!-- 测试历史:仍然进入历史记录页面 -->
<view class="menu-item" bindtap="goToHistory">
<view class="menu-icon-wrap menu-icon-amber">
<text class="menu-icon">🕒</text>
</view>
<view class="menu-content">
<text class="menu-title">测试历史</text>
<text class="menu-sub">{{testCount > 0 ? testCount + '条记录' : '查看过往所有测试记录'}}</text>
</view>
<text class="menu-chevron"></text>
</view>
<view class="menu-divider" wx:if="{{hasEnterprise}}"></view>
<!-- 我的简历:仅绑定企业的用户可见,展示当前企业下的简历并可设默认 -->
<view class="menu-item" wx:if="{{hasEnterprise}}" bindtap="goToMyResume">
<view class="menu-icon-wrap menu-icon-indigo">
<text class="menu-icon">📋</text>
</view>
<view class="menu-content">
<text class="menu-title">我的简历</text>
<text class="menu-sub">查看与设置默认简历</text>
</view>
<text class="menu-chevron"></text>
</view>
</view>
</view>
<view class="bottom-safe"></view>
</view>

View File

@@ -0,0 +1,534 @@
/* pages/profile/index.wxss */
page {
background-color: #F9FAFB;
}
.page {
min-height: 100vh;
background-color: #F9FAFB;
padding-bottom: 140rpx;
}
/* ===== 顶部栏 ===== */
.topbar {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16rpx 40rpx 24rpx;
background: #F9FAFB;
}
.topbar-title {
font-size: 48rpx;
font-weight: 700;
color: #111827;
letter-spacing: -0.5rpx;
}
.topbar-actions {
display: flex;
gap: 12rpx;
align-items: center;
}
.settings-btn {
width: 72rpx;
height: 72rpx;
display: flex;
align-items: center;
justify-content: center;
}
.settings-icon {
font-size: 44rpx;
color: #4B5563;
}
.icon-btn {
width: 72rpx;
height: 72rpx;
border-radius: 50%;
background: transparent;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
border: none;
}
.icon-btn::after {
border: none;
}
.icon-text {
font-size: 36rpx;
}
/* ===== 用户卡片 ===== */
.user-card {
margin: 0 32rpx 28rpx;
background: #FFFFFF;
border-radius: 28rpx;
padding: 36rpx 32rpx;
display: flex;
align-items: center;
gap: 20rpx;
box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.05);
position: relative;
}
.user-card:active {
opacity: 0.9;
}
/* 头像区域(已登录,仅展示) */
.avatar-wrap {
position: relative;
width: 144rpx;
height: 144rpx;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
}
.avatar-ring {
width: 144rpx;
height: 144rpx;
border-radius: 50%;
padding: 4rpx;
background: linear-gradient(135deg, #FFDEE9 0%, #B5FFFC 100%);
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
box-shadow: inset 0 2rpx 10rpx rgba(0,0,0,0.05);
box-sizing: border-box;
}
.avatar-img {
width: 128rpx;
height: 128rpx;
border-radius: 50%;
border: 4rpx solid #FFFFFF;
display: block;
}
.avatar-letter-wrap {
width: 128rpx;
height: 128rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
border: 4rpx solid #FFFFFF;
}
.avatar-letter {
font-size: 64rpx;
font-weight: 700;
color: #FFFFFF;
}
.online-dot {
position: absolute;
bottom: 8rpx;
right: 8rpx;
width: 28rpx;
height: 28rpx;
background: #22C55E;
border-radius: 50%;
border: 4rpx solid #FFFFFF;
z-index: 10;
pointer-events: none;
}
/* 用户信息 */
.user-meta {
flex: 1;
min-width: 0;
display: flex;
flex-direction: column;
justify-content: center;
gap: 8rpx;
padding-left: 12rpx;
}
.user-name {
font-size: 36rpx;
font-weight: 700;
color: #111827;
display: block;
}
.user-sub {
font-size: 24rpx;
color: #9CA3AF;
}
.nickname-text {
font-size: 36rpx;
font-weight: 700;
color: #111827;
line-height: 1.3;
display: block;
}
/* 类型标签 */
.tags-scroll {
width: 100%;
white-space: nowrap;
}
.tags-row {
display: flex;
gap: 10rpx;
flex-wrap: nowrap;
}
.tag {
display: inline-flex;
align-items: center;
padding: 6rpx 16rpx;
border-radius: 10rpx;
flex-shrink: 0;
}
.tag-text {
font-size: 20rpx;
font-weight: 600;
white-space: nowrap;
}
.tag-purple {
background: #EDE9FE;
}
.tag-purple .tag-text {
color: #7C3AED;
}
.tag-blue {
background: #DBEAFE;
}
.tag-blue .tag-text {
color: #2563EB;
}
.tag-orange {
background: #FEF3C7;
}
.tag-orange .tag-text {
color: #D97706;
}
.tag-gray {
background: #F3F4F6;
}
.tag-gray .tag-text {
color: #9CA3AF;
}
.chevron {
font-size: 48rpx;
color: #D1D5DB;
flex-shrink: 0;
}
/* ===== Section ===== */
.section {
margin-bottom: 28rpx;
}
.px-section {
padding: 0 32rpx;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
margin-bottom: 20rpx;
}
.section-title {
font-size: 32rpx;
font-weight: 700;
color: #1F2937;
display: block;
margin-bottom: 20rpx;
}
.section-header .section-title {
margin-bottom: 0;
}
.section-link {
display: flex;
align-items: center;
}
.section-link-text {
font-size: 24rpx;
color: #F43F5E;
font-weight: 500;
}
/* ===== 测试结果卡片(横向滚动) ===== */
.cards-scroll {
width: 100%;
white-space: nowrap;
}
.cards-row {
display: flex;
gap: 24rpx;
padding: 8rpx 32rpx 16rpx;
width: max-content;
}
.result-card {
width: 280rpx;
height: 240rpx;
border-radius: 24rpx;
padding: 32rpx 28rpx 24rpx;
display: flex;
flex-direction: column;
justify-content: space-between;
position: relative;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.06);
border: 1rpx solid rgba(0,0,0,0.04);
background: #FFFFFF;
flex-shrink: 0;
}
.card-deco {
position: absolute;
top: -24rpx;
right: -24rpx;
width: 120rpx;
height: 120rpx;
border-radius: 50%;
opacity: 0.3;
}
.card-purple .card-deco { background: #DDD6FE; }
.card-blue .card-deco { background: #BFDBFE; }
.card-orange .card-deco { background: #FDE68A; }
.card-rose .card-deco { background: #FECDD3; }
.card-icon-wrap {
width: 64rpx;
height: 64rpx;
border-radius: 16rpx;
display: flex;
align-items: center;
justify-content: center;
}
.card-icon-purple { background: #EDE9FE; }
.card-icon-blue { background: #DBEAFE; }
.card-icon-orange { background: #FEF3C7; }
.card-icon-rose { background: #FFE4E6; }
.card-icon {
font-size: 32rpx;
}
.card-label {
font-size: 22rpx;
color: #9CA3AF;
font-weight: 500;
}
.card-value {
font-size: 44rpx;
font-weight: 800;
letter-spacing: -1rpx;
}
.card-purple .card-value { color: #7C3AED; }
.card-blue .card-value { color: #2563EB; }
.card-orange .card-value { color: #D97706; }
.card-rose .card-value { color: #E11D48; }
.card-time {
font-size: 20rpx;
color: #D1D5DB;
}
.card-time {
font-size: 20rpx;
color: #D1D5DB;
}
/* ===== 推广中心 ===== */
.promo-card {
background: #FFFFFF;
border-radius: 32rpx;
padding: 40rpx;
box-shadow: 0 4rpx 24rpx rgba(0,0,0,0.05);
border: 1rpx solid rgba(0,0,0,0.04);
}
.promo-header {
display: flex;
align-items: center;
margin-bottom: 40rpx;
}
.promo-title-wrap {
display: flex;
align-items: center;
gap: 12rpx;
}
.promo-icon {
font-size: 32rpx;
}
.promo-title {
font-size: 32rpx;
font-weight: 700;
color: #1F2937;
}
.promo-stats {
display: flex;
justify-content: space-between;
align-items: center;
}
.promo-stat-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
}
.promo-stat-label {
font-size: 24rpx;
color: #9CA3AF;
}
.promo-stat-value {
font-size: 36rpx;
font-weight: 700;
color: #111827;
font-family: "DIN Alternate", "Courier New", Courier, monospace;
}
.promo-stat-divider {
width: 1rpx;
height: 60rpx;
background: #F3F4F6;
}
.promo-highlight {
color: #F43F5E;
}
/* ===== 服务菜单 ===== */
.menu-card {
background: #FFFFFF;
border-radius: 24rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0,0,0,0.05);
border: 1rpx solid rgba(0,0,0,0.04);
}
.menu-item {
display: flex;
align-items: center;
padding: 32rpx 36rpx;
gap: 28rpx;
position: relative;
}
.menu-item:active {
background: #F9FAFB;
}
.menu-divider {
height: 1rpx;
background: #F3F4F6;
margin: 0 36rpx;
}
.menu-icon-wrap {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.menu-icon-red { background: #FEF2F2; }
.menu-icon-indigo { background: #EEF2FF; }
.menu-icon-amber { background: #FFFBEB; }
.menu-icon-purple { background: #EDE9FE; }
.menu-icon {
font-size: 36rpx;
}
.menu-content {
flex: 1;
min-width: 0;
}
.menu-title {
font-size: 30rpx;
font-weight: 600;
color: #111827;
display: block;
margin-bottom: 6rpx;
}
.menu-sub {
font-size: 24rpx;
color: #9CA3AF;
display: block;
}
.menu-chevron {
font-size: 48rpx;
color: #D1D5DB;
flex-shrink: 0;
}
/* ===== 退出登录按钮 ===== */
.logout-btn {
width: 100%;
padding: 32rpx;
background: #FFF1F2;
border: 1rpx solid #FFE4E6;
border-radius: 24rpx;
display: flex;
align-items: center;
justify-content: center;
gap: 16rpx;
box-sizing: border-box;
}
.logout-btn:active {
opacity: 0.8;
}
.logout-icon {
font-size: 32rpx;
}
.logout-text {
font-size: 30rpx;
font-weight: 600;
color: #EF4444;
}
.bottom-safe {
height: 40rpx;
}