fix: 仪表盘与用户管理、分析/测试接口、双端结果页与个人中心展示调整

- admin:Dashboard/Users 与超管 Users 联动后端统计
- api:Dashboard、Analyze、Test、超管 AppUser 逻辑更新
- 微信/抖音小程序:result、profile 页面与样式
- 新增 admin 公共组件与需求文档截图

Made-with: Cursor
This commit is contained in:
卡若
2026-03-27 18:26:03 +08:00
parent aca2e263bb
commit 68c73f6ca9
23 changed files with 2706 additions and 437 deletions

View File

@@ -2,6 +2,47 @@
const app = getApp()
const payment = require('../../utils/payment')
const { hasPhone, bindPhoneByCode, isProfileComplete } = require('../../utils/phoneAuth.js')
const { mbtiDescriptions } = require('../../utils/descriptions')
function buildSceneFallback(baseResult) {
const mbti = baseResult.mbti || ''
const d = mbtiDescriptions[mbti] || {}
const jobs = (d.careers || []).slice(0, 2).join('、') || '专业岗位'
const name = d.name || baseResult.title || ''
return {
careerDevelopment: `结合 ${mbti}${name ? '' + name + '' : ''} 特质,${jobs} 等与系统性、长期主义更契合的路径往往更容易做出成绩;建议前 13 年夯实基本功与协作习惯,再向骨干或专家角色过渡。`,
familyParenting: '家庭互动中可减少「对错评判」、增加情感确认;给孩子清晰边界的同时也留出讨论与试错空间,更利于信任感与自驱力。',
partnerCofounder: '寻找合伙人时建议重点考察责任感与信息透明度,角色分工、决策机制与退出规则尽量书面化;互补型搭档通常比同特质堆叠更有效。'
}
}
/** 仅在后端已下发完整面相等字段时合并;预览脱敏接口不下发,避免未付费用户看到正文 */
function mergeSceneBlocks(apiData, baseResult) {
const trim = (s) => (s && String(s).trim()) || ''
const apiC = trim(apiData.careerDevelopment)
const apiF = trim(apiData.familyParenting)
const apiP = trim(apiData.partnerCofounder)
const fa = apiData.faceAnalysis
const bone = apiData.boneAnalysis
const rel = typeof apiData.relationship === 'string' ? apiData.relationship.trim() : ''
const hasUnlockedReport =
rel.length > 8 ||
(typeof fa === 'string' && fa.length > 40) ||
(fa && typeof fa === 'object' && !Array.isArray(fa)) ||
(typeof apiData.faceAnalysisText === 'string' && apiData.faceAnalysisText.length > 40) ||
(typeof bone === 'string' && bone.length > 40) ||
(bone && typeof bone === 'object' && !Array.isArray(bone)) ||
(typeof apiData.boneAnalysisText === 'string' && apiData.boneAnalysisText.length > 40)
if (!hasUnlockedReport) {
return { careerDevelopment: '', familyParenting: '', partnerCofounder: '' }
}
const fb = buildSceneFallback(baseResult)
return {
careerDevelopment: apiC || fb.careerDevelopment,
familyParenting: apiF || fb.familyParenting,
partnerCofounder: apiP || fb.partnerCofounder
}
}
const { getEnterpriseIdForApiPayload } = require('../../utils/enterpriseContext.js')
Page({
@@ -43,7 +84,10 @@ Page({
portrait: null,
hrView: null,
bossView: null,
resumeHighlights: ''
resumeHighlights: '',
careerDevelopment: '',
familyParenting: '',
partnerCofounder: ''
},
// 当前这次AI分析对应的测试记录IDmbti_test_results.id
testResultId: null,
@@ -237,7 +281,7 @@ Page({
// 处理API返回结果
processResult(apiData) {
const result = {
const base = {
mbti: apiData.mbti?.type || '',
title: apiData.mbti?.title || '',
summary: apiData.personalitySummary || apiData.overview || '',
@@ -251,7 +295,12 @@ Page({
boneAnalysisText: typeof apiData.boneAnalysis === 'string' ? apiData.boneAnalysis : '',
careers: Array.isArray(apiData.careers) ? apiData.careers : [],
relationship: apiData.relationship || '',
gallupTop3: Array.isArray(apiData.gallupTop3) ? apiData.gallupTop3 : [],
gallupTop3: Array.isArray(apiData.gallupTop3) ? apiData.gallupTop3 : []
}
const scene = mergeSceneBlocks(apiData, base)
const result = {
...base,
...scene,
faceAnalysis: null,
boneAnalysis: null,
portrait: apiData.portrait || null,
@@ -282,17 +331,21 @@ Page({
// 优先使用后端 /api/analyze 返回的价格信息,避免二次请求
if (apiData._payment) {
const p = apiData._payment || {}
let amountYuan = typeof p.amountYuan === 'number'
? p.amountYuan
: (p.amountFen ? (p.amountFen / 100) : 0)
if (p.requiresPayment && amountYuan <= 0) amountYuan = 1
this.setData({
payInfo: {
requiresPayment: !!p.requiresPayment,
isPaid: false,
amountYuan: typeof p.amountYuan === 'number'
? p.amountYuan
: (p.amountFen ? (p.amountFen / 100) : 0)
amountYuan
}
})
} else {
// 拍照直连分析未带 _payment 时,拉 runtime 定价(与历史详情入口互不覆盖)
this.initPayInfoFromRuntime()
}
// 无 _payment 时(如从历史详情进入)不在这里调 initPayInfoFromRuntime避免异步 getRuntimeConfig 后覆盖详情接口返回的 needPaymentToUnlock由调用方用 detail 的 payload 单独设置 payInfo
},
@@ -322,6 +375,7 @@ Page({
isPaid: !!detailPayload.isPaid,
amountYuan: needPay ? amountYuan : 0
},
hasPhone: hasPhone(),
isProfileComplete: isProfileComplete()
})
return
@@ -336,18 +390,25 @@ Page({
: Number(facePriceRaw || 0)
const requiresByConfig = !!(reportRequires && reportRequires.face)
const requiresPayment =
let requiresPayment =
typeof recordRequires === 'boolean' ? recordRequires : requiresByConfig
// 系统设置需付款但金额为0 则直接可查看,不展示付费墙
const needPay = requiresPayment && facePrice > 0
const amountYuan = facePrice > 0 ? facePrice : 0
let amountYuan = facePrice > 0 ? facePrice : 0
let needPay = requiresPayment && amountYuan > 0
// 后台开启人脸报告付费但未配有效单价时,按 ¥1 展示并走支付
if (requiresByConfig && !needPay) {
amountYuan = 1
needPay = true
requiresPayment = true
}
this.setData({
payInfo: {
requiresPayment: needPay,
isPaid: !!recordIsPaid,
amountYuan
}
amountYuan: needPay ? amountYuan : 0
},
hasPhone: hasPhone(),
isProfileComplete: isProfileComplete()
})
})
.catch(() => {
@@ -356,7 +417,9 @@ Page({
requiresPayment: false,
isPaid: !!recordIsPaid,
amountYuan: 0
}
},
hasPhone: hasPhone(),
isProfileComplete: isProfileComplete()
})
})
},
@@ -365,10 +428,14 @@ Page({
wx.navigateTo({ url: '/pages/user-profile/index' })
},
// 解锁完整报告:发起人脸测试付费
/** 付费墙主按钮:先支付,支付后再引导手机号与资料 */
onTapUnlockPay() {
this.unlockFullReport()
},
// 解锁完整报告:发起人脸测试付费(不要求先完善资料)
unlockFullReport() {
const { payInfo, testResultId, hasReloadedAfterPay, isProfileComplete } = this.data
if (!isProfileComplete) return
const { payInfo, testResultId, hasReloadedAfterPay } = this.data
if (!payInfo.requiresPayment || payInfo.isPaid) return
app.ensureLogin().then((logged) => {
@@ -381,12 +448,12 @@ Page({
success: () => {
wx.showToast({ title: '已解锁完整报告', icon: 'success' })
// 本地先标记已付费,避免按钮仍然提示“需要解锁”
this.setData({
'payInfo.isPaid': true
'payInfo.isPaid': true,
hasPhone: hasPhone(),
isProfileComplete: isProfileComplete()
})
// 避免重复触发刷新:只在还没刷新的情况下,延迟 0.5s 拉一次详情
if (testResultId && !hasReloadedAfterPay) {
this.setData({ hasReloadedAfterPay: true })
setTimeout(() => {
@@ -394,14 +461,31 @@ Page({
}, 500)
}
},
fail: () => {
// 支付失败或取消,这里不做额外处理
}
fail: () => {}
})
})
},
// AI结果页付费按钮就地触发微信手机号授权然后调用 unlockFullReport
/** 支付成功后:仅绑定手机号,不重复发起支付 */
onPostPayBindPhone(e) {
const { code, errMsg } = e.detail || {}
if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) {
wx.showToast({ title: '需要手机号以便保存报告', icon: 'none' })
return
}
if (!code) {
wx.showToast({ title: '获取手机号失败', icon: 'none' })
return
}
bindPhoneByCode(code)
.then(() => {
this.setData({ hasPhone: true, isProfileComplete: isProfileComplete() })
wx.showToast({ title: '已绑定手机号', icon: 'success' })
})
.catch(() => {})
},
// 兼容旧版:付费前绑手机(现流程已改为先付费,此方法可不再使用)
onGetPhoneNumberForFacePay(e) {
const { code, errMsg } = e.detail || {}
if (errMsg && errMsg.indexOf('getPhoneNumber:fail') === 0) {

View File

@@ -44,6 +44,21 @@
<text class="status-text">{{hasError ? '分析出现异常' : '分析完成,以下是您的个性化报告'}}</text>
</view>
<!-- 支付成功后:引导手机号 → 完善资料(不挡报告阅读) -->
<view class="post-pay-guide" wx:if="{{!hasError && payInfo.isPaid && (!hasPhone || !isProfileComplete)}}">
<text class="post-pay-guide-title">再完成两步,报告更好归属到您的账号</text>
<text class="post-pay-guide-desc">① 授权手机号 ② 完善头像与昵称(「我的」- 个人资料)</text>
<view class="post-pay-guide-actions">
<button
wx:if="{{!hasPhone}}"
class="post-pay-guide-btn phone"
open-type="getPhoneNumber"
bindgetphonenumber="onPostPayBindPhone"
>授权手机号</button>
<view wx:if="{{hasPhone && !isProfileComplete}}" class="post-pay-guide-btn profile" bindtap="goToCompleteProfile">去完善资料</view>
</view>
</view>
<!-- 错误内容 -->
<view class="error-content" wx:if="{{hasError}}">
<text class="error-title">错误信息</text>
@@ -56,23 +71,30 @@
<!-- 报告预览区:未解锁时整区加盖高遮罩,避免概述/盖洛普等全文外露 -->
<view class="report-gate-wrap" wx:if="{{!hasError}}">
<view
class="card personality-card {{(!isProfileComplete || (isProfileComplete && payInfo.requiresPayment && !payInfo.isPaid)) ? 'personality-card--locked' : ''}}"
class="card personality-card {{(payInfo.requiresPayment && !payInfo.isPaid) ? 'personality-card--locked' : ''}}"
>
<text class="card-title">性格类型分析</text>
<text class="personality-card-sub">MBTI · PDP · DISC 联合速览</text>
<view class="personality-type">
<text class="type-code">{{result.mbti}}</text>
<text class="type-name">{{result.title}}</text>
<view class="type-chip-row" wx:if="{{result.pdp || result.disc}}">
<text class="type-chip" wx:if="{{result.pdp}}">{{result.pdpEmoji}}{{result.pdp}}</text>
<text class="type-chip type-chip--disc" wx:if="{{result.disc}}">DISC {{result.disc}}</text>
</view>
<text class="type-desc">{{result.summary}}</text>
</view>
<view class="type-details">
<view class="type-box pdp-box">
<text class="type-emoji" wx:if="{{result.pdpEmoji}}">{{result.pdpEmoji}}</text>
<text class="type-label">PDP主性格</text>
<text class="type-value">{{result.pdp || '--'}}</text>
<text class="type-sub" wx:if="{{result.pdpAux}}">辅助: {{result.pdpAux}}</text>
</view>
<view class="type-box disc-box">
<text class="type-emoji type-emoji--disc">◎</text>
<text class="type-label">DISC类型</text>
<text class="type-value">{{result.disc || '--'}}型</text>
<text class="type-sub" wx:if="{{result.discAux}}">辅助: {{result.discAux}}</text>
@@ -95,7 +117,7 @@
</view>
<view
class="card report-gate-teaser {{(!isProfileComplete || (isProfileComplete && payInfo.requiresPayment && !payInfo.isPaid)) ? 'report-gate-teaser--locked' : ''}}"
class="card report-gate-teaser {{(payInfo.requiresPayment && !payInfo.isPaid) ? 'report-gate-teaser--locked' : ''}}"
wx:if="{{result.summary}}"
>
<text class="card-title">性格概述</text>
@@ -103,7 +125,7 @@
</view>
<view
class="card gallup-card report-gate-teaser {{(!isProfileComplete || (isProfileComplete && payInfo.requiresPayment && !payInfo.isPaid)) ? 'report-gate-teaser--locked' : ''}}"
class="card gallup-card report-gate-teaser {{(payInfo.requiresPayment && !payInfo.isPaid) ? 'report-gate-teaser--locked' : ''}}"
wx:if="{{result.gallupTop3.length > 0}}"
>
<text class="card-title">盖洛普前三大优势</text>
@@ -115,51 +137,67 @@
</view>
</view>
<!-- 未解锁:三大生活场景仅标题 + 占位行(正文付费后见) -->
<view class="card scene-teaser-bundle" wx:if="{{payInfo.requiresPayment && !payInfo.isPaid}}">
<text class="scene-bundle-hint">以下版块解锁后展示完整正文</text>
<view class="scene-teaser-item">
<text class="scene-teaser-title">职业发展方向</text>
<view class="scene-teaser-lines">
<view class="scene-teaser-line"></view>
<view class="scene-teaser-line scene-teaser-line--short"></view>
</view>
</view>
<view class="scene-teaser-item">
<text class="scene-teaser-title">家庭亲子关系</text>
<view class="scene-teaser-lines">
<view class="scene-teaser-line"></view>
<view class="scene-teaser-line scene-teaser-line--short"></view>
</view>
</view>
<view class="scene-teaser-item scene-teaser-item--last">
<text class="scene-teaser-title">寻找合伙人</text>
<view class="scene-teaser-lines">
<view class="scene-teaser-line"></view>
<view class="scene-teaser-line scene-teaser-line--short"></view>
</view>
</view>
</view>
<!-- 全幅遮罩 + 底部操作(覆盖性格概述、盖洛普等主要预览,仅留顶部少量「悬念」) -->
<view
class="report-gate-overlay"
wx:if="{{!isProfileComplete || (isProfileComplete && payInfo.requiresPayment && !payInfo.isPaid)}}"
wx:if="{{payInfo.requiresPayment && !payInfo.isPaid}}"
>
<view class="report-gate-gradient"></view>
<!-- 底部白区内:标题 + 说明 + 按钮作为一组上下居中 -->
<view class="report-gate-cta-wrap">
<view class="report-gate-panel">
<text class="paywall-fake-title">完整版性格深度解析</text>
<text class="paywall-tip">解锁后将展示完整报告内容</text>
<view
class="paywall-btn paywall-btn-inline"
wx:if="{{!isProfileComplete}}"
bindtap="goToCompleteProfile"
>
<text class="paywall-btn-main">完善资料</text>
<text class="paywall-fake-title">完整版深度解析</text>
<text class="paywall-tip">含面相、骨相、人际与职业 / 家庭 / 合伙三大场景</text>
<view class="paywall-btn paywall-btn-inline paywall-btn--compact" bindtap="onTapUnlockPay">
<text class="paywall-btn-main">{{payInfo.amountYuan || 1}} 元解锁</text>
</view>
<block wx:elif="{{isProfileComplete && payInfo.requiresPayment && !payInfo.isPaid}}">
<button
class="paywall-btn paywall-btn-inline"
wx:if="{{!hasPhone}}"
open-type="getPhoneNumber"
bindgetphonenumber="onGetPhoneNumberForFacePay"
>
<text class="paywall-btn-main">解锁完整报告</text>
<text class="paywall-btn-price">¥{{payInfo.amountYuan || 0}} / 次</text>
</button>
<view
class="paywall-btn paywall-btn-inline"
wx:elif="{{hasPhone}}"
bindtap="unlockFullReport"
>
<text class="paywall-btn-main">解锁完整报告</text>
<text class="paywall-btn-price">¥{{payInfo.amountYuan || 0}} / 次</text>
</view>
</block>
<text class="paywall-tip paywall-tip-panel" wx:if="{{isProfileComplete}}">一次性解锁本次{{reportTitle || '分析报告'}},永久保存在「历史记录」中</text>
<text class="paywall-tip paywall-tip-panel">解锁后可在「历史记录」再次查看;绑定手机与资料见上方绿色提示条</text>
</view>
</view>
</view>
</view>
<!-- ========== 详细分析区(解锁后可见) ========== -->
<block wx:if="{{!hasError && isProfileComplete && (!payInfo.requiresPayment || payInfo.isPaid)}}">
<block wx:if="{{!hasError && (!payInfo.requiresPayment || payInfo.isPaid)}}">
<!-- 生活场景深度分析AI 或本地兜底) -->
<view class="card scene-detail-card" wx:if="{{result.careerDevelopment}}">
<text class="card-title">职业发展方向</text>
<text class="card-text">{{result.careerDevelopment}}</text>
</view>
<view class="card scene-detail-card" wx:if="{{result.familyParenting}}">
<text class="card-title">家庭亲子关系</text>
<text class="card-text">{{result.familyParenting}}</text>
</view>
<view class="card scene-detail-card" wx:if="{{result.partnerCofounder}}">
<text class="card-title">寻找合伙人</text>
<text class="card-text">{{result.partnerCofounder}}</text>
</view>
<!-- 面相分析 -->
<block wx:if="{{result.faceAnalysis}}">
@@ -304,18 +342,5 @@
</view>
</block>
<!-- 操作按钮 -->
<view class="action-section" wx:if="{{!hasError}}">
<button class="btn btn-primary" open-type="share">
<text class="btn-text">分享我的分析报告</text>
</button>
<view class="btn btn-outline" bindtap="retake">
<text class="btn-text-outline">重新分析</text>
</view>
<view class="btn btn-outline" bindtap="goHome">
<text class="btn-text-outline">返回首页</text>
</view>
</view>
</view>
</view>

View File

@@ -323,17 +323,79 @@
top: 0;
bottom: 0;
pointer-events: none;
/* 高透明白雾,避免大块灰底发闷 */
/* 约前 40% 区域保持清晰预览,以下渐强为「约 60% 可读」观感 */
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0) 0%,
rgba(255, 255, 255, 0.22) 10%,
rgba(255, 255, 255, 0.48) 28%,
rgba(255, 255, 255, 0.72) 48%,
rgba(255, 255, 255, 0.88) 100%
rgba(255, 255, 255, 0) 38%,
rgba(255, 255, 255, 0.2) 52%,
rgba(255, 255, 255, 0.55) 68%,
rgba(255, 255, 255, 0.82) 88%,
rgba(255, 255, 255, 0.92) 100%
);
}
/* 支付成功后:手机号 / 资料引导条 */
.post-pay-guide {
margin: 16rpx 24rpx 0;
padding: 24rpx 28rpx;
background: linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%);
border-radius: 20rpx;
border: 1rpx solid rgba(16, 185, 129, 0.35);
}
.post-pay-guide-title {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #065f46;
margin-bottom: 8rpx;
}
.post-pay-guide-desc {
display: block;
font-size: 24rpx;
color: #047857;
line-height: 1.45;
margin-bottom: 20rpx;
}
.post-pay-guide-actions {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
}
.post-pay-guide-btn.phone {
margin: 0;
padding: 0 36rpx;
height: 72rpx;
line-height: 72rpx;
font-size: 28rpx;
font-weight: 600;
color: #fff;
background: linear-gradient(135deg, #10b981 0%, #34d399 100%);
border-radius: 36rpx;
border: none;
}
.post-pay-guide-btn.phone::after {
border: none;
}
.post-pay-guide-btn.profile {
padding: 0 36rpx;
height: 72rpx;
line-height: 72rpx;
font-size: 28rpx;
font-weight: 600;
color: #065f46;
background: #fff;
border-radius: 36rpx;
border: 2rpx solid #10b981;
text-align: center;
}
.report-gate-panel {
position: relative;
z-index: 2;
@@ -362,23 +424,102 @@
max-width: 640rpx;
margin: 28rpx auto 0;
box-sizing: border-box;
display: flex !important;
flex-direction: column !important;
align-items: center !important;
padding: 28rpx 24rpx !important;
}
.paywall-btn-inline .paywall-btn-price {
display: block;
width: 100%;
text-align: center;
margin-top: 10rpx;
font-size: 22rpx;
line-height: 1.45;
font-weight: 400;
}
/* 主按钮单行:仅金额 +「元解锁」 */
.paywall-btn--compact {
flex-direction: row !important;
justify-content: center !important;
padding: 32rpx 40rpx !important;
}
.paywall-btn--compact .paywall-btn-main {
font-size: 34rpx;
letter-spacing: 2rpx;
}
.report-gate-panel .paywall-btn-inline::after {
border: none;
}
/* 未解锁:三大场景标题预览 */
.scene-teaser-bundle {
text-align: left;
padding-bottom: 28rpx !important;
}
.scene-bundle-hint {
display: block;
font-size: 22rpx;
color: #94a3b8;
margin-bottom: 20rpx;
}
.scene-teaser-item {
padding: 20rpx 0;
border-bottom: 1rpx solid #f1f5f9;
}
.scene-teaser-item--last {
border-bottom: none;
padding-bottom: 8rpx;
}
.scene-teaser-title {
display: block;
font-size: 28rpx;
font-weight: 600;
color: #334155;
margin-bottom: 16rpx;
}
.scene-teaser-lines {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.scene-teaser-line {
height: 14rpx;
border-radius: 8rpx;
background: linear-gradient(90deg, #e2e8f0 0%, #f1f5f9 100%);
width: 100%;
}
.scene-teaser-line--short {
width: 62%;
}
.scene-detail-card {
border-left: 6rpx solid #e63946;
padding-left: 28rpx !important;
}
/* 未解锁:限制概述与盖洛普可见行数 */
.report-gate-teaser--locked .card-text {
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 5;
-webkit-line-clamp: 8;
overflow: hidden;
text-overflow: ellipsis;
}
.report-gate-teaser--locked .gallup-list {
max-height: 96rpx;
max-height: 140rpx;
overflow: hidden;
position: relative;
}
@@ -428,6 +569,48 @@
text-align: center;
}
.personality-card-sub {
display: block;
font-size: 22rpx;
color: #94a3b8;
margin: -8rpx 0 24rpx;
letter-spacing: 1rpx;
}
.type-chip-row {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 12rpx;
margin: 12rpx 0 8rpx;
}
.type-chip {
font-size: 22rpx;
color: #64748b;
background: #f8fafc;
border: 1rpx solid #e2e8f0;
padding: 8rpx 20rpx;
border-radius: 999rpx;
}
.type-chip--disc {
color: #2563eb;
background: #eff6ff;
border-color: #bfdbfe;
}
.type-emoji {
display: block;
font-size: 36rpx;
margin-bottom: 4rpx;
}
.type-emoji--disc {
opacity: 0.35;
font-size: 28rpx;
}
.personality-type {
margin-bottom: 32rpx;
}

View File

@@ -14,6 +14,8 @@ Page({
discType: '',
pdpType: '',
aiType: '',
/** 面相记录中的盖洛普前三摘要(/api/test/recent 或本地 aiResult */
gallupPreview: '',
mbtiTime: '',
discTime: '',
pdpTime: '',
@@ -158,6 +160,7 @@ Page({
// DISC resultText 后端已含「型」type badge 只显示字母,去掉「型」
const discType = r.disc ? r.disc.resultText.replace(/型$/, '') : ''
const gallupPreview = (r.ai && r.ai.gallupPreview) ? String(r.ai.gallupPreview) : ''
this.setData({
testCount: totalCount,
hasResults: !!(r.mbti || r.disc || r.pdp || r.ai),
@@ -165,6 +168,7 @@ Page({
discType,
pdpType: r.pdp ? r.pdp.resultText : '',
aiType: r.ai ? r.ai.resultText : '',
gallupPreview,
mbtiTime: r.mbti ? r.mbti.testTime : '',
discTime: r.disc ? r.disc.testTime : '',
pdpTime: r.pdp ? r.pdp.testTime : '',
@@ -209,6 +213,11 @@ Page({
const pdpResult = wx.getStorageSync('pdpResult')
const aiResult = wx.getStorageSync('aiResult')
let gallupPreview = ''
if (aiResult && Array.isArray(aiResult.gallupTop3) && aiResult.gallupTop3.length) {
gallupPreview = aiResult.gallupTop3.slice(0, 3).map(String).join('、')
}
let testCount = 0
if (mbtiResult) testCount++
if (discResult) testCount++
@@ -228,7 +237,8 @@ Page({
mbtiType: mbtiResult ? getTypeOnly(mbtiResult, 'mbti') : '',
discType: discResult ? getTypeOnly(discResult, 'disc') : '',
pdpType: pdpResult ? getTypeOnly(pdpResult, 'pdp') : '',
aiType: aiResult ? (aiResult.mbtiType || aiResult.type || '') : '',
aiType: aiResult ? (aiResult.mbti || aiResult.mbtiType || aiResult.type || '') : '',
gallupPreview,
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)),
@@ -282,19 +292,34 @@ Page({
},
viewMBTI() {
const id = this.data.mbtiResultId
wx.navigateTo({ url: id ? `/pages/result/mbti?id=${id}&type=mbti` : '/pages/result/mbti' })
if (id) wx.navigateTo({ url: `/pages/result/mbti?id=${id}&type=mbti` })
else wx.navigateTo({ url: '/pages/test/mbti' })
},
viewDISC() {
const id = this.data.discResultId
wx.navigateTo({ url: id ? `/pages/result/disc?id=${id}&type=disc` : '/pages/result/disc' })
if (id) wx.navigateTo({ url: `/pages/result/disc?id=${id}&type=disc` })
else wx.navigateTo({ url: '/pages/test/disc' })
},
viewPDP() {
const id = this.data.pdpResultId
wx.navigateTo({ url: id ? `/pages/result/pdp?id=${id}&type=pdp` : '/pages/result/pdp' })
if (id) wx.navigateTo({ url: `/pages/result/pdp?id=${id}&type=pdp` })
else wx.navigateTo({ url: '/pages/test/pdp' })
},
/** 盖洛普随面相报告:有记录看报告,否则去拍摄 */
viewGallup() {
const id = this.data.aiResultId
if (id) wx.navigateTo({ url: `/pages/index/result?id=${id}&type=ai` })
else wx.switchTab({ url: '/pages/index/camera' })
},
viewAI() {
const id = this.data.aiResultId
wx.navigateTo({ url: id ? `/pages/index/result?id=${id}&type=ai` : '/pages/index/result' })
if (id) wx.navigateTo({ url: `/pages/index/result?id=${id}&type=ai` })
else wx.switchTab({ url: '/pages/index/camera' })
},
/** 动作测试:进入底部「拍摄」页 */
goToActionTest() {
try { require('../../utils/analytics').track('tap_action_test_camera', {}) } catch (e) {}
wx.switchTab({ url: '/pages/index/camera' })
},
logout() {

View File

@@ -79,52 +79,70 @@
<text class="depth-header-chevron"></text>
</view>
</view>
<block wx:if="{{hasResults}}">
<scroll-view scroll-x class="cards-scroll cards-scroll--in-card" enhanced show-scrollbar="{{false}}">
<view class="cards-row cards-row--in-card">
<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>
<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>
<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>
<view class="result-card card-rose" wx:if="{{aiType && !reviewMode}}" 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>
<scroll-view scroll-x class="cards-scroll cards-scroll--in-card" enhanced show-scrollbar="{{false}}">
<view class="cards-row cards-row--in-card">
<view class="result-card card-purple {{mbtiType ? '' : 'result-card--placeholder'}}" 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">{{mbtiType ? mbtiTime : '点击进入问卷'}}</text>
</view>
</scroll-view>
</block>
<view wx:else class="depth-empty-hint">
<text>完成任一测评后,此处展示最近一次结果;右上角可查看全部历史。</text>
<view class="result-card card-orange {{pdpType ? '' : 'result-card--placeholder'}}" 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">{{pdpType ? pdpTime : '点击进入问卷'}}</text>
</view>
<view class="result-card card-blue {{discType ? '' : 'result-card--placeholder'}}" 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 ? discType + '型' : '未测评'}}</text>
<text class="card-time">{{discType ? discTime : '点击进入问卷'}}</text>
</view>
<view class="result-card card-teal {{(gallupPreview || aiType) ? '' : 'result-card--placeholder'}}" bindtap="viewGallup" wx:if="{{!reviewMode}}">
<view class="card-deco"></view>
<view class="card-icon-wrap card-icon-teal">
<text class="card-icon">⭐</text>
</view>
<text class="card-label">盖洛普优势</text>
<text class="card-value card-value--small">{{gallupPreview || (aiType ? '见面相报告' : '未测评')}}</text>
<text class="card-time">{{gallupPreview ? aiTime : (aiType ? '已拍面相可查看' : '先完成面相拍摄')}}</text>
</view>
<view class="result-card card-rose {{aiType ? '' : 'result-card--placeholder'}}" bindtap="viewAI" wx:if="{{!reviewMode}}">
<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">{{aiType ? aiTime : '去拍摄分析'}}</text>
</view>
</view>
</scroll-view>
<view class="depth-empty-hint depth-empty-hint--compact">
<text>灰色为未完成项,点击即可进入测评或拍摄;右上方可查看全部历史。</text>
</view>
<view class="depth-inner-divider"></view>
<view class="menu-item menu-item--flat" bindtap="goToActionTest" wx:if="{{!reviewMode}}">
<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">拍摄人像照片,生成面相、骨相与盖洛普等解析</text>
</view>
<text class="menu-chevron"></text>
</view>
<view class="menu-divider menu-divider--in-card" wx:if="{{!reviewMode}}"></view>
<view class="menu-item menu-item--flat" bindtap="goToDeepService">
<view class="menu-icon-wrap menu-icon-purple">
<text class="menu-icon">✨</text>

View File

@@ -325,6 +325,11 @@ custom-tab-bar {
line-height: 1.55;
}
.depth-empty-hint--compact {
padding-top: 4rpx;
padding-bottom: 16rpx;
}
.depth-inner-divider {
height: 1rpx;
background: #F3F4F6;
@@ -391,6 +396,30 @@ custom-tab-bar {
flex-shrink: 0;
}
/* 未完成测评:灰阶可点击 */
.result-card--placeholder {
background: #f3f4f6 !important;
border-color: #e5e7eb !important;
box-shadow: none !important;
}
.result-card--placeholder .card-deco {
opacity: 0.12 !important;
}
.result-card--placeholder .card-label,
.result-card--placeholder .card-time {
color: #9ca3af !important;
}
.result-card--placeholder .card-value {
color: #6b7280 !important;
}
.result-card--placeholder .card-icon-wrap {
opacity: 0.65;
}
.card-deco {
position: absolute;
top: -24rpx;
@@ -405,6 +434,7 @@ custom-tab-bar {
.card-blue .card-deco { background: #BFDBFE; }
.card-orange .card-deco { background: #FDE68A; }
.card-rose .card-deco { background: #FECDD3; }
.card-teal .card-deco { background: #99f6e4; }
.card-icon-wrap {
width: 64rpx;
@@ -419,6 +449,7 @@ custom-tab-bar {
.card-icon-blue { background: #DBEAFE; }
.card-icon-orange { background: #FEF3C7; }
.card-icon-rose { background: #FFE4E6; }
.card-icon-teal { background: #ccfbf1; }
.card-icon {
font-size: 32rpx;
@@ -440,6 +471,17 @@ custom-tab-bar {
.card-blue .card-value { color: #2563EB; }
.card-orange .card-value { color: #D97706; }
.card-rose .card-value { color: #E11D48; }
.card-teal .card-value { color: #0f766e; }
.card-value--small {
font-size: 26rpx !important;
font-weight: 700 !important;
line-height: 1.35;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
overflow: hidden;
}
.card-time {
font-size: 20rpx;