feat: 双端小程序分享路径与测试入口上下文统一
1、修复了结果页、测试页与 test-select 的跳转上下文丢失问题。 2、调整 api/Test 与小程序 app/share 逻辑,补全第三方/企业场景参数透传。 3、优化 DISC/MBTI/PDP/SBTI 结果页样式与按钮交互,一致化微信与抖音端。 Made-with: Cursor
This commit is contained in:
@@ -581,14 +581,17 @@ class Test extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过分享链接查看测试结果(无需登录,凭 st 校验防篡改)
|
||||
* GET /api/test/share-detail?id=123&st=...
|
||||
* 通过分享链接查看测试结果(无需登录)
|
||||
* - 推荐:GET /api/test/share-detail?id=123&type=sbti(type 与库中 testType 一致即可)
|
||||
* - 兼容旧链:GET ...&st=...(凭 st 校验,可不传 type)
|
||||
*/
|
||||
public function shareDetail()
|
||||
{
|
||||
$id = (int) Request::param('id', 0);
|
||||
$st = trim((string) Request::param('st', ''));
|
||||
if ($id <= 0 || $st === '') {
|
||||
$typeParam = trim((string) Request::param('type', ''));
|
||||
|
||||
if ($id <= 0) {
|
||||
return error('缺少参数', 400);
|
||||
}
|
||||
|
||||
@@ -597,16 +600,23 @@ class Test extends BaseController
|
||||
return error('记录不存在', 404);
|
||||
}
|
||||
|
||||
if (!$this->verifyShareToken($row, $st)) {
|
||||
return error('分享链接无效或已失效', 403);
|
||||
}
|
||||
|
||||
$testType = (string) ($row['testType'] ?? '');
|
||||
if ($testType === 'resume') {
|
||||
$rowTestType = (string) ($row['testType'] ?? '');
|
||||
if ($rowTestType === 'resume') {
|
||||
return error('该类型不支持分享查看', 403);
|
||||
}
|
||||
|
||||
$out = $this->buildTestDetailPayload($row);
|
||||
if ($st !== '') {
|
||||
if (!$this->verifyShareToken($row, $st)) {
|
||||
return error('分享链接无效或已失效', 403);
|
||||
}
|
||||
} else {
|
||||
if (!$this->shareDetailTypeMatchesRow($typeParam, $rowTestType)) {
|
||||
return error('参数不匹配', 400);
|
||||
}
|
||||
}
|
||||
|
||||
// 分享落地:访客不应因「答题人资料未完善」看到截断结果,也不应引导访客去完善自己的资料
|
||||
$out = $this->buildTestDetailPayload($row, true);
|
||||
|
||||
return success(array_merge($out, [
|
||||
'shareToken' => $this->computeShareToken($row),
|
||||
@@ -614,9 +624,32 @@ class Test extends BaseController
|
||||
}
|
||||
|
||||
/**
|
||||
* 与 detail 接口一致的详情结构(data 为结果 JSON)
|
||||
* 公开分享(无 st)时校验 URL 的 type 与记录 testType 一致,避免串页
|
||||
*/
|
||||
protected function buildTestDetailPayload(array $row): array
|
||||
protected function shareDetailTypeMatchesRow(string $typeParam, string $rowTestType): bool
|
||||
{
|
||||
$a = strtolower(trim($typeParam));
|
||||
$b = strtolower(trim($rowTestType));
|
||||
if ($a === '' || $b === '') {
|
||||
return false;
|
||||
}
|
||||
if ($a === $b) {
|
||||
return true;
|
||||
}
|
||||
// 人脸:库内可能是 face 或 ai
|
||||
if (($a === 'ai' || $a === 'face') && ($b === 'ai' || $b === 'face')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* 与 detail 接口一致的详情结构(data 为结果 JSON)
|
||||
*
|
||||
* @param bool $forShareViewer true=分享链接打开(/api/test/share-detail):不因答题人资料未完善而截断结果;未付费仍按定价截断
|
||||
*/
|
||||
protected function buildTestDetailPayload(array $row, bool $forShareViewer = false): array
|
||||
{
|
||||
$raw = $row['resultData'] ?? ($row['result'] ?? null);
|
||||
$data = null;
|
||||
@@ -631,15 +664,16 @@ class Test extends BaseController
|
||||
$needPaymentToUnlock = $requiresPayment && !$isPaid && $paidAmount > 0;
|
||||
$subjectUserId = (int) ($row['userId'] ?? 0);
|
||||
$profileIncomplete = $subjectUserId > 0 ? !self::isWechatProfileComplete($subjectUserId) : false;
|
||||
$applyProfileGate = $profileIncomplete && !$forShareViewer;
|
||||
|
||||
if ($data !== null && is_array($data)) {
|
||||
if (in_array($testType, ['face', 'ai'], true)) {
|
||||
$eid = isset($row['enterpriseId']) ? (int) $row['enterpriseId'] : 0;
|
||||
$data = self::stripFaceAiSbtiIfEnterpriseDisabled($data, $eid > 0 ? $eid : null);
|
||||
if ($needPaymentToUnlock || $profileIncomplete) {
|
||||
if ($needPaymentToUnlock || $applyProfileGate) {
|
||||
$data = self::filterFaceResultToPreview($data);
|
||||
}
|
||||
} elseif ($needPaymentToUnlock || $profileIncomplete) {
|
||||
} elseif ($needPaymentToUnlock || $applyProfileGate) {
|
||||
$data = $this->filterResultToPartial($testType, $data);
|
||||
}
|
||||
}
|
||||
@@ -654,7 +688,7 @@ class Test extends BaseController
|
||||
'paidAmount' => $paidAmount,
|
||||
'amountYuan' => $paidAmount > 0 ? round($paidAmount / 100, 2) : 0,
|
||||
'needPaymentToUnlock' => $needPaymentToUnlock,
|
||||
'profileIncomplete' => $profileIncomplete,
|
||||
'profileIncomplete' => $forShareViewer ? false : $profileIncomplete,
|
||||
'orderId' => isset($row['orderId']) ? (int) $row['orderId'] : null,
|
||||
'paidAt' => isset($row['paidAt']) ? (int) $row['paidAt'] : null,
|
||||
];
|
||||
@@ -813,7 +847,11 @@ class Test extends BaseController
|
||||
return error('保存测试结果失败', 500);
|
||||
}
|
||||
|
||||
return success(null, '提交成功');
|
||||
// 返回记录 id,供小程序结果页带参跳转与分享(shareToken 由 /api/test/detail 下发)
|
||||
return success([
|
||||
'id' => (int) $id,
|
||||
'testType' => $testType,
|
||||
], '提交成功');
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -368,13 +368,18 @@ App({
|
||||
tt.setStorageSync(key, result)
|
||||
this.globalData[key] = result
|
||||
|
||||
if (this.globalData.token) {
|
||||
const scope = this.globalData.appScope || 'personal'
|
||||
const storedUser = tt.getStorageSync('userInfo') || null
|
||||
const enterpriseId =
|
||||
scope === 'enterprise'
|
||||
? (this.globalData.enterpriseIdFromScene || (this.globalData.userInfo && this.globalData.userInfo.enterpriseId) || (storedUser && storedUser.enterpriseId) || null)
|
||||
: null
|
||||
if (!this.globalData.token) {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
const scope = this.globalData.appScope || 'personal'
|
||||
const storedUser = tt.getStorageSync('userInfo') || null
|
||||
const enterpriseId =
|
||||
scope === 'enterprise'
|
||||
? (this.globalData.enterpriseIdFromScene || (this.globalData.userInfo && this.globalData.userInfo.enterpriseId) || (storedUser && storedUser.enterpriseId) || null)
|
||||
: null
|
||||
|
||||
return new Promise((resolve) => {
|
||||
tt.request({
|
||||
url: `${this.globalData.apiBase}/api/test/submit`,
|
||||
method: 'POST',
|
||||
@@ -390,9 +395,17 @@ App({
|
||||
enterpriseId: enterpriseId || undefined,
|
||||
testDuration: result.testDuration || 0,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200 && res.data.data && typeof res.data.data === 'object') {
|
||||
resolve(res.data.data)
|
||||
} else {
|
||||
resolve({})
|
||||
}
|
||||
},
|
||||
fail: () => resolve({})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
getTestResult(type) {
|
||||
|
||||
@@ -106,8 +106,6 @@ Page({
|
||||
|
||||
onLoad(options) {
|
||||
this._payInfoSetByDetail = false
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
|
||||
// 同步审核模式
|
||||
this.setData({ reviewMode: !!app.globalData.reviewMode })
|
||||
@@ -136,55 +134,95 @@ Page({
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 从历史记录进入:根据ID从后端读取数据库中的结果
|
||||
if (id && type === 'ai') {
|
||||
const token = app.globalData.token || tt.getStorageSync('token')
|
||||
const apiBase = app.globalData.apiBase
|
||||
if (!token || !apiBase) {
|
||||
tt.showToast({ title: '未登录,无法读取历史记录', icon: 'none' })
|
||||
const idStr = options && options.id != null && options.id !== '' ? String(options.id) : ''
|
||||
const st = options && options.st ? String(options.st).trim() : ''
|
||||
const rawType = options && options.type ? String(options.type).toLowerCase() : ''
|
||||
const faceType = rawType || (idStr ? 'ai' : '')
|
||||
|
||||
if (idStr && (faceType === 'ai' || faceType === 'face')) {
|
||||
this.setData({ testResultId: idStr })
|
||||
this.loadFaceRecordById(idStr, st, faceType)
|
||||
return
|
||||
}
|
||||
|
||||
if (idStr) {
|
||||
tt.showToast({ title: '链接参数无效', icon: 'none' })
|
||||
setTimeout(() => tt.navigateBack(), 1500)
|
||||
return
|
||||
}
|
||||
|
||||
this.startAnalysis()
|
||||
},
|
||||
|
||||
loadFaceRecordById(id, st, typeParam) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
if (!apiBase) {
|
||||
tt.showToast({ title: '配置异常', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const token = app.globalData.token || tt.getStorageSync('token') || ''
|
||||
|
||||
const applyPayload = (payload) => {
|
||||
tt.hideLoading()
|
||||
const apiData = payload.data || payload
|
||||
this.initPayInfoFromRuntime(!!payload.requiresPayment, !!payload.isPaid, payload)
|
||||
this.processResult(apiData)
|
||||
}
|
||||
|
||||
const loadDetailAuthed = () => {
|
||||
if (!token) {
|
||||
tt.hideLoading()
|
||||
tt.showToast({ title: '未登录,无法查看该记录', icon: 'none' })
|
||||
setTimeout(() => tt.navigateBack(), 1500)
|
||||
return
|
||||
}
|
||||
|
||||
tt.showLoading({ title: '加载历史记录...' })
|
||||
tt.showLoading({ title: '加载中...' })
|
||||
tt.request({
|
||||
url: `${apiBase}/api/test/detail`,
|
||||
method: 'GET',
|
||||
header: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
header: { Authorization: `Bearer ${token}` },
|
||||
data: { id },
|
||||
success: (res) => {
|
||||
tt.hideLoading()
|
||||
if (res.statusCode === 200 && res.data && res.data.data) {
|
||||
const payload = res.data.data
|
||||
const apiData = payload.data || payload
|
||||
// 历史详情场景下,记录当前测试记录ID
|
||||
this.setData({ testResultId: id })
|
||||
// 先确定付费状态(设置 _payInfoSetByDetail 标记),再渲染结果
|
||||
// 避免 processResult 内部异步拉全局配置覆盖掉数据库级别的付费判定
|
||||
this.initPayInfoFromRuntime(
|
||||
!!payload.requiresPayment,
|
||||
!!payload.isPaid,
|
||||
payload
|
||||
)
|
||||
this.processResult(apiData)
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200 && res.data.data) {
|
||||
applyPayload(res.data.data)
|
||||
} else {
|
||||
tt.hideLoading()
|
||||
tt.showToast({ title: res.data?.message || '加载失败', icon: 'none' })
|
||||
setTimeout(() => tt.navigateBack(), 1500)
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
tt.hideLoading()
|
||||
tt.showToast({ title: '网络错误,加载失败', icon: 'none' })
|
||||
tt.showToast({ title: '网络错误', icon: 'none' })
|
||||
setTimeout(() => tt.navigateBack(), 1500)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 正常从拍照流程进入:调用 /api/analyze
|
||||
this.startAnalysis()
|
||||
tt.showLoading({ title: '加载中...' })
|
||||
const data = {
|
||||
id: String(id),
|
||||
type: typeParam === 'face' ? 'face' : 'ai'
|
||||
}
|
||||
if (st) data.st = st
|
||||
|
||||
tt.request({
|
||||
url: `${apiBase}/api/test/share-detail`,
|
||||
method: 'GET',
|
||||
data,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200 && res.data.data) {
|
||||
applyPayload(res.data.data)
|
||||
return
|
||||
}
|
||||
tt.hideLoading()
|
||||
loadDetailAuthed()
|
||||
},
|
||||
fail: () => {
|
||||
tt.hideLoading()
|
||||
loadDetailAuthed()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onShow() {
|
||||
@@ -606,7 +644,14 @@ Page({
|
||||
const r = this.data.result
|
||||
const rm = this.data.reviewMode
|
||||
const t = rm ? '测试结果' : (this.data.aiAnalysisText || '分析')
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
const { getResultSharePath, getSharePathByScope } = require('../../utils/share')
|
||||
const tid = this.data.testResultId
|
||||
if (tid) {
|
||||
return {
|
||||
title: `${t}:我是${r?.mbti} ${r?.pdpEmoji}${r?.pdp}型,来测测你的!`,
|
||||
path: getResultSharePath('/pages/index/result', { id: tid, type: 'ai' })
|
||||
}
|
||||
}
|
||||
return {
|
||||
title: `${t}:我是${r?.mbti} ${r?.pdpEmoji}${r?.pdp}型,来测测你的!`,
|
||||
path: getSharePathByScope('/pages/index/index')
|
||||
@@ -617,10 +662,11 @@ Page({
|
||||
const r = this.data.result
|
||||
const rm = this.data.reviewMode
|
||||
const t = rm ? '测试结果' : (this.data.aiAnalysisText || '分析')
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
const { getResultShareTimelineQuery, buildShareQuery } = require('../../utils/share')
|
||||
const tid = this.data.testResultId
|
||||
return {
|
||||
title: `${t}:我是${r?.mbti} ${r?.pdpEmoji}${r?.pdp}型,来测测你的!`,
|
||||
query: buildShareQuery()
|
||||
query: tid ? getResultShareTimelineQuery({ id: tid, type: 'ai' }) : buildShareQuery()
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@@ -34,12 +34,23 @@ Page({
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadStats()
|
||||
this.loadBindings(true)
|
||||
app.ensureLogin()
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
tt.showToast({ title: '请先登录后查看推广中心', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.loadStats()
|
||||
this.loadBindings(true)
|
||||
})
|
||||
.catch(() => {
|
||||
tt.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||
})
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadStats()
|
||||
const token = app.globalData.token || tt.getStorageSync('token')
|
||||
if (token) this.loadStats()
|
||||
},
|
||||
|
||||
/** 加载推广统计数据 */
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const app = getApp()
|
||||
const { getApiBase } = require('../../utils/request')
|
||||
|
||||
Page({
|
||||
@@ -8,7 +9,18 @@ Page({
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadPoster()
|
||||
app.ensureLogin()
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
this.setData({ loading: false, loadingText: '请先登录后生成海报' })
|
||||
tt.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.loadPoster()
|
||||
})
|
||||
.catch(() => {
|
||||
this.setData({ loading: false, loadingText: '登录失败,请重试' })
|
||||
})
|
||||
},
|
||||
|
||||
/** 从后端接口下载完整合成海报 */
|
||||
|
||||
@@ -44,8 +44,8 @@ Page({
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
const id = options && options.id != null && options.id !== '' ? String(options.id) : ''
|
||||
const type = options && options.type ? String(options.type).toLowerCase() : ''
|
||||
if (id && type === 'disc') {
|
||||
this.setData({ testResultId: id })
|
||||
this.loadDetail(id)
|
||||
|
||||
@@ -31,8 +31,8 @@ Page({
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
const id = options && options.id != null && options.id !== '' ? String(options.id) : ''
|
||||
const type = options && options.type ? String(options.type).toLowerCase() : ''
|
||||
|
||||
if (id && type === 'mbti') {
|
||||
this.setData({ testResultId: id })
|
||||
|
||||
@@ -48,8 +48,8 @@ Page({
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
const id = options && options.id != null && options.id !== '' ? String(options.id) : ''
|
||||
const type = options && options.type ? String(options.type).toLowerCase() : ''
|
||||
if (id && type === 'pdp') {
|
||||
this.setData({ testResultId: id })
|
||||
this.loadDetail(id)
|
||||
|
||||
@@ -28,12 +28,28 @@ Page({
|
||||
const fileUrl = options.fileUrl ? decodeURIComponent(options.fileUrl) : ''
|
||||
this.setData({ fileUrl })
|
||||
|
||||
// 从历史记录进入:直接读已存的数据,不重新生成
|
||||
if (options.id && options.type === 'resume') {
|
||||
this.loadFromHistory(options.id)
|
||||
} else {
|
||||
this.fetchResumeAnalysis()
|
||||
const run = () => {
|
||||
if (options.id && options.type === 'resume') {
|
||||
this.loadFromHistory(options.id)
|
||||
} else {
|
||||
this.fetchResumeAnalysis()
|
||||
}
|
||||
}
|
||||
|
||||
app.ensureLogin()
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
this.setData({
|
||||
loading: false,
|
||||
error: '请先登录后查看报告;若已登录请下拉重试或返回重进。'
|
||||
})
|
||||
return
|
||||
}
|
||||
run()
|
||||
})
|
||||
.catch(() => {
|
||||
this.setData({ loading: false, error: '登录失败,请返回重试。' })
|
||||
})
|
||||
},
|
||||
|
||||
onShow() {
|
||||
|
||||
@@ -6,10 +6,12 @@ Page({
|
||||
permFace: true,
|
||||
permMbti: true,
|
||||
permPdp: true,
|
||||
permDisc: true
|
||||
permDisc: true,
|
||||
allTestsDisabled: false
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
app.ensureLogin().catch(() => {})
|
||||
this._syncPerms()
|
||||
},
|
||||
|
||||
@@ -19,11 +21,16 @@ Page({
|
||||
|
||||
_syncPerms() {
|
||||
const p = app.globalData.enterprisePermissions
|
||||
const permFace = !p || p.face !== false
|
||||
const permMbti = !p || p.mbti !== false
|
||||
const permPdp = !p || p.pdp !== false
|
||||
const permDisc = !p || p.disc !== false
|
||||
this.setData({
|
||||
permFace: !p || p.face !== false,
|
||||
permMbti: !p || p.mbti !== false,
|
||||
permPdp: !p || p.pdp !== false,
|
||||
permDisc: !p || p.disc !== false
|
||||
permFace,
|
||||
permMbti,
|
||||
permPdp,
|
||||
permDisc,
|
||||
allTestsDisabled: p && !permMbti && !permPdp && !permDisc
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@@ -37,4 +37,8 @@
|
||||
</view>
|
||||
<text class="entry-arrow">→</text>
|
||||
</view>
|
||||
|
||||
<view class="card perm-disabled-hint" tt:if="{{allTestsDisabled}}">
|
||||
<text class="perm-disabled-text">当前环境下暂未开放问卷测试,请联系管理员或从首页进入。</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -35,6 +35,17 @@
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.perm-disabled-hint {
|
||||
text-align: center;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.perm-disabled-text {
|
||||
font-size: 28rpx;
|
||||
color: #888;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 入口卡片(与 result 页 next-btn 同色系) */
|
||||
.entry-card {
|
||||
display: flex;
|
||||
|
||||
@@ -25,8 +25,17 @@ Page({
|
||||
timer: null,
|
||||
|
||||
onLoad() {
|
||||
loadQuestions('disc', {})
|
||||
app.ensureLogin()
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
this.setData({ loading: false })
|
||||
tt.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||
return Promise.reject(new Error('login'))
|
||||
}
|
||||
return loadQuestions('disc', {})
|
||||
})
|
||||
.then((questions) => {
|
||||
if (!questions) return
|
||||
if (!questions.length) {
|
||||
tt.showToast({ title: '暂无题目', icon: 'none' })
|
||||
this.setData({ loading: false })
|
||||
@@ -47,6 +56,7 @@ Page({
|
||||
this.startTimer()
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err && err.message === 'login') return
|
||||
this.setData({ loading: false })
|
||||
tt.showToast({ title: (err && err.message) || '加载失败', icon: 'none' })
|
||||
})
|
||||
@@ -179,9 +189,16 @@ Page({
|
||||
tt.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)
|
||||
app.saveTestResult('disc', resultData).then((extra) => {
|
||||
const rid = extra && extra.id
|
||||
if (rid) {
|
||||
tt.redirectTo({ url: `/pages/result/disc?id=${rid}&type=disc` })
|
||||
} else {
|
||||
tt.redirectTo({ url: '/pages/result/disc' })
|
||||
}
|
||||
})
|
||||
} else {
|
||||
tt.redirectTo({ url: '/pages/result/disc' })
|
||||
}
|
||||
|
||||
tt.redirectTo({ url: '/pages/result/disc' })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -27,8 +27,17 @@ Page({
|
||||
timer: null,
|
||||
|
||||
onLoad() {
|
||||
loadQuestions('mbti', {})
|
||||
app.ensureLogin()
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
this.setData({ loading: false })
|
||||
tt.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||
return Promise.reject(new Error('login'))
|
||||
}
|
||||
return loadQuestions('mbti', {})
|
||||
})
|
||||
.then((questions) => {
|
||||
if (!questions) return
|
||||
const total = questions.length
|
||||
if (!total) {
|
||||
tt.showToast({ title: '暂无题目', icon: 'none' })
|
||||
@@ -52,6 +61,7 @@ Page({
|
||||
this.startTimer()
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err && err.message === 'login') return
|
||||
this.setData({ loading: false })
|
||||
tt.showToast({ title: (err && err.message) || '加载失败', icon: 'none' })
|
||||
})
|
||||
@@ -213,10 +223,14 @@ Page({
|
||||
}
|
||||
tt.setStorageSync('mbtiResult', resultData)
|
||||
try { require('../../utils/analytics').track('test_complete', { type: 'mbti', result: result.mbtiType, confidence: result.confidence, duration: resultData.testDuration }) } catch (e) {}
|
||||
app.saveTestResult('mbti', resultData)
|
||||
|
||||
tt.redirectTo({
|
||||
url: '/pages/result/mbti'
|
||||
app.saveTestResult('mbti', resultData).then((extra) => {
|
||||
const rid = extra && extra.id
|
||||
if (rid) {
|
||||
tt.redirectTo({ url: `/pages/result/mbti?id=${rid}&type=mbti` })
|
||||
} else {
|
||||
tt.redirectTo({ url: '/pages/result/mbti' })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@@ -25,8 +25,17 @@ Page({
|
||||
timer: null,
|
||||
|
||||
onLoad() {
|
||||
loadQuestions('pdp', {})
|
||||
app.ensureLogin()
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
this.setData({ loading: false })
|
||||
tt.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||
return Promise.reject(new Error('login'))
|
||||
}
|
||||
return loadQuestions('pdp', {})
|
||||
})
|
||||
.then((questions) => {
|
||||
if (!questions) return
|
||||
if (!questions.length) {
|
||||
tt.showToast({ title: '暂无题目', icon: 'none' })
|
||||
this.setData({ loading: false })
|
||||
@@ -47,6 +56,7 @@ Page({
|
||||
this.startTimer()
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err && err.message === 'login') return
|
||||
this.setData({ loading: false })
|
||||
tt.showToast({ title: (err && err.message) || '加载失败', icon: 'none' })
|
||||
})
|
||||
@@ -182,9 +192,16 @@ Page({
|
||||
tt.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)
|
||||
app.saveTestResult('pdp', resultData).then((extra) => {
|
||||
const rid = extra && extra.id
|
||||
if (rid) {
|
||||
tt.redirectTo({ url: `/pages/result/pdp?id=${rid}&type=pdp` })
|
||||
} else {
|
||||
tt.redirectTo({ url: '/pages/result/pdp' })
|
||||
}
|
||||
})
|
||||
} else {
|
||||
tt.redirectTo({ url: '/pages/result/pdp' })
|
||||
}
|
||||
|
||||
tt.redirectTo({ url: '/pages/result/pdp' })
|
||||
}
|
||||
})
|
||||
|
||||
@@ -40,4 +40,44 @@ function getSharePathByScope(personalBasePath) {
|
||||
return getSharePath(base)
|
||||
}
|
||||
|
||||
module.exports = { buildShareQuery, getSharePath, getSharePathByScope }
|
||||
/** 测试结果页分享:id + type + fs=1 + 邀请参数 */
|
||||
function getResultSharePath(resultPagePath, opts) {
|
||||
const id = opts && opts.id
|
||||
const type = opts && opts.type
|
||||
if (!id || !type) {
|
||||
return getSharePathByScope('/pages/index/index')
|
||||
}
|
||||
const q =
|
||||
'id=' +
|
||||
encodeURIComponent(String(id)) +
|
||||
'&type=' +
|
||||
encodeURIComponent(String(type)) +
|
||||
'&fs=1'
|
||||
const inv = buildShareQuery()
|
||||
return inv ? resultPagePath + '?' + q + '&' + inv : resultPagePath + '?' + q
|
||||
}
|
||||
|
||||
/** 单页模式 / 部分场景分享用 query:id + type + fs=1 + 邀请参数 */
|
||||
function getResultShareTimelineQuery(opts) {
|
||||
const id = opts && opts.id
|
||||
const type = opts && opts.type
|
||||
if (!id || !type) {
|
||||
return buildShareQuery()
|
||||
}
|
||||
const base =
|
||||
'id=' +
|
||||
encodeURIComponent(String(id)) +
|
||||
'&type=' +
|
||||
encodeURIComponent(String(type)) +
|
||||
'&fs=1'
|
||||
const inv = buildShareQuery()
|
||||
return inv ? base + '&' + inv : base
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
buildShareQuery,
|
||||
getSharePath,
|
||||
getSharePathByScope,
|
||||
getResultSharePath,
|
||||
getResultShareTimelineQuery
|
||||
}
|
||||
|
||||
@@ -47,8 +47,8 @@ App({
|
||||
// 超管配置的默认企业 ID(无 scene/eid 等入口参数时回落)
|
||||
defaultEnterpriseId: null,
|
||||
// API基础地址(开发时用本地,生产环境替换为实际域名)
|
||||
apiBase: 'https://mbtiapi.quwanzhi.com',
|
||||
//apiBase: 'http://mbti.com',
|
||||
//apiBase: 'https://mbtiapi.quwanzhi.com',
|
||||
apiBase: 'http://mbti.com',
|
||||
// VIP信息
|
||||
vipInfo: null,
|
||||
// 测试次数
|
||||
@@ -418,16 +418,19 @@ App({
|
||||
})
|
||||
},
|
||||
|
||||
// 保存测试结果
|
||||
// 保存测试结果(同步服务端后 resolve { id, testType },用于结果页 URL 与分享)
|
||||
saveTestResult(type, result) {
|
||||
const { getEnterpriseIdForApiPayload } = require('./utils/enterpriseContext.js')
|
||||
const key = `${type}Result`
|
||||
wx.setStorageSync(key, result)
|
||||
this.globalData[key] = result
|
||||
|
||||
// 同步到服务器(需携带 token,后端从 JWT 解析 userId)
|
||||
if (this.globalData.token) {
|
||||
const enterpriseId = getEnterpriseIdForApiPayload()
|
||||
|
||||
if (!this.globalData.token) {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
const enterpriseId = getEnterpriseIdForApiPayload()
|
||||
return new Promise((resolve) => {
|
||||
wx.request({
|
||||
url: `${this.globalData.apiBase}/api/test/submit`,
|
||||
method: 'POST',
|
||||
@@ -443,9 +446,17 @@ App({
|
||||
enterpriseId: enterpriseId != null ? enterpriseId : undefined,
|
||||
testDuration: result.testDuration || 0,
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
},
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200 && res.data.data && typeof res.data.data === 'object') {
|
||||
resolve(res.data.data)
|
||||
} else {
|
||||
resolve({})
|
||||
}
|
||||
},
|
||||
fail: () => resolve({})
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
// 获取测试结果
|
||||
|
||||
@@ -108,8 +108,6 @@ Page({
|
||||
|
||||
onLoad(options) {
|
||||
this._payInfoSetByDetail = false
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
|
||||
// 加载文案配置(分析中提示、报告标题等)
|
||||
const tc = app.globalData.textConfig
|
||||
@@ -135,55 +133,101 @@ Page({
|
||||
}).catch(() => {})
|
||||
}
|
||||
|
||||
// 从历史记录进入:根据ID从后端读取数据库中的结果
|
||||
if (id && type === 'ai') {
|
||||
const token = app.globalData.token || wx.getStorageSync('token')
|
||||
const apiBase = app.globalData.apiBase
|
||||
if (!token || !apiBase) {
|
||||
wx.showToast({ title: '未登录,无法读取历史记录', icon: 'none' })
|
||||
const idStr = options && options.id != null && options.id !== '' ? String(options.id) : ''
|
||||
const st = options && options.st ? String(options.st).trim() : ''
|
||||
const rawType = options && options.type ? String(options.type).toLowerCase() : ''
|
||||
// 人脸记录在库中可能是 face 或 ai;仅 id 时默认识别为人脸报告(兼容旧分享)
|
||||
const faceType = rawType || (idStr ? 'ai' : '')
|
||||
|
||||
// 带记录 id:只读库展示,禁止落入 startAnalysis 造成「二次分析」
|
||||
if (idStr && (faceType === 'ai' || faceType === 'face')) {
|
||||
this.setData({ testResultId: idStr })
|
||||
this.loadFaceRecordById(idStr, st, faceType)
|
||||
return
|
||||
}
|
||||
|
||||
if (idStr) {
|
||||
wx.showToast({ title: '链接参数无效', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
return
|
||||
}
|
||||
|
||||
// 无 id:正常从拍照流程进入,调用 /api/analyze
|
||||
this.startAnalysis()
|
||||
},
|
||||
|
||||
/**
|
||||
* 加载人脸报告:优先免登录 share-detail(好友分享),失败且已登录再 detail
|
||||
*/
|
||||
loadFaceRecordById(id, st, typeParam) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
if (!apiBase) {
|
||||
wx.showToast({ title: '配置异常', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const token = app.globalData.token || wx.getStorageSync('token') || ''
|
||||
|
||||
const applyPayload = (payload) => {
|
||||
wx.hideLoading()
|
||||
const apiData = payload.data || payload
|
||||
this.initPayInfoFromRuntime(!!payload.requiresPayment, !!payload.isPaid, payload)
|
||||
this.processResult(apiData)
|
||||
}
|
||||
|
||||
const loadDetailAuthed = () => {
|
||||
if (!token) {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '未登录,无法查看该记录', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
return
|
||||
}
|
||||
|
||||
wx.showLoading({ title: '加载历史记录...' })
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/detail`,
|
||||
method: 'GET',
|
||||
header: {
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
header: { Authorization: `Bearer ${token}` },
|
||||
data: { id },
|
||||
success: (res) => {
|
||||
wx.hideLoading()
|
||||
if (res.statusCode === 200 && res.data && res.data.data) {
|
||||
const payload = res.data.data
|
||||
const apiData = payload.data || payload
|
||||
// 历史详情场景下,记录当前测试记录ID
|
||||
this.setData({ testResultId: id })
|
||||
// 先确定付费状态(设置 _payInfoSetByDetail 标记),再渲染结果
|
||||
// 避免 processResult 内部异步拉全局配置覆盖掉数据库级别的付费判定
|
||||
this.initPayInfoFromRuntime(
|
||||
!!payload.requiresPayment,
|
||||
!!payload.isPaid,
|
||||
payload
|
||||
)
|
||||
this.processResult(apiData)
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200 && res.data.data) {
|
||||
applyPayload(res.data.data)
|
||||
} else {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
}
|
||||
},
|
||||
fail: () => {
|
||||
wx.hideLoading()
|
||||
wx.showToast({ title: '网络错误,加载失败', icon: 'none' })
|
||||
wx.showToast({ title: '网络错误', icon: 'none' })
|
||||
setTimeout(() => wx.navigateBack(), 1500)
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// 正常从拍照流程进入:调用 /api/analyze
|
||||
this.startAnalysis()
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
const data = {
|
||||
id: String(id),
|
||||
type: typeParam === 'face' ? 'face' : 'ai'
|
||||
}
|
||||
if (st) data.st = st
|
||||
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/share-detail`,
|
||||
method: 'GET',
|
||||
data,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200 && res.data.data) {
|
||||
applyPayload(res.data.data)
|
||||
return
|
||||
}
|
||||
wx.hideLoading()
|
||||
loadDetailAuthed()
|
||||
},
|
||||
fail: () => {
|
||||
wx.hideLoading()
|
||||
loadDetailAuthed()
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
onShow() {
|
||||
@@ -631,7 +675,14 @@ Page({
|
||||
onShareAppMessage() {
|
||||
const r = this.data.result
|
||||
const t = this.data.aiAnalysisText || '智能分析'
|
||||
const { getSharePathByScope } = require('../../utils/share')
|
||||
const { getResultSharePath, getSharePathByScope } = require('../../utils/share')
|
||||
const tid = this.data.testResultId
|
||||
if (tid) {
|
||||
return {
|
||||
title: `${t}我是${r?.mbti} ${r?.pdpEmoji}${r?.pdp}型,来测测你的!`,
|
||||
path: getResultSharePath('/pages/index/result', { id: tid, type: 'ai' })
|
||||
}
|
||||
}
|
||||
return {
|
||||
title: `${t}我是${r?.mbti} ${r?.pdpEmoji}${r?.pdp}型,来测测你的!`,
|
||||
path: getSharePathByScope('/pages/index/index')
|
||||
@@ -641,7 +692,14 @@ Page({
|
||||
onShareTimeline() {
|
||||
const r = this.data.result
|
||||
const t = this.data.aiAnalysisText || '智能分析'
|
||||
const { buildShareQuery } = require('../../utils/share')
|
||||
const { getResultShareTimelineQuery, buildShareQuery } = require('../../utils/share')
|
||||
const tid = this.data.testResultId
|
||||
if (tid) {
|
||||
return {
|
||||
title: `${t}我是${r?.mbti} ${r?.pdpEmoji}${r?.pdp}型,来测测你的!`,
|
||||
query: getResultShareTimelineQuery({ id: tid, type: 'ai' })
|
||||
}
|
||||
}
|
||||
return {
|
||||
title: `${t}我是${r?.mbti} ${r?.pdpEmoji}${r?.pdp}型,来测测你的!`,
|
||||
query: buildShareQuery()
|
||||
|
||||
@@ -34,12 +34,24 @@ Page({
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadStats()
|
||||
this.loadBindings(true)
|
||||
// 分享直达时 silentLogin 可能未完成,先 ensureLogin 再拉推广数据,避免 401 空白
|
||||
app.ensureLogin()
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
wx.showToast({ title: '请先登录后查看推广中心', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.loadStats()
|
||||
this.loadBindings(true)
|
||||
})
|
||||
.catch(() => {
|
||||
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||
})
|
||||
},
|
||||
|
||||
onShow() {
|
||||
this.loadStats()
|
||||
const token = app.globalData.token || wx.getStorageSync('token')
|
||||
if (token) this.loadStats()
|
||||
},
|
||||
|
||||
/** 加载推广统计数据 */
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
const app = getApp()
|
||||
const { getApiBase } = require('../../utils/request')
|
||||
const { getEffectiveEnterpriseId } = require('../../utils/enterpriseContext.js')
|
||||
|
||||
@@ -9,7 +10,18 @@ Page({
|
||||
},
|
||||
|
||||
onLoad() {
|
||||
this.loadPoster()
|
||||
app.ensureLogin()
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
this.setData({ loading: false, loadingText: '请先登录后生成海报' })
|
||||
wx.showToast({ title: '请先登录', icon: 'none' })
|
||||
return
|
||||
}
|
||||
this.loadPoster()
|
||||
})
|
||||
.catch(() => {
|
||||
this.setData({ loading: false, loadingText: '登录失败,请重试' })
|
||||
})
|
||||
},
|
||||
|
||||
/** 从后端接口下载完整合成海报 */
|
||||
|
||||
@@ -43,18 +43,24 @@ Page({
|
||||
testResultId: null,
|
||||
shareToken: '',
|
||||
hasReloadedAfterPay: false,
|
||||
fromShare: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
const fromShareFs =
|
||||
options && (String(options.fs) === '1' || options.from === 'share')
|
||||
const id = options && options.id != null && options.id !== '' ? String(options.id) : ''
|
||||
const st = options && options.st ? String(options.st).trim() : ''
|
||||
const type = options && options.type ? String(options.type).toLowerCase() : ''
|
||||
|
||||
if (id && type === 'disc') {
|
||||
this.setData({ testResultId: id })
|
||||
if (options.st) {
|
||||
this.loadShareDetail(id, options.st)
|
||||
} else {
|
||||
this.loadDetail(id)
|
||||
}
|
||||
this.setData({ testResultId: id, fromShare: !!fromShareFs })
|
||||
this.loadShareDetail(id, st, 'disc')
|
||||
return
|
||||
}
|
||||
if (id && st) {
|
||||
this.setData({ testResultId: id, fromShare: true })
|
||||
this.loadShareDetail(id, st, '')
|
||||
return
|
||||
}
|
||||
const raw = wx.getStorageSync('discResult')
|
||||
@@ -86,7 +92,11 @@ Page({
|
||||
isPaid,
|
||||
amountYuan: needPaymentToUnlock ? amountYuan : 0
|
||||
}
|
||||
this.setData({ payInfo })
|
||||
const patch = { payInfo }
|
||||
if (payload.id != null && payload.id !== '') {
|
||||
patch.testResultId = String(payload.id)
|
||||
}
|
||||
this.setData(patch)
|
||||
},
|
||||
|
||||
loadDetail(id) {
|
||||
@@ -111,14 +121,17 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
loadShareDetail(id, st) {
|
||||
loadShareDetail(id, st, testType) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
if (!apiBase) { wx.showToast({ title: '配置异常', icon: 'none' }); return }
|
||||
const data = { id: String(id) }
|
||||
if (st) data.st = st
|
||||
if (testType) data.type = testType
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/share-detail`,
|
||||
method: 'GET',
|
||||
data: { id, st },
|
||||
data,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
this.applyDetailPayload(res.data.data || {})
|
||||
@@ -185,6 +198,10 @@ Page({
|
||||
wx.navigateTo({ url: '/pages/test/disc' })
|
||||
},
|
||||
|
||||
goWantTest() {
|
||||
wx.navigateTo({ url: '/pages/test/disc' })
|
||||
},
|
||||
|
||||
goHome() {
|
||||
const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal'
|
||||
if (scope === 'enterprise') {
|
||||
@@ -201,8 +218,7 @@ Page({
|
||||
title: `我的DISC类型是${result?.dominantType}型(${result?.description?.title}),来测测你的吧!`,
|
||||
path: getResultSharePath('/pages/result/disc', {
|
||||
id: this.data.testResultId,
|
||||
type: 'disc',
|
||||
shareToken: this.data.shareToken
|
||||
type: 'disc'
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -214,8 +230,7 @@ Page({
|
||||
title: `我的DISC类型是${result?.dominantType}型(${result?.description?.title}),来测测你的吧!`,
|
||||
query: getResultShareTimelineQuery({
|
||||
id: this.data.testResultId,
|
||||
type: 'disc',
|
||||
shareToken: this.data.shareToken
|
||||
type: 'disc'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--pages/result/disc.wxml - DISC结果(按旧版模板重构)-->
|
||||
<view class="result-page">
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<view class="content-container">
|
||||
<scroll-view class="content-scroll {{fromShare ? 'content-scroll--with-fixed-footer' : ''}}" scroll-y>
|
||||
<view class="content-container {{fromShare ? 'content-container--with-fixed-footer' : ''}}">
|
||||
<view class="type-card">
|
||||
<view class="type-header">
|
||||
<text class="type-label">您的DISC性格类型</text>
|
||||
@@ -25,7 +25,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="paywall-card" wx:elif="{{result && result.locked}}">
|
||||
<view class="paywall-card" wx:elif="{{result && result.locked && !fromShare}}">
|
||||
<view class="paywall-content">
|
||||
<text class="paywall-fake-title">完整报告需完善资料</text>
|
||||
<text class="paywall-fake-line">请补全头像、昵称并绑定手机号后查看完整 DISC 报告。</text>
|
||||
@@ -88,4 +88,7 @@
|
||||
</view> -->
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="result-share-footer" wx:if="{{fromShare}}">
|
||||
<button class="result-share-footer-btn" bindtap="goWantTest">我也要测试</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -15,6 +15,14 @@
|
||||
padding-bottom: 64rpx;
|
||||
}
|
||||
|
||||
.content-container--with-fixed-footer {
|
||||
padding-bottom: calc(64rpx + 120rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.content-scroll--with-fixed-footer {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.type-card,
|
||||
.analysis-card {
|
||||
background-color: #fff;
|
||||
@@ -195,3 +203,35 @@
|
||||
.paywall-btn { position: absolute; left: 5%; right: 5%; width: 90%; bottom: 132rpx; z-index: 3; padding: 20rpx 0; border-radius: 999rpx; background: linear-gradient(135deg, #3B82F6 0%, #60a5fa 100%); box-shadow: 0 8rpx 24rpx rgba(59,130,246,0.35); display: flex; flex-direction: row; align-items: baseline; justify-content: center; gap: 12rpx; }
|
||||
.paywall-btn-main { font-size: 30rpx; color: #fff; font-weight: 600; }
|
||||
.paywall-btn-price { font-size: 24rpx; color: #ffe5f0; }
|
||||
|
||||
.result-share-footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 200;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 20rpx 32rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
background: #f5f5f5;
|
||||
box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.result-share-footer-btn {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #2563eb 0%, #60a5fa 100%);
|
||||
border: none;
|
||||
}
|
||||
.result-share-footer-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -30,19 +30,24 @@ Page({
|
||||
shareToken: '',
|
||||
hasReloadedAfterPay: false,
|
||||
hasPhone: false,
|
||||
fromShare: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
const fromShareFs =
|
||||
options && (String(options.fs) === '1' || options.from === 'share')
|
||||
const id = options && options.id != null && options.id !== '' ? String(options.id) : ''
|
||||
const st = options && options.st ? String(options.st).trim() : ''
|
||||
const type = options && options.type ? String(options.type).toLowerCase() : ''
|
||||
|
||||
if (id && type === 'mbti') {
|
||||
this.setData({ testResultId: id })
|
||||
if (options.st) {
|
||||
this.loadShareDetail(id, options.st)
|
||||
} else {
|
||||
this.loadDetail(id)
|
||||
}
|
||||
this.setData({ testResultId: id, fromShare: !!fromShareFs })
|
||||
this.loadShareDetail(id, st, 'mbti')
|
||||
return
|
||||
}
|
||||
if (id && st) {
|
||||
this.setData({ testResultId: id, fromShare: true })
|
||||
this.loadShareDetail(id, st, '')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -83,10 +88,14 @@ Page({
|
||||
isPaid,
|
||||
amountYuan: needPaymentToUnlock ? amountYuan : 0
|
||||
}
|
||||
this.setData({
|
||||
const patch = {
|
||||
payInfo,
|
||||
shareToken: payload.shareToken || ''
|
||||
})
|
||||
}
|
||||
if (payload.id != null && payload.id !== '') {
|
||||
patch.testResultId = String(payload.id)
|
||||
}
|
||||
this.setData(patch)
|
||||
},
|
||||
|
||||
loadDetail(id) {
|
||||
@@ -114,17 +123,20 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
loadShareDetail(id, st) {
|
||||
loadShareDetail(id, st, testType) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
if (!apiBase) {
|
||||
wx.showToast({ title: '配置异常', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const data = { id: String(id) }
|
||||
if (st) data.st = st
|
||||
if (testType) data.type = testType
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/share-detail`,
|
||||
method: 'GET',
|
||||
data: { id, st },
|
||||
data,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
this.applyDetailPayload(res.data.data || {})
|
||||
@@ -243,6 +255,10 @@ Page({
|
||||
wx.navigateTo({ url: '/pages/test/mbti' })
|
||||
},
|
||||
|
||||
goWantTest() {
|
||||
wx.navigateTo({ url: '/pages/test/mbti' })
|
||||
},
|
||||
|
||||
goHome() {
|
||||
const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal'
|
||||
if (scope === 'enterprise') {
|
||||
@@ -259,8 +275,7 @@ Page({
|
||||
title: `我的MBTI类型是${result?.mbtiType}(${result?.description?.name}),来测测你的吧!`,
|
||||
path: getResultSharePath('/pages/result/mbti', {
|
||||
id: this.data.testResultId,
|
||||
type: 'mbti',
|
||||
shareToken: this.data.shareToken
|
||||
type: 'mbti'
|
||||
}),
|
||||
imageUrl: '/images/share-mbti.png'
|
||||
}
|
||||
@@ -273,8 +288,7 @@ Page({
|
||||
title: `我的MBTI类型是${result?.mbtiType}(${result?.description?.name}),来测测你的吧!`,
|
||||
query: getResultShareTimelineQuery({
|
||||
id: this.data.testResultId,
|
||||
type: 'mbti',
|
||||
shareToken: this.data.shareToken
|
||||
type: 'mbti'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--pages/result/mbti.wxml - MBTI结果页面(支持付费墙)-->
|
||||
<view class="result-page">
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<view class="content-container">
|
||||
<scroll-view class="content-scroll {{fromShare ? 'content-scroll--with-fixed-footer' : ''}}" scroll-y>
|
||||
<view class="content-container {{fromShare ? 'content-container--with-fixed-footer' : ''}}">
|
||||
<view class="type-card">
|
||||
<view class="type-header">
|
||||
<text class="type-label">您的MBTI性格类型</text>
|
||||
@@ -42,7 +42,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="paywall-card" wx:elif="{{result && result.locked}}">
|
||||
<view class="paywall-card" wx:elif="{{result && result.locked && !fromShare}}">
|
||||
<view class="paywall-content">
|
||||
<text class="paywall-fake-title">完整报告需完善资料</text>
|
||||
<text class="paywall-fake-line">请补全头像、昵称并绑定手机号后查看完整性格分析。</text>
|
||||
@@ -114,4 +114,7 @@
|
||||
</view> -->
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="result-share-footer" wx:if="{{fromShare}}">
|
||||
<button class="result-share-footer-btn" bindtap="goWantTest">我也要测试</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -15,6 +15,14 @@
|
||||
padding-bottom: 64rpx;
|
||||
}
|
||||
|
||||
.content-container--with-fixed-footer {
|
||||
padding-bottom: calc(64rpx + 120rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.content-scroll--with-fixed-footer {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.type-card,
|
||||
.analysis-card {
|
||||
background-color: #fff;
|
||||
@@ -247,3 +255,35 @@
|
||||
}
|
||||
.paywall-btn-main { font-size: 30rpx; color: #fff; font-weight: 600; }
|
||||
.paywall-btn-price { font-size: 24rpx; color: #ffe5f0; }
|
||||
|
||||
.result-share-footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 200;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 20rpx 32rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
background: #f5f5f5;
|
||||
box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.result-share-footer-btn {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #e63946 0%, #ff6b9d 100%);
|
||||
border: none;
|
||||
}
|
||||
.result-share-footer-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -46,18 +46,24 @@ Page({
|
||||
testResultId: null,
|
||||
shareToken: '',
|
||||
hasReloadedAfterPay: false,
|
||||
fromShare: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
const fromShareFs =
|
||||
options && (String(options.fs) === '1' || options.from === 'share')
|
||||
const id = options && options.id != null && options.id !== '' ? String(options.id) : ''
|
||||
const st = options && options.st ? String(options.st).trim() : ''
|
||||
const type = options && options.type ? String(options.type).toLowerCase() : ''
|
||||
|
||||
if (id && type === 'pdp') {
|
||||
this.setData({ testResultId: id })
|
||||
if (options.st) {
|
||||
this.loadShareDetail(id, options.st)
|
||||
} else {
|
||||
this.loadDetail(id)
|
||||
}
|
||||
this.setData({ testResultId: id, fromShare: !!fromShareFs })
|
||||
this.loadShareDetail(id, st, 'pdp')
|
||||
return
|
||||
}
|
||||
if (id && st) {
|
||||
this.setData({ testResultId: id, fromShare: true })
|
||||
this.loadShareDetail(id, st, '')
|
||||
return
|
||||
}
|
||||
const result = wx.getStorageSync('pdpResult')
|
||||
@@ -89,7 +95,11 @@ Page({
|
||||
isPaid,
|
||||
amountYuan: needPaymentToUnlock ? amountYuan : 0
|
||||
}
|
||||
this.setData({ payInfo })
|
||||
const patch = { payInfo }
|
||||
if (payload.id != null && payload.id !== '') {
|
||||
patch.testResultId = String(payload.id)
|
||||
}
|
||||
this.setData(patch)
|
||||
},
|
||||
|
||||
loadDetail(id) {
|
||||
@@ -114,14 +124,17 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
loadShareDetail(id, st) {
|
||||
loadShareDetail(id, st, testType) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
if (!apiBase) { wx.showToast({ title: '配置异常', icon: 'none' }); return }
|
||||
const data = { id: String(id) }
|
||||
if (st) data.st = st
|
||||
if (testType) data.type = testType
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/share-detail`,
|
||||
method: 'GET',
|
||||
data: { id, st },
|
||||
data,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
this.applyDetailPayload(res.data.data || {})
|
||||
@@ -190,6 +203,10 @@ Page({
|
||||
wx.navigateTo({ url: '/pages/test/pdp' })
|
||||
},
|
||||
|
||||
goWantTest() {
|
||||
wx.navigateTo({ url: '/pages/test/pdp' })
|
||||
},
|
||||
|
||||
goHome() {
|
||||
const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal'
|
||||
if (scope === 'enterprise') {
|
||||
@@ -206,8 +223,7 @@ Page({
|
||||
title: `我的PDP类型是${line},来测测你的吧!`,
|
||||
path: getResultSharePath('/pages/result/pdp', {
|
||||
id: this.data.testResultId,
|
||||
type: 'pdp',
|
||||
shareToken: this.data.shareToken
|
||||
type: 'pdp'
|
||||
})
|
||||
}
|
||||
},
|
||||
@@ -219,8 +235,7 @@ Page({
|
||||
title: `我的PDP类型是${line},来测测你的吧!`,
|
||||
query: getResultShareTimelineQuery({
|
||||
id: this.data.testResultId,
|
||||
type: 'pdp',
|
||||
shareToken: this.data.shareToken
|
||||
type: 'pdp'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--pages/result/pdp.wxml - PDP结果(按旧版模板重构)-->
|
||||
<view class="result-page">
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<view class="content-container">
|
||||
<scroll-view class="content-scroll {{fromShare ? 'content-scroll--with-fixed-footer' : ''}}" scroll-y>
|
||||
<view class="content-container {{fromShare ? 'content-container--with-fixed-footer' : ''}}">
|
||||
<view class="type-card">
|
||||
<view class="type-header">
|
||||
<text class="type-label">PDP性格类型</text>
|
||||
@@ -26,7 +26,7 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="paywall-card" wx:elif="{{result && result.locked}}">
|
||||
<view class="paywall-card" wx:elif="{{result && result.locked && !fromShare}}">
|
||||
<view class="paywall-content">
|
||||
<text class="paywall-fake-title">完整报告需完善资料</text>
|
||||
<text class="paywall-fake-line">请补全头像、昵称并绑定手机号后查看完整 PDP 报告。</text>
|
||||
@@ -94,4 +94,7 @@
|
||||
</view> -->
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="result-share-footer" wx:if="{{fromShare}}">
|
||||
<button class="result-share-footer-btn" bindtap="goWantTest">我也要测试</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -15,6 +15,14 @@
|
||||
padding-bottom: 64rpx;
|
||||
}
|
||||
|
||||
.content-container--with-fixed-footer {
|
||||
padding-bottom: calc(64rpx + 120rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.content-scroll--with-fixed-footer {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.type-card,
|
||||
.analysis-card {
|
||||
background-color: #fff;
|
||||
@@ -209,3 +217,35 @@
|
||||
.paywall-btn { position: absolute; left: 5%; right: 5%; width: 90%; bottom: 132rpx; z-index: 3; padding: 20rpx 0; border-radius: 999rpx; background: linear-gradient(135deg, #F59E0B 0%, #fbbf24 100%); box-shadow: 0 8rpx 24rpx rgba(245,158,11,0.35); display: flex; flex-direction: row; align-items: baseline; justify-content: center; gap: 12rpx; }
|
||||
.paywall-btn-main { font-size: 30rpx; color: #fff; font-weight: 600; }
|
||||
.paywall-btn-price { font-size: 24rpx; color: #ffe5f0; }
|
||||
|
||||
.result-share-footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 200;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 20rpx 32rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
background: #f5f5f5;
|
||||
box-shadow: 0 -8rpx 24rpx rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
.result-share-footer-btn {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #d97706 0%, #fbbf24 100%);
|
||||
border: none;
|
||||
}
|
||||
.result-share-footer-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -29,12 +29,29 @@ Page({
|
||||
const fileUrl = options.fileUrl ? decodeURIComponent(options.fileUrl) : ''
|
||||
this.setData({ fileUrl })
|
||||
|
||||
// 从历史记录进入:直接读已存的数据,不重新生成
|
||||
if (options.id && options.type === 'resume') {
|
||||
this.loadFromHistory(options.id)
|
||||
} else {
|
||||
this.fetchResumeAnalysis()
|
||||
const run = () => {
|
||||
if (options.id && options.type === 'resume') {
|
||||
this.loadFromHistory(options.id)
|
||||
} else {
|
||||
this.fetchResumeAnalysis()
|
||||
}
|
||||
}
|
||||
|
||||
// 分享直达:需 token 调 detail / analyze,等静默登录完成再请求,避免白屏
|
||||
app.ensureLogin()
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
this.setData({
|
||||
loading: false,
|
||||
error: '请先登录后查看报告;若已登录请下拉重试或返回重进。'
|
||||
})
|
||||
return
|
||||
}
|
||||
run()
|
||||
})
|
||||
.catch(() => {
|
||||
this.setData({ loading: false, error: '登录失败,请返回重试。' })
|
||||
})
|
||||
},
|
||||
|
||||
onShow() {
|
||||
|
||||
@@ -54,20 +54,28 @@ Page({
|
||||
testResultId: null,
|
||||
shareToken: '',
|
||||
hasReloadedAfterPay: false,
|
||||
hasPhone: false
|
||||
hasPhone: false,
|
||||
/** 分享落地(path 带 fs=1 或旧版仅 id+st),用于隐藏「去完善资料」、展示底部「我也要测试」 */
|
||||
fromShare: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
const id = options && options.id
|
||||
const type = options && options.type
|
||||
const fromShareFs =
|
||||
options && (String(options.fs) === '1' || options.from === 'share')
|
||||
const id = options && options.id != null && options.id !== '' ? String(options.id) : ''
|
||||
const st = options && options.st ? String(options.st).trim() : ''
|
||||
const type = options && options.type ? String(options.type).toLowerCase() : ''
|
||||
|
||||
// id + type:免登录拉取(/api/test/share-detail)
|
||||
if (id && type === 'sbti') {
|
||||
this.setData({ testResultId: id })
|
||||
if (options.st) {
|
||||
this.loadShareDetail(id, options.st)
|
||||
} else {
|
||||
this.loadDetail(id)
|
||||
}
|
||||
this.setData({ testResultId: id, fromShare: !!fromShareFs })
|
||||
this.loadShareDetail(id, st, 'sbti')
|
||||
return
|
||||
}
|
||||
// 旧版分享仅 id + st
|
||||
if (id && st) {
|
||||
this.setData({ testResultId: id, fromShare: true })
|
||||
this.loadShareDetail(id, st, '')
|
||||
return
|
||||
}
|
||||
|
||||
@@ -108,10 +116,14 @@ Page({
|
||||
isPaid,
|
||||
amountYuan: needPaymentToUnlock ? amountYuan : 0
|
||||
}
|
||||
this.setData({
|
||||
const patch = {
|
||||
payInfo,
|
||||
shareToken: payload.shareToken || ''
|
||||
})
|
||||
}
|
||||
if (payload.id != null && payload.id !== '') {
|
||||
patch.testResultId = String(payload.id)
|
||||
}
|
||||
this.setData(patch)
|
||||
},
|
||||
|
||||
loadDetail(id) {
|
||||
@@ -139,17 +151,20 @@ Page({
|
||||
})
|
||||
},
|
||||
|
||||
loadShareDetail(id, st) {
|
||||
loadShareDetail(id, st, testType) {
|
||||
const apiBase = app.globalData?.apiBase || ''
|
||||
if (!apiBase) {
|
||||
wx.showToast({ title: '配置异常', icon: 'none' })
|
||||
return
|
||||
}
|
||||
const data = { id: String(id) }
|
||||
if (st) data.st = st
|
||||
if (testType) data.type = testType
|
||||
wx.showLoading({ title: '加载中...' })
|
||||
wx.request({
|
||||
url: `${apiBase}/api/test/share-detail`,
|
||||
method: 'GET',
|
||||
data: { id, st },
|
||||
data,
|
||||
success: (res) => {
|
||||
if (res.statusCode === 200 && res.data && res.data.code === 200) {
|
||||
this.applyDetailPayload(res.data.data || {})
|
||||
@@ -262,6 +277,11 @@ Page({
|
||||
wx.navigateTo({ url: '/pages/test/sbti' })
|
||||
},
|
||||
|
||||
/** 分享落地页:引导好友自己做测试 */
|
||||
goWantTest() {
|
||||
wx.navigateTo({ url: '/pages/test/sbti' })
|
||||
},
|
||||
|
||||
goHome() {
|
||||
const scope = (getApp().globalData && getApp().globalData.appScope) || 'personal'
|
||||
if (scope === 'enterprise') {
|
||||
@@ -281,8 +301,7 @@ Page({
|
||||
title: `我的 SBTI 类型是 ${code}(${label}),来测测你的吧!`,
|
||||
path: getResultSharePath('/pages/result/sbti', {
|
||||
id: this.data.testResultId,
|
||||
type: 'sbti',
|
||||
shareToken: this.data.shareToken
|
||||
type: 'sbti'
|
||||
}),
|
||||
imageUrl: img
|
||||
}
|
||||
@@ -297,8 +316,7 @@ Page({
|
||||
title: `我的 SBTI 类型是 ${code}(${label}),来测测你的吧!`,
|
||||
query: getResultShareTimelineQuery({
|
||||
id: this.data.testResultId,
|
||||
type: 'sbti',
|
||||
shareToken: this.data.shareToken
|
||||
type: 'sbti'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--pages/result/sbti.wxml-->
|
||||
<view class="result-page">
|
||||
<scroll-view class="content-scroll" scroll-y>
|
||||
<view class="content-container">
|
||||
<scroll-view class="content-scroll {{fromShare ? 'content-scroll--with-fixed-footer' : ''}}" scroll-y>
|
||||
<view class="content-container {{fromShare ? 'content-container--with-fixed-footer' : ''}}">
|
||||
|
||||
<!-- 主类型卡片:有图且图加载成功时隐藏顶部三行(与图内文案重复);无图/失败/加载中仍显示 -->
|
||||
<view class="card main-type-card">
|
||||
@@ -72,8 +72,8 @@
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 强制完善资料卡片 -->
|
||||
<view class="card paywall-card" wx:elif="{{result && result.locked}}">
|
||||
<!-- 强制完善资料卡片(分享落地 fromShare 不展示,避免引导访客完善自己的资料) -->
|
||||
<view class="card paywall-card" wx:elif="{{result && result.locked && !fromShare}}">
|
||||
<view class="paywall-content">
|
||||
<text class="paywall-fake-title">完整报告需完善资料</text>
|
||||
<text class="paywall-fake-line">请补全头像、昵称并绑定手机号后查看完整解读。</text>
|
||||
@@ -107,4 +107,7 @@
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
<view class="result-share-footer" wx:if="{{fromShare}}">
|
||||
<button class="result-share-footer-btn" bindtap="goWantTest">我也要测试</button>
|
||||
</view>
|
||||
</view>
|
||||
@@ -15,6 +15,15 @@
|
||||
padding-bottom: 64rpx;
|
||||
}
|
||||
|
||||
/* 底部固定「我也要测试」时,为滚动内容预留空间(按钮约 96rpx + 安全区) */
|
||||
.content-container--with-fixed-footer {
|
||||
padding-bottom: calc(64rpx + 120rpx + env(safe-area-inset-bottom));
|
||||
}
|
||||
|
||||
.content-scroll--with-fixed-footer {
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.card {
|
||||
background-color: #fff;
|
||||
border-radius: 24rpx;
|
||||
@@ -268,3 +277,40 @@
|
||||
font-weight: 400;
|
||||
line-height: 1.65;
|
||||
}
|
||||
|
||||
/* 分享落地:底部固定条 */
|
||||
.result-share-footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 200;
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
padding: 20rpx 32rpx;
|
||||
padding-bottom: calc(20rpx + env(safe-area-inset-bottom));
|
||||
background: #f2f7f3;
|
||||
box-shadow: 0 -8rpx 24rpx rgba(45, 61, 53, 0.08);
|
||||
}
|
||||
|
||||
/* 与 SBTI 答题页主按钮(pages/test/sbti)主题绿一致 */
|
||||
.result-share-footer-btn {
|
||||
width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
min-height: 96rpx;
|
||||
line-height: 96rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 30rpx;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
background: linear-gradient(135deg, #5a7268 0%, #6f8a7e 100%);
|
||||
border: none;
|
||||
box-shadow: 0 8rpx 24rpx rgba(90, 114, 104, 0.28);
|
||||
}
|
||||
|
||||
.result-share-footer-btn::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
@@ -7,7 +7,9 @@ Page({
|
||||
permMbti: true,
|
||||
permSbti: true,
|
||||
permPdp: true,
|
||||
permDisc: true
|
||||
permDisc: true,
|
||||
/** 四类入口均被企业权限关闭时提示,避免误以为白屏 */
|
||||
allTestsDisabled: false
|
||||
},
|
||||
|
||||
onLoad(options) {
|
||||
@@ -17,6 +19,8 @@ Page({
|
||||
try {
|
||||
wx.showShareMenu({ withShareTicket: true, menus: ['shareAppMessage', 'shareTimeline'] })
|
||||
} catch (e) {}
|
||||
// 分享直达时尽早完成静默登录,避免用户点进子页时仍无 token
|
||||
app.ensureLogin().catch(() => {})
|
||||
this._syncPerms()
|
||||
},
|
||||
|
||||
@@ -26,12 +30,18 @@ Page({
|
||||
|
||||
_syncPerms() {
|
||||
const p = app.globalData.enterprisePermissions
|
||||
const permFace = !p || p.face !== false
|
||||
const permMbti = !p || p.mbti !== false
|
||||
const permSbti = !p || p.sbti !== false
|
||||
const permPdp = !p || p.pdp !== false
|
||||
const permDisc = !p || p.disc !== false
|
||||
this.setData({
|
||||
permFace: !p || p.face !== false,
|
||||
permMbti: !p || p.mbti !== false,
|
||||
permSbti: !p || p.sbti !== false,
|
||||
permPdp: !p || p.pdp !== false,
|
||||
permDisc: !p || p.disc !== false
|
||||
permFace,
|
||||
permMbti,
|
||||
permSbti,
|
||||
permPdp,
|
||||
permDisc,
|
||||
allTestsDisabled: p && !permMbti && !permSbti && !permPdp && !permDisc
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@@ -48,4 +48,8 @@
|
||||
</view>
|
||||
<text class="entry-arrow">→</text>
|
||||
</view>
|
||||
|
||||
<view class="card perm-disabled-hint" wx:if="{{allTestsDisabled}}">
|
||||
<text class="perm-disabled-text">当前环境下暂未开放问卷测试,请联系管理员或从首页进入。</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -35,6 +35,17 @@
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.perm-disabled-hint {
|
||||
text-align: center;
|
||||
padding: 32rpx;
|
||||
}
|
||||
|
||||
.perm-disabled-text {
|
||||
font-size: 28rpx;
|
||||
color: #888;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* 入口卡片(与 result 页 next-btn 同色系) */
|
||||
.entry-card {
|
||||
display: flex;
|
||||
|
||||
@@ -28,8 +28,17 @@ Page({
|
||||
try {
|
||||
require('../../utils/thirdPartyContext.js').ingestThirdPartyOnPageLoad(options || {}, app)
|
||||
} catch (e) {}
|
||||
loadQuestions('disc', {})
|
||||
app.ensureLogin()
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
this.setData({ loading: false })
|
||||
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||
return Promise.reject(new Error('login'))
|
||||
}
|
||||
return loadQuestions('disc', {})
|
||||
})
|
||||
.then((questions) => {
|
||||
if (!questions) return
|
||||
if (!questions.length) {
|
||||
wx.showToast({ title: '暂无题目', icon: 'none' })
|
||||
this.setData({ loading: false })
|
||||
@@ -53,6 +62,7 @@ Page({
|
||||
this.startTimer()
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err && err.message === 'login') return
|
||||
this.setData({ loading: false })
|
||||
wx.showToast({ title: (err && err.message) || '加载失败', icon: 'none' })
|
||||
})
|
||||
@@ -189,10 +199,17 @@ 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)
|
||||
app.saveTestResult('disc', resultData).then((extra) => {
|
||||
const rid = extra && extra.id
|
||||
if (rid) {
|
||||
wx.redirectTo({ url: `/pages/result/disc?id=${rid}&type=disc` })
|
||||
} else {
|
||||
wx.redirectTo({ url: '/pages/result/disc' })
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.redirectTo({ url: '/pages/result/disc' })
|
||||
}
|
||||
|
||||
wx.redirectTo({ url: '/pages/result/disc' })
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
|
||||
@@ -30,8 +30,18 @@ Page({
|
||||
try {
|
||||
require('../../utils/thirdPartyContext.js').ingestThirdPartyOnPageLoad(options || {}, app)
|
||||
} catch (e) {}
|
||||
loadQuestions('mbti', {})
|
||||
// 分享直达本页时 silentLogin 可能尚未完成,须先 ensureLogin 再拉题,否则 401 → 白屏
|
||||
app.ensureLogin()
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
this.setData({ loading: false })
|
||||
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||
return Promise.reject(new Error('login'))
|
||||
}
|
||||
return loadQuestions('mbti', {})
|
||||
})
|
||||
.then((questions) => {
|
||||
if (!questions) return
|
||||
const total = questions.length
|
||||
if (!total) {
|
||||
wx.showToast({ title: '暂无题目', icon: 'none' })
|
||||
@@ -58,6 +68,7 @@ Page({
|
||||
this.startTimer()
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err && err.message === 'login') return
|
||||
this.setData({ loading: false })
|
||||
wx.showToast({ title: (err && err.message) || '加载失败', icon: 'none' })
|
||||
})
|
||||
@@ -229,11 +240,15 @@ Page({
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
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'
|
||||
app.saveTestResult('mbti', resultData).then((extra) => {
|
||||
const rid = extra && extra.id
|
||||
if (rid) {
|
||||
wx.redirectTo({ url: `/pages/result/mbti?id=${rid}&type=mbti` })
|
||||
} else {
|
||||
wx.redirectTo({ url: '/pages/result/mbti' })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@@ -28,8 +28,17 @@ Page({
|
||||
try {
|
||||
require('../../utils/thirdPartyContext.js').ingestThirdPartyOnPageLoad(options || {}, app)
|
||||
} catch (e) {}
|
||||
loadQuestions('pdp', {})
|
||||
app.ensureLogin()
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
this.setData({ loading: false })
|
||||
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||
return Promise.reject(new Error('login'))
|
||||
}
|
||||
return loadQuestions('pdp', {})
|
||||
})
|
||||
.then((questions) => {
|
||||
if (!questions) return
|
||||
if (!questions.length) {
|
||||
wx.showToast({ title: '暂无题目', icon: 'none' })
|
||||
this.setData({ loading: false })
|
||||
@@ -53,6 +62,7 @@ Page({
|
||||
this.startTimer()
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err && err.message === 'login') return
|
||||
this.setData({ loading: false })
|
||||
wx.showToast({ title: (err && err.message) || '加载失败', icon: 'none' })
|
||||
})
|
||||
@@ -192,10 +202,17 @@ 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)
|
||||
app.saveTestResult('pdp', resultData).then((extra) => {
|
||||
const rid = extra && extra.id
|
||||
if (rid) {
|
||||
wx.redirectTo({ url: `/pages/result/pdp?id=${rid}&type=pdp` })
|
||||
} else {
|
||||
wx.redirectTo({ url: '/pages/result/pdp' })
|
||||
}
|
||||
})
|
||||
} else {
|
||||
wx.redirectTo({ url: '/pages/result/pdp' })
|
||||
}
|
||||
|
||||
wx.redirectTo({ url: '/pages/result/pdp' })
|
||||
},
|
||||
|
||||
onShareAppMessage() {
|
||||
|
||||
@@ -27,9 +27,18 @@ Page({
|
||||
try {
|
||||
require('../../utils/thirdPartyContext.js').ingestThirdPartyOnPageLoad(options || {}, app)
|
||||
} catch (e) {}
|
||||
fetchQuestionBank('sbti', {})
|
||||
app.ensureLogin()
|
||||
.then((ok) => {
|
||||
if (!ok) {
|
||||
this.setData({ loading: false })
|
||||
wx.showToast({ title: '登录失败,请重试', icon: 'none' })
|
||||
return Promise.reject(new Error('login'))
|
||||
}
|
||||
return fetchQuestionBank('sbti', {})
|
||||
})
|
||||
.then((all) => {
|
||||
if (!all || !all.length) {
|
||||
if (!all) return
|
||||
if (!all.length) {
|
||||
wx.showToast({ title: '暂无题目', icon: 'none' })
|
||||
this.setData({ loading: false })
|
||||
return
|
||||
@@ -62,6 +71,7 @@ Page({
|
||||
this.startTimer()
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err && err.message === 'login') return
|
||||
this.setData({ loading: false })
|
||||
wx.showToast({ title: (err && err.message) || '加载失败', icon: 'none' })
|
||||
})
|
||||
@@ -236,7 +246,6 @@ Page({
|
||||
timestamp: new Date().toISOString()
|
||||
}
|
||||
wx.setStorageSync('sbtiResult', resultData)
|
||||
app.saveTestResult('sbti', resultData)
|
||||
try {
|
||||
require('../../utils/analytics').track('test_complete', {
|
||||
type: 'sbti',
|
||||
@@ -245,8 +254,13 @@ Page({
|
||||
})
|
||||
} catch (e) {}
|
||||
|
||||
wx.redirectTo({
|
||||
url: '/pages/result/sbti'
|
||||
app.saveTestResult('sbti', resultData).then((extra) => {
|
||||
const rid = extra && extra.id
|
||||
if (rid) {
|
||||
wx.redirectTo({ url: `/pages/result/sbti?id=${rid}&type=sbti` })
|
||||
} else {
|
||||
wx.redirectTo({ url: '/pages/result/sbti' })
|
||||
}
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
@@ -7,12 +7,19 @@
|
||||
"condition": {
|
||||
"miniprogram": {
|
||||
"list": [
|
||||
{
|
||||
"name": "分享的测试数据",
|
||||
"pathName": "pages/result/sbti",
|
||||
"query": "id=6229&type=sbti&uid=24&fs=1",
|
||||
"scene": null,
|
||||
"launchMode": "default"
|
||||
},
|
||||
{
|
||||
"name": "MBTI",
|
||||
"pathName": "pages/test/mbti",
|
||||
"query": "phone=18649947301",
|
||||
"scene": null,
|
||||
"launchMode": "default"
|
||||
"launchMode": "default",
|
||||
"scene": null
|
||||
},
|
||||
{
|
||||
"name": "pages/enterprise/index",
|
||||
|
||||
@@ -58,24 +58,23 @@ function getSharePathByScope(personalBasePath) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 分享「测试结果页」:必须带 id、type、st(后端下发的 shareToken),否则好友无法打开详情
|
||||
* 分享「测试结果页」:仅需 id + type(好友免登录,由 /api/test/share-detail 按 type 校验)
|
||||
* @param {string} resultPagePath 如 /pages/result/mbti
|
||||
* @param {{ id: string|number, type: string, shareToken: string }} opts
|
||||
* @param {{ id: string|number, type: string }} opts
|
||||
*/
|
||||
function getResultSharePath(resultPagePath, opts) {
|
||||
const id = opts && opts.id
|
||||
const type = opts && opts.type
|
||||
const shareToken = opts && opts.shareToken
|
||||
if (!id || !type || !shareToken) {
|
||||
if (!id || !type) {
|
||||
return getSharePathByScope('/pages/index/index')
|
||||
}
|
||||
// fs=1:分享落地,结果页用于区分访客(隐藏「去完善资料」、展示「我也要测试」)
|
||||
const q =
|
||||
'id=' +
|
||||
encodeURIComponent(String(id)) +
|
||||
'&type=' +
|
||||
encodeURIComponent(String(type)) +
|
||||
'&st=' +
|
||||
encodeURIComponent(String(shareToken))
|
||||
'&fs=1'
|
||||
const inv = buildShareQuery()
|
||||
return inv ? resultPagePath + '?' + q + '&' + inv : resultPagePath + '?' + q
|
||||
}
|
||||
@@ -86,8 +85,7 @@ function getResultSharePath(resultPagePath, opts) {
|
||||
function getResultShareTimelineQuery(opts) {
|
||||
const id = opts && opts.id
|
||||
const type = opts && opts.type
|
||||
const shareToken = opts && opts.shareToken
|
||||
if (!id || !type || !shareToken) {
|
||||
if (!id || !type) {
|
||||
return buildShareQuery()
|
||||
}
|
||||
const base =
|
||||
@@ -95,8 +93,7 @@ function getResultShareTimelineQuery(opts) {
|
||||
encodeURIComponent(String(id)) +
|
||||
'&type=' +
|
||||
encodeURIComponent(String(type)) +
|
||||
'&st=' +
|
||||
encodeURIComponent(String(shareToken))
|
||||
'&fs=1'
|
||||
const inv = buildShareQuery()
|
||||
return inv ? base + '&' + inv : base
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user