// pages/VideoDetails/VideoDetails.js const api = require('../../utils/api'); const store = require('../../utils/store'); const format = require('../../utils/format'); const POLL_INTERVAL = 1500; const POLL_MAX = 8; Page({ data: { code: '', owned: false, loading: true, buying: false, VideoTitle: '', VideoDetails: '', coverUrl: '', priceYuan: '', playdata: [], VideoPlayerSrc: '', HiddenVideoControl: true, top: 0, }, onLoad(options) { const code = options.code; if (!code) { wx.switchTab({ url: '/pages/kecheng/kecheng' }); return; } this.setData({ code: code }); this.load(code); }, onShow() { if (this.data.code && this.data.owned) this.load(this.data.code); }, load(code) { api .getCourse(code) .then((course) => { this.setData({ loading: false, owned: course.owned, VideoTitle: course.title || '', VideoDetails: course.intro || '', coverUrl: course.coverUrl || '', priceYuan: format.fenToYuan(course.priceFen), }); if (course.owned) this.loadLessons(code); }) .catch((err) => { this.setData({ loading: false }); wx.showToast({ title: err.message || '课程加载失败', icon: 'none' }); setTimeout(() => wx.switchTab({ url: '/pages/kecheng/kecheng' }), 1500); }); }, loadLessons(code) { api .getLessons(code) .then((list) => { const rows = (list || []).map((item) => ({ chapterId: item.chapterId, playId: item.playId, chapterTitle: item.chapterTitle, lessonTitle: item.lessonTitle, duration: item.duration, publishedAt: format.toDateString(item.publishedAt), coverUrl: item.coverUrl, })); this.setData({ playdata: rows }); }) .catch((err) => { this.setData({ playdata: [] }); console.warn('[VideoDetails] 课时加载失败', err); }); }, /** 点课时:向后端换一次性播放地址,视频本身不直接暴露。 */ onTapLesson(e) { const item = this.data.playdata[e.currentTarget.dataset.index]; if (!item) return; wx.showLoading({ title: '加载中', mask: true }); api .getPlayUrl(this.data.code, item.chapterId, item.playId) .then((res) => { wx.hideLoading(); this.setData({ VideoPlayerSrc: res.url, HiddenVideoControl: false, top: 420 }); }) .catch((err) => { wx.hideLoading(); wx.showToast({ title: err.message || '无法播放', icon: 'none' }); }); }, /** 购买:下单 → 拉起支付 → 轮询订单状态确认发货。 */ buyNow() { if (this.data.buying) return; if (typeof wx.requestVirtualPayment !== 'function') { wx.showToast({ title: '当前微信版本不支持支付,请升级微信', icon: 'none' }); return; } this.setData({ buying: true }); wx.showLoading({ title: '发起支付', mask: true }); api .createOrder(this.data.code) .then((order) => { wx.hideLoading(); return api.requestVirtualPayment(order.payData).then(() => order.outTradeNo); }) .then((outTradeNo) => this.waitForDelivery(outTradeNo)) .catch((err) => { wx.hideLoading(); this.setData({ buying: false }); // 用户主动取消不算错误 if (err && err.errMsg && err.errMsg.indexOf('cancel') >= 0) return; wx.showToast({ title: (err && err.message) || '支付失败', icon: 'none' }); }); }, /** * 支付成功后权益由服务端发货推送发放,存在秒级延迟,这里轮询确认。 * 即便轮询全部超时也不会丢单:服务端有定时查单兜底。 */ waitForDelivery(outTradeNo) { let attempts = 0; const poll = () => { attempts += 1; return api.getOrder(outTradeNo).then((order) => { if (order.status === 2) { this.setData({ buying: false, owned: true }); wx.showToast({ title: '购买成功', icon: 'success', mask: true }); this.load(this.data.code); return; } if (attempts >= POLL_MAX || order.status === 3 || order.status === 4) { this.setData({ buying: false }); wx.showToast({ title: '支付已提交,开通可能稍有延迟', icon: 'none', duration: 3000 }); return; } return new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL)).then(poll); }); }; return poll(); }, onShareAppMessage() { return { title: '叶雷讲数学', path: '/pages/index/index' }; }, });