From 0aab56c842c9d42705952b8414df123823228e5b Mon Sep 17 00:00:00 2001 From: Ghost <106998207@qq.com> Date: Tue, 14 Apr 2026 10:39:14 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=B0=8F=E7=A8=8B=E5=BA=8F=E7=BB=93?= =?UTF-8?q?=E6=9E=9C/=E9=80=89=E9=A2=98=E9=A1=B5=E4=B8=8E=20Test=20?= =?UTF-8?q?=E8=B7=AF=E7=94=B1=E5=8F=8A=E5=88=86=E4=BA=AB=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1、修复了 result 与 test 各题型页及 test-select 的跳转与数据问题。 2、调整 api/Test 与 route 注册。 3、优化 app 入口与 share 工具。 Made-with: Cursor --- api/app/controller/api/Test.php | 96 +++++++++++++++++++++----- api/route/api.php | 2 + miniprogram/app.js | 4 +- miniprogram/pages/result/disc.js | 81 ++++++++++++++++------ miniprogram/pages/result/mbti.js | 79 ++++++++++++++++----- miniprogram/pages/result/pdp.js | 79 +++++++++++++++------ miniprogram/pages/result/sbti.js | 78 ++++++++++++++++----- miniprogram/pages/test-select/index.js | 19 +++++ miniprogram/pages/test/disc.js | 19 +++++ miniprogram/pages/test/mbti.js | 19 +++++ miniprogram/pages/test/pdp.js | 19 +++++ miniprogram/pages/test/sbti.js | 19 +++++ miniprogram/utils/share.js | 52 +++++++++++++- 13 files changed, 468 insertions(+), 98 deletions(-) diff --git a/api/app/controller/api/Test.php b/api/app/controller/api/Test.php index 87e0e34..5936d31 100644 --- a/api/app/controller/api/Test.php +++ b/api/app/controller/api/Test.php @@ -573,6 +573,51 @@ class Test extends BaseController return error('记录不存在', 404); } + $out = $this->buildTestDetailPayload($row); + + return success(array_merge($out, [ + 'shareToken' => $this->computeShareToken($row), + ])); + } + + /** + * 通过分享链接查看测试结果(无需登录,凭 st 校验防篡改) + * GET /api/test/share-detail?id=123&st=... + */ + public function shareDetail() + { + $id = (int) Request::param('id', 0); + $st = trim((string) Request::param('st', '')); + if ($id <= 0 || $st === '') { + return error('缺少参数', 400); + } + + $row = Db::name('test_results')->where('id', $id)->find(); + if (!$row) { + return error('记录不存在', 404); + } + + if (!$this->verifyShareToken($row, $st)) { + return error('分享链接无效或已失效', 403); + } + + $testType = (string) ($row['testType'] ?? ''); + if ($testType === 'resume') { + return error('该类型不支持分享查看', 403); + } + + $out = $this->buildTestDetailPayload($row); + + return success(array_merge($out, [ + 'shareToken' => $this->computeShareToken($row), + ])); + } + + /** + * 与 detail 接口一致的详情结构(data 为结果 JSON) + */ + protected function buildTestDetailPayload(array $row): array + { $raw = $row['resultData'] ?? ($row['result'] ?? null); $data = null; if ($raw !== null && $raw !== '') { @@ -583,9 +628,10 @@ class Test extends BaseController $isPaid = (int) ($row['isPaid'] ?? 0); $paidAmount = isset($row['paidAmount']) ? (int) $row['paidAmount'] : 0; $testType = $row['testType'] ?? ''; - // 仅当需要付款且未付款且金额>0 时才脱敏;系统设置需付款但金额为0 则直接可查看 $needPaymentToUnlock = $requiresPayment && !$isPaid && $paidAmount > 0; - $profileIncomplete = !self::isWechatProfileComplete($userId); + $subjectUserId = (int) ($row['userId'] ?? 0); + $profileIncomplete = $subjectUserId > 0 ? !self::isWechatProfileComplete($subjectUserId) : false; + if ($data !== null && is_array($data)) { if (in_array($testType, ['face', 'ai'], true)) { if ($needPaymentToUnlock || $profileIncomplete) { @@ -596,20 +642,38 @@ class Test extends BaseController } } - return success([ - 'id' => $row['id'], - 'testType' => $testType, - 'createdAt' => $row['createdAt'], - 'data' => $data, - 'requiresPayment' => $requiresPayment, - 'isPaid' => $isPaid, - 'paidAmount' => $paidAmount, - 'amountYuan' => $paidAmount > 0 ? round($paidAmount / 100, 2) : 0, - 'needPaymentToUnlock'=> $needPaymentToUnlock, - 'profileIncomplete' => $profileIncomplete, - 'orderId' => isset($row['orderId']) ? (int) $row['orderId'] : null, - 'paidAt' => isset($row['paidAt']) ? (int) $row['paidAt'] : null, - ]); + return [ + 'id' => $row['id'], + 'testType' => $testType, + 'createdAt' => $row['createdAt'], + 'data' => $data, + 'requiresPayment' => $requiresPayment, + 'isPaid' => $isPaid, + 'paidAmount' => $paidAmount, + 'amountYuan' => $paidAmount > 0 ? round($paidAmount / 100, 2) : 0, + 'needPaymentToUnlock' => $needPaymentToUnlock, + 'profileIncomplete' => $profileIncomplete, + 'orderId' => isset($row['orderId']) ? (int) $row['orderId'] : null, + 'paidAt' => isset($row['paidAt']) ? (int) $row['paidAt'] : null, + ]; + } + + protected function computeShareToken(array $row): string + { + $secret = (string) env('SHARE_LINK_SECRET', env('WECHAT_APP_SECRET', 'mbti-share-link-change-me')); + $payload = (string) ($row['id'] ?? 0) . '|' . (string) ($row['userId'] ?? 0) . '|' . (string) ($row['createdAt'] ?? 0); + + return substr(hash_hmac('sha256', $payload, $secret), 0, 32); + } + + protected function verifyShareToken(array $row, string $token): bool + { + $token = trim($token); + if ($token === '' || strlen($token) !== 32) { + return false; + } + + return hash_equals($this->computeShareToken($row), $token); } /** diff --git a/api/route/api.php b/api/route/api.php index d126783..1ba81b1 100644 --- a/api/route/api.php +++ b/api/route/api.php @@ -23,6 +23,8 @@ Route::group('api', function () { Route::post('analyze', 'api.Analyze/index'); // 小程序埋点批量上报(无需登录;带 token 时关联 user_id) Route::post('analytics/events', 'api.Analytics/batch'); + // 好友打开分享卡片:凭 st 校验查看他人测试结果(无需登录) + Route::get('test/share-detail', 'api.Test/shareDetail'); })->middleware('cors'); // 前端需要认证的API路由 diff --git a/miniprogram/app.js b/miniprogram/app.js index d46c4e3..6273602 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, // 测试次数 diff --git a/miniprogram/pages/result/disc.js b/miniprogram/pages/result/disc.js index d85b3c3..1cf211c 100644 --- a/miniprogram/pages/result/disc.js +++ b/miniprogram/pages/result/disc.js @@ -41,6 +41,7 @@ Page({ ], payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 }, testResultId: null, + shareToken: '', hasReloadedAfterPay: false, }, @@ -49,7 +50,11 @@ Page({ const type = options && options.type if (id && type === 'disc') { this.setData({ testResultId: id }) - this.loadDetail(id) + if (options.st) { + this.loadShareDetail(id, options.st) + } else { + this.loadDetail(id) + } return } const raw = wx.getStorageSync('discResult') @@ -64,6 +69,26 @@ Page({ } }, + applyDetailPayload(payload) { + const data = payload.data || payload + const isPaid = !!payload.isPaid + const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0 + const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0) + const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0) + const r = withPercentagesInt(data) + this.setData({ + result: r, + typeSummaryLine: getTypeOnly(data, 'disc'), + shareToken: payload.shareToken || '' + }) + const payInfo = { + requiresPayment: needPaymentToUnlock, + isPaid, + amountYuan: needPaymentToUnlock ? amountYuan : 0 + } + this.setData({ payInfo }) + }, + loadDetail(id) { const apiBase = app.globalData?.apiBase || '' const token = app.globalData?.token || wx.getStorageSync('token') || '' @@ -76,23 +101,7 @@ Page({ data: { id }, success: (res) => { if (res.statusCode === 200 && res.data && res.data.code === 200) { - const payload = res.data.data || {} - const data = payload.data || payload - const isPaid = !!payload.isPaid - const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0 - const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0) - const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0) - const r = withPercentagesInt(data) - this.setData({ - result: r, - typeSummaryLine: getTypeOnly(data, 'disc') - }) - const payInfo = { - requiresPayment: needPaymentToUnlock, - isPaid, - amountYuan: needPaymentToUnlock ? amountYuan : 0 - } - this.setData({ payInfo }) + this.applyDetailPayload(res.data.data || {}) } else { wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' }) } @@ -102,6 +111,26 @@ Page({ }) }, + loadShareDetail(id, st) { + const apiBase = app.globalData?.apiBase || '' + if (!apiBase) { wx.showToast({ title: '配置异常', icon: 'none' }); return } + wx.showLoading({ title: '加载中...' }) + wx.request({ + url: `${apiBase}/api/test/share-detail`, + method: 'GET', + data: { id, st }, + success: (res) => { + if (res.statusCode === 200 && res.data && res.data.code === 200) { + this.applyDetailPayload(res.data.data || {}) + } else { + wx.showToast({ title: res.data?.message || '分享链接无效或已失效', icon: 'none' }) + } + }, + fail: () => wx.showToast({ title: '网络错误', icon: 'none' }), + complete: () => wx.hideLoading() + }) + }, + onShow() { if (this.data.testResultId) return const raw = wx.getStorageSync('discResult') @@ -167,19 +196,27 @@ Page({ onShareAppMessage() { const result = this.data.result - const { getSharePathByScope } = require('../../utils/share') + const { getResultSharePath } = require('../../utils/share') return { title: `我的DISC类型是${result?.dominantType}型(${result?.description?.title}),来测测你的吧!`, - path: getSharePathByScope('/pages/index/index') + path: getResultSharePath('/pages/result/disc', { + id: this.data.testResultId, + type: 'disc', + shareToken: this.data.shareToken + }) } }, onShareTimeline() { const result = this.data.result - const { buildShareQuery } = require('../../utils/share') + const { getResultShareTimelineQuery } = require('../../utils/share') return { title: `我的DISC类型是${result?.dominantType}型(${result?.description?.title}),来测测你的吧!`, - query: buildShareQuery() + query: getResultShareTimelineQuery({ + id: this.data.testResultId, + type: 'disc', + shareToken: this.data.shareToken + }) } } }) diff --git a/miniprogram/pages/result/mbti.js b/miniprogram/pages/result/mbti.js index 16e65a9..4a50608 100644 --- a/miniprogram/pages/result/mbti.js +++ b/miniprogram/pages/result/mbti.js @@ -26,6 +26,8 @@ Page({ amountYuan: 0 }, testResultId: null, + /** 后端 HMAC,用于好友打开结果详情;来自 /api/test/detail 或 /api/test/share-detail */ + shareToken: '', hasReloadedAfterPay: false, hasPhone: false, }, @@ -36,7 +38,11 @@ Page({ if (id && type === 'mbti') { this.setData({ testResultId: id }) - this.loadDetail(id) + if (options.st) { + this.loadShareDetail(id, options.st) + } else { + this.loadDetail(id) + } return } @@ -65,6 +71,24 @@ Page({ wx.navigateTo({ url: '/pages/user-profile/index' }) }, + applyDetailPayload(payload) { + const data = payload.data || payload + const isPaid = !!payload.isPaid + const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0 + const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0) + const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0) + this.applyResult(data) + const payInfo = { + requiresPayment: needPaymentToUnlock, + isPaid, + amountYuan: needPaymentToUnlock ? amountYuan : 0 + } + this.setData({ + payInfo, + shareToken: payload.shareToken || '' + }) + }, + loadDetail(id) { const apiBase = app.globalData?.apiBase || '' const token = app.globalData?.token || wx.getStorageSync('token') || '' @@ -80,19 +104,7 @@ Page({ data: { id }, success: (res) => { if (res.statusCode === 200 && res.data && res.data.code === 200) { - const payload = res.data.data || {} - const data = payload.data || payload - const isPaid = !!payload.isPaid - const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0 - const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0) - const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0) - this.applyResult(data) - const payInfo = { - requiresPayment: needPaymentToUnlock, - isPaid, - amountYuan: needPaymentToUnlock ? amountYuan : 0 - } - this.setData({ payInfo }) + this.applyDetailPayload(res.data.data || {}) } else { wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' }) } @@ -102,6 +114,29 @@ Page({ }) }, + loadShareDetail(id, st) { + const apiBase = app.globalData?.apiBase || '' + if (!apiBase) { + wx.showToast({ title: '配置异常', icon: 'none' }) + return + } + wx.showLoading({ title: '加载中...' }) + wx.request({ + url: `${apiBase}/api/test/share-detail`, + method: 'GET', + data: { id, st }, + success: (res) => { + if (res.statusCode === 200 && res.data && res.data.code === 200) { + this.applyDetailPayload(res.data.data || {}) + } else { + wx.showToast({ title: res.data?.message || '分享链接无效或已失效', icon: 'none' }) + } + }, + fail: () => wx.showToast({ title: '网络错误', icon: 'none' }), + complete: () => wx.hideLoading() + }) + }, + applyResult(result) { if (!result) return const desc = result.description || {} @@ -219,20 +254,28 @@ Page({ onShareAppMessage() { const result = this.data.result - const { getSharePathByScope } = require('../../utils/share') + const { getResultSharePath } = require('../../utils/share') return { title: `我的MBTI类型是${result?.mbtiType}(${result?.description?.name}),来测测你的吧!`, - path: getSharePathByScope('/pages/index/index'), + path: getResultSharePath('/pages/result/mbti', { + id: this.data.testResultId, + type: 'mbti', + shareToken: this.data.shareToken + }), imageUrl: '/images/share-mbti.png' } }, onShareTimeline() { const result = this.data.result - const { buildShareQuery } = require('../../utils/share') + const { getResultShareTimelineQuery } = require('../../utils/share') return { title: `我的MBTI类型是${result?.mbtiType}(${result?.description?.name}),来测测你的吧!`, - query: buildShareQuery() + query: getResultShareTimelineQuery({ + id: this.data.testResultId, + type: 'mbti', + shareToken: this.data.shareToken + }) } } }) diff --git a/miniprogram/pages/result/pdp.js b/miniprogram/pages/result/pdp.js index cbb6a45..5eee0b8 100644 --- a/miniprogram/pages/result/pdp.js +++ b/miniprogram/pages/result/pdp.js @@ -44,6 +44,7 @@ Page({ ], payInfo: { requiresPayment: false, isPaid: false, amountYuan: 0 }, testResultId: null, + shareToken: '', hasReloadedAfterPay: false, }, @@ -52,7 +53,11 @@ Page({ const type = options && options.type if (id && type === 'pdp') { this.setData({ testResultId: id }) - this.loadDetail(id) + if (options.st) { + this.loadShareDetail(id, options.st) + } else { + this.loadDetail(id) + } return } const result = wx.getStorageSync('pdpResult') @@ -68,6 +73,25 @@ Page({ } }, + applyDetailPayload(payload) { + const data = payload.data || payload + const isPaid = !!payload.isPaid + const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0 + const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0) + const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0) + this.setData({ + result: withPercentagesInt(data), + typeSummaryLine: getTypeOnly(data, 'pdp'), + shareToken: payload.shareToken || '' + }) + const payInfo = { + requiresPayment: needPaymentToUnlock, + isPaid, + amountYuan: needPaymentToUnlock ? amountYuan : 0 + } + this.setData({ payInfo }) + }, + loadDetail(id) { const apiBase = app.globalData?.apiBase || '' const token = app.globalData?.token || wx.getStorageSync('token') || '' @@ -80,22 +104,7 @@ Page({ data: { id }, success: (res) => { if (res.statusCode === 200 && res.data && res.data.code === 200) { - const payload = res.data.data || {} - const data = payload.data || payload - const isPaid = !!payload.isPaid - const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0 - const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0) - const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0) - this.setData({ - result: withPercentagesInt(data), - typeSummaryLine: getTypeOnly(data, 'pdp') - }) - const payInfo = { - requiresPayment: needPaymentToUnlock, - isPaid, - amountYuan: needPaymentToUnlock ? amountYuan : 0 - } - this.setData({ payInfo }) + this.applyDetailPayload(res.data.data || {}) } else { wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' }) } @@ -105,6 +114,26 @@ Page({ }) }, + loadShareDetail(id, st) { + const apiBase = app.globalData?.apiBase || '' + if (!apiBase) { wx.showToast({ title: '配置异常', icon: 'none' }); return } + wx.showLoading({ title: '加载中...' }) + wx.request({ + url: `${apiBase}/api/test/share-detail`, + method: 'GET', + data: { id, st }, + success: (res) => { + if (res.statusCode === 200 && res.data && res.data.code === 200) { + this.applyDetailPayload(res.data.data || {}) + } else { + wx.showToast({ title: res.data?.message || '分享链接无效或已失效', icon: 'none' }) + } + }, + fail: () => wx.showToast({ title: '网络错误', icon: 'none' }), + complete: () => wx.hideLoading() + }) + }, + onShow() { if (this.data.testResultId) return const raw = wx.getStorageSync('pdpResult') @@ -172,19 +201,27 @@ Page({ onShareAppMessage() { const line = this.data.typeSummaryLine || this.data.result?.description?.type || '' - const { getSharePathByScope } = require('../../utils/share') + const { getResultSharePath } = require('../../utils/share') return { title: `我的PDP类型是${line},来测测你的吧!`, - path: getSharePathByScope('/pages/index/index') + path: getResultSharePath('/pages/result/pdp', { + id: this.data.testResultId, + type: 'pdp', + shareToken: this.data.shareToken + }) } }, onShareTimeline() { const line = this.data.typeSummaryLine || this.data.result?.description?.type || '' - const { buildShareQuery } = require('../../utils/share') + const { getResultShareTimelineQuery } = require('../../utils/share') return { title: `我的PDP类型是${line},来测测你的吧!`, - query: buildShareQuery() + query: getResultShareTimelineQuery({ + id: this.data.testResultId, + type: 'pdp', + shareToken: this.data.shareToken + }) } } }) diff --git a/miniprogram/pages/result/sbti.js b/miniprogram/pages/result/sbti.js index aff4818..1872c5a 100644 --- a/miniprogram/pages/result/sbti.js +++ b/miniprogram/pages/result/sbti.js @@ -52,6 +52,7 @@ Page({ amountYuan: 0 }, testResultId: null, + shareToken: '', hasReloadedAfterPay: false, hasPhone: false }, @@ -62,7 +63,11 @@ Page({ if (id && type === 'sbti') { this.setData({ testResultId: id }) - this.loadDetail(id) + if (options.st) { + this.loadShareDetail(id, options.st) + } else { + this.loadDetail(id) + } return } @@ -91,6 +96,24 @@ Page({ wx.navigateTo({ url: '/pages/user-profile/index' }) }, + applyDetailPayload(payload) { + const data = payload.data || payload + const isPaid = !!payload.isPaid + const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0 + const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0) + const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0) + this.applyResult(data) + const payInfo = { + requiresPayment: needPaymentToUnlock, + isPaid, + amountYuan: needPaymentToUnlock ? amountYuan : 0 + } + this.setData({ + payInfo, + shareToken: payload.shareToken || '' + }) + }, + loadDetail(id) { const apiBase = app.globalData?.apiBase || '' const token = app.globalData?.token || wx.getStorageSync('token') || '' @@ -106,19 +129,7 @@ Page({ data: { id }, success: (res) => { if (res.statusCode === 200 && res.data && res.data.code === 200) { - const payload = res.data.data || {} - const data = payload.data || payload - const isPaid = !!payload.isPaid - const paidAmount = payload.paidAmount != null ? Number(payload.paidAmount) : 0 - const amountYuan = payload.amountYuan != null ? Number(payload.amountYuan) : (paidAmount > 0 ? paidAmount / 100 : 0) - const needPaymentToUnlock = payload.needPaymentToUnlock === true || (!!payload.requiresPayment && !isPaid && paidAmount > 0) - this.applyResult(data) - const payInfo = { - requiresPayment: needPaymentToUnlock, - isPaid, - amountYuan: needPaymentToUnlock ? amountYuan : 0 - } - this.setData({ payInfo }) + this.applyDetailPayload(res.data.data || {}) } else { wx.showToast({ title: res.data?.message || '加载失败', icon: 'none' }) } @@ -128,6 +139,29 @@ Page({ }) }, + loadShareDetail(id, st) { + const apiBase = app.globalData?.apiBase || '' + if (!apiBase) { + wx.showToast({ title: '配置异常', icon: 'none' }) + return + } + wx.showLoading({ title: '加载中...' }) + wx.request({ + url: `${apiBase}/api/test/share-detail`, + method: 'GET', + data: { id, st }, + success: (res) => { + if (res.statusCode === 200 && res.data && res.data.code === 200) { + this.applyDetailPayload(res.data.data || {}) + } else { + wx.showToast({ title: res.data?.message || '分享链接无效或已失效', icon: 'none' }) + } + }, + fail: () => wx.showToast({ title: '网络错误', icon: 'none' }), + complete: () => wx.hideLoading() + }) + }, + applyResult(result) { if (!result) return const normalized = normalizeSbtiResultForDisplay(result) @@ -239,25 +273,33 @@ Page({ onShareAppMessage() { const result = this.data.result - const { getSharePathByScope } = require('../../utils/share') + const { getResultSharePath } = require('../../utils/share') const label = result?.sbtiCn || result?.finalType?.cn || 'SBTI' const code = result?.sbtiType || result?.finalType?.code || '' const img = this.data.typeImageUrl || '/images/share-mbti.png' return { title: `我的 SBTI 类型是 ${code}(${label}),来测测你的吧!`, - path: getSharePathByScope('/pages/index/index'), + path: getResultSharePath('/pages/result/sbti', { + id: this.data.testResultId, + type: 'sbti', + shareToken: this.data.shareToken + }), imageUrl: img } }, onShareTimeline() { const result = this.data.result - const { buildShareQuery } = require('../../utils/share') + const { getResultShareTimelineQuery } = require('../../utils/share') const label = result?.sbtiCn || result?.finalType?.cn || 'SBTI' const code = result?.sbtiType || result?.finalType?.code || '' return { title: `我的 SBTI 类型是 ${code}(${label}),来测测你的吧!`, - query: buildShareQuery() + query: getResultShareTimelineQuery({ + id: this.data.testResultId, + type: 'sbti', + shareToken: this.data.shareToken + }) } } }) diff --git a/miniprogram/pages/test-select/index.js b/miniprogram/pages/test-select/index.js index b064235..1e6862f 100644 --- a/miniprogram/pages/test-select/index.js +++ b/miniprogram/pages/test-select/index.js @@ -14,6 +14,9 @@ Page({ try { require('../../utils/thirdPartyContext.js').ingestThirdPartyOnPageLoad(options || {}, app) } catch (e) {} + try { + wx.showShareMenu({ withShareTicket: true, menus: ['shareAppMessage', 'shareTimeline'] }) + } catch (e) {} this._syncPerms() }, @@ -46,5 +49,21 @@ Page({ goDISC() { wx.navigateTo({ url: '/pages/test/disc' }) + }, + + onShareAppMessage() { + const { getSharePath } = require('../../utils/share') + return { + title: '4 大详细性格测试,来测测你的性格类型', + path: getSharePath('/pages/test-select/index') + } + }, + + onShareTimeline() { + const { buildShareQuery } = require('../../utils/share') + return { + title: '4 大详细性格测试,来测测你的性格类型', + query: buildShareQuery() + } } }) diff --git a/miniprogram/pages/test/disc.js b/miniprogram/pages/test/disc.js index 71d0740..8eda201 100644 --- a/miniprogram/pages/test/disc.js +++ b/miniprogram/pages/test/disc.js @@ -47,6 +47,9 @@ Page({ try { require('../../utils/analytics').track('test_start', { type: 'disc', total: questions.length }) } catch (e) {} + try { + wx.showShareMenu({ withShareTicket: true, menus: ['shareAppMessage', 'shareTimeline'] }) + } catch (e) {} this.startTimer() }) .catch((err) => { @@ -190,5 +193,21 @@ Page({ } wx.redirectTo({ url: '/pages/result/disc' }) + }, + + onShareAppMessage() { + const { getSharePath } = require('../../utils/share') + return { + title: '来测测你的 DISC 性格类型', + path: getSharePath('/pages/test/disc') + } + }, + + onShareTimeline() { + const { buildShareQuery } = require('../../utils/share') + return { + title: '来测测你的 DISC 性格类型', + query: buildShareQuery() + } } }) diff --git a/miniprogram/pages/test/mbti.js b/miniprogram/pages/test/mbti.js index d9f0add..bf22a46 100644 --- a/miniprogram/pages/test/mbti.js +++ b/miniprogram/pages/test/mbti.js @@ -52,6 +52,9 @@ Page({ try { require('../../utils/analytics').track('test_start', { type: 'mbti', total }) } catch (e) {} + try { + wx.showShareMenu({ withShareTicket: true, menus: ['shareAppMessage', 'shareTimeline'] }) + } catch (e) {} this.startTimer() }) .catch((err) => { @@ -280,5 +283,21 @@ Page({ confidence, description: mbtiDescriptions[mbtiType] || {} } + }, + + onShareAppMessage() { + const { getSharePath } = require('../../utils/share') + return { + title: '来测测你的 MBTI 性格类型', + path: getSharePath('/pages/test/mbti') + } + }, + + onShareTimeline() { + const { buildShareQuery } = require('../../utils/share') + return { + title: '来测测你的 MBTI 性格类型', + query: buildShareQuery() + } } }) diff --git a/miniprogram/pages/test/pdp.js b/miniprogram/pages/test/pdp.js index d1006ee..d771962 100644 --- a/miniprogram/pages/test/pdp.js +++ b/miniprogram/pages/test/pdp.js @@ -47,6 +47,9 @@ Page({ try { require('../../utils/analytics').track('test_start', { type: 'pdp', total: questions.length }) } catch (e) {} + try { + wx.showShareMenu({ withShareTicket: true, menus: ['shareAppMessage', 'shareTimeline'] }) + } catch (e) {} this.startTimer() }) .catch((err) => { @@ -193,5 +196,21 @@ Page({ } wx.redirectTo({ url: '/pages/result/pdp' }) + }, + + onShareAppMessage() { + const { getSharePath } = require('../../utils/share') + return { + title: '来测测你的 PDP 性格类型', + path: getSharePath('/pages/test/pdp') + } + }, + + onShareTimeline() { + const { buildShareQuery } = require('../../utils/share') + return { + title: '来测测你的 PDP 性格类型', + query: buildShareQuery() + } } }) diff --git a/miniprogram/pages/test/sbti.js b/miniprogram/pages/test/sbti.js index ef2ccb6..ea140c1 100644 --- a/miniprogram/pages/test/sbti.js +++ b/miniprogram/pages/test/sbti.js @@ -56,6 +56,9 @@ Page({ try { require('../../utils/analytics').track('test_start', { type: 'sbti', total }) } catch (e) {} + try { + wx.showShareMenu({ withShareTicket: true, menus: ['shareAppMessage', 'shareTimeline'] }) + } catch (e) {} this.startTimer() }) .catch((err) => { @@ -245,5 +248,21 @@ Page({ wx.redirectTo({ url: '/pages/result/sbti' }) + }, + + onShareAppMessage() { + const { getSharePath } = require('../../utils/share') + return { + title: '来测测你的 SBTI 类型', + path: getSharePath('/pages/test/sbti') + } + }, + + onShareTimeline() { + const { buildShareQuery } = require('../../utils/share') + return { + title: '来测测你的 SBTI 类型', + query: buildShareQuery() + } } }) diff --git a/miniprogram/utils/share.js b/miniprogram/utils/share.js index 86ebee2..4f8a42a 100644 --- a/miniprogram/utils/share.js +++ b/miniprogram/utils/share.js @@ -57,4 +57,54 @@ function getSharePathByScope(personalBasePath) { return getSharePath(base) } -module.exports = { buildShareQuery, getSharePath, getSharePathByScope } +/** + * 分享「测试结果页」:必须带 id、type、st(后端下发的 shareToken),否则好友无法打开详情 + * @param {string} resultPagePath 如 /pages/result/mbti + * @param {{ id: string|number, type: string, shareToken: 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) { + return getSharePathByScope('/pages/index/index') + } + const q = + 'id=' + + encodeURIComponent(String(id)) + + '&type=' + + encodeURIComponent(String(type)) + + '&st=' + + encodeURIComponent(String(shareToken)) + const inv = buildShareQuery() + return inv ? resultPagePath + '?' + q + '&' + inv : resultPagePath + '?' + q +} + +/** + * 朋友圈分享 query(不含 ?) + */ +function getResultShareTimelineQuery(opts) { + const id = opts && opts.id + const type = opts && opts.type + const shareToken = opts && opts.shareToken + if (!id || !type || !shareToken) { + return buildShareQuery() + } + const base = + 'id=' + + encodeURIComponent(String(id)) + + '&type=' + + encodeURIComponent(String(type)) + + '&st=' + + encodeURIComponent(String(shareToken)) + const inv = buildShareQuery() + return inv ? base + '&' + inv : base +} + +module.exports = { + buildShareQuery, + getSharePath, + getSharePathByScope, + getResultSharePath, + getResultShareTimelineQuery +}