From c946584a7d5bb56b1fc85e68dd0494056731caca Mon Sep 17 00:00:00 2001
From: Ghost <106998207@qq.com>
Date: Tue, 14 Apr 2026 16:40:20 +0800
Subject: [PATCH] =?UTF-8?q?feat:=20=E5=8F=8C=E7=AB=AF=E5=B0=8F=E7=A8=8B?=
=?UTF-8?q?=E5=BA=8F=E5=88=86=E4=BA=AB=E8=B7=AF=E5=BE=84=E4=B8=8E=E6=B5=8B?=
=?UTF-8?q?=E8=AF=95=E5=85=A5=E5=8F=A3=E4=B8=8A=E4=B8=8B=E6=96=87=E7=BB=9F?=
=?UTF-8?q?=E4=B8=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
1、修复了结果页、测试页与 test-select 的跳转上下文丢失问题。
2、调整 api/Test 与小程序 app/share 逻辑,补全第三方/企业场景参数透传。
3、优化 DISC/MBTI/PDP/SBTI 结果页样式与按钮交互,一致化微信与抖音端。
Made-with: Cursor
---
api/app/controller/api/Test.php | 70 +++++++---
douyin-miniprogram/app.js | 31 +++--
douyin-miniprogram/pages/index/result.js | 114 +++++++++++-----
douyin-miniprogram/pages/promo/index.js | 17 ++-
douyin-miniprogram/pages/promo/poster.js | 14 +-
douyin-miniprogram/pages/result/disc.js | 4 +-
douyin-miniprogram/pages/result/mbti.js | 4 +-
douyin-miniprogram/pages/result/pdp.js | 4 +-
douyin-miniprogram/pages/result/resume.js | 26 +++-
douyin-miniprogram/pages/test-select/index.js | 17 ++-
.../pages/test-select/index.ttml | 4 +
.../pages/test-select/index.ttss | 11 ++
douyin-miniprogram/pages/test/disc.js | 25 +++-
douyin-miniprogram/pages/test/mbti.js | 22 +++-
douyin-miniprogram/pages/test/pdp.js | 25 +++-
douyin-miniprogram/utils/share.js | 42 +++++-
miniprogram/app.js | 29 ++--
miniprogram/pages/index/result.js | 124 +++++++++++++-----
miniprogram/pages/promo/index.js | 18 ++-
miniprogram/pages/promo/poster.js | 14 +-
miniprogram/pages/result/disc.js | 45 ++++---
miniprogram/pages/result/disc.wxml | 9 +-
miniprogram/pages/result/disc.wxss | 40 ++++++
miniprogram/pages/result/mbti.js | 46 ++++---
miniprogram/pages/result/mbti.wxml | 9 +-
miniprogram/pages/result/mbti.wxss | 40 ++++++
miniprogram/pages/result/pdp.js | 45 ++++---
miniprogram/pages/result/pdp.wxml | 9 +-
miniprogram/pages/result/pdp.wxss | 40 ++++++
miniprogram/pages/result/resume.js | 27 +++-
miniprogram/pages/result/sbti.js | 52 +++++---
miniprogram/pages/result/sbti.wxml | 11 +-
miniprogram/pages/result/sbti.wxss | 46 +++++++
miniprogram/pages/test-select/index.js | 22 +++-
miniprogram/pages/test-select/index.wxml | 4 +
miniprogram/pages/test-select/index.wxss | 11 ++
miniprogram/pages/test/disc.js | 25 +++-
miniprogram/pages/test/mbti.js | 23 +++-
miniprogram/pages/test/pdp.js | 25 +++-
miniprogram/pages/test/sbti.js | 24 +++-
miniprogram/project.private.config.json | 11 +-
miniprogram/utils/share.js | 17 +--
42 files changed, 942 insertions(+), 254 deletions(-)
diff --git a/api/app/controller/api/Test.php b/api/app/controller/api/Test.php
index 42f1538..0315f1c 100644
--- a/api/app/controller/api/Test.php
+++ b/api/app/controller/api/Test.php
@@ -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,
+ ], '提交成功');
}
/**
diff --git a/douyin-miniprogram/app.js b/douyin-miniprogram/app.js
index ab88361..4d6a9bf 100644
--- a/douyin-miniprogram/app.js
+++ b/douyin-miniprogram/app.js
@@ -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) {
diff --git a/douyin-miniprogram/pages/index/result.js b/douyin-miniprogram/pages/index/result.js
index ca4f444..1ed3f15 100644
--- a/douyin-miniprogram/pages/index/result.js
+++ b/douyin-miniprogram/pages/index/result.js
@@ -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()
}
}
})
diff --git a/douyin-miniprogram/pages/promo/index.js b/douyin-miniprogram/pages/promo/index.js
index 1835f6b..b01516c 100644
--- a/douyin-miniprogram/pages/promo/index.js
+++ b/douyin-miniprogram/pages/promo/index.js
@@ -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()
},
/** 加载推广统计数据 */
diff --git a/douyin-miniprogram/pages/promo/poster.js b/douyin-miniprogram/pages/promo/poster.js
index 4562585..cef9b70 100644
--- a/douyin-miniprogram/pages/promo/poster.js
+++ b/douyin-miniprogram/pages/promo/poster.js
@@ -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: '登录失败,请重试' })
+ })
},
/** 从后端接口下载完整合成海报 */
diff --git a/douyin-miniprogram/pages/result/disc.js b/douyin-miniprogram/pages/result/disc.js
index 3ca1b74..a831ec5 100644
--- a/douyin-miniprogram/pages/result/disc.js
+++ b/douyin-miniprogram/pages/result/disc.js
@@ -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)
diff --git a/douyin-miniprogram/pages/result/mbti.js b/douyin-miniprogram/pages/result/mbti.js
index 1167b24..f67168a 100644
--- a/douyin-miniprogram/pages/result/mbti.js
+++ b/douyin-miniprogram/pages/result/mbti.js
@@ -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 })
diff --git a/douyin-miniprogram/pages/result/pdp.js b/douyin-miniprogram/pages/result/pdp.js
index 90bc987..0fdd763 100644
--- a/douyin-miniprogram/pages/result/pdp.js
+++ b/douyin-miniprogram/pages/result/pdp.js
@@ -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)
diff --git a/douyin-miniprogram/pages/result/resume.js b/douyin-miniprogram/pages/result/resume.js
index 879bddb..0f10c85 100644
--- a/douyin-miniprogram/pages/result/resume.js
+++ b/douyin-miniprogram/pages/result/resume.js
@@ -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() {
diff --git a/douyin-miniprogram/pages/test-select/index.js b/douyin-miniprogram/pages/test-select/index.js
index 4ca9b4b..1a29e2f 100644
--- a/douyin-miniprogram/pages/test-select/index.js
+++ b/douyin-miniprogram/pages/test-select/index.js
@@ -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
})
},
diff --git a/douyin-miniprogram/pages/test-select/index.ttml b/douyin-miniprogram/pages/test-select/index.ttml
index de65326..e5a013d 100644
--- a/douyin-miniprogram/pages/test-select/index.ttml
+++ b/douyin-miniprogram/pages/test-select/index.ttml
@@ -37,4 +37,8 @@
→
+
+
+ 当前环境下暂未开放问卷测试,请联系管理员或从首页进入。
+
diff --git a/douyin-miniprogram/pages/test-select/index.ttss b/douyin-miniprogram/pages/test-select/index.ttss
index 8772159..1b7a7cc 100644
--- a/douyin-miniprogram/pages/test-select/index.ttss
+++ b/douyin-miniprogram/pages/test-select/index.ttss
@@ -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;
diff --git a/douyin-miniprogram/pages/test/disc.js b/douyin-miniprogram/pages/test/disc.js
index 9b1e747..95b88d2 100644
--- a/douyin-miniprogram/pages/test/disc.js
+++ b/douyin-miniprogram/pages/test/disc.js
@@ -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' })
}
})
diff --git a/douyin-miniprogram/pages/test/mbti.js b/douyin-miniprogram/pages/test/mbti.js
index 935a13b..6bbefe0 100644
--- a/douyin-miniprogram/pages/test/mbti.js
+++ b/douyin-miniprogram/pages/test/mbti.js
@@ -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' })
+ }
})
},
diff --git a/douyin-miniprogram/pages/test/pdp.js b/douyin-miniprogram/pages/test/pdp.js
index ee73df0..f7063a4 100644
--- a/douyin-miniprogram/pages/test/pdp.js
+++ b/douyin-miniprogram/pages/test/pdp.js
@@ -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' })
}
})
diff --git a/douyin-miniprogram/utils/share.js b/douyin-miniprogram/utils/share.js
index 6b421b2..d9c0928 100644
--- a/douyin-miniprogram/utils/share.js
+++ b/douyin-miniprogram/utils/share.js
@@ -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
+}
diff --git a/miniprogram/app.js b/miniprogram/app.js
index 6273602..ff6ad56 100644
--- a/miniprogram/app.js
+++ b/miniprogram/app.js
@@ -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({})
})
- }
+ })
},
// 获取测试结果
diff --git a/miniprogram/pages/index/result.js b/miniprogram/pages/index/result.js
index 21c02f4..65bad75 100644
--- a/miniprogram/pages/index/result.js
+++ b/miniprogram/pages/index/result.js
@@ -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()
diff --git a/miniprogram/pages/promo/index.js b/miniprogram/pages/promo/index.js
index 65bb867..7df81cb 100644
--- a/miniprogram/pages/promo/index.js
+++ b/miniprogram/pages/promo/index.js
@@ -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()
},
/** 加载推广统计数据 */
diff --git a/miniprogram/pages/promo/poster.js b/miniprogram/pages/promo/poster.js
index f1ed8fa..27e8879 100644
--- a/miniprogram/pages/promo/poster.js
+++ b/miniprogram/pages/promo/poster.js
@@ -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: '登录失败,请重试' })
+ })
},
/** 从后端接口下载完整合成海报 */
diff --git a/miniprogram/pages/result/disc.js b/miniprogram/pages/result/disc.js
index 1cf211c..8d03ce1 100644
--- a/miniprogram/pages/result/disc.js
+++ b/miniprogram/pages/result/disc.js
@@ -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'
})
}
}
diff --git a/miniprogram/pages/result/disc.wxml b/miniprogram/pages/result/disc.wxml
index 4e89e27..69c7923 100644
--- a/miniprogram/pages/result/disc.wxml
+++ b/miniprogram/pages/result/disc.wxml
@@ -1,7 +1,7 @@
-
-
+
+
diff --git a/miniprogram/pages/result/disc.wxss b/miniprogram/pages/result/disc.wxss
index 61eae0f..0c1b64d 100644
--- a/miniprogram/pages/result/disc.wxss
+++ b/miniprogram/pages/result/disc.wxss
@@ -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;
+}
diff --git a/miniprogram/pages/result/mbti.js b/miniprogram/pages/result/mbti.js
index 4a50608..4360694 100644
--- a/miniprogram/pages/result/mbti.js
+++ b/miniprogram/pages/result/mbti.js
@@ -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'
})
}
}
diff --git a/miniprogram/pages/result/mbti.wxml b/miniprogram/pages/result/mbti.wxml
index 8c21893..2e47f76 100644
--- a/miniprogram/pages/result/mbti.wxml
+++ b/miniprogram/pages/result/mbti.wxml
@@ -1,7 +1,7 @@
-
-
+
+
diff --git a/miniprogram/pages/result/mbti.wxss b/miniprogram/pages/result/mbti.wxss
index 46c458c..9997d2f 100644
--- a/miniprogram/pages/result/mbti.wxss
+++ b/miniprogram/pages/result/mbti.wxss
@@ -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;
+}
diff --git a/miniprogram/pages/result/pdp.js b/miniprogram/pages/result/pdp.js
index 5eee0b8..9e46dd0 100644
--- a/miniprogram/pages/result/pdp.js
+++ b/miniprogram/pages/result/pdp.js
@@ -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'
})
}
}
diff --git a/miniprogram/pages/result/pdp.wxml b/miniprogram/pages/result/pdp.wxml
index cd55370..ea10fce 100644
--- a/miniprogram/pages/result/pdp.wxml
+++ b/miniprogram/pages/result/pdp.wxml
@@ -1,7 +1,7 @@
-
-
+
+
diff --git a/miniprogram/pages/result/pdp.wxss b/miniprogram/pages/result/pdp.wxss
index c799dbd..54e1b98 100644
--- a/miniprogram/pages/result/pdp.wxss
+++ b/miniprogram/pages/result/pdp.wxss
@@ -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;
+}
diff --git a/miniprogram/pages/result/resume.js b/miniprogram/pages/result/resume.js
index f204189..24ef995 100644
--- a/miniprogram/pages/result/resume.js
+++ b/miniprogram/pages/result/resume.js
@@ -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() {
diff --git a/miniprogram/pages/result/sbti.js b/miniprogram/pages/result/sbti.js
index 1872c5a..0c16445 100644
--- a/miniprogram/pages/result/sbti.js
+++ b/miniprogram/pages/result/sbti.js
@@ -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'
})
}
}
diff --git a/miniprogram/pages/result/sbti.wxml b/miniprogram/pages/result/sbti.wxml
index 5ed7af9..0e84eac 100644
--- a/miniprogram/pages/result/sbti.wxml
+++ b/miniprogram/pages/result/sbti.wxml
@@ -1,7 +1,7 @@
-
-
+
+
\ No newline at end of file
diff --git a/miniprogram/pages/result/sbti.wxss b/miniprogram/pages/result/sbti.wxss
index c2d7f71..5e30857 100644
--- a/miniprogram/pages/result/sbti.wxss
+++ b/miniprogram/pages/result/sbti.wxss
@@ -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;
+}
diff --git a/miniprogram/pages/test-select/index.js b/miniprogram/pages/test-select/index.js
index 1e6862f..dc46329 100644
--- a/miniprogram/pages/test-select/index.js
+++ b/miniprogram/pages/test-select/index.js
@@ -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
})
},
diff --git a/miniprogram/pages/test-select/index.wxml b/miniprogram/pages/test-select/index.wxml
index 64bcd17..9545917 100644
--- a/miniprogram/pages/test-select/index.wxml
+++ b/miniprogram/pages/test-select/index.wxml
@@ -48,4 +48,8 @@
→
+
+
+ 当前环境下暂未开放问卷测试,请联系管理员或从首页进入。
+
diff --git a/miniprogram/pages/test-select/index.wxss b/miniprogram/pages/test-select/index.wxss
index df42e79..db42da5 100644
--- a/miniprogram/pages/test-select/index.wxss
+++ b/miniprogram/pages/test-select/index.wxss
@@ -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;
diff --git a/miniprogram/pages/test/disc.js b/miniprogram/pages/test/disc.js
index 8eda201..5d98b7d 100644
--- a/miniprogram/pages/test/disc.js
+++ b/miniprogram/pages/test/disc.js
@@ -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() {
diff --git a/miniprogram/pages/test/mbti.js b/miniprogram/pages/test/mbti.js
index bf22a46..ea51cd8 100644
--- a/miniprogram/pages/test/mbti.js
+++ b/miniprogram/pages/test/mbti.js
@@ -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' })
+ }
})
},
diff --git a/miniprogram/pages/test/pdp.js b/miniprogram/pages/test/pdp.js
index d771962..3585fc8 100644
--- a/miniprogram/pages/test/pdp.js
+++ b/miniprogram/pages/test/pdp.js
@@ -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() {
diff --git a/miniprogram/pages/test/sbti.js b/miniprogram/pages/test/sbti.js
index ea140c1..fb6f6bc 100644
--- a/miniprogram/pages/test/sbti.js
+++ b/miniprogram/pages/test/sbti.js
@@ -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' })
+ }
})
},
diff --git a/miniprogram/project.private.config.json b/miniprogram/project.private.config.json
index 334a8bf..e5bb153 100644
--- a/miniprogram/project.private.config.json
+++ b/miniprogram/project.private.config.json
@@ -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",
diff --git a/miniprogram/utils/share.js b/miniprogram/utils/share.js
index 4f8a42a..28295e1 100644
--- a/miniprogram/utils/share.js
+++ b/miniprogram/utils/share.js
@@ -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
}