文案配置化 + 支付开关

小程序
- 页面固定文案全部改读后台配置(购买、登录、资料、首页、分享、介绍页),代码里不再硬编码
- 已拥有课程时在当前页提示,不再跳购买页再提示
- 介绍页正文由代码常量改为读 settings.page.formula.content

服务端
- 课程相关报错文案(不存在 / 已下架 / 课时不存在 / 未购买 / 已过期 / 重复购买 / 未配置支付商品)改读配置,代码保留默认值兜底
- 新增支付总开关 pay.blocked:打开即拦截下单并下发配置里的提示;开关存在数据库里,开发库与生产库各一份(开发库已开,生产库未设)

后台
- 系统配置页按页面分组折叠,默认收起;认不出来的配置归入「其他」

配套数据(不在代码里):对应的 settings 行已写入生产库与开发库
This commit is contained in:
Yeomai committed 2026-10-01 11:28:29 +08:00
1 parent c45025fc86
commit 879e74ebda
21 files changed
+325 -103

No files matched your search

+32 -18
View File
@@ -10,8 +10,10 @@ const api = require('../../utils/api');
const format = require('../../utils/format');
const store = require('../../utils/store');
const app = getApp();
const POLL_INTERVAL = 1500;
// 微信的发货推送实测要 13~17 秒才到,原来只等 12 秒,用户多半看到「可能稍有延迟」而不是「购买成功」
// 微信的发货推送实测要 13~17 秒才到,原来只等 12 秒,用户多半先拿到兜底提示而不是成功提示
const POLL_MAX = 20;
/** iPhone 上跑 Apple 支付要求微信客户端 ≥ 这个版本 */
@@ -50,9 +52,17 @@ Page({
coverUrl: '',
priceYuan: '',
list: [],
// 页面上的固定文案全部来自后台配置,这里只放容器
textBuy: '',
textBuyList: '',
textPaying: '',
textOwned: '',
textEmpty: '',
},
onLoad(options) {
// 页面可能被直接打开(分享、扫码),先等初始化把站点配置读回来
app.ready().then(() => this.applyTexts());
const code = options && options.code;
if (code) {
this.setData({ code: code });
@@ -62,6 +72,16 @@ Page({
this.loadList();
},
applyTexts() {
this.setData({
textBuy: store.cfg('course.buy.button'),
textBuyList: store.cfg('course.buy.buttonList'),
textPaying: store.cfg('course.buy.paying'),
textOwned: store.cfg('course.buy.owned'),
textEmpty: store.cfg('course.buy.empty'),
});
},
load(code) {
api
.getCourse(code)
@@ -74,12 +94,12 @@ Page({
priceYuan: format.fenToYuan(course.priceFen),
});
// 已经拥有就不用再买了,避免重复下单
if (course.owned) this.rejectOut('你已经拥有这门课程');
if (course.owned) this.rejectOut(store.cfg('course.tip.owned'));
})
.catch((err) => {
this.setData({ loading: false });
// 课程被下架或课程号拼错,后端会返回错误,这里按同样的方式退出
this.rejectOut((err && err.message) || '课程不存在');
this.rejectOut((err && err.message) || store.cfg('course.tip.notFound'));
});
},
@@ -100,7 +120,7 @@ Page({
})
.catch((err) => {
this.setData({ loading: false });
wx.showToast({ title: (err && err.message) || '课程加载失败', icon: 'none' });
wx.showToast({ title: (err && err.message) || store.cfg('course.tip.loadFailed'), icon: 'none' });
});
},
@@ -131,9 +151,9 @@ Page({
// 购买的前置条件:用户主动使用过登录功能。静默登录不算。
if (!store.isLoggedIn()) {
wx.showModal({
title: '请先登录',
content: '使用微信登录后才能购买课程',
confirmText: '去登录',
title: store.cfg('course.login.title'),
content: store.cfg('course.login.desc'),
confirmText: store.cfg('course.login.confirm'),
success: (res) => {
if (res.confirm) {
wx.navigateTo({ url: '/pages/LoginOrOutDetails/LoginOrOutDetails' });
@@ -152,7 +172,7 @@ Page({
if (!this.checkIosVersion()) return;
this.setData({ buying: true });
wx.showLoading({ title: '发起支付', mask: true });
wx.showLoading({ title: store.cfg('course.loading.paying'), mask: true });
api
.createOrder(code)
@@ -188,13 +208,7 @@ Page({
if (!version || compareVersion(version, IOS_MIN_WECHAT) >= 0) return true;
wx.showModal({
title: '请先升级微信',
content:
'iPhone 上购买课程需要微信 ' +
IOS_MIN_WECHAT +
' 及以上版本,你当前是 ' +
version +
'。请到 App Store 更新微信后重试。',
content: '请先升级微信',
showCancel: false,
confirmText: '知道了',
});
@@ -212,7 +226,7 @@ Page({
return api.getOrder(outTradeNo).then((order) => {
if (order.status === 2) {
this.setData({ buying: false, owned: true });
wx.showToast({ title: '购买成功', icon: 'success', mask: true });
wx.showToast({ title: store.cfg('course.tip.buySuccess'), icon: 'success', mask: true });
// 单课程模式从详情页进来,买完退回去;列表模式留在原地刷新
if (this.data.code) {
setTimeout(() => this.back(), 1500);
@@ -223,7 +237,7 @@ Page({
}
if (attempts >= POLL_MAX || order.status === 3 || order.status === 4) {
this.setData({ buying: false });
wx.showToast({ title: '支付已提交,开通可能稍有延迟', icon: 'none', duration: 3000 });
wx.showToast({ title: store.cfg('course.tip.payPending'), icon: 'none', duration: 3000 });
return;
}
return new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL)).then(poll);
@@ -233,6 +247,6 @@ Page({
},
onShareAppMessage() {
return { title: '叶雷讲数学', path: '/pages/index/index' };
return { title: store.cfg('share.title'), path: '/pages/index/index' };
},
});
+3 -3
View File
@@ -10,7 +10,7 @@
class="buy-btn"
disabled="{{buying || owned}}"
bindtap="buyNow"
>{{owned ? '已拥有' : (buying ? '支付中…' : '立即购买')}}</button>
>{{owned ? textOwned : (buying ? textPaying : textBuy)}}</button>
</view>
<view class="buy-list" wx:else>
@@ -25,10 +25,10 @@
disabled="{{buying || item.owned}}"
data-code="{{item.code}}"
bindtap="buyNow"
>{{item.owned ? '已拥有' : '购买'}}</button>
>{{item.owned ? textOwned : textBuyList}}</button>
</view>
<view class="buy-empty" wx:if="{{!loading && list.length === 0}}">
<text>暂无可购买的课程</text>
<text>{{textEmpty}}</text>
</view>
</view>
</view>