四端上传
This commit is contained in:
1 parent
25bbb5f9c8
commit
c6e2cb4d5b
276 files changed
+17812
-3
No files matched your search
@@ -0,0 +1,156 @@
|
||||
// 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' };
|
||||
},
|
||||
});
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
<!--pages/VideoDetails/VideoDetails.wxml-->
|
||||
|
||||
<!-- 已拥有:正常播放 -->
|
||||
<block wx:if="{{owned}}">
|
||||
<video hidden="{{HiddenVideoControl}}" class="videoplayer" src="{{VideoPlayerSrc}}" />
|
||||
<scroll-view class="scrollarea" scroll-y type="list" style="top:{{top}}rpx;">
|
||||
<view class="jieshao">
|
||||
<text decode class="video_biaoti"> {{VideoTitle}}</text>
|
||||
<text decode class="video_miaoshu"> {{VideoDetails}}</text>
|
||||
<text decode class="video_info"> 已更新{{playdata.length}}期</text>
|
||||
</view>
|
||||
|
||||
<view class="videolistcontainer" bind:tap="onTapLesson"
|
||||
wx:for="{{playdata}}" wx:for-item="item" wx:for-index="index" wx:key="index" data-index="{{index}}">
|
||||
<view class="videoimage">
|
||||
<image class="coverimage" src="{{item.coverUrl}}" mode="aspectFill" />
|
||||
</view>
|
||||
<view class="videoinfo">
|
||||
<text class="videotitle">{{item.chapterTitle}}</text>
|
||||
<text class="videodetails">{{item.lessonTitle}}</text>
|
||||
<view class="videoinfo2">
|
||||
<text class="videodata">{{item.publishedAt}}</text>
|
||||
<text class="videotime">{{item.duration}}min</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</block>
|
||||
|
||||
<!-- 未拥有:展示介绍与购买入口 -->
|
||||
<block wx:else>
|
||||
<scroll-view class="scrollarea" scroll-y type="list">
|
||||
<view class="jieshao">
|
||||
<text decode class="video_biaoti"> {{VideoTitle}}</text>
|
||||
<text decode class="video_miaoshu"> {{VideoDetails}}</text>
|
||||
</view>
|
||||
|
||||
<view style="padding: 0 40rpx;">
|
||||
<image style="width: 100%; border-radius: 16rpx;" src="{{coverUrl}}" mode="widthFix" />
|
||||
</view>
|
||||
|
||||
<view class="fixed-btn-wrapper">
|
||||
<view class="btn-group">
|
||||
<button class="DianJiXiangQing1" bind:tap="buyNow" disabled="{{buying}}">
|
||||
<text class="DianJiXiangQing2">{{buying ? '处理中…' : '立即购买 ¥' + priceYuan}}</text>
|
||||
</button>
|
||||
<button class="DianJiXiangQing1" open-type="contact">
|
||||
<text class="DianJiXiangQing2">咨询客服</text>
|
||||
</button>
|
||||
</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</block>
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
.videoplayer {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 420rpx;
|
||||
width: 750rpx;
|
||||
z-index: 999;
|
||||
}
|
||||
.scrollarea {
|
||||
position: relative;
|
||||
top: 420rpx;
|
||||
flex: 1;
|
||||
overflow-y: hidden;
|
||||
|
||||
}
|
||||
|
||||
.jieshao {
|
||||
margin-bottom: 10rpx;
|
||||
/* box-shadow: 0 4rpx 4rpx rgba(150, 150, 150, 0.5); */
|
||||
position: relative;
|
||||
height: 200rpx;
|
||||
}
|
||||
.video_biaoti {
|
||||
text-align: center;
|
||||
display: block;
|
||||
font-size: 40rpx;
|
||||
}
|
||||
|
||||
.video_miaoshu {
|
||||
text-align: center;
|
||||
display: block;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.video_info {
|
||||
position:absolute;
|
||||
bottom: 10rpx;
|
||||
display: block;
|
||||
font-size: 24rpx;
|
||||
right: 30rpx;
|
||||
}
|
||||
.video_list_content {
|
||||
margin: 10rpx 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
|
||||
.videolistcontainer {
|
||||
border-radius: 20rpx;
|
||||
margin: 10rpx 10rpx;
|
||||
box-shadow: 0 0 4rpx rgba(0, 0, 0, 0.5);
|
||||
width: 730rpx;
|
||||
height: 200rpx;
|
||||
position: relative;
|
||||
display: flex;
|
||||
justify-content: left;
|
||||
align-items: center;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
|
||||
.videoimage {
|
||||
width: 320rpx;
|
||||
height: 180rpx;
|
||||
border-radius: 10rpx;
|
||||
margin-left: 10rpx;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 5rpx 10rpx rgba(0, 0, 0, 0.5);
|
||||
}
|
||||
|
||||
.videoinfo{
|
||||
position: relative;
|
||||
width: 390rpx;
|
||||
height: 180rpx;
|
||||
border-radius: 10rpx;
|
||||
margin-left: 20rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
.videotitle {
|
||||
text-align: left;
|
||||
font-size: 30rpx;
|
||||
}
|
||||
.videodetails {
|
||||
display: block;
|
||||
margin: 10rpx 0rpx;
|
||||
font-size: 25rpx;
|
||||
line-height: 30rpx;
|
||||
}
|
||||
|
||||
.videoinfo2 {
|
||||
position:absolute;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content:left;
|
||||
}
|
||||
.videodata {
|
||||
font-size: 20rpx;
|
||||
}
|
||||
|
||||
.videotime {
|
||||
font-size: 20rpx;
|
||||
margin-left: 40rpx;
|
||||
}
|
||||
.coverimage {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
Reference in new issue
Block a user