更新小程序,优化单页模式下的用户引导逻辑,确保用户在朋友圈等环境中能够顺利登录和访问完整内容。调整章节内容获取逻辑,确保未授权用户无法访问完整内容。新增手机号同步功能,提升用户资料管理体验。
This commit is contained in:
@@ -8,8 +8,8 @@ const { parseScene } = require('./utils/scene.js')
|
|||||||
App({
|
App({
|
||||||
globalData: {
|
globalData: {
|
||||||
// API基础地址 - 连接真实后端
|
// API基础地址 - 连接真实后端
|
||||||
// baseUrl: 'https://soulapi.quwanzhi.com',
|
baseUrl: 'https://soulapi.quwanzhi.com',
|
||||||
baseUrl: 'https://souldev.quwanzhi.com',
|
// baseUrl: 'https://souldev.quwanzhi.com',
|
||||||
// baseUrl: 'http://localhost:8080',
|
// baseUrl: 'http://localhost:8080',
|
||||||
|
|
||||||
|
|
||||||
@@ -61,6 +61,10 @@ App({
|
|||||||
// TabBar相关
|
// TabBar相关
|
||||||
currentTab: 0,
|
currentTab: 0,
|
||||||
|
|
||||||
|
// 是否处于「单页模式」(如朋友圈文章里的单页预览)
|
||||||
|
// 用于在受限环境下给出引导文案,提示用户点击底部「前往小程序」进入完整体验
|
||||||
|
isSinglePageMode: false,
|
||||||
|
|
||||||
// 更新检测:上次检测时间戳,避免频繁请求
|
// 更新检测:上次检测时间戳,避免频繁请求
|
||||||
lastUpdateCheck: 0
|
lastUpdateCheck: 0
|
||||||
},
|
},
|
||||||
@@ -69,6 +73,16 @@ App({
|
|||||||
this.globalData.readSectionIds = wx.getStorageSync('readSectionIds') || []
|
this.globalData.readSectionIds = wx.getStorageSync('readSectionIds') || []
|
||||||
// 获取系统信息
|
// 获取系统信息
|
||||||
this.getSystemInfo()
|
this.getSystemInfo()
|
||||||
|
|
||||||
|
// 场景值兜底:1154 为「朋友圈单页模式」进入
|
||||||
|
try {
|
||||||
|
const launchOpts = wx.getLaunchOptionsSync ? wx.getLaunchOptionsSync() : null
|
||||||
|
if (launchOpts && launchOpts.scene === 1154) {
|
||||||
|
this.globalData.isSinglePageMode = true
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[App] 读取 LaunchOptions 失败:', e)
|
||||||
|
}
|
||||||
|
|
||||||
// 检查登录状态
|
// 检查登录状态
|
||||||
this.checkLoginStatus()
|
this.checkLoginStatus()
|
||||||
@@ -208,6 +222,11 @@ App({
|
|||||||
const systemInfo = wx.getSystemInfoSync()
|
const systemInfo = wx.getSystemInfoSync()
|
||||||
this.globalData.systemInfo = systemInfo
|
this.globalData.systemInfo = systemInfo
|
||||||
this.globalData.statusBarHeight = systemInfo.statusBarHeight || 44
|
this.globalData.statusBarHeight = systemInfo.statusBarHeight || 44
|
||||||
|
|
||||||
|
// 微信在单页模式下会在 systemInfo.mode 标记 singlePage
|
||||||
|
if (systemInfo.mode === 'singlePage') {
|
||||||
|
this.globalData.isSinglePageMode = true
|
||||||
|
}
|
||||||
|
|
||||||
// 计算导航栏高度
|
// 计算导航栏高度
|
||||||
const menuButton = wx.getMenuButtonBoundingClientRect()
|
const menuButton = wx.getMenuButtonBoundingClientRect()
|
||||||
@@ -219,6 +238,33 @@ App({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 若当前处于朋友圈等「单页模式」,在尝试登录/购买前给用户友好提示,
|
||||||
|
* 引导用户点击底部「前往小程序」进入完整小程序再操作。
|
||||||
|
* 返回 false 表示应中断当前操作。
|
||||||
|
*/
|
||||||
|
ensureFullAppForAuth() {
|
||||||
|
// 每次调用时再做一次兜底检测,避免全局标记遗漏
|
||||||
|
try {
|
||||||
|
const sys = wx.getSystemInfoSync()
|
||||||
|
if (sys && sys.mode === 'singlePage') {
|
||||||
|
this.globalData.isSinglePageMode = true
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[App] ensureFullAppForAuth getSystemInfoSync error:', e)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.globalData.isSinglePageMode) return true
|
||||||
|
|
||||||
|
wx.showModal({
|
||||||
|
title: '请前往完整小程序',
|
||||||
|
content: '当前为朋友圈单页,仅支持部分浏览。如需登录和解锁内容,请点击底部「前往小程序」后再操作。',
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '我知道了',
|
||||||
|
})
|
||||||
|
return false
|
||||||
|
},
|
||||||
|
|
||||||
// 检查登录状态
|
// 检查登录状态
|
||||||
checkLoginStatus() {
|
checkLoginStatus() {
|
||||||
try {
|
try {
|
||||||
@@ -386,6 +432,9 @@ App({
|
|||||||
|
|
||||||
// 登录方法 - 获取openId用于支付(加固错误处理,避免审核报“登录报错”)
|
// 登录方法 - 获取openId用于支付(加固错误处理,避免审核报“登录报错”)
|
||||||
async login() {
|
async login() {
|
||||||
|
if (!this.ensureFullAppForAuth()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const loginRes = await new Promise((resolve, reject) => {
|
const loginRes = await new Promise((resolve, reject) => {
|
||||||
wx.login({ success: resolve, fail: reject })
|
wx.login({ success: resolve, fail: reject })
|
||||||
@@ -446,6 +495,9 @@ App({
|
|||||||
|
|
||||||
// 获取openId (支付必需)
|
// 获取openId (支付必需)
|
||||||
async getOpenId() {
|
async getOpenId() {
|
||||||
|
if (!this.ensureFullAppForAuth()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
// 先检查缓存
|
// 先检查缓存
|
||||||
const cachedOpenId = wx.getStorageSync('openId')
|
const cachedOpenId = wx.getStorageSync('openId')
|
||||||
if (cachedOpenId) {
|
if (cachedOpenId) {
|
||||||
@@ -499,6 +551,9 @@ App({
|
|||||||
|
|
||||||
// 手机号登录:需同时传 wx.login 的 code 与 getPhoneNumber 的 phoneCode
|
// 手机号登录:需同时传 wx.login 的 code 与 getPhoneNumber 的 phoneCode
|
||||||
async loginWithPhone(phoneCode) {
|
async loginWithPhone(phoneCode) {
|
||||||
|
if (!this.ensureFullAppForAuth()) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
const loginRes = await new Promise((resolve, reject) => {
|
const loginRes = await new Promise((resolve, reject) => {
|
||||||
wx.login({ success: resolve, fail: reject })
|
wx.login({ success: resolve, fail: reject })
|
||||||
|
|||||||
@@ -370,6 +370,9 @@ Page({
|
|||||||
this.setData({ showLeadModal: false, leadPhone: '' })
|
this.setData({ showLeadModal: false, leadPhone: '' })
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// 阻止弹窗内部点击事件冒泡到遮罩层
|
||||||
|
stopPropagation() {},
|
||||||
|
|
||||||
onLeadPhoneInput(e) {
|
onLeadPhoneInput(e) {
|
||||||
this.setData({ leadPhone: (e.detail.value || '').trim() })
|
this.setData({ leadPhone: (e.detail.value || '').trim() })
|
||||||
},
|
},
|
||||||
@@ -407,6 +410,30 @@ Page({
|
|||||||
this.setData({ showLeadModal: false, leadPhone: '' })
|
this.setData({ showLeadModal: false, leadPhone: '' })
|
||||||
if (res && res.success) {
|
if (res && res.success) {
|
||||||
wx.setStorageSync('lead_last_submit_ts', Date.now())
|
wx.setStorageSync('lead_last_submit_ts', Date.now())
|
||||||
|
|
||||||
|
// 若用户资料中尚未保存手机号,则顺手同步到资料(不影响本次提交结果)
|
||||||
|
try {
|
||||||
|
const currentPhone = (app.globalData.userInfo?.phone || '').trim()
|
||||||
|
if (!currentPhone && userId) {
|
||||||
|
await app.request({
|
||||||
|
url: '/api/miniprogram/user/profile',
|
||||||
|
method: 'POST',
|
||||||
|
data: {
|
||||||
|
userId,
|
||||||
|
phone
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (app.globalData.userInfo) {
|
||||||
|
app.globalData.userInfo.phone = phone
|
||||||
|
wx.setStorageSync('userInfo', app.globalData.userInfo)
|
||||||
|
}
|
||||||
|
wx.setStorageSync('user_phone', phone)
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// 资料同步失败不影响前端提示
|
||||||
|
console.log('[Index] 同步手机号到用户资料失败:', e && e.message)
|
||||||
|
}
|
||||||
|
|
||||||
wx.showToast({ title: res.message || '提交成功,卡若会尽快联系您', icon: 'success' })
|
wx.showToast({ title: res.message || '提交成功,卡若会尽快联系您', icon: 'success' })
|
||||||
} else {
|
} else {
|
||||||
wx.showToast({ title: (res && res.message) || '提交失败', icon: 'none' })
|
wx.showToast({ title: (res && res.message) || '提交失败', icon: 'none' })
|
||||||
|
|||||||
@@ -189,7 +189,8 @@
|
|||||||
|
|
||||||
<!-- 链接卡若 - 留资弹窗(未填手机/微信号时) -->
|
<!-- 链接卡若 - 留资弹窗(未填手机/微信号时) -->
|
||||||
<view class="lead-mask" wx:if="{{showLeadModal}}" catchtap="closeLeadModal">
|
<view class="lead-mask" wx:if="{{showLeadModal}}" catchtap="closeLeadModal">
|
||||||
<view class="lead-box" catchtap="">
|
<!-- 使用 catchtap="stopPropagation" 阻止内部点击冒泡到遮罩层,避免点击输入框时弹窗被关闭 -->
|
||||||
|
<view class="lead-box" catchtap="stopPropagation">
|
||||||
<text class="lead-title">留下联系方式</text>
|
<text class="lead-title">留下联系方式</text>
|
||||||
<text class="lead-desc">方便卡若与您联系</text>
|
<text class="lead-desc">方便卡若与您联系</text>
|
||||||
<input class="lead-input" placeholder="请输入手机号" type="number" maxlength="11" value="{{leadPhone}}" bindinput="onLeadPhoneInput"/>
|
<input class="lead-input" placeholder="请输入手机号" type="number" maxlength="11" value="{{leadPhone}}" bindinput="onLeadPhoneInput"/>
|
||||||
|
|||||||
@@ -898,7 +898,11 @@
|
|||||||
.lead-btn {
|
.lead-btn {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
height: 80rpx;
|
height: 80rpx;
|
||||||
line-height: 80rpx;
|
/* 使用 flex 垂直居中文本,避免小程序默认 padding 导致按钮文字下沉 */
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0;
|
||||||
border-radius: 16rpx;
|
border-radius: 16rpx;
|
||||||
font-size: 30rpx;
|
font-size: 30rpx;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
|
|||||||
@@ -619,6 +619,22 @@ Page({
|
|||||||
|
|
||||||
// 显示登录弹窗(每次打开时协议未勾选,符合审核要求)
|
// 显示登录弹窗(每次打开时协议未勾选,符合审核要求)
|
||||||
showLogin() {
|
showLogin() {
|
||||||
|
// 朋友圈等单页模式下,不直接弹登录,用官方推荐的方式引导用户「前往小程序」
|
||||||
|
try {
|
||||||
|
const sys = wx.getSystemInfoSync()
|
||||||
|
const isSinglePage = (sys && sys.mode === 'singlePage') || getApp().globalData.isSinglePageMode
|
||||||
|
if (isSinglePage) {
|
||||||
|
wx.showModal({
|
||||||
|
title: '请前往完整小程序',
|
||||||
|
content: '当前为朋友圈单页,仅支持部分体验。想登录并管理账户,请点击底部「前往小程序」后再操作。',
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '我知道了',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[My] 检测单页模式失败,回退为正常登录弹窗:', e)
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
this.setData({ showLoginModal: true, agreeProtocol: false })
|
this.setData({ showLoginModal: true, agreeProtocol: false })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -106,7 +106,9 @@ Page({
|
|||||||
id = ch.id
|
id = ch.id
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
const chRes = await app.request({ url: `/api/miniprogram/book/chapter/by-mid/${mid}`, silent: true })
|
const resolveUrl = `/api/miniprogram/book/chapter/by-mid/${mid}`
|
||||||
|
const uid = app.globalData.userInfo?.id
|
||||||
|
const chRes = await app.request({ url: uid ? resolveUrl + '?userId=' + encodeURIComponent(uid) : resolveUrl, silent: true })
|
||||||
if (chRes && chRes.id) id = chRes.id
|
if (chRes && chRes.id) id = chRes.id
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('[Read] by-mid 解析失败:', e)
|
console.warn('[Read] by-mid 解析失败:', e)
|
||||||
@@ -223,11 +225,13 @@ Page({
|
|||||||
}
|
}
|
||||||
this.setData({ section })
|
this.setData({ section })
|
||||||
|
|
||||||
if (res && res.content) {
|
// 已解锁用 data.content(完整内容),未解锁用 content(预览);先 determineAccessState 再 loadContent 保证顺序正确
|
||||||
const { lines, segments } = contentParser.parseContent(res.content)
|
const displayContent = accessManager.canAccessFullContent(accessState) ? (res.data?.content ?? res.content) : res.content
|
||||||
|
if (res && displayContent) {
|
||||||
|
const { lines, segments } = contentParser.parseContent(displayContent)
|
||||||
const previewCount = Math.ceil(lines.length * 0.2)
|
const previewCount = Math.ceil(lines.length * 0.2)
|
||||||
const updates = {
|
const updates = {
|
||||||
content: res.content,
|
content: displayContent,
|
||||||
contentParagraphs: lines,
|
contentParagraphs: lines,
|
||||||
contentSegments: segments,
|
contentSegments: segments,
|
||||||
previewParagraphs: lines.slice(0, previewCount),
|
previewParagraphs: lines.slice(0, previewCount),
|
||||||
@@ -236,8 +240,8 @@ Page({
|
|||||||
}
|
}
|
||||||
if (res.mid) updates.sectionMid = res.mid
|
if (res.mid) updates.sectionMid = res.mid
|
||||||
this.setData(updates)
|
this.setData(updates)
|
||||||
// 写入本地缓存,供离线/重试降级使用
|
// 写入本地缓存(存 displayContent,供离线/重试降级使用)
|
||||||
try { wx.setStorageSync(cacheKey, res) } catch (_) {}
|
try { wx.setStorageSync(cacheKey, { ...res, content: displayContent }) } catch (_) {}
|
||||||
if (accessManager.canAccessFullContent(accessState)) {
|
if (accessManager.canAccessFullContent(accessState)) {
|
||||||
app.markSectionAsRead(id)
|
app.markSectionAsRead(id)
|
||||||
}
|
}
|
||||||
@@ -314,15 +318,20 @@ Page({
|
|||||||
return titles[id] || `章节 ${id}`
|
return titles[id] || `章节 ${id}`
|
||||||
},
|
},
|
||||||
|
|
||||||
// 根据 id/mid 构造章节接口路径(优先使用 mid)
|
// 根据 id/mid 构造章节接口路径(优先使用 mid)。必须带 userId 才能让后端正确判断付费用户并返回完整内容
|
||||||
_getChapterUrl(params = {}) {
|
_getChapterUrl(params = {}) {
|
||||||
const { id, mid } = params
|
const { id, mid } = params
|
||||||
const finalMid = (mid !== undefined && mid !== null) ? mid : this.data.sectionMid
|
const finalMid = (mid !== undefined && mid !== null) ? mid : this.data.sectionMid
|
||||||
|
let url
|
||||||
if (finalMid) {
|
if (finalMid) {
|
||||||
return `/api/miniprogram/book/chapter/by-mid/${finalMid}`
|
url = `/api/miniprogram/book/chapter/by-mid/${finalMid}`
|
||||||
|
} else {
|
||||||
|
const finalId = id || this.data.sectionId
|
||||||
|
url = `/api/miniprogram/book/chapter/${finalId}`
|
||||||
}
|
}
|
||||||
const finalId = id || this.data.sectionId
|
const userId = app.globalData.userInfo?.id
|
||||||
return `/api/miniprogram/book/chapter/${finalId}`
|
if (userId) url += (url.includes('?') ? '&' : '?') + 'userId=' + encodeURIComponent(userId)
|
||||||
|
return url
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
||||||
@@ -651,8 +660,8 @@ Page({
|
|||||||
: '📚 Soul创业派对 - 真实商业故事'
|
: '📚 Soul创业派对 - 真实商业故事'
|
||||||
return {
|
return {
|
||||||
title: shareTitle,
|
title: shareTitle,
|
||||||
path: ref ? `/pages/read/read?${q}&ref=${ref}` : `/pages/read/read?${q}`,
|
path: ref ? `/pages/read/read?${q}&ref=${ref}` : `/pages/read/read?${q}`
|
||||||
imageUrl: '/assets/share-cover.png'
|
// 不设置 imageUrl,使用当前阅读页截图作为分享卡片中间图片
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -670,6 +679,22 @@ Page({
|
|||||||
|
|
||||||
// 显示登录弹窗(每次打开协议未勾选,符合审核要求)
|
// 显示登录弹窗(每次打开协议未勾选,符合审核要求)
|
||||||
showLoginModal() {
|
showLoginModal() {
|
||||||
|
// 朋友圈等单页模式下,不直接弹登录,用官方推荐的方式引导用户「前往小程序」
|
||||||
|
try {
|
||||||
|
const sys = wx.getSystemInfoSync()
|
||||||
|
const isSinglePage = (sys && sys.mode === 'singlePage') || app.globalData.isSinglePageMode
|
||||||
|
if (isSinglePage) {
|
||||||
|
wx.showModal({
|
||||||
|
title: '请前往完整小程序',
|
||||||
|
content: '当前为朋友圈单页,仅支持部分浏览。想登录继续阅读,请点击底部「前往小程序」后再操作。',
|
||||||
|
showCancel: false,
|
||||||
|
confirmText: '我知道了',
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('[Read] 检测单页模式失败,回退为正常登录流程:', e)
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
this.setData({ showLoginModal: true, agreeProtocol: false })
|
this.setData({ showLoginModal: true, agreeProtocol: false })
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -23,12 +23,26 @@
|
|||||||
"condition": {
|
"condition": {
|
||||||
"miniprogram": {
|
"miniprogram": {
|
||||||
"list": [
|
"list": [
|
||||||
|
{
|
||||||
|
"name": "pages/my/my",
|
||||||
|
"pathName": "pages/my/my",
|
||||||
|
"query": "",
|
||||||
|
"scene": null,
|
||||||
|
"launchMode": "singlePage"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pages/read/read",
|
||||||
|
"pathName": "pages/read/read",
|
||||||
|
"query": "mid=20",
|
||||||
|
"launchMode": "default",
|
||||||
|
"scene": null
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "pages/read/read",
|
"name": "pages/read/read",
|
||||||
"pathName": "pages/read/read",
|
"pathName": "pages/read/read",
|
||||||
"query": "mid=1",
|
"query": "mid=1",
|
||||||
"scene": null,
|
"launchMode": "default",
|
||||||
"launchMode": "default"
|
"scene": null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -199,9 +199,13 @@ func findChapterAndRespond(c *gin.Context, whereFn func(*gorm.DB) *gorm.DB) {
|
|||||||
returnContent = previewContent(ch.Content)
|
returnContent = previewContent(ch.Content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// data 中的 content 必须与外层 content 一致,避免泄露完整内容给未授权用户
|
||||||
|
chForResponse := ch
|
||||||
|
chForResponse.Content = returnContent
|
||||||
|
|
||||||
out := gin.H{
|
out := gin.H{
|
||||||
"success": true,
|
"success": true,
|
||||||
"data": ch,
|
"data": chForResponse,
|
||||||
"content": returnContent,
|
"content": returnContent,
|
||||||
"chapterTitle": ch.ChapterTitle,
|
"chapterTitle": ch.ChapterTitle,
|
||||||
"partTitle": ch.PartTitle,
|
"partTitle": ch.PartTitle,
|
||||||
|
|||||||
Reference in New Issue
Block a user