小程序 - 页面固定文案全部改读后台配置(购买、登录、资料、首页、分享、介绍页),代码里不再硬编码 - 已拥有课程时在当前页提示,不再跳购买页再提示 - 介绍页正文由代码常量改为读 settings.page.formula.content 服务端 - 课程相关报错文案(不存在 / 已下架 / 课时不存在 / 未购买 / 已过期 / 重复购买 / 未配置支付商品)改读配置,代码保留默认值兜底 - 新增支付总开关 pay.blocked:打开即拦截下单并下发配置里的提示;开关存在数据库里,开发库与生产库各一份(开发库已开,生产库未设) 后台 - 系统配置页按页面分组折叠,默认收起;认不出来的配置归入「其他」 配套数据(不在代码里):对应的 settings 行已写入生产库与开发库
184 lines
5.4 KiB
JavaScript
184 lines
5.4 KiB
JavaScript
// pages/index/index.js
|
|
const api = require('../../utils/api');
|
|
const store = require('../../utils/store');
|
|
const config = require('../../config/index');
|
|
|
|
const app = getApp();
|
|
const updateManager = wx.getUpdateManager();
|
|
|
|
const PAGE_SIZE = 10;
|
|
|
|
/** 广播文字默认色(红),配置里没填或填得不像颜色时用它 */
|
|
const DEFAULT_BROADCAST_COLOR = '#e60012';
|
|
|
|
/**
|
|
* 只认安全的颜色写法,其余一律回退默认色。
|
|
* 后台是文本框,填错一个字符不该把首页样式搞坏,也不该让这个值变成注入点。
|
|
*/
|
|
function safeColor(value) {
|
|
const v = String(value || '').trim();
|
|
if (/^#[0-9a-fA-F]{3}$|^#[0-9a-fA-F]{6}$|^#[0-9a-fA-F]{8}$/.test(v)) return v;
|
|
if (/^rgba?\([\d\s.,%]+\)$/.test(v)) return v;
|
|
if (/^hsla?\([\d\s.,%]+\)$/.test(v)) return v;
|
|
if (/^[a-zA-Z]{3,20}$/.test(v)) return v; // red / orange / tomato ...
|
|
return DEFAULT_BROADCAST_COLOR;
|
|
}
|
|
|
|
Page({
|
|
data: {
|
|
staticBase: config.STATIC_BASE_URL,
|
|
banners: [],
|
|
courseList: [],
|
|
freeCourseList: [],
|
|
articles: [],
|
|
page: 1,
|
|
articleTotal: 0,
|
|
loadingMore: false,
|
|
noMore: false,
|
|
title1: '',
|
|
title2: '',
|
|
title3: '',
|
|
footerText: '',
|
|
/** 首页信息广播文案,空则不显示整条 */
|
|
broadcast: '',
|
|
/** 文案超出宽度时才横向滚动 */
|
|
broadcastScroll: false,
|
|
/** 广播文字颜色,取自 home.broadcast_color */
|
|
broadcastColor: DEFAULT_BROADCAST_COLOR,
|
|
},
|
|
|
|
onLoad() {
|
|
// 等应用初始化完成再取配置,避免首屏读到空缓存
|
|
app.ready().then(() => this.applyConfig());
|
|
this.loadAll();
|
|
},
|
|
|
|
applyConfig() {
|
|
this.setData(
|
|
{
|
|
title1: store.cfg('home.title.paid'),
|
|
title2: store.cfg('home.title.trial'),
|
|
title3: store.cfg('home.title.public'),
|
|
footerText: store.cfg('home.footer_text'),
|
|
broadcast: store.cfg('home.broadcast') || '',
|
|
broadcastColor: safeColor(store.cfg('home.broadcast_color')),
|
|
broadcastScroll: false,
|
|
},
|
|
// 等渲染完再量宽度:只有真放不下才让它滚动,短文案就静静居中
|
|
() => this.measureBroadcast(),
|
|
);
|
|
},
|
|
|
|
/** 量一下广播文字有没有超出容器,超了才加滚动动画。量不到就当不超。 */
|
|
measureBroadcast() {
|
|
if (!this.data.broadcast) return;
|
|
const query = wx.createSelectorQuery().in(this);
|
|
query.select('.broadcast-body').boundingClientRect();
|
|
query.select('.broadcast-text').boundingClientRect();
|
|
query.exec((res) => {
|
|
const box = res && res[0];
|
|
const text = res && res[1];
|
|
if (!box || !text) return;
|
|
if (text.width > box.width) this.setData({ broadcastScroll: true });
|
|
});
|
|
},
|
|
|
|
loadAll() {
|
|
this.setData({ page: 1, noMore: false });
|
|
|
|
api
|
|
.getBanners()
|
|
.then((list) => this.setData({ banners: list || [] }))
|
|
.catch((err) => console.error('[index] 轮播加载失败', err));
|
|
|
|
api
|
|
.getHomeCourses()
|
|
.then((list) => this.setData({ courseList: list || [] }))
|
|
.catch((err) => console.error('[index] 新课加载失败', err));
|
|
|
|
api
|
|
.getHomeFreeCourses()
|
|
.then((list) => this.setData({ freeCourseList: list || [] }))
|
|
.catch((err) => console.error('[index] 试听列表加载失败', err));
|
|
|
|
api
|
|
.getArticles(1, PAGE_SIZE)
|
|
.then((res) => this.setData({ articles: res.items || [], articleTotal: res.total || 0, page: 1 }))
|
|
.catch((err) => console.error('[index] 文章加载失败', err));
|
|
},
|
|
|
|
onShow() {
|
|
updateManager.onCheckForUpdate(() => {});
|
|
updateManager.onUpdateReady(() => {
|
|
wx.showModal({
|
|
title: store.cfg('app.update.title'),
|
|
showCancel: false,
|
|
content: store.cfg('app.update.content'),
|
|
success: (res) => {
|
|
if (res.confirm) updateManager.applyUpdate();
|
|
},
|
|
});
|
|
});
|
|
updateManager.onUpdateFailed(() => {});
|
|
},
|
|
|
|
onPullDownRefresh() {
|
|
Promise.all([this.reload()]).then(() => wx.stopPullDownRefresh());
|
|
},
|
|
|
|
async reload() {
|
|
try {
|
|
const cfg = await api.getConfig();
|
|
store.setSiteConfig(cfg);
|
|
this.applyConfig();
|
|
} catch (err) {
|
|
console.warn('[index] 配置刷新失败', err);
|
|
}
|
|
this.loadAll();
|
|
},
|
|
|
|
onReachBottom() {
|
|
if (this.data.loadingMore || this.data.noMore) return;
|
|
const next = this.data.page + 1;
|
|
this.setData({ loadingMore: true });
|
|
|
|
api
|
|
.getArticles(next, PAGE_SIZE)
|
|
.then((res) => {
|
|
const merged = this.data.articles.concat(res.items || []);
|
|
this.setData({
|
|
articles: merged,
|
|
page: next,
|
|
loadingMore: false,
|
|
noMore: merged.length >= (res.total || 0),
|
|
});
|
|
})
|
|
.catch((err) => {
|
|
console.error('[index] 加载更多失败', err);
|
|
this.setData({ loadingMore: false });
|
|
});
|
|
},
|
|
|
|
toArticle(e) {
|
|
wx.navigateTo({ url: '/pages/WebPageDetails/WebPageDetails?id=' + e.currentTarget.dataset.id });
|
|
},
|
|
|
|
toNewCourse(e) {
|
|
wx.navigateTo({ url: '/pages/IndexNewCourseDetails/IndexNewCourseDetails?id=' + e.currentTarget.dataset.id });
|
|
},
|
|
|
|
toFreeCourse(e) {
|
|
wx.navigateTo({
|
|
url: '/pages/IndexFreeCourseDetails/IndexFreeCourseDetails?seriesNo=' + e.currentTarget.dataset.series,
|
|
});
|
|
},
|
|
|
|
onShareAppMessage() {
|
|
return { title: store.cfg('share.title'), path: '/pages/index/index' };
|
|
},
|
|
|
|
onShareTimeline() {
|
|
return { title: store.cfg('share.title') };
|
|
},
|
|
});
|