This commit is contained in:
Alex-larget
2026-03-30 18:09:29 +08:00
parent fd0151813a
commit 07c26281ff
19 changed files with 1259 additions and 2767 deletions

View File

@@ -11,8 +11,8 @@ const DEFAULT_APP_ID = 'wxb8bbb2b10dec74aa'
const DEFAULT_MCH_ID = '1318592501'
const DEFAULT_WITHDRAW_TMPL_ID = 'u3MbZGPRkrZIk-I7QdpwzFxnO_CeQPaCWF2FkiIablE'
// baseUrl 手动切换(注释方式):
const API_BASE_URL = 'http://localhost:8080'
// const API_BASE_URL = 'https://soulapi.quwanzhi.com'
// const API_BASE_URL = 'http://localhost:9090'
const API_BASE_URL = 'https://soulapi.quwanzhi.com'
const CONFIG_CACHE_KEY = 'mpConfigCacheV1'
// 与上传版本号对齐;设置页展示优先用 wx.getAccountInfoSync().miniProgram.version正式版否则用本字段
const APP_DISPLAY_VERSION = '1.7.2'

View File

@@ -46,6 +46,18 @@ const READ_UI_DEFAULTS = {
timelineTitleSuffix: '(预览{percent}%',
}
/** 章节未返回前:用默认比例与价格生成 readUi避免云端 read_preview_ui 为空时付费墙/预览条不显示 */
function buildReadUiFilled(pct, priceTok) {
const base = { ...READ_UI_DEFAULTS }
const filled = {}
Object.keys(base).forEach((k) => {
filled[k] = String(base[k] || '')
.replace(/\{percent\}/g, String(pct))
.replace(/\{price\}/g, String(priceTok))
})
return filled
}
/** 阅读页解析正文用:人物字典 + #标签(与 /config/read-extras 一致) */
function getContentParseConfig() {
const g = getApp().globalData || {}
@@ -161,11 +173,11 @@ Page({
previewPercent: 20,
/** 未解锁时:按 previewPercent 裁切可见高度px。0 表示未启用/待测量 */
previewMaxHeightPx: 0,
/** mpUi.readPage.beforeLoginHint未登录付费墙上方说明文案 */
readBeforeLoginHint: '',
/** 朋友圈单页标题与说明mpUi.readPage.singlePageTitle / singlePagePaywallHint */
readSinglePageTitle: '解锁全文',
readSinglePageHint: '',
/** 与 mpPagePopups READ_PAGE_FALLBACK 一致getConfig 前首屏即有字 */
readBeforeLoginHint: '试读进度与下方百分比以后台配置为准;登录后可购买解锁全文。',
readSinglePageTitle: '解锁完整内容',
readSinglePageHint:
'当前为朋友圈单页预览,无法在此登录或付款。请点击底部「前往小程序」进入完整版后再解锁本章。',
showPaywall: false,
// 上一篇/下一篇
@@ -211,8 +223,8 @@ Page({
// 未付费预览比例(与后端 unpaid_preview_percent / 章节 preview_percent 一致)
effectivePreviewPercent: 20,
// 付费墙 / 朋友圈文案(后端 read_preview_ui + 占位符已替换)
readUi: {},
// 付费墙 / 朋友圈文案(后端 read_preview_ui + 占位符已替换;无云端时用 READ_UI_DEFAULTS
readUi: buildReadUiFilled(20, '1'),
},
_isLockedState(state) {
@@ -305,8 +317,16 @@ Page({
/** 章节接口 meta预览比例 + 付费墙/朋友圈文案模板 */
_applyChapterMetaFromResponse(res) {
if (!res || typeof res !== 'object') return
const rawPct =
typeof res.unpaidPreviewPercent === 'number'
? res.unpaidPreviewPercent
: typeof res.unpaid_preview_percent === 'number'
? res.unpaid_preview_percent
: null
const pct =
typeof res.unpaidPreviewPercent === 'number' ? res.unpaidPreviewPercent : 20
rawPct != null && !isNaN(Number(rawPct))
? Math.min(100, Math.max(1, Math.round(Number(rawPct))))
: 20
const priceNum =
res.price != null
? Number(res.price)
@@ -368,36 +388,30 @@ Page({
Promise.all([
app.getConfig(),
app.getReadExtras()
]).then(([cfg, extras]) => {
if (cfg) {
const mp = (cfg && cfg.mpConfig) || {}
const auditMode = !!mp.auditMode
app.globalData.auditMode = auditMode
const readBeforeLoginHint =
mpPagePopups.getReadPageContent(app, 'beforeLoginHint') ||
String((mp.mpUi && mp.mpUi.readPage && mp.mpUi.readPage.beforeLoginHint) || '').trim()
let readSinglePageTitle =
mpPagePopups.getReadPageContent(app, 'singlePageTitle') ||
String((mp.mpUi && mp.mpUi.readPage && mp.mpUi.readPage.singlePageTitle) || '').trim() ||
'解锁全文'
readSinglePageTitle = readSinglePageTitle || '解锁全文'
const readSinglePageHint =
mpPagePopups.getReadPageContent(app, 'singlePagePaywallHint') ||
String((mp.mpUi && mp.mpUi.readPage && mp.mpUi.readPage.singlePagePaywallHint) || '').trim()
if (typeof this.setData === 'function') {
this.setData({
auditMode,
readBeforeLoginHint,
readSinglePageTitle,
readSinglePageHint,
})
])
.then(([cfg, extras]) => {
if (cfg) {
const mp = (cfg && cfg.mpConfig) || {}
const auditMode = !!mp.auditMode
app.globalData.auditMode = auditMode
if (typeof this.setData === 'function') {
this.setData({ auditMode })
}
}
}
if (extras && Array.isArray(extras.linkTags)) {
app.globalData.linkTagsConfig = extras.linkTags
app.globalData.linkedMiniprograms = extras.linkedMiniprograms || []
}
}).catch(() => {})
if (extras && Array.isArray(extras.linkTags)) {
app.globalData.linkTagsConfig = extras.linkTags
app.globalData.linkedMiniprograms = extras.linkedMiniprograms || []
}
})
.catch(() => {})
.finally(() => {
if (typeof this.setData !== 'function') return
this.setData({
readBeforeLoginHint: mpPagePopups.getReadPageContent(app, 'beforeLoginHint'),
readSinglePageTitle: mpPagePopups.getReadPageContent(app, 'singlePageTitle'),
readSinglePageHint: mpPagePopups.getReadPageContent(app, 'singlePagePaywallHint'),
})
})
// 支持 scene扫码、mid、id、ref、gift代付
const sceneStr = (options && options.scene) || ''
@@ -644,6 +658,7 @@ Page({
const cached = wx.getStorageSync(cacheKey)
if (cached && cached.content) {
await app.getReadExtras()
this._applyChapterMetaFromResponse(cached)
const { lines, segments } = contentParser.parseContent(cached.content, getContentParseConfig())
// 预览比例由前端按高度裁切,这里渲染完整内容
const previewCount = lines.length
@@ -1082,6 +1097,14 @@ Page({
}
},
// 朋友圈/剪贴板文案:控制长度,避免复制过长
_formatMomentsClipboardText(text) {
const t = String(text || '').trim()
const maxLen = 150
const truncated = t.length > maxLen ? t.slice(0, maxLen) + '...' : t
return `${truncated}\n👉`
},
// 复制链接
copyLink() {
const userInfo = app.globalData.userInfo
@@ -1108,9 +1131,10 @@ Page({
推荐给正在创业或想创业的朋友,搜"卡若创业派对"小程序就能看!
#创业派对 #私域运营 #商业案例`
const copyText = this._formatMomentsClipboardText(shareText)
wx.setClipboardData({
data: shareText
data: copyText
// 不额外 showToast系统已有「内容已复制」避免与自定义文案叠两层
})
},
@@ -1149,8 +1173,10 @@ Page({
const copyText = paras
? `${title}\n\n${paras}${footer}`
: `${title}${footer || `\n\n—— 预览约 ${this.data.effectivePreviewPercent ?? 20}% ,搜「卡若创业派对」小程序阅读全文 ——`}`
const clipboardText = this._formatMomentsClipboardText(copyText)
wx.setClipboardData({
data: copyText,
data: clipboardText,
success: () => {
// 系统会在 setClipboardData 成功后自动 toast「内容已复制」与下方引导弹窗重复先关掉再弹窗
wx.hideToast()

View File

@@ -142,7 +142,7 @@
<block wx:if="{{readSinglePageMode}}">
<text class="paywall-title">{{readUi.singlePageUnlockTitle || '解锁完整内容'}}</text>
<text class="paywall-desc" wx:if="{{readUi.fullUnlockDesc}}">{{readUi.fullUnlockDesc}}</text>
<text class="paywall-desc">{{readUi.fullUnlockDesc || '可先上滑阅读预览需要全文时点下方「支付¥1」查看说明'}}</text>
<view class="purchase-options" wx:if="{{!auditMode}}">
<view class="purchase-btn purchase-section purchase-btn--fulltext" bindtap="onUnlockTapInSinglePage">
<text class="btn-label btn-label--block">{{readUi.singlePagePayButtonText || '支付解锁'}}</text>
@@ -229,7 +229,7 @@
<block wx:if="{{readSinglePageMode}}">
<text class="paywall-title">{{readUi.singlePageUnlockTitle || '解锁完整内容'}}</text>
<text class="paywall-desc" wx:if="{{readUi.fullUnlockDesc}}">{{readUi.fullUnlockDesc}}</text>
<text class="paywall-desc">{{readUi.fullUnlockDesc || '可先上滑阅读预览需要全文时点下方「支付¥1」查看说明'}}</text>
<view class="purchase-options" wx:if="{{!auditMode}}">
<view class="purchase-btn purchase-section purchase-btn--fulltext" bindtap="onUnlockTapInSinglePage">
<text class="btn-label btn-label--block">{{readUi.singlePagePayButtonText || '支付解锁'}}</text>
@@ -243,6 +243,7 @@
<text class="paywall-title">{{readUi.fullUnlockTitle || '解锁完整内容'}}</text>
<text class="paywall-desc">{{readUi.fullUnlockDesc || '可先上滑阅读预览'}}</text>
<text class="paywall-subdesc" wx:if="{{readUi.fullLockedProgressText}}">{{readUi.fullLockedProgressText}}</text>
<text class="paywall-subdesc" wx:else>已阅读约 {{effectivePreviewPercent}}% ,购买后继续阅读</text>
<view class="purchase-options" wx:if="{{!auditMode}}">
<view class="purchase-btn purchase-section" bindtap="handlePurchaseSection">
<text class="btn-label">购买本章</text>
@@ -261,10 +262,6 @@
</view>
<view class="paywall-audit-tip" wx:if="{{auditMode}}">审核中,暂不支持购买</view>
<text class="paywall-tip" wx:if="{{!auditMode}}">{{readUi.fullPaywallTip || '分享给好友一起学习,还能赚取佣金'}}</text>
<view class="gift-share-row" bindtap="showGiftShareModal" wx:if="{{isLoggedIn && !auditMode}}">
<icon name="gift" size="40" color="#00CED1" customClass="gift-share-icon"></icon>
<text class="gift-share-text">代付分享</text>
</view>
</block>
</view>

View File

@@ -812,25 +812,7 @@ button.action-share-native {
color: rgba(255, 255, 255, 0.6);
}
/* ===== 代付分享 ===== */
.btn-gift-inline {
/* 与 btn-share-inline 同风格 */
}
.gift-share-row {
display: flex;
align-items: center;
justify-content: center;
gap: 12rpx;
margin-top: 24rpx;
padding: 20rpx;
background: rgba(255, 215, 0, 0.08);
border-radius: 24rpx;
border: 1rpx solid rgba(255, 215, 0, 0.2);
}
.gift-share-icon { font-size: 32rpx; }
.gift-share-text { font-size: 28rpx; color: #FFD700; }
/* 代付分享弹窗 */
/* ===== 代付分享弹窗 ===== */
.gift-modal { padding: 32rpx; }
.gift-desc {
font-size: 28rpx;

View File

@@ -610,9 +610,15 @@ Page({
// 随机选择一条文案
const randomIndex = Math.floor(Math.random() * shareTexts.length)
const shareText = shareTexts[randomIndex]
// 剪贴板文案太长时会影响粘贴体验截取约150字+省略号,并加👉提示
const t = String(shareText || '').trim()
const maxLen = 150
const truncated = t.length > maxLen ? t.slice(0, maxLen) + '...' : t
const clipboardText = `${truncated}\n👉`
wx.setClipboardData({
data: shareText,
data: clipboardText,
success: () => {
wx.showModal({
title: '文案已复制',

View File

@@ -23,6 +23,13 @@
"condition": {
"miniprogram": {
"list": [
{
"name": "阅读详情",
"pathName": "pages/read/read",
"query": "mid=233",
"scene": null,
"launchMode": "default"
},
{
"name": "个人资料",
"pathName": "pages/avatar-nickname/avatar-nickname",

View File

@@ -1,15 +1,51 @@
/**
* 从 mpConfig.mpUi.pagePopupItems 按 pagePath + key 取文案(管理端 CRUD
* 兼容旧版 mpUi.memberDetailPage / readPage 字段。
* 云端未配置或 content 为空时,使用本文件内写死兜底(与种子文案一致)。
*
* 当前代码显式引用的键(与 soul-admin 默认种子 / db.defaultMpUi 一致,勿改 key 除非双端同步):
* - MEMBER_PATH unlockIntroTitle, unlockIntroBody → member-detail.js _showUnlockIntroThenLogin
* - READ_PATH beforeLoginHint, singlePageTitle, singlePagePaywallHint → read.js onLoad
* - READ_PATH global* → app.js getGlobalPopupContent与 pagePopupItems 同页路径存全局键)
*/
const MEMBER_PATH = '/pages/member-detail/member-detail'
const READ_PATH = '/pages/read/read'
/** 阅读页 mpUi 兜底pagePopupItems / readPage 均无或为空时) */
const READ_PAGE_FALLBACK = {
beforeLoginHint: '试读进度与下方百分比以后台配置为准;登录后可购买解锁全文。',
singlePageTitle: '解锁完整内容',
singlePagePaywallHint:
'当前为朋友圈单页预览,无法在此登录或付款。请点击底部「前往小程序」进入完整版后再解锁本章。',
}
/** 成员详情解锁说明兜底 */
const MEMBER_DETAIL_FALLBACK = {
unlockIntroTitle: '解锁与链接说明',
unlockIntroBody:
'「链接」用于提交留资,由对方通过获客计划跟进;「解锁」用于复制手机/微信号后自行添加好友。\n\n请确认已了解后再登录。',
}
/**
* 全局弹窗键与 app.js showModal 兜底一致pagePath 仍为 READ_PATHkey 以 global 开头)
*/
const GLOBAL_POPUP_FALLBACK = {
globalSinglePageAuthTitle: '请打开完整小程序',
globalSinglePageAuthBody:
'当前是朋友圈预览,无法在这里登录或付款。请先点击屏幕底部「前往小程序」,进入完整版后再解锁本章。',
globalAvatarGuideTitle: '设置头像与昵称',
globalAvatarGuideBody: '头像与昵称会出现在名片与匹配卡片上,方便伙伴认出你。',
globalVipProfileHintTitle: '补全对外展示信息',
globalVipProfileHintBody: 'VIP 名片与派对场景会展示头像与昵称,补全后对方更容易认出你。',
globalVipPhoneTitle: '补全手机号',
globalVipPhoneBody: '手机号用于找伙伴、提现验证与重要通知,仅本人可见。',
globalVipWechatOptionalTitle: '补全微信号(可选)',
globalVipWechatOptionalBody: '填写微信号后,对方在允许的场景下能更快加到你;不填也可继续使用。',
globalAppUpdateTitle: '更新提示',
globalAppUpdateBody: '新版本已准备好,重启后即可使用',
}
function getList(app) {
const mpUi = app.globalData.configCache && app.globalData.configCache.mpConfig
? app.globalData.configCache.mpConfig.mpUi
@@ -42,9 +78,18 @@ function getReadPageContent(app, key) {
const rp = (app.globalData.configCache && app.globalData.configCache.mpConfig &&
app.globalData.configCache.mpConfig.mpUi &&
app.globalData.configCache.mpConfig.mpUi.readPage) || {}
if (key === 'beforeLoginHint') return String(rp.beforeLoginHint || '').trim()
if (key === 'singlePageTitle') return String(rp.singlePageTitle || '').trim()
if (key === 'singlePagePaywallHint') return String(rp.singlePagePaywallHint || '').trim()
if (key === 'beforeLoginHint') {
const s = String(rp.beforeLoginHint || '').trim()
return s || READ_PAGE_FALLBACK.beforeLoginHint
}
if (key === 'singlePageTitle') {
const s = String(rp.singlePageTitle || '').trim()
return s || READ_PAGE_FALLBACK.singlePageTitle
}
if (key === 'singlePagePaywallHint') {
const s = String(rp.singlePagePaywallHint || '').trim()
return s || READ_PAGE_FALLBACK.singlePagePaywallHint
}
return ''
}
@@ -54,15 +99,31 @@ function getMemberDetailContent(app, key) {
const md = (app.globalData.configCache && app.globalData.configCache.mpConfig &&
app.globalData.configCache.mpConfig.mpUi &&
app.globalData.configCache.mpConfig.mpUi.memberDetailPage) || {}
if (key === 'unlockIntroTitle') return String(md.unlockIntroTitle || '').trim()
if (key === 'unlockIntroBody') return String(md.unlockIntroBody || '').trim()
if (key === 'unlockIntroTitle') {
const s = String(md.unlockIntroTitle || '').trim()
return s || MEMBER_DETAIL_FALLBACK.unlockIntroTitle
}
if (key === 'unlockIntroBody') {
const s = String(md.unlockIntroBody || '').trim()
return s || MEMBER_DETAIL_FALLBACK.unlockIntroBody
}
return ''
}
/**
* 全局弹窗文案:与阅读页共用 pagePath后台种子挂在 /pages/read/readkey 为 global* 开头
*/
function getGlobalPopupContent(app, key) {
const v = getPagePopupContent(app, READ_PATH, key)
if (v) return v
return GLOBAL_POPUP_FALLBACK[key] || ''
}
module.exports = {
getPagePopupContent,
getReadPageContent,
getMemberDetailContent,
getGlobalPopupContent,
MEMBER_PATH,
READ_PATH,
}