feat: 管理端与用户端批量更新(订单/邀请双码/tabBar/审核与分销等)
- 管理端:用户详情、企业看板双小程序码、超管设置与用户列表等 - 后端:订单接口、邀请码企业版+个人版、分析与测试相关调整 - 微信小程序/抖音:自定义 tabBar 居中与拍摄钮上移、相机页与结果页、订单页、支付与统计 - 新增 faceResultDetail 工具与 mp 分析迁移脚本 Made-with: Cursor
This commit is contained in:
@@ -132,6 +132,7 @@ Page({
|
||||
this.cameraContext.takePhoto({
|
||||
quality: 'high',
|
||||
success: (res) => {
|
||||
try { require('../../utils/analytics').track('take_photo', { index: this.data.photos.length + 1 }) } catch (e) {}
|
||||
const photos = [...this.data.photos, res.tempImagePath]
|
||||
const photoIndex = photos.length
|
||||
const guideText = this.data.guideTexts[photoIndex] || '拍摄完成'
|
||||
@@ -231,11 +232,13 @@ Page({
|
||||
wx.hideLoading()
|
||||
wx.setStorageSync('aiPhotos', urls)
|
||||
this.setData({ uploading: false })
|
||||
try { require('../../utils/analytics').track('photo_upload_success', { count: urls.length }) } catch (e) {}
|
||||
wx.navigateTo({ url: '/pages/index/result' })
|
||||
})
|
||||
.catch((err) => {
|
||||
wx.hideLoading()
|
||||
this.setData({ uploading: false })
|
||||
try { require('../../utils/analytics').track('photo_upload_fail', { error: (err && err.message) || '' }) } catch (e) {}
|
||||
wx.showToast({ title: err.message || '上传失败', icon: 'none' })
|
||||
})
|
||||
},
|
||||
|
||||
@@ -102,6 +102,7 @@ Page({
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
this._payInfoSetByDetail = false
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
|
||||
@@ -154,13 +155,14 @@ Page({
|
||||
const apiData = payload.data || payload
|
||||
// 历史详情场景下,记录当前测试记录ID
|
||||
this.setData({ testResultId: id })
|
||||
this.processResult(apiData)
|
||||
// 从历史进入:金额与是否需付款以 test_results 的 paidAmount/needPaymentToUnlock 为准
|
||||
// 先确定付费状态(设置 _payInfoSetByDetail 标记),再渲染结果
|
||||
// 避免 processResult 内部异步拉全局配置覆盖掉数据库级别的付费判定
|
||||
this.initPayInfoFromRuntime(
|
||||
!!payload.requiresPayment,
|
||||
!!payload.isPaid,
|
||||
payload
|
||||
)
|
||||
this.processResult(apiData)
|
||||
} else {
|
||||
wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
@@ -254,6 +256,7 @@ Page({
|
||||
setTimeout(() => this.showNoFaceError(msg), 300)
|
||||
} else if (bodyCode === 200) {
|
||||
const apiData = res.data.data || res.data
|
||||
try { require('../../utils/analytics').track('ai_analysis_complete', { mbti: (apiData.mbti && apiData.mbti.type) || '' }) } catch (e) {}
|
||||
this.setData({ progress: 100, analyzingTip: '分析完成!' })
|
||||
setTimeout(() => {
|
||||
this.processResult(apiData)
|
||||
@@ -342,8 +345,9 @@ Page({
|
||||
amountYuan
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// 拍照直连分析未带 _payment 时,拉 runtime 定价(与历史详情入口互不覆盖)
|
||||
} else if (!this._payInfoSetByDetail) {
|
||||
// 拍照直连分析未带 _payment 时,拉 runtime 定价
|
||||
// 历史详情入口已由 initPayInfoFromRuntime(detailPayload) 处理过,不再覆盖
|
||||
this.initPayInfoFromRuntime()
|
||||
}
|
||||
},
|
||||
@@ -369,6 +373,8 @@ Page({
|
||||
const paidAmount = Number(detailPayload.paidAmount ?? 0)
|
||||
const amountYuan = detailPayload.amountYuan != null ? Number(detailPayload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0)
|
||||
const needPay = detailPayload.needPaymentToUnlock === true || (!!detailPayload.requiresPayment && !detailPayload.isPaid && paidAmount > 0)
|
||||
// 标记:历史详情已确定付费状态,processResult 中不再用全局配置覆盖
|
||||
this._payInfoSetByDetail = true
|
||||
this.setData({
|
||||
payInfo: {
|
||||
requiresPayment: needPay,
|
||||
@@ -430,6 +436,7 @@ Page({
|
||||
|
||||
/** 付费墙主按钮:先支付,支付后再引导手机号与资料 */
|
||||
onTapUnlockPay() {
|
||||
try { require('../../utils/analytics').track('tap_unlock_pay', { amount: this.data.payInfo.amountYuan, testResultId: this.data.testResultId }) } catch (e) {}
|
||||
this.unlockFullReport()
|
||||
},
|
||||
|
||||
@@ -534,16 +541,14 @@ Page({
|
||||
const payload = res.data.data
|
||||
const apiData = payload.data || payload
|
||||
|
||||
// 详情接口在后台会根据 isPaid 返回完整结构,这里直接复用已有处理逻辑
|
||||
this.processResult(apiData)
|
||||
|
||||
// 同步数据库中的付费状态与金额(test_results.paidAmount)
|
||||
// 先确定付费状态,再渲染结果(避免 processResult 异步覆盖)
|
||||
const recordRequires =
|
||||
typeof payload.requiresPayment === 'boolean' || typeof payload.requiresPayment === 'number'
|
||||
? !!payload.requiresPayment
|
||||
: null
|
||||
const recordIsPaid = !!payload.isPaid
|
||||
this.initPayInfoFromRuntime(recordRequires, recordIsPaid, payload)
|
||||
this.processResult(apiData)
|
||||
} else {
|
||||
console.error('刷新完整报告失败', res)
|
||||
}
|
||||
@@ -577,11 +582,6 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
// 跳转到详情性格测试选择页(MBTI / PDP / DISC 三选一)
|
||||
goToMBTI() {
|
||||
wx.navigateTo({ url: '/pages/test-select/index' })
|
||||
},
|
||||
|
||||
shareResult() {
|
||||
wx.showShareMenu({ withShareTicket: true, menus: ['shareAppMessage', 'shareTimeline'] })
|
||||
},
|
||||
|
||||
@@ -74,15 +74,10 @@
|
||||
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>
|
||||
|
||||
@@ -110,10 +105,6 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="next-btn" bindtap="goToMBTI">
|
||||
<text class="next-btn-text">下一步:详细性格测试 →</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view
|
||||
|
||||
@@ -323,11 +323,11 @@
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
pointer-events: none;
|
||||
/* 约前 40% 区域保持清晰预览,以下渐强为「约 60% 可读」观感 */
|
||||
/* 约前 50% 区域保持清晰预览,以下渐强变白 */
|
||||
background: linear-gradient(
|
||||
180deg,
|
||||
rgba(255, 255, 255, 0) 0%,
|
||||
rgba(255, 255, 255, 0) 38%,
|
||||
rgba(255, 255, 255, 0) 50%,
|
||||
rgba(255, 255, 255, 0.2) 52%,
|
||||
rgba(255, 255, 255, 0.55) 68%,
|
||||
rgba(255, 255, 255, 0.82) 88%,
|
||||
|
||||
81
miniprogram/pages/order/index.js
Normal file
81
miniprogram/pages/order/index.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// pages/order/index.js - 我的订单(支付记录)
|
||||
const app = getApp()
|
||||
const { request } = require('../../utils/request')
|
||||
|
||||
Page({
|
||||
data: {
|
||||
list: [],
|
||||
total: 0,
|
||||
page: 1,
|
||||
pageSize: 20,
|
||||
hasMore: false,
|
||||
loading: false,
|
||||
empty: false
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadList(true)
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.loadList(true)
|
||||
.then(() => wx.stopPullDownRefresh())
|
||||
.catch(() => wx.stopPullDownRefresh())
|
||||
},
|
||||
|
||||
loadList(reset) {
|
||||
const token = app.globalData.token || wx.getStorageSync('token')
|
||||
if (!token) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return Promise.resolve()
|
||||
}
|
||||
|
||||
const page = reset ? 1 : this.data.page
|
||||
if (this.data.loading) return Promise.resolve()
|
||||
this.setData({ loading: true })
|
||||
|
||||
return new Promise((resolve) => {
|
||||
request({
|
||||
url: `/api/orders?page=${page}&pageSize=${this.data.pageSize}`,
|
||||
method: 'GET',
|
||||
needAuth: true,
|
||||
success: (res) => {
|
||||
const body = res.data
|
||||
if (res.statusCode === 200 && body && body.code === 200 && body.data) {
|
||||
const d = body.data
|
||||
const raw = Array.isArray(d.list) ? d.list : []
|
||||
const list = reset ? raw : this.data.list.concat(raw)
|
||||
this.setData({
|
||||
list,
|
||||
total: d.total || 0,
|
||||
page: d.page || page,
|
||||
hasMore: !!d.hasMore,
|
||||
empty: list.length === 0,
|
||||
loading: false
|
||||
})
|
||||
} else {
|
||||
this.setData({
|
||||
list: reset ? [] : this.data.list,
|
||||
empty: reset && (!this.data.list || this.data.list.length === 0),
|
||||
loading: false
|
||||
})
|
||||
if (body && body.message) wx.showToast({ title: body.message, icon: 'none' })
|
||||
}
|
||||
resolve()
|
||||
},
|
||||
fail: () => {
|
||||
this.setData({ loading: false })
|
||||
wx.showToast({ title: '加载失败', icon: 'none' })
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
loadMore() {
|
||||
if (!this.data.hasMore || this.data.loading) return
|
||||
this.setData({ page: this.data.page + 1 }, () => {
|
||||
this.loadList(false)
|
||||
})
|
||||
}
|
||||
})
|
||||
7
miniprogram/pages/order/index.json
Normal file
7
miniprogram/pages/order/index.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"navigationBarTitleText": "我的订单",
|
||||
"navigationBarBackgroundColor": "#FFFFFF",
|
||||
"navigationBarTextStyle": "black",
|
||||
"enablePullDownRefresh": true,
|
||||
"usingComponents": {}
|
||||
}
|
||||
32
miniprogram/pages/order/index.wxml
Normal file
32
miniprogram/pages/order/index.wxml
Normal file
@@ -0,0 +1,32 @@
|
||||
<!--pages/order/index.wxml-->
|
||||
<view class="page">
|
||||
<view class="loading-wrap" wx:if="{{loading && list.length === 0}}">
|
||||
<text class="loading-text">加载中...</text>
|
||||
</view>
|
||||
|
||||
<view class="empty" wx:elif="{{empty && !loading}}">
|
||||
<text class="empty-icon">🧾</text>
|
||||
<text class="empty-title">暂无订单</text>
|
||||
<text class="empty-desc">购买专业报告或测试服务后,订单会显示在这里</text>
|
||||
</view>
|
||||
|
||||
<view class="list" wx:else>
|
||||
<view class="order-card" wx:for="{{list}}" wx:key="id">
|
||||
<view class="card-head">
|
||||
<text class="title">{{item.productTitle}}</text>
|
||||
<text class="status status-{{item.status}}">{{item.statusText}}</text>
|
||||
</view>
|
||||
<text class="order-no">单号 {{item.orderNo}}</text>
|
||||
<view class="card-foot">
|
||||
<text class="time">{{item.payTimeStr || item.createdAtStr}}</text>
|
||||
<text class="amount">¥{{item.amountYuan}}</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="load-more" wx:if="{{hasMore}}" bindtap="loadMore">
|
||||
<text class="load-more-text">{{loading ? '加载中...' : '加载更多'}}</text>
|
||||
</view>
|
||||
<view class="list-end" wx:if="{{!hasMore && list.length > 0}}">
|
||||
<text class="list-end-text">没有更多了</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
134
miniprogram/pages/order/index.wxss
Normal file
134
miniprogram/pages/order/index.wxss
Normal file
@@ -0,0 +1,134 @@
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
background: #f5f5f5;
|
||||
padding: 24rpx;
|
||||
padding-bottom: 48rpx;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.loading-wrap {
|
||||
padding: 120rpx 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.loading-text {
|
||||
font-size: 28rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
|
||||
.empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding-top: 160rpx;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 100rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 32rpx;
|
||||
color: #374151;
|
||||
font-weight: 600;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.empty-desc {
|
||||
font-size: 26rpx;
|
||||
color: #9ca3af;
|
||||
text-align: center;
|
||||
padding: 0 48rpx;
|
||||
}
|
||||
|
||||
.order-card {
|
||||
background: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 28rpx;
|
||||
margin-bottom: 24rpx;
|
||||
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.04);
|
||||
}
|
||||
|
||||
.card-head {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
gap: 16rpx;
|
||||
margin-bottom: 12rpx;
|
||||
}
|
||||
|
||||
.title {
|
||||
flex: 1;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #111827;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.status {
|
||||
font-size: 24rpx;
|
||||
flex-shrink: 0;
|
||||
padding: 4rpx 14rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.status-pending {
|
||||
color: #d97706;
|
||||
background: #fffbeb;
|
||||
}
|
||||
|
||||
.status-paid,
|
||||
.status-completed {
|
||||
color: #059669;
|
||||
background: #ecfdf5;
|
||||
}
|
||||
|
||||
.status-cancelled {
|
||||
color: #6b7280;
|
||||
background: #f3f4f6;
|
||||
}
|
||||
|
||||
.order-no {
|
||||
font-size: 24rpx;
|
||||
color: #9ca3af;
|
||||
margin-bottom: 16rpx;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.card-foot {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.time {
|
||||
font-size: 24rpx;
|
||||
color: #6b7280;
|
||||
}
|
||||
|
||||
.amount {
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
color: #7c3aed;
|
||||
}
|
||||
|
||||
.load-more {
|
||||
text-align: center;
|
||||
padding: 24rpx;
|
||||
}
|
||||
|
||||
.load-more-text {
|
||||
font-size: 28rpx;
|
||||
color: #7c3aed;
|
||||
}
|
||||
|
||||
.list-end {
|
||||
text-align: center;
|
||||
padding: 16rpx;
|
||||
}
|
||||
|
||||
.list-end-text {
|
||||
font-size: 24rpx;
|
||||
color: #9ca3af;
|
||||
}
|
||||
@@ -278,6 +278,14 @@ Page({
|
||||
try { require('../../utils/analytics').track('tap_deep_service', {}) } catch (e) {}
|
||||
wx.navigateTo({ url: '/pages/purchase/index' })
|
||||
},
|
||||
goToOrders() {
|
||||
if (!this.data.hasLogin) {
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
try { require('../../utils/analytics').track('tap_my_orders', {}) } catch (e) {}
|
||||
wx.navigateTo({ url: '/pages/order/index' })
|
||||
},
|
||||
goToPurchase() { wx.navigateTo({ url: '/pages/purchase/index' }) },
|
||||
goToPurchasePersonal() { wx.navigateTo({ url: '/pages/purchase/index?tab=personal' }) },
|
||||
goToPurchaseEnterprise() { wx.navigateTo({ url: '/pages/purchase/index?tab=enterprise' }) },
|
||||
@@ -316,10 +324,10 @@ Page({
|
||||
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' })
|
||||
/** 详细性格测试:MBTI / PDP / DISC 选择页 */
|
||||
goToTestSelect() {
|
||||
try { require('../../utils/analytics').track('tap_test_select_from_profile', {}) } catch (e) {}
|
||||
wx.navigateTo({ url: '/pages/test-select/index' })
|
||||
},
|
||||
|
||||
logout() {
|
||||
|
||||
@@ -129,20 +129,20 @@
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="depth-empty-hint depth-empty-hint--compact">
|
||||
<text>灰色为未完成项,点击即可进入测评或拍摄;右上方可查看全部历史。</text>
|
||||
<text>灰色为未完成项,点击即可进入测评;右上方可查看全部历史。</text>
|
||||
</view>
|
||||
<view class="depth-inner-divider"></view>
|
||||
<view class="menu-item menu-item--flat" bindtap="goToActionTest" wx:if="{{!reviewMode}}">
|
||||
<view class="menu-item menu-item--flat" bindtap="goToTestSelect">
|
||||
<view class="menu-icon-wrap menu-icon-amber">
|
||||
<text class="menu-icon">📷</text>
|
||||
<text class="menu-icon">🧠</text>
|
||||
</view>
|
||||
<view class="menu-content">
|
||||
<text class="menu-title">动作测试(拍摄)</text>
|
||||
<text class="menu-sub">拍摄人像照片,生成面相、骨相与盖洛普等解析</text>
|
||||
<text class="menu-title">详细性格测试</text>
|
||||
<text class="menu-sub">MBTI、PDP、DISC 三套问卷,任选一项开始</text>
|
||||
</view>
|
||||
<text class="menu-chevron">›</text>
|
||||
</view>
|
||||
<view class="menu-divider menu-divider--in-card" wx:if="{{!reviewMode}}"></view>
|
||||
<view class="menu-divider menu-divider--in-card"></view>
|
||||
<view class="menu-item menu-item--flat" bindtap="goToDeepService">
|
||||
<view class="menu-icon-wrap menu-icon-purple">
|
||||
<text class="menu-icon">✨</text>
|
||||
@@ -153,6 +153,17 @@
|
||||
</view>
|
||||
<text class="menu-chevron">›</text>
|
||||
</view>
|
||||
<view class="menu-divider menu-divider--in-card"></view>
|
||||
<view class="menu-item menu-item--flat" bindtap="goToOrders">
|
||||
<view class="menu-icon-wrap menu-icon-emerald">
|
||||
<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="{{hasEnterprise}}"></view>
|
||||
<view class="menu-item menu-item--flat" wx:if="{{hasEnterprise}}" bindtap="goToMyResume">
|
||||
<view class="menu-icon-wrap menu-icon-indigo">
|
||||
|
||||
@@ -568,6 +568,7 @@ custom-tab-bar {
|
||||
.menu-icon-red { background: #FEF2F2; }
|
||||
.menu-icon-indigo { background: #EEF2FF; }
|
||||
.menu-icon-amber { background: #FFFBEB; }
|
||||
.menu-icon-emerald { background: #ECFDF5; }
|
||||
.menu-icon-purple { background: #EDE9FE; }
|
||||
|
||||
.menu-icon {
|
||||
|
||||
@@ -1,157 +1,157 @@
|
||||
// pages/result/disc.js - DISC结果页(支持付费墙 + 历史详情拉取)
|
||||
const app = getApp()
|
||||
const payment = require('../../utils/payment')
|
||||
|
||||
function toIntPercent(v) {
|
||||
if (v == null) return 0
|
||||
const n = typeof v === 'number' ? v : Number(v)
|
||||
return Number.isFinite(n) ? Math.round(n) : 0
|
||||
}
|
||||
|
||||
function withPercentagesInt(data) {
|
||||
if (!data) return data
|
||||
const p = data.percentages || {}
|
||||
const out = { ...data }
|
||||
out.percentagesInt = {
|
||||
D: toIntPercent(p.D ?? p.d),
|
||||
I: toIntPercent(p.I ?? p.i),
|
||||
S: toIntPercent(p.S ?? p.s),
|
||||
C: toIntPercent(p.C ?? p.c)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
result: null,
|
||||
typeList: [
|
||||
{ type: 'D', label: 'D型 - 支配型', colorClass: 'fill-d' },
|
||||
{ type: 'I', label: 'I型 - 影响型', colorClass: 'fill-i' },
|
||||
{ type: 'S', label: 'S型 - 稳健型', colorClass: 'fill-s' },
|
||||
{ type: 'C', label: 'C型 - 谨慎型', colorClass: 'fill-c' }
|
||||
],
|
||||
payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 },
|
||||
testResultId: null,
|
||||
hasReloadedAfterPay: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
if (id && type === 'disc') {
|
||||
this.setData({ testResultId: id })
|
||||
this.loadDetail(id)
|
||||
return
|
||||
}
|
||||
const result = wx.getStorageSync('discResult')
|
||||
if (result) {
|
||||
this.setData({ result: withPercentagesInt(result) })
|
||||
this.initPayInfoFromRuntime('disc')
|
||||
} else {
|
||||
wx.showToast({ title: '暂无测试结果', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
}
|
||||
},
|
||||
|
||||
loadDetail(id) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
const token = app.globalData?.token || wx.getStorageSync('token') || ''
|
||||
if (!apiBase) { wx.showToast({ title: '配置异常', icon: 'none' }); return }
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/detail`,
|
||||
method: 'GET',
|
||||
header: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
data: { id },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
const payload = res.data.data || {}
|
||||
const data = payload.data || payload
|
||||
const isPaid = !!payload.isPaid
|
||||
const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0
|
||||
const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0)
|
||||
const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0)
|
||||
this.setData({ result: withPercentagesInt(data) })
|
||||
const payInfo = {
|
||||
requiresPayment: needPaymentToUnlock,
|
||||
isPaid,
|
||||
amountYuan: needPaymentToUnlock ? amountYuan : 0
|
||||
}
|
||||
this.setData({ payInfo })
|
||||
} else {
|
||||
wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => wx.showToast({ title: '网络错误', icon: 'none' }),
|
||||
complete: () => wx.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
initPayInfoFromRuntime(testType) {
|
||||
app.getRuntimeConfig()
|
||||
.then((cfg) => {
|
||||
const reportRequires = cfg.reportRequiresPayment || {}
|
||||
const pricing = cfg.pricing || {}
|
||||
const requiresPayment = !!(reportRequires && reportRequires[testType])
|
||||
const amountYuan = Number(pricing[testType]) || (requiresPayment ? 1.98 : 0)
|
||||
this.setData({
|
||||
payInfo: { requiresPayment, isPaid: false, amountYuan }
|
||||
})
|
||||
})
|
||||
.catch(() => this.setData({ payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 } }))
|
||||
},
|
||||
|
||||
unlockFullReport() {
|
||||
const { payInfo, testResultId, hasReloadedAfterPay } = this.data
|
||||
if (!payInfo.requiresPayment || payInfo.isPaid) return
|
||||
app.ensureLogin && app.ensureLogin().then((logged) => {
|
||||
if (!logged) { wx.showToast({ title: '请先登录', icon: 'none' }); return }
|
||||
payment.purchaseDiscTest({
|
||||
testResultId: testResultId || undefined,
|
||||
success: () => {
|
||||
wx.showToast({ title: '已解锁完整报告', icon: 'success' })
|
||||
this.setData({ 'payInfo.isPaid': true })
|
||||
if (testResultId && !hasReloadedAfterPay) {
|
||||
this.setData({ hasReloadedAfterPay: true })
|
||||
setTimeout(() => this.loadDetail(testResultId), 500)
|
||||
}
|
||||
},
|
||||
fail: () => {}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
retakeTest() {
|
||||
if (!this.data.testResultId) {
|
||||
wx.removeStorageSync('discResult')
|
||||
}
|
||||
wx.navigateTo({ url: '/pages/test/disc' })
|
||||
},
|
||||
|
||||
goHome() {
|
||||
const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal'
|
||||
if (scope === 'enterprise') {
|
||||
wx.navigateTo({ url: '/pages/enterprise/index' })
|
||||
} else {
|
||||
wx.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
const result = this.data.result
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的DISC类型是${result?.dominantType}型(${result?.description?.title}),来测测你的吧!`,
|
||||
path: getSharePathByScope('/pages/index/index')
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const result = this.data.result
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的DISC类型是${result?.dominantType}型(${result?.description?.title}),来测测你的吧!`,
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
// pages/result/disc.js - DISC结果页(支持付费墙 + 历史详情拉取)
|
||||
const app = getApp()
|
||||
const payment = require('../../utils/payment')
|
||||
|
||||
function toIntPercent(v) {
|
||||
if (v == null) return 0
|
||||
const n = typeof v === 'number' ? v : Number(v)
|
||||
return Number.isFinite(n) ? Math.round(n) : 0
|
||||
}
|
||||
|
||||
function withPercentagesInt(data) {
|
||||
if (!data) return data
|
||||
const p = data.percentages || {}
|
||||
const out = { ...data }
|
||||
out.percentagesInt = {
|
||||
D: toIntPercent(p.D ?? p.d),
|
||||
I: toIntPercent(p.I ?? p.i),
|
||||
S: toIntPercent(p.S ?? p.s),
|
||||
C: toIntPercent(p.C ?? p.c)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
result: null,
|
||||
typeList: [
|
||||
{ type: 'D', label: 'D型 - 支配型', colorClass: 'fill-d' },
|
||||
{ type: 'I', label: 'I型 - 影响型', colorClass: 'fill-i' },
|
||||
{ type: 'S', label: 'S型 - 稳健型', colorClass: 'fill-s' },
|
||||
{ type: 'C', label: 'C型 - 谨慎型', colorClass: 'fill-c' }
|
||||
],
|
||||
payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 },
|
||||
testResultId: null,
|
||||
hasReloadedAfterPay: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
if (id && type === 'disc') {
|
||||
this.setData({ testResultId: id })
|
||||
this.loadDetail(id)
|
||||
return
|
||||
}
|
||||
const result = wx.getStorageSync('discResult')
|
||||
if (result) {
|
||||
this.setData({ result: withPercentagesInt(result) })
|
||||
this.initPayInfoFromRuntime('disc')
|
||||
} else {
|
||||
wx.showToast({ title: '暂无测试结果', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
}
|
||||
},
|
||||
|
||||
loadDetail(id) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
const token = app.globalData?.token || wx.getStorageSync('token') || ''
|
||||
if (!apiBase) { wx.showToast({ title: '配置异常', icon: 'none' }); return }
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/detail`,
|
||||
method: 'GET',
|
||||
header: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
data: { id },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
const payload = res.data.data || {}
|
||||
const data = payload.data || payload
|
||||
const isPaid = !!payload.isPaid
|
||||
const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0
|
||||
const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0)
|
||||
const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0)
|
||||
this.setData({ result: withPercentagesInt(data) })
|
||||
const payInfo = {
|
||||
requiresPayment: needPaymentToUnlock,
|
||||
isPaid,
|
||||
amountYuan: needPaymentToUnlock ? amountYuan : 0
|
||||
}
|
||||
this.setData({ payInfo })
|
||||
} else {
|
||||
wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => wx.showToast({ title: '网络错误', icon: 'none' }),
|
||||
complete: () => wx.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
initPayInfoFromRuntime(testType) {
|
||||
app.getRuntimeConfig()
|
||||
.then((cfg) => {
|
||||
const reportRequires = cfg.reportRequiresPayment || {}
|
||||
const pricing = cfg.pricing || {}
|
||||
const requiresPayment = !!(reportRequires && reportRequires[testType])
|
||||
const amountYuan = Number(pricing[testType]) || (requiresPayment ? 1.98 : 0)
|
||||
this.setData({
|
||||
payInfo: { requiresPayment, isPaid: false, amountYuan }
|
||||
})
|
||||
})
|
||||
.catch(() => this.setData({ payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 } }))
|
||||
},
|
||||
|
||||
unlockFullReport() {
|
||||
const { payInfo, testResultId, hasReloadedAfterPay } = this.data
|
||||
if (!payInfo.requiresPayment || payInfo.isPaid) return
|
||||
app.ensureLogin && app.ensureLogin().then((logged) => {
|
||||
if (!logged) { wx.showToast({ title: '请先登录', icon: 'none' }); return }
|
||||
payment.purchaseDiscTest({
|
||||
testResultId: testResultId || undefined,
|
||||
success: () => {
|
||||
wx.showToast({ title: '已解锁完整报告', icon: 'success' })
|
||||
this.setData({ 'payInfo.isPaid': true })
|
||||
if (testResultId && !hasReloadedAfterPay) {
|
||||
this.setData({ hasReloadedAfterPay: true })
|
||||
setTimeout(() => this.loadDetail(testResultId), 500)
|
||||
}
|
||||
},
|
||||
fail: () => {}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
retakeTest() {
|
||||
if (!this.data.testResultId) {
|
||||
wx.removeStorageSync('discResult')
|
||||
}
|
||||
wx.navigateTo({ url: '/pages/test/disc' })
|
||||
},
|
||||
|
||||
goHome() {
|
||||
const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal'
|
||||
if (scope === 'enterprise') {
|
||||
wx.navigateTo({ url: '/pages/enterprise/index' })
|
||||
} else {
|
||||
wx.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
const result = this.data.result
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的DISC类型是${result?.dominantType}型(${result?.description?.title}),来测测你的吧!`,
|
||||
path: getSharePathByScope('/pages/index/index')
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const result = this.data.result
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的DISC类型是${result?.dominantType}型(${result?.description?.title}),来测测你的吧!`,
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-section">
|
||||
<!-- <view class="action-section">
|
||||
<button class="btn btn-primary" open-type="share">
|
||||
<text class="btn-text">分享结果</text>
|
||||
</button>
|
||||
@@ -75,7 +75,7 @@
|
||||
<view class="btn btn-outline" bindtap="goHome">
|
||||
<text class="btn-text-outline">返回首页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
@@ -91,7 +91,7 @@
|
||||
<text class="relationship-text">{{mbtiDesc.relationships}}</text>
|
||||
</view>
|
||||
|
||||
<view class="action-section">
|
||||
<!-- <view class="action-section">
|
||||
<button class="btn btn-primary" open-type="share">
|
||||
<text class="btn-text">分享结果</text>
|
||||
</button>
|
||||
@@ -101,7 +101,7 @@
|
||||
<view class="btn btn-outline" bindtap="goHome">
|
||||
<text class="btn-text-outline">返回首页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
@@ -1,157 +1,157 @@
|
||||
// pages/result/pdp.js - PDP结果页(支持付费墙 + 历史详情拉取)
|
||||
const app = getApp()
|
||||
const payment = require('../../utils/payment')
|
||||
|
||||
const PDP_KEYS = ['Tiger', 'Peacock', 'Koala', 'Owl', 'Chameleon']
|
||||
|
||||
function toIntPercent(v) {
|
||||
if (v == null) return 0
|
||||
const n = typeof v === 'number' ? v : Number(v)
|
||||
return Number.isFinite(n) ? Math.round(n) : 0
|
||||
}
|
||||
|
||||
function withPercentagesInt(data) {
|
||||
if (!data) return data
|
||||
const p = data.percentages || {}
|
||||
const out = { ...data }
|
||||
const ints = {}
|
||||
PDP_KEYS.forEach((k) => { ints[k] = toIntPercent(p[k] ?? p[k.toLowerCase()]) })
|
||||
out.percentagesInt = ints
|
||||
return out
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
result: null,
|
||||
typeList: [
|
||||
{ type: 'Tiger', emoji: '🐅', label: '老虎型', colorClass: 'fill-tiger' },
|
||||
{ type: 'Peacock', emoji: '🦚', label: '孔雀型', colorClass: 'fill-peacock' },
|
||||
{ type: 'Koala', emoji: '🐨', label: '考拉型', colorClass: 'fill-koala' },
|
||||
{ type: 'Owl', emoji: '🦉', label: '猫头鹰型', colorClass: 'fill-owl' },
|
||||
{ type: 'Chameleon', emoji: '🦎', label: '变色龙型', colorClass: 'fill-chameleon' }
|
||||
],
|
||||
payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 },
|
||||
testResultId: null,
|
||||
hasReloadedAfterPay: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
if (id && type === 'pdp') {
|
||||
this.setData({ testResultId: id })
|
||||
this.loadDetail(id)
|
||||
return
|
||||
}
|
||||
const result = wx.getStorageSync('pdpResult')
|
||||
if (result) {
|
||||
this.setData({ result: withPercentagesInt(result) })
|
||||
this.initPayInfoFromRuntime('pdp')
|
||||
} else {
|
||||
wx.showToast({ title: '暂无测试结果', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
}
|
||||
},
|
||||
|
||||
loadDetail(id) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
const token = app.globalData?.token || wx.getStorageSync('token') || ''
|
||||
if (!apiBase) { wx.showToast({ title: '配置异常', icon: 'none' }); return }
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/detail`,
|
||||
method: 'GET',
|
||||
header: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
data: { id },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
const payload = res.data.data || {}
|
||||
const data = payload.data || payload
|
||||
const isPaid = !!payload.isPaid
|
||||
const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0
|
||||
const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0)
|
||||
const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0)
|
||||
this.setData({ result: withPercentagesInt(data) })
|
||||
const payInfo = {
|
||||
requiresPayment: needPaymentToUnlock,
|
||||
isPaid,
|
||||
amountYuan: needPaymentToUnlock ? amountYuan : 0
|
||||
}
|
||||
this.setData({ payInfo })
|
||||
} else {
|
||||
wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => wx.showToast({ title: '网络错误', icon: 'none' }),
|
||||
complete: () => wx.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
initPayInfoFromRuntime(testType) {
|
||||
app.getRuntimeConfig()
|
||||
.then((cfg) => {
|
||||
const reportRequires = cfg.reportRequiresPayment || {}
|
||||
const pricing = cfg.pricing || {}
|
||||
const requiresPayment = !!(reportRequires && reportRequires[testType])
|
||||
const amountYuan = Number(pricing[testType]) || (requiresPayment ? 1.98 : 0)
|
||||
this.setData({
|
||||
payInfo: { requiresPayment, isPaid: false, amountYuan }
|
||||
})
|
||||
})
|
||||
.catch(() => this.setData({ payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 } }))
|
||||
},
|
||||
|
||||
unlockFullReport() {
|
||||
const { payInfo, testResultId, hasReloadedAfterPay } = this.data
|
||||
if (!payInfo.requiresPayment || payInfo.isPaid) return
|
||||
app.ensureLogin && app.ensureLogin().then((logged) => {
|
||||
if (!logged) { wx.showToast({ title: '请先登录', icon: 'none' }); return }
|
||||
payment.purchasePdpTest({
|
||||
testResultId: testResultId || undefined,
|
||||
success: () => {
|
||||
wx.showToast({ title: '已解锁完整报告', icon: 'success' })
|
||||
this.setData({ 'payInfo.isPaid': true })
|
||||
if (testResultId && !hasReloadedAfterPay) {
|
||||
this.setData({ hasReloadedAfterPay: true })
|
||||
setTimeout(() => this.loadDetail(testResultId), 500)
|
||||
}
|
||||
},
|
||||
fail: () => {}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
retakeTest() {
|
||||
if (!this.data.testResultId) {
|
||||
wx.removeStorageSync('pdpResult')
|
||||
}
|
||||
wx.navigateTo({ url: '/pages/test/pdp' })
|
||||
},
|
||||
|
||||
goHome() {
|
||||
const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal'
|
||||
if (scope === 'enterprise') {
|
||||
wx.navigateTo({ url: '/pages/enterprise/index' })
|
||||
} else {
|
||||
wx.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
const result = this.data.result
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的PDP类型是${result?.description?.type}${result?.description?.emoji},来测测你的吧!`,
|
||||
path: getSharePathByScope('/pages/index/index')
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const result = this.data.result
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的PDP类型是${result?.description?.type}${result?.description?.emoji},来测测你的吧!`,
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
// pages/result/pdp.js - PDP结果页(支持付费墙 + 历史详情拉取)
|
||||
const app = getApp()
|
||||
const payment = require('../../utils/payment')
|
||||
|
||||
const PDP_KEYS = ['Tiger', 'Peacock', 'Koala', 'Owl', 'Chameleon']
|
||||
|
||||
function toIntPercent(v) {
|
||||
if (v == null) return 0
|
||||
const n = typeof v === 'number' ? v : Number(v)
|
||||
return Number.isFinite(n) ? Math.round(n) : 0
|
||||
}
|
||||
|
||||
function withPercentagesInt(data) {
|
||||
if (!data) return data
|
||||
const p = data.percentages || {}
|
||||
const out = { ...data }
|
||||
const ints = {}
|
||||
PDP_KEYS.forEach((k) => { ints[k] = toIntPercent(p[k] ?? p[k.toLowerCase()]) })
|
||||
out.percentagesInt = ints
|
||||
return out
|
||||
}
|
||||
|
||||
Page({
|
||||
data: {
|
||||
result: null,
|
||||
typeList: [
|
||||
{ type: 'Tiger', emoji: '🐅', label: '老虎型', colorClass: 'fill-tiger' },
|
||||
{ type: 'Peacock', emoji: '🦚', label: '孔雀型', colorClass: 'fill-peacock' },
|
||||
{ type: 'Koala', emoji: '🐨', label: '考拉型', colorClass: 'fill-koala' },
|
||||
{ type: 'Owl', emoji: '🦉', label: '猫头鹰型', colorClass: 'fill-owl' },
|
||||
{ type: 'Chameleon', emoji: '🦎', label: '变色龙型', colorClass: 'fill-chameleon' }
|
||||
],
|
||||
payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 },
|
||||
testResultId: null,
|
||||
hasReloadedAfterPay: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
if (id && type === 'pdp') {
|
||||
this.setData({ testResultId: id })
|
||||
this.loadDetail(id)
|
||||
return
|
||||
}
|
||||
const result = wx.getStorageSync('pdpResult')
|
||||
if (result) {
|
||||
this.setData({ result: withPercentagesInt(result) })
|
||||
this.initPayInfoFromRuntime('pdp')
|
||||
} else {
|
||||
wx.showToast({ title: '暂无测试结果', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
}
|
||||
},
|
||||
|
||||
loadDetail(id) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
const token = app.globalData?.token || wx.getStorageSync('token') || ''
|
||||
if (!apiBase) { wx.showToast({ title: '配置异常', icon: 'none' }); return }
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/detail`,
|
||||
method: 'GET',
|
||||
header: token ? { Authorization: `Bearer ${token}` } : {},
|
||||
data: { id },
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
const payload = res.data.data || {}
|
||||
const data = payload.data || payload
|
||||
const isPaid = !!payload.isPaid
|
||||
const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0
|
||||
const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0)
|
||||
const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0)
|
||||
this.setData({ result: withPercentagesInt(data) })
|
||||
const payInfo = {
|
||||
requiresPayment: needPaymentToUnlock,
|
||||
isPaid,
|
||||
amountYuan: needPaymentToUnlock ? amountYuan : 0
|
||||
}
|
||||
this.setData({ payInfo })
|
||||
} else {
|
||||
wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' })
|
||||
}
|
||||
},
|
||||
fail: () => wx.showToast({ title: '网络错误', icon: 'none' }),
|
||||
complete: () => wx.hideLoading()
|
||||
})
|
||||
},
|
||||
|
||||
initPayInfoFromRuntime(testType) {
|
||||
app.getRuntimeConfig()
|
||||
.then((cfg) => {
|
||||
const reportRequires = cfg.reportRequiresPayment || {}
|
||||
const pricing = cfg.pricing || {}
|
||||
const requiresPayment = !!(reportRequires && reportRequires[testType])
|
||||
const amountYuan = Number(pricing[testType]) || (requiresPayment ? 1.98 : 0)
|
||||
this.setData({
|
||||
payInfo: { requiresPayment, isPaid: false, amountYuan }
|
||||
})
|
||||
})
|
||||
.catch(() => this.setData({ payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 } }))
|
||||
},
|
||||
|
||||
unlockFullReport() {
|
||||
const { payInfo, testResultId, hasReloadedAfterPay } = this.data
|
||||
if (!payInfo.requiresPayment || payInfo.isPaid) return
|
||||
app.ensureLogin && app.ensureLogin().then((logged) => {
|
||||
if (!logged) { wx.showToast({ title: '请先登录', icon: 'none' }); return }
|
||||
payment.purchasePdpTest({
|
||||
testResultId: testResultId || undefined,
|
||||
success: () => {
|
||||
wx.showToast({ title: '已解锁完整报告', icon: 'success' })
|
||||
this.setData({ 'payInfo.isPaid': true })
|
||||
if (testResultId && !hasReloadedAfterPay) {
|
||||
this.setData({ hasReloadedAfterPay: true })
|
||||
setTimeout(() => this.loadDetail(testResultId), 500)
|
||||
}
|
||||
},
|
||||
fail: () => {}
|
||||
})
|
||||
})
|
||||
},
|
||||
|
||||
retakeTest() {
|
||||
if (!this.data.testResultId) {
|
||||
wx.removeStorageSync('pdpResult')
|
||||
}
|
||||
wx.navigateTo({ url: '/pages/test/pdp' })
|
||||
},
|
||||
|
||||
goHome() {
|
||||
const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal'
|
||||
if (scope === 'enterprise') {
|
||||
wx.navigateTo({ url: '/pages/enterprise/index' })
|
||||
} else {
|
||||
wx.switchTab({ url: '/pages/index/index' })
|
||||
}
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
const result = this.data.result
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的PDP类型是${result?.description?.type}${result?.description?.emoji},来测测你的吧!`,
|
||||
path: getSharePathByScope('/pages/index/index')
|
||||
}
|
||||
},
|
||||
|
||||
onShareTimeline() {
|
||||
const result = this.data.result
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
return {
|
||||
title: `我的PDP类型是${result?.description?.type}${result?.description?.emoji},来测测你的吧!`,
|
||||
query: buildShareQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -71,7 +71,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="action-section">
|
||||
<!-- <view class="action-section">
|
||||
<button class="btn btn-primary" open-type="share">
|
||||
<text class="btn-text">分享结果</text>
|
||||
</button>
|
||||
@@ -81,7 +81,7 @@
|
||||
<view class="btn btn-outline" bindtap="goHome">
|
||||
<text class="btn-text-outline">返回首页</text>
|
||||
</view>
|
||||
</view>
|
||||
</view> -->
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
|
||||
@@ -23,10 +23,15 @@ Page({
|
||||
onLoad() {
|
||||
const questions = shuffleQuestions(discQuestions)
|
||||
this.setData({ questions, currentQuestion: questions[0] })
|
||||
try { require('../../utils/analytics').track('test_start', { type: 'disc', total: questions.length }) } catch (e) {}
|
||||
this.startTimer()
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (this._advanceTimer) {
|
||||
clearTimeout(this._advanceTimer)
|
||||
this._advanceTimer = null
|
||||
}
|
||||
if (this.timer) clearInterval(this.timer)
|
||||
},
|
||||
|
||||
@@ -48,28 +53,48 @@ Page({
|
||||
},
|
||||
|
||||
selectAnswer(e) {
|
||||
if (this._advanceTimer) {
|
||||
clearTimeout(this._advanceTimer)
|
||||
this._advanceTimer = null
|
||||
}
|
||||
const value = e.currentTarget.dataset.value
|
||||
const questionId = this.data.currentQuestion.id
|
||||
let answers = { ...this.data.answers }
|
||||
answers[questionId] = value
|
||||
|
||||
this.setData({
|
||||
selectedAnswer: value,
|
||||
answers: answers,
|
||||
answeredCount: Object.keys(answers).length,
|
||||
progress: (Object.keys(answers).length / this.data.total) * 100
|
||||
})
|
||||
const cq = this.data.currentQuestion
|
||||
if (value == null || !cq || cq.id == null) return
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.data.currentIndex < this.data.total - 1) {
|
||||
this.nextQuestion()
|
||||
} else {
|
||||
this.submitTest()
|
||||
const questionId = cq.id
|
||||
const idx = this.data.currentIndex
|
||||
const tot = this.data.total
|
||||
const answers = { ...this.data.answers }
|
||||
answers[questionId] = value
|
||||
const answeredCount = Object.keys(answers).length
|
||||
|
||||
this.setData(
|
||||
{
|
||||
selectedAnswer: value,
|
||||
answers,
|
||||
answeredCount,
|
||||
progress: tot ? (answeredCount / tot) * 100 : 0
|
||||
},
|
||||
() => {
|
||||
this._advanceTimer = setTimeout(() => {
|
||||
this._advanceTimer = null
|
||||
const d = this.data
|
||||
if (d.currentIndex !== idx || !d.currentQuestion || d.currentQuestion.id !== questionId) return
|
||||
if (idx < tot - 1) {
|
||||
this.nextQuestion()
|
||||
} else {
|
||||
this.submitTest()
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
}, 300)
|
||||
)
|
||||
},
|
||||
|
||||
prevQuestion() {
|
||||
if (this._advanceTimer) {
|
||||
clearTimeout(this._advanceTimer)
|
||||
this._advanceTimer = null
|
||||
}
|
||||
if (this.data.currentIndex > 0) {
|
||||
const newIndex = this.data.currentIndex - 1
|
||||
const newQuestion = this.data.questions[newIndex]
|
||||
@@ -131,6 +156,7 @@ Page({
|
||||
|
||||
// 本地缓存 + 全局缓存
|
||||
wx.setStorageSync('discResult', resultData)
|
||||
try { require('../../utils/analytics').track('test_complete', { type: 'disc', result: dominantType, duration: resultData.testDuration }) } catch (e) {}
|
||||
if (app && typeof app.saveTestResult === 'function') {
|
||||
app.saveTestResult('disc', resultData)
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ Page({
|
||||
total,
|
||||
progress: total ? Math.round((1 / total) * 100) : 0
|
||||
})
|
||||
try { require('../../utils/analytics').track('test_start', { type: 'mbti', total }) } catch (e) {}
|
||||
this.startTimer()
|
||||
},
|
||||
|
||||
@@ -43,6 +44,10 @@ Page({
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (this._advanceTimer) {
|
||||
clearTimeout(this._advanceTimer)
|
||||
this._advanceTimer = null
|
||||
}
|
||||
if (this.timer) {
|
||||
clearInterval(this.timer)
|
||||
}
|
||||
@@ -66,36 +71,53 @@ Page({
|
||||
}, 1000)
|
||||
},
|
||||
|
||||
// 选择答案(用闭包保存题号,避免 setTimeout 与 setData 时序导致最后一题未提交)
|
||||
// 选择答案:防抖 + 过期回调校验,避免连点导致重复 next 漏题
|
||||
selectAnswer(e) {
|
||||
if (this._advanceTimer) {
|
||||
clearTimeout(this._advanceTimer)
|
||||
this._advanceTimer = null
|
||||
}
|
||||
const value = e.currentTarget.dataset.value
|
||||
const questionId = this.data.currentQuestion.id
|
||||
const cq = this.data.currentQuestion
|
||||
if (value == null || !cq || cq.id == null) return
|
||||
|
||||
const questionId = cq.id
|
||||
const idx = this.data.currentIndex
|
||||
const tot = this.data.total
|
||||
|
||||
let answers = { ...this.data.answers }
|
||||
const answers = { ...this.data.answers }
|
||||
answers[questionId] = value
|
||||
const answeredCount = Object.keys(answers).length
|
||||
const progress = tot ? Math.round(((idx + 1) / tot) * 100) : 0
|
||||
|
||||
this.setData({
|
||||
selectedAnswer: value,
|
||||
answers,
|
||||
answeredCount,
|
||||
progress
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (idx < tot - 1) {
|
||||
this.nextQuestion()
|
||||
} else {
|
||||
this.submitTest()
|
||||
this.setData(
|
||||
{
|
||||
selectedAnswer: value,
|
||||
answers,
|
||||
answeredCount,
|
||||
progress
|
||||
},
|
||||
() => {
|
||||
this._advanceTimer = setTimeout(() => {
|
||||
this._advanceTimer = null
|
||||
const d = this.data
|
||||
if (d.currentIndex !== idx || !d.currentQuestion || d.currentQuestion.id !== questionId) return
|
||||
if (idx < tot - 1) {
|
||||
this.nextQuestion()
|
||||
} else {
|
||||
this.submitTest()
|
||||
}
|
||||
}, 320)
|
||||
}
|
||||
}, 320)
|
||||
)
|
||||
},
|
||||
|
||||
// 上一题
|
||||
prevQuestion() {
|
||||
if (this._advanceTimer) {
|
||||
clearTimeout(this._advanceTimer)
|
||||
this._advanceTimer = null
|
||||
}
|
||||
if (this.data.currentIndex > 0) {
|
||||
const newIndex = this.data.currentIndex - 1
|
||||
const newQuestion = this.data.questions[newIndex]
|
||||
@@ -181,6 +203,7 @@ Page({
|
||||
}
|
||||
wx.setStorageSync('mbtiResult', resultData)
|
||||
app.saveTestResult('mbti', resultData)
|
||||
try { require('../../utils/analytics').track('test_complete', { type: 'mbti', result: result.mbtiType, confidence: result.confidence, duration: resultData.testDuration }) } catch (e) {}
|
||||
|
||||
wx.redirectTo({
|
||||
url: '/pages/result/mbti'
|
||||
|
||||
@@ -23,10 +23,15 @@ Page({
|
||||
onLoad() {
|
||||
const questions = shuffleQuestions(pdpQuestions)
|
||||
this.setData({ questions, currentQuestion: questions[0] })
|
||||
try { require('../../utils/analytics').track('test_start', { type: 'pdp', total: questions.length }) } catch (e) {}
|
||||
this.startTimer()
|
||||
},
|
||||
|
||||
onUnload() {
|
||||
if (this._advanceTimer) {
|
||||
clearTimeout(this._advanceTimer)
|
||||
this._advanceTimer = null
|
||||
}
|
||||
if (this.timer) clearInterval(this.timer)
|
||||
},
|
||||
|
||||
@@ -53,28 +58,48 @@ Page({
|
||||
},
|
||||
|
||||
selectAnswer(e) {
|
||||
if (this._advanceTimer) {
|
||||
clearTimeout(this._advanceTimer)
|
||||
this._advanceTimer = null
|
||||
}
|
||||
const value = e.currentTarget.dataset.value
|
||||
const questionId = this.data.currentQuestion.id
|
||||
let answers = { ...this.data.answers }
|
||||
answers[questionId] = value
|
||||
|
||||
this.setData({
|
||||
selectedAnswer: value,
|
||||
answers: answers,
|
||||
answeredCount: Object.keys(answers).length,
|
||||
progress: (Object.keys(answers).length / this.data.total) * 100
|
||||
})
|
||||
const cq = this.data.currentQuestion
|
||||
if (value == null || !cq || cq.id == null) return
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.data.currentIndex < this.data.total - 1) {
|
||||
this.nextQuestion()
|
||||
} else {
|
||||
this.submitTest()
|
||||
const questionId = cq.id
|
||||
const idx = this.data.currentIndex
|
||||
const tot = this.data.total
|
||||
const answers = { ...this.data.answers }
|
||||
answers[questionId] = value
|
||||
const answeredCount = Object.keys(answers).length
|
||||
|
||||
this.setData(
|
||||
{
|
||||
selectedAnswer: value,
|
||||
answers,
|
||||
answeredCount,
|
||||
progress: tot ? (answeredCount / tot) * 100 : 0
|
||||
},
|
||||
() => {
|
||||
this._advanceTimer = setTimeout(() => {
|
||||
this._advanceTimer = null
|
||||
const d = this.data
|
||||
if (d.currentIndex !== idx || !d.currentQuestion || d.currentQuestion.id !== questionId) return
|
||||
if (idx < tot - 1) {
|
||||
this.nextQuestion()
|
||||
} else {
|
||||
this.submitTest()
|
||||
}
|
||||
}, 300)
|
||||
}
|
||||
}, 300)
|
||||
)
|
||||
},
|
||||
|
||||
prevQuestion() {
|
||||
if (this._advanceTimer) {
|
||||
clearTimeout(this._advanceTimer)
|
||||
this._advanceTimer = null
|
||||
}
|
||||
if (this.data.currentIndex > 0) {
|
||||
const newIndex = this.data.currentIndex - 1
|
||||
const newQuestion = this.data.questions[newIndex]
|
||||
@@ -134,6 +159,7 @@ Page({
|
||||
|
||||
// 本地缓存 + 全局缓存
|
||||
wx.setStorageSync('pdpResult', resultData)
|
||||
try { require('../../utils/analytics').track('test_complete', { type: 'pdp', result: dominantType, duration: resultData.testDuration }) } catch (e) {}
|
||||
if (app && typeof app.saveTestResult === 'function') {
|
||||
app.saveTestResult('pdp', resultData)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user