// pages/index/index.js const api = require('../../utils/api'); const store = require('../../utils/store'); const config = require('../../config/index'); const app = getApp(); 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)); }, 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') }; }, });