update:学生端课堂报告查看
This commit is contained in:
parent
3efa7b0ad8
commit
844cde0fb6
@ -61,6 +61,7 @@ Page({
|
||||
],
|
||||
mockMonthData: {},
|
||||
mockQuarterData: {},
|
||||
studentReportLoading: false,
|
||||
},
|
||||
|
||||
/**
|
||||
@ -71,62 +72,95 @@ Page({
|
||||
const nowYear = now.getFullYear()
|
||||
const nowMonth = now.getMonth() + 1
|
||||
const nowQuarter = Math.ceil(nowMonth / 3)
|
||||
const isTrainee = wx.getStorageSync('roleId') && wx.getStorageSync('roleId') == 5
|
||||
this.setData({
|
||||
nowYear, nowMonth, nowQuarter,
|
||||
selectedYear: nowYear,
|
||||
selectedMonth: nowMonth,
|
||||
selectedQuarter: nowQuarter,
|
||||
pickerYear: nowYear,
|
||||
isTrainee,
|
||||
activeTab: isTrainee ? 0 : 1,
|
||||
})
|
||||
|
||||
if(wx.getStorageSync('roleId') && wx.getStorageSync('roleId') == 5 ){
|
||||
this.setData({
|
||||
isTrainee: true
|
||||
}, () => {
|
||||
this.getTrainClass(true)
|
||||
this.fetchFitnessReport()
|
||||
})
|
||||
await this.getTrainClass(true)
|
||||
if (isTrainee) {
|
||||
await this.fetchFitnessReport()
|
||||
} else {
|
||||
await this.getStudentReport()
|
||||
}
|
||||
},
|
||||
|
||||
async getStudentReport() {
|
||||
const res = await getReport()
|
||||
let list = []
|
||||
res.data.categoryScoreList.forEach(e => {
|
||||
list.push({
|
||||
...e,
|
||||
width: e.score >= 60 && e.score <= 80 ? (((e.score - 60) / 4)*5 + 25) + '%' : ((e.score - 60) / 0.4 + 2) + '%',
|
||||
grade: e.score >= 90 ? 'grate' : e.score >= 80 ? 'good' : e.score >= 60 ? 'normal' : ''
|
||||
})
|
||||
})
|
||||
|
||||
res.data.width = res.data.totalScore >= 60 && res.data.totalScore <= 80 ? (((res.data.totalScore - 60) / 4)*5 + 25) + '%' : ((res.data.totalScore - 60) / 0.4 + 2) + '%',
|
||||
res.data.grade = res.data.totalScore >= 90 ? 'grate' : res.data.totalScore >= 80 ? 'good' : res.data.totalScore >= 60 ? 'normal' : ''
|
||||
res.data.totalScore = res.data.totalScore.toFixed(0)
|
||||
res.data.bodyFunction = res.data.bodyFunction.toFixed(0)
|
||||
res.data.bodyQuality = res.data.bodyQuality.toFixed(0)
|
||||
res.data.bodyShape = res.data.bodyShape.toFixed(0)
|
||||
this.setData({
|
||||
report: res.data,
|
||||
list
|
||||
})
|
||||
_toScoreValue(value) {
|
||||
const score = Number(value || 0)
|
||||
return Number.isFinite(score) ? score : 0
|
||||
},
|
||||
|
||||
_formatScore(value) {
|
||||
return this._toScoreValue(value).toFixed(0)
|
||||
},
|
||||
|
||||
_getScoreGrade(score) {
|
||||
const value = this._toScoreValue(score)
|
||||
return value >= 90 ? 'grate' : value >= 80 ? 'good' : value >= 60 ? 'normal' : ''
|
||||
},
|
||||
|
||||
_getScoreWidth(score) {
|
||||
const value = Math.max(0, Math.min(100, this._toScoreValue(score)))
|
||||
if (value >= 60 && value <= 80) return (((value - 60) / 4) * 5 + 25) + '%'
|
||||
return Math.max(2, (value - 60) / 0.4 + 2) + '%'
|
||||
},
|
||||
|
||||
_hasReportRecord(item) {
|
||||
const scoreTrendChart = (item && item.scoreTrendChart) || {}
|
||||
const axisY = Array.isArray(scoreTrendChart.axisY) ? scoreTrendChart.axisY : []
|
||||
return axisY.length > 0
|
||||
},
|
||||
|
||||
_getAverageScore(items) {
|
||||
const valid = (items || []).filter(item => item && item.hasRecord)
|
||||
if (!valid.length) return 0
|
||||
return valid.reduce((sum, item) => sum + this._toScoreValue(item.lastScore), 0) / valid.length
|
||||
},
|
||||
|
||||
_buildStudentReport(rawData) {
|
||||
const sourceList = (rawData && rawData.categoryScoreList) || []
|
||||
const list = sourceList.map(item => {
|
||||
const score = this._toScoreValue(item.lastScore)
|
||||
const scoreTrendChart = item.scoreTrendChart || { axisX: [], axisY: [] }
|
||||
const reportItem = { ...item, scoreTrendChart }
|
||||
const hasRecord = this._hasReportRecord(reportItem)
|
||||
return { ...reportItem, hasRecord, score: this._formatScore(score), width: this._getScoreWidth(score), grade: this._getScoreGrade(score) }
|
||||
}).filter(item => item.hasRecord)
|
||||
const shapeList = list.filter(item => item.categoryValue === 1)
|
||||
const functionList = list.filter(item => item.categoryValue === 2)
|
||||
const qualityList = list.filter(item => item.categoryValue !== 1 && item.categoryValue !== 2)
|
||||
const totalScore = this._getAverageScore(list)
|
||||
return {
|
||||
report: { totalScore: this._formatScore(totalScore), bodyShape: this._formatScore(this._getAverageScore(shapeList)), bodyFunction: this._formatScore(this._getAverageScore(functionList)), bodyQuality: this._formatScore(this._getAverageScore(qualityList)), width: this._getScoreWidth(totalScore), grade: this._getScoreGrade(totalScore) },
|
||||
list,
|
||||
}
|
||||
},
|
||||
|
||||
async getStudentReport() {
|
||||
this.setData({ studentReportLoading: true })
|
||||
try {
|
||||
const res = await getReport()
|
||||
const viewData = this._buildStudentReport(res.data || {})
|
||||
this.setData({ ...viewData, fitnessItems: viewData.list.map(item => ({ ...item, unit: UNIT_MAP[item.name] || '' })), studentReportLoading: false })
|
||||
} catch (e) {
|
||||
this.setData({ report: {}, list: [], fitnessItems: [], studentReportLoading: false })
|
||||
}
|
||||
},
|
||||
|
||||
// 切换体测/课堂 Tab
|
||||
switchTab(e) {
|
||||
const tab = Number(e.currentTarget.dataset.tab)
|
||||
if (tab === this.data.activeTab) return
|
||||
this.setData({ activeTab: tab }, () => {
|
||||
if (tab === 1) {
|
||||
// 切到体测报告时绘制雷达图
|
||||
this.drawRadarChart(this)
|
||||
}
|
||||
if (tab === 0 && this.data.roomList.length === 0) this.getTrainClass(true)
|
||||
if (this.data.isTrainee && tab === 1) this.drawRadarChart(this)
|
||||
})
|
||||
},
|
||||
|
||||
// 切换课堂报告子Tab (课堂/月/季度)
|
||||
switchClassroomSubTab(e) {
|
||||
const tab = Number(e.currentTarget.dataset.tab)
|
||||
if (tab === this.data.classroomSubTab) return
|
||||
@ -409,7 +443,7 @@ Page({
|
||||
})
|
||||
this.setData({
|
||||
roomList: refresh ? newList : [...this.data.roomList, ...newList],
|
||||
page: this.data.page + 1,
|
||||
pageIndex: this.data.pageIndex + 1,
|
||||
hasMore: res.data.total > (this.data.pageIndex) * 20,
|
||||
})
|
||||
|
||||
@ -447,16 +481,10 @@ Page({
|
||||
*/
|
||||
onShow() {
|
||||
tabService.updateIndex(this, 2)
|
||||
if(wx.getStorageSync('roleId') && wx.getStorageSync('roleId') == 5){ //学员
|
||||
this.setData({
|
||||
isTrainee:true
|
||||
})
|
||||
}
|
||||
const isTrainee = wx.getStorageSync('roleId') && wx.getStorageSync('roleId') == 5
|
||||
this.setData({ isTrainee })
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
*/
|
||||
onHide() {
|
||||
|
||||
},
|
||||
@ -472,53 +500,40 @@ Page({
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
async onPullDownRefresh() {
|
||||
if (this.data.isTrainee) {
|
||||
const { activeTab, classroomSubTab } = this.data
|
||||
if (activeTab === 0) {
|
||||
// 课堂报告 Tab
|
||||
if (classroomSubTab === 0) {
|
||||
// 课堂列表
|
||||
this.setData({ pageIndex: 1, hasMore: true }, async () => {
|
||||
await this.getTrainClass(true)
|
||||
wx.stopPullDownRefresh()
|
||||
wx.showToast({ title: '刷新成功', icon: 'none' })
|
||||
})
|
||||
} else if (classroomSubTab === 1) {
|
||||
// 月报告
|
||||
await this.fetchMonthReport()
|
||||
const { activeTab, classroomSubTab, isTrainee } = this.data
|
||||
if (activeTab === 0) {
|
||||
if (classroomSubTab === 0) {
|
||||
this.setData({ pageIndex: 1, hasMore: true }, async () => {
|
||||
await this.getTrainClass(true)
|
||||
wx.stopPullDownRefresh()
|
||||
wx.showToast({ title: '刷新成功', icon: 'none' })
|
||||
} else if (classroomSubTab === 2) {
|
||||
// 季度报告
|
||||
await this.fetchQuarterReport()
|
||||
wx.stopPullDownRefresh()
|
||||
wx.showToast({ title: '刷新成功', icon: 'none' })
|
||||
}
|
||||
} else if (activeTab === 1) {
|
||||
// 体测报告 Tab
|
||||
await this.fetchFitnessReport()
|
||||
this.drawRadarChart(this)
|
||||
})
|
||||
} else if (classroomSubTab === 1) {
|
||||
await this.fetchMonthReport()
|
||||
wx.stopPullDownRefresh()
|
||||
wx.showToast({ title: '刷新成功', icon: 'none' })
|
||||
} else if (classroomSubTab === 2) {
|
||||
await this.fetchQuarterReport()
|
||||
wx.stopPullDownRefresh()
|
||||
wx.showToast({ title: '刷新成功', icon: 'none' })
|
||||
}
|
||||
} else if (isTrainee && activeTab === 1) {
|
||||
await this.fetchFitnessReport()
|
||||
this.drawRadarChart(this)
|
||||
wx.stopPullDownRefresh()
|
||||
wx.showToast({ title: '刷新成功', icon: 'none' })
|
||||
} else {
|
||||
await this.getStudentReport()
|
||||
wx.stopPullDownRefresh()
|
||||
wx.showToast({ title: '刷新成功!', icon: 'none' })
|
||||
wx.showToast({ title: '刷新成功', icon: 'none' })
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {
|
||||
this.getTrainClass()
|
||||
if (this.data.activeTab === 0 && this.data.classroomSubTab === 0) this.getTrainClass()
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@ -1,8 +1,17 @@
|
||||
<!-- pages/parent/report/report.wxml -->
|
||||
<view class="page-content" style="padding: 0 30rpx; padding-bottom: 300rpx;box-sizing: border-box;">
|
||||
<mp-bar background="linear-gradient(190deg, #49a5fc 0%, #f7f7f7 800rpx, #f7f7f7 100%)" title="报告" color="#fff" back="{{false}}"></mp-bar>
|
||||
<view class="tab-switcher">
|
||||
<view class="tab-btn {{activeTab === 0 ? 'tab-btn-active' : ''}}" data-tab="0" bind:tap="switchTab">
|
||||
课堂报告
|
||||
</view>
|
||||
<view class="tab-btn {{activeTab === 1 ? 'tab-btn-active' : ''}}" data-tab="1" bind:tap="switchTab">
|
||||
{{isTrainee ? '体测报告' : '学生报告'}}
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 非学员:原有学生报告 -->
|
||||
<view class="top" wx:if="{{!isTrainee}}">
|
||||
<view class="top" wx:if="{{!isTrainee && activeTab === 1 && list && list.length > 0}}">
|
||||
<view class="tit">
|
||||
<image src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/heart.png" class="heart" />
|
||||
健康身体 坚持运动
|
||||
@ -102,17 +111,12 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 学员:双Tab报告 -->
|
||||
<view wx:else class="trainee-wrap">
|
||||
<!-- Tab 切换按钮 -->
|
||||
<view class="tab-switcher">
|
||||
<view class="tab-btn {{activeTab === 0 ? 'tab-btn-active' : ''}}" data-tab="0" bind:tap="switchTab">
|
||||
课堂报告
|
||||
</view>
|
||||
<view class="tab-btn {{activeTab === 1 ? 'tab-btn-active' : ''}}" data-tab="1" bind:tap="switchTab">
|
||||
体测报告
|
||||
</view>
|
||||
<view class="report-empty" wx:if="{{!isTrainee && activeTab === 1 && !studentReportLoading && (!list || list.length === 0)}}">
|
||||
<view class="report-empty-icon">暂无</view>
|
||||
<text>暂无学生报告数据</text>
|
||||
</view>
|
||||
<!-- 课堂/体测报告 -->
|
||||
<view wx:if="{{isTrainee || activeTab === 0}}" class="trainee-wrap">
|
||||
<!-- 课堂报告 -->
|
||||
<block wx:if="{{activeTab === 0}}">
|
||||
<!-- 子Tab: 课堂 / 月 / 季度 -->
|
||||
@ -136,8 +140,12 @@
|
||||
</view>
|
||||
<mp-icon icon="arrow" size="{{13}}" color="#ccc"></mp-icon>
|
||||
</view>
|
||||
<view class="more-tip" wx:if="{{hasMore}}">加载中...</view>
|
||||
<view class="more-tip" wx:else>没有更多了哦~</view>
|
||||
<view class="more-tip" wx:if="{{roomList.length > 0 && hasMore}}">加载中...</view>
|
||||
<view class="more-tip" wx:elif="{{roomList.length > 0 && !hasMore}}">没有更多了哦~</view>
|
||||
<view class="report-empty" wx:if="{{!roomList || roomList.length === 0}}">
|
||||
<view class="report-empty-icon">暂无</view>
|
||||
<text>暂无课堂报告数据</text>
|
||||
</view>
|
||||
</block>
|
||||
<!-- 月报告 -->
|
||||
<block wx:elif="{{classroomSubTab === 1}}">
|
||||
@ -278,7 +286,7 @@
|
||||
</block>
|
||||
</block>
|
||||
<!-- 体测报告 -->
|
||||
<block wx:elif="{{activeTab === 1}}">
|
||||
<block wx:if="{{isTrainee && activeTab === 1}}">
|
||||
<view class="fitness-card">
|
||||
<view class="fitness-chart-title">体测成绩对比</view>
|
||||
<!-- 雷达图 Canvas -->
|
||||
@ -381,4 +389,4 @@
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -882,4 +882,32 @@
|
||||
.picker-year-arrow-disabled {
|
||||
opacity: 0.3;
|
||||
color: #ccc;
|
||||
}
|
||||
}
|
||||
|
||||
.report-empty {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 260rpx;
|
||||
margin: 30rpx 0;
|
||||
background: #fff;
|
||||
border-radius: 20rpx;
|
||||
box-shadow: 0 4rpx 16rpx rgba(73,165,252,0.1);
|
||||
color: #9aa7b2;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.report-empty-icon {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 18rpx;
|
||||
background: #eef8ff;
|
||||
color: #26A5F6;
|
||||
font-size: 24rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@ -1,147 +1,164 @@
|
||||
const utils = require("../../../utils/utils");
|
||||
|
||||
// subpackage/teacher/resource-detail/resource-detail.js
|
||||
Page({
|
||||
|
||||
/**
|
||||
* 页面的初始数据
|
||||
*/
|
||||
data: {
|
||||
title: '口令',
|
||||
value: '00:00',
|
||||
duration: '00:00',
|
||||
aduio:null,
|
||||
data:{},
|
||||
isPlay:false,
|
||||
percent:0,
|
||||
isReload:false,//是否重复播放
|
||||
isShow:false,//是否展示弹窗
|
||||
title: "音乐",
|
||||
data: {},
|
||||
playlist: [],
|
||||
currentIndex: 0,
|
||||
value: "00:00",
|
||||
duration: "00:00",
|
||||
percent: 0,
|
||||
isPlay: false,
|
||||
isSingleLoop: false,
|
||||
isShow: false,
|
||||
isClosing: false
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面加载
|
||||
*/
|
||||
onLoad(options) {
|
||||
console.log(JSON.parse(options.data))
|
||||
let current = {};
|
||||
try {
|
||||
current = JSON.parse(decodeURIComponent(options.data || "{}"));
|
||||
} catch (error) {
|
||||
console.error("资源参数解析失败", error);
|
||||
}
|
||||
|
||||
this.setData({
|
||||
data: JSON.parse(options.data),
|
||||
title: JSON.parse(options.data).id == 1 ? '口令' : JSON.parse(options.data).id == 2 ? '音乐' : '视频'
|
||||
})
|
||||
this.loadAudio(JSON.parse(options.data).resourceUrl)
|
||||
data: current,
|
||||
playlist: current.resourceUrl ? [current] : [],
|
||||
title: current.id == 1 ? "口令" : "音乐"
|
||||
});
|
||||
|
||||
this.createAudioContext();
|
||||
if (current.resourceUrl) {
|
||||
this.loadTrack(current, false);
|
||||
}
|
||||
|
||||
const eventChannel = this.getOpenerEventChannel && this.getOpenerEventChannel();
|
||||
if (eventChannel && eventChannel.on) {
|
||||
eventChannel.on("setPlaylist", ({ playlist = [], currentIndex = 0 }) => {
|
||||
if (!playlist.length) return;
|
||||
const safeIndex = Math.min(Math.max(Number(currentIndex) || 0, 0), playlist.length - 1);
|
||||
this.setData({ playlist, currentIndex: safeIndex, data: playlist[safeIndex] });
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
loadAudio(url) {
|
||||
createAudioContext() {
|
||||
this.audioCtx = wx.createInnerAudioContext();
|
||||
this.audioCtx.src = url
|
||||
this.audioCtx.onCanplay(() => {
|
||||
this.audioCtx.duration
|
||||
this.audioCtx.currentTime
|
||||
console.log(this.audioCtx.currentTime)
|
||||
console.log(this.audioCtx.duration)
|
||||
console.log(this.audioCtx.currentTime)
|
||||
this.setData({
|
||||
value: utils.formatTimeStr(this.audioCtx.currentTime),
|
||||
duration: utils.formatTimeStr(this.audioCtx.duration),
|
||||
})
|
||||
})
|
||||
this.audioCtx.obeyMuteSwitch = false;
|
||||
|
||||
this.audioCtx.onTimeUpdate(() => {
|
||||
console.log('在播放咯')
|
||||
console.log(this.data.percent)
|
||||
console.log('在播放咯')
|
||||
this.audioCtx.duration
|
||||
this.audioCtx.currentTime
|
||||
this.setData({
|
||||
value: utils.formatTimeStr(this.audioCtx.currentTime),
|
||||
duration: utils.formatTimeStr(this.audioCtx.duration),
|
||||
percent:((this.audioCtx.currentTime/this.audioCtx.duration)*100).toFixed(0)
|
||||
})
|
||||
})
|
||||
this.audioCtx.onCanplay(() => this.updateProgress());
|
||||
this.audioCtx.onTimeUpdate(() => this.updateProgress());
|
||||
this.audioCtx.onPlay(() => this.setData({ isPlay: true }));
|
||||
this.audioCtx.onPause(() => this.setData({ isPlay: false }));
|
||||
this.audioCtx.onStop(() => this.setData({ isPlay: false }));
|
||||
this.audioCtx.onEnded(() => {
|
||||
if (!this.data.isSingleLoop) this.changeTrack(1, true);
|
||||
});
|
||||
this.audioCtx.onError((error) => {
|
||||
console.error("音频播放失败", error);
|
||||
this.setData({ isPlay: false });
|
||||
wx.showToast({ title: "音频加载失败,请稍后重试", icon: "none" });
|
||||
});
|
||||
},
|
||||
|
||||
btnCilck(){
|
||||
console.log(this.audioCtx.src)
|
||||
if(this.data.isPlay){
|
||||
this.audioCtx.pause()
|
||||
this.setData({
|
||||
isPlay:false
|
||||
})
|
||||
}else{
|
||||
this.audioCtx.play()
|
||||
this.setData({
|
||||
isPlay:true
|
||||
})
|
||||
loadTrack(track, autoplay = true) {
|
||||
if (!track || !track.resourceUrl || !this.audioCtx) return;
|
||||
this.audioCtx.stop();
|
||||
this.audioCtx.loop = this.data.isSingleLoop;
|
||||
this.audioCtx.src = track.resourceUrl;
|
||||
this.setData({
|
||||
data: track,
|
||||
value: "00:00",
|
||||
duration: "00:00",
|
||||
percent: 0,
|
||||
isPlay: autoplay
|
||||
});
|
||||
if (autoplay) this.audioCtx.play();
|
||||
},
|
||||
|
||||
updateProgress() {
|
||||
if (!this.audioCtx) return;
|
||||
const currentTime = Number(this.audioCtx.currentTime) || 0;
|
||||
const duration = Number(this.audioCtx.duration) || 0;
|
||||
this.setData({
|
||||
value: utils.formatTimeStr(currentTime),
|
||||
duration: utils.formatTimeStr(duration),
|
||||
percent: duration ? Math.min((currentTime / duration) * 100, 100) : 0
|
||||
});
|
||||
},
|
||||
|
||||
btnCilck() {
|
||||
if (!this.audioCtx || !this.data.data.resourceUrl) return;
|
||||
this.data.isPlay ? this.audioCtx.pause() : this.audioCtx.play();
|
||||
},
|
||||
|
||||
playPrevious() {
|
||||
this.changeTrack(-1, true);
|
||||
},
|
||||
|
||||
playNext() {
|
||||
this.changeTrack(1, true);
|
||||
},
|
||||
|
||||
changeTrack(step, autoplay) {
|
||||
const { playlist, currentIndex } = this.data;
|
||||
if (!playlist.length) return;
|
||||
const nextIndex = (currentIndex + step + playlist.length) % playlist.length;
|
||||
this.setData({ currentIndex: nextIndex }, () => this.loadTrack(playlist[nextIndex], autoplay));
|
||||
},
|
||||
|
||||
selectTrack(e) {
|
||||
const index = Number(e.currentTarget.dataset.index);
|
||||
if (!Number.isInteger(index) || !this.data.playlist[index]) return;
|
||||
this.setData({ currentIndex: index }, () => {
|
||||
this.loadTrack(this.data.playlist[index], true);
|
||||
this.hideList();
|
||||
});
|
||||
},
|
||||
|
||||
toggleLoop() {
|
||||
const isSingleLoop = !this.data.isSingleLoop;
|
||||
if (this.audioCtx) this.audioCtx.loop = isSingleLoop;
|
||||
this.setData({ isSingleLoop });
|
||||
wx.showToast({ title: isSingleLoop ? "单曲循环" : "列表循环", icon: "none" });
|
||||
},
|
||||
|
||||
seekAudio(e) {
|
||||
if (!this.audioCtx || !this.audioCtx.duration) return;
|
||||
wx.createSelectorQuery().in(this).select("#audio-progress").boundingClientRect((rect) => {
|
||||
if (!rect || !rect.width) return;
|
||||
const ratio = Math.min(Math.max((e.detail.x - rect.left) / rect.width, 0), 1);
|
||||
this.audioCtx.seek(this.audioCtx.duration * ratio);
|
||||
}).exec();
|
||||
},
|
||||
|
||||
showList() {
|
||||
if (this.closeTimer) {
|
||||
clearTimeout(this.closeTimer);
|
||||
this.closeTimer = null;
|
||||
}
|
||||
this.setData({ isShow: true, isClosing: false });
|
||||
},
|
||||
|
||||
showList(){
|
||||
this.setData({
|
||||
isShow:true
|
||||
})
|
||||
hideList() {
|
||||
if (!this.data.isShow || this.data.isClosing) return;
|
||||
this.setData({ isClosing: true });
|
||||
this.closeTimer = setTimeout(() => {
|
||||
this.setData({ isShow: false, isClosing: false });
|
||||
this.closeTimer = null;
|
||||
}, 240);
|
||||
},
|
||||
|
||||
reload(){
|
||||
wx.showToast({
|
||||
title: this.data.isReload ?'列表循环':'单曲循环',
|
||||
icon:'none'
|
||||
})
|
||||
this.audioCtx.loop = true
|
||||
this.setData({
|
||||
isReload:!this.data.isReload
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面初次渲染完成
|
||||
*/
|
||||
onReady() {
|
||||
preventClose() {},
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面显示
|
||||
*/
|
||||
onShow() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面隐藏
|
||||
*/
|
||||
onHide() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 生命周期函数--监听页面卸载
|
||||
*/
|
||||
onUnload() {
|
||||
if(this.audioCtx){
|
||||
if (this.closeTimer) clearTimeout(this.closeTimer);
|
||||
if (this.audioCtx) {
|
||||
this.audioCtx.stop();
|
||||
if(this.audioCtx.destory){
|
||||
this.audioCtx.destory();
|
||||
}
|
||||
if (this.audioCtx.destroy) this.audioCtx.destroy();
|
||||
this.audioCtx = null;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面相关事件处理函数--监听用户下拉动作
|
||||
*/
|
||||
onPullDownRefresh() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 页面上拉触底事件的处理函数
|
||||
*/
|
||||
onReachBottom() {
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 用户点击右上角分享
|
||||
*/
|
||||
onShareAppMessage() {
|
||||
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
@ -1,10 +1,9 @@
|
||||
{
|
||||
"usingComponents": {
|
||||
"mp-bar": "weui-miniprogram/navigation-bar/navigation-bar",
|
||||
"mp-icon": "weui-miniprogram/icon/icon",
|
||||
"mp-half-screen-dialog": "weui-miniprogram/half-screen-dialog/half-screen-dialog"
|
||||
"mp-icon": "weui-miniprogram/icon/icon"
|
||||
},
|
||||
"backgroundColorContent": "#f7f7f7",
|
||||
"backgroundColor": "#f7f7f7",
|
||||
"navigationStyle": "custom"
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,32 +1,73 @@
|
||||
<!--subpackage/teacher/resource-detail/resource-detail.wxml-->
|
||||
<view class="page-content pad">
|
||||
<mp-bar background="linear-gradient(190deg, #49a5fc 0%, #f7f7f7 800rpx, #f7f7f7 100%)" title="{{title}}" color="rgba(255,255,255,0.8)"></mp-bar>
|
||||
<view class="page-content player-page">
|
||||
<mp-bar background="transparent" title="{{title}}" color="#ffffff"></mp-bar>
|
||||
|
||||
<view class="card">
|
||||
|
||||
<view class="title">{{data.resourceName}}</view>
|
||||
<view class="time">
|
||||
{{value}}
|
||||
<view>{{duration}}</view>
|
||||
<view class="player-card">
|
||||
<view class="record-wrap {{isPlay ? 'record-playing' : ''}}">
|
||||
<view class="record">
|
||||
<view class="record-line line-one"></view>
|
||||
<view class="record-line line-two"></view>
|
||||
<view class="record-core">
|
||||
<text class="record-note">♫</text>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="progress">
|
||||
<view class="active" style="width:{{percent+'%'}}"></view>
|
||||
<view class="song-name">{{data.resourceName || '暂无音频'}}</view>
|
||||
<view class="song-count" wx:if="{{playlist.length}}">{{currentIndex + 1}} / {{playlist.length}}</view>
|
||||
|
||||
<view class="progress-area">
|
||||
<view id="audio-progress" class="progress" bind:tap="seekAudio">
|
||||
<view class="active" style="width: {{percent}}%;">
|
||||
<view class="progress-dot"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="time">
|
||||
<text>{{value}}</text>
|
||||
<text>{{duration}}</text>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="btnlist">
|
||||
<image bind:tap="reload" src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/teacher/{{isReload?'reload': 'repeat'}}.png" class="img" />
|
||||
<image src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/teacher/play-previous.png" class="img" />
|
||||
<view class="btn" bind:tap="btnCilck">
|
||||
<mp-icon icon="{{!isPlay?'play':'pause'}}" type="field" size="{{50}}" color="#fff"></mp-icon>
|
||||
<view class="tool-button" bind:tap="toggleLoop">
|
||||
<image src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/teacher/{{isSingleLoop ? 'reload' : 'repeat'}}.png" class="tool-icon" />
|
||||
<text>{{isSingleLoop ? '单曲' : '列表'}}</text>
|
||||
</view>
|
||||
<image bind:tap="playPrevious" src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/teacher/play-previous.png" class="switch-icon" />
|
||||
<view class="play-button" bind:tap="btnCilck">
|
||||
<mp-icon icon="{{!isPlay ? 'play' : 'pause'}}" type="field" size="52" color="#ffffff"></mp-icon>
|
||||
</view>
|
||||
<image bind:tap="playNext" src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/teacher/play-next.png" class="switch-icon" />
|
||||
<view class="tool-button" bind:tap="showList">
|
||||
<image src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/teacher/play-list.png" class="tool-icon" />
|
||||
<text>列表</text>
|
||||
</view>
|
||||
<image src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/teacher/play-next.png" class="img" />
|
||||
<image bind:tap="showList" src="https://yuedong-wechatapplet.oss-cn-shanghai.aliyuncs.com/static/teacher/play-list.png" class="img" />
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<mp-half-screen-dialog bindbuttontap="buttontap" show="{{isShow}}">
|
||||
<view slot="footer" class="none">1</view>
|
||||
<view slot="title" class="none"></view>
|
||||
</mp-half-screen-dialog>
|
||||
</view>
|
||||
<view wx:if="{{isShow}}" class="playlist-mask {{isClosing ? 'playlist-mask-leave' : 'playlist-mask-enter'}}" bind:tap="hideList">
|
||||
<view class="playlist-panel {{isClosing ? 'playlist-panel-leave' : 'playlist-panel-enter'}}" catch:tap="preventClose">
|
||||
<view class="playlist-header">
|
||||
<view>
|
||||
<view class="playlist-title">播放列表</view>
|
||||
<view class="playlist-subtitle">共 {{playlist.length}} 首</view>
|
||||
</view>
|
||||
<view class="close-button" bind:tap="hideList">×</view>
|
||||
</view>
|
||||
<scroll-view scroll-y class="playlist-scroll" scroll-into-view="song-{{currentIndex}}">
|
||||
<view wx:if="{{!playlist.length}}" class="empty-list">暂无可播放歌曲</view>
|
||||
<view
|
||||
wx:for="{{playlist}}"
|
||||
wx:key="resourceUrl"
|
||||
id="song-{{index}}"
|
||||
class="playlist-item {{index === currentIndex ? 'playlist-active' : ''}}"
|
||||
data-index="{{index}}"
|
||||
bind:tap="selectTrack"
|
||||
>
|
||||
<view class="song-index">{{index === currentIndex && isPlay ? '♪' : index + 1}}</view>
|
||||
<view class="playlist-name">{{item.resourceName}}</view>
|
||||
<view wx:if="{{index === currentIndex}}" class="playing-label">{{isPlay ? '播放中' : '当前'}}</view>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@ -1,78 +1,161 @@
|
||||
/* subpackage/teacher/resource-detail/resource-detail.wxss */
|
||||
.pad{
|
||||
padding: 0 30rpx;
|
||||
.player-page {
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
padding: 0 30rpx 60rpx;
|
||||
overflow: hidden;
|
||||
background: linear-gradient(160deg, #42a4fc 0%, #7fc8ff 28%, #f4f8fc 64%, #f7f7f7 100%);
|
||||
}
|
||||
|
||||
.card{
|
||||
padding: 60rpx 30rpx;
|
||||
box-sizing: border-box;
|
||||
box-shadow: 0 10rpx 10rpx rgba(226, 222, 222,.4);
|
||||
background-color: #fff;
|
||||
border-radius: 20rpx;
|
||||
margin-top: 30rpx;
|
||||
.player-card {
|
||||
margin-top: 38rpx;
|
||||
padding: 66rpx 34rpx 54rpx;
|
||||
border: 1rpx solid rgba(255, 255, 255, .75);
|
||||
border-radius: 36rpx;
|
||||
background: rgba(255, 255, 255, .94);
|
||||
box-shadow: 0 24rpx 60rpx rgba(35, 116, 180, .18);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title{
|
||||
font-weight: bold;
|
||||
font-size: 48rpx;
|
||||
}
|
||||
|
||||
.time{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
color: #636363;
|
||||
font-size: 32rpx;
|
||||
margin-top: 100rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.progress{
|
||||
width: 100%;
|
||||
height: 24rpx;
|
||||
border-radius: 24rpx;
|
||||
background-color: #e4e4e4;
|
||||
}
|
||||
.active{
|
||||
background-color: #FBB716;
|
||||
border-radius: 24rpx;
|
||||
height: 24rpx;
|
||||
width: 10%;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.btn{
|
||||
background-color: #FBB716;
|
||||
.record-wrap {
|
||||
width: 330rpx;
|
||||
height: 330rpx;
|
||||
margin: 0 auto 52rpx;
|
||||
padding: 18rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
width: 140rpx;
|
||||
height: 140rpx;
|
||||
margin: auto;
|
||||
background: rgba(251, 183, 22, .16);
|
||||
box-shadow: 0 18rpx 38rpx rgba(48, 66, 84, .2);
|
||||
}
|
||||
|
||||
.record {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: 50%;
|
||||
background: repeating-radial-gradient(circle, #2d3741 0, #2d3741 10rpx, #202832 11rpx, #202832 17rpx);
|
||||
}
|
||||
|
||||
.btnlist{
|
||||
.record-playing .record { animation: recordRotate 7s linear infinite; }
|
||||
.record-line { position: absolute; border: 2rpx solid rgba(255, 255, 255, .2); border-radius: 50%; }
|
||||
.line-one { width: 210rpx; height: 210rpx; }
|
||||
.line-two { width: 150rpx; height: 150rpx; }
|
||||
.record-core {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin-top: 80rpx;
|
||||
justify-content: center;
|
||||
width: 108rpx;
|
||||
height: 108rpx;
|
||||
border: 12rpx solid #f7c143;
|
||||
border-radius: 50%;
|
||||
background: #fbb716;
|
||||
}
|
||||
.record-note { color: #fff; font-size: 52rpx; line-height: 1; }
|
||||
|
||||
@keyframes recordRotate { from { transform: rotate(0); } to { transform: rotate(360deg); } }
|
||||
|
||||
.song-name {
|
||||
min-height: 58rpx;
|
||||
overflow: hidden;
|
||||
color: #1d2a36;
|
||||
font-size: 42rpx;
|
||||
font-weight: 600;
|
||||
line-height: 58rpx;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.img{
|
||||
width: 64rpx;
|
||||
height: 64rpx;
|
||||
margin: 20rpx;
|
||||
.song-count { margin-top: 10rpx; color: #96a0aa; font-size: 24rpx; }
|
||||
.progress-area { margin-top: 70rpx; }
|
||||
.progress {
|
||||
width: 100%;
|
||||
height: 10rpx;
|
||||
border-radius: 10rpx;
|
||||
background: #e5eaf0;
|
||||
}
|
||||
.active { position: relative; height: 10rpx; border-radius: 10rpx; background: linear-gradient(90deg, #ffd465, #fbb716); }
|
||||
.progress-dot {
|
||||
position: absolute;
|
||||
top: -8rpx;
|
||||
right: -13rpx;
|
||||
width: 26rpx;
|
||||
height: 26rpx;
|
||||
border: 5rpx solid #fff;
|
||||
border-radius: 50%;
|
||||
background: #fbb716;
|
||||
box-shadow: 0 3rpx 10rpx rgba(251, 183, 22, .55);
|
||||
}
|
||||
.time { display: flex; justify-content: space-between; margin-top: 18rpx; color: #87929d; font-size: 25rpx; }
|
||||
|
||||
.btnlist { display: flex; align-items: center; justify-content: space-between; margin-top: 58rpx; }
|
||||
.play-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 142rpx;
|
||||
height: 142rpx;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(145deg, #ffd052, #f5a900);
|
||||
box-shadow: 0 16rpx 30rpx rgba(251, 183, 22, .35);
|
||||
}
|
||||
.switch-icon { width: 62rpx; height: 62rpx; padding: 12rpx; }
|
||||
.tool-button { display: flex; flex-direction: column; align-items: center; min-width: 72rpx; color: #7e8994; font-size: 21rpx; }
|
||||
.tool-icon { width: 50rpx; height: 50rpx; margin-bottom: 5rpx; }
|
||||
|
||||
.playlist-mask {
|
||||
position: fixed;
|
||||
z-index: 99;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
background: rgba(13, 26, 39, .48);
|
||||
}
|
||||
.playlist-mask-enter { animation: maskFadeIn 240ms ease-out both; }
|
||||
.playlist-mask-leave { animation: maskFadeOut 240ms ease-in both; }
|
||||
.playlist-panel {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
padding: 38rpx 30rpx calc(24rpx + env(safe-area-inset-bottom));
|
||||
border-radius: 34rpx 34rpx 0 0;
|
||||
background: #fff;
|
||||
box-shadow: 0 -16rpx 50rpx rgba(18, 43, 65, .15);
|
||||
}
|
||||
.playlist-panel-enter { animation: playlistSlideUp 300ms cubic-bezier(.22, .8, .3, 1) both; }
|
||||
.playlist-panel-leave { animation: playlistSlideDown 240ms ease-in both; }
|
||||
|
||||
@keyframes maskFadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
.none{
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
height: 0;
|
||||
@keyframes maskFadeOut {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
.weui-half-screen-dialog__ft{
|
||||
padding: 0;
|
||||
}
|
||||
@keyframes playlistSlideUp {
|
||||
from { transform: translate3d(0, 100%, 0); }
|
||||
to { transform: translate3d(0, 0, 0); }
|
||||
}
|
||||
|
||||
@keyframes playlistSlideDown {
|
||||
from { transform: translate3d(0, 0, 0); }
|
||||
to { transform: translate3d(0, 100%, 0); }
|
||||
}
|
||||
.playlist-header { display: flex; align-items: center; justify-content: space-between; padding: 0 10rpx 28rpx; }
|
||||
.playlist-title { color: #172532; font-size: 36rpx; font-weight: 600; }
|
||||
.playlist-subtitle { margin-top: 6rpx; color: #9aa4ad; font-size: 24rpx; }
|
||||
.close-button { width: 64rpx; color: #8c969f; font-size: 48rpx; font-weight: 300; line-height: 64rpx; text-align: center; }
|
||||
.playlist-scroll { max-height: 650rpx; }
|
||||
.playlist-item { display: flex; align-items: center; min-height: 96rpx; padding: 0 22rpx; border-radius: 18rpx; color: #34424e; }
|
||||
.playlist-active { color: #eaa400; background: #fff8e8; }
|
||||
.song-index { width: 52rpx; flex-shrink: 0; color: #a0a9b2; font-size: 26rpx; text-align: left; }
|
||||
.playlist-active .song-index { color: #f1ac00; }
|
||||
.playlist-name { overflow: hidden; flex: 1; font-size: 29rpx; text-align: left; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.playing-label { margin-left: 20rpx; color: #f1ac00; font-size: 22rpx; }
|
||||
.empty-list { padding: 100rpx 0; color: #a0a9b2; font-size: 28rpx; text-align: center; }
|
||||
|
||||
@ -59,10 +59,21 @@ Page({
|
||||
}else if(this.data.id == 4){
|
||||
return
|
||||
}else {
|
||||
const data = e.currentTarget.dataset.item
|
||||
data.id = this.data.id
|
||||
const data = Object.assign({}, e.currentTarget.dataset.item, { id: this.data.id })
|
||||
const playlist = []
|
||||
const collectResources = (nodes) => {
|
||||
(nodes || []).forEach((node) => {
|
||||
(node.resourceDetails || []).forEach((item) => playlist.push(Object.assign({}, item, { id: this.data.id })))
|
||||
collectResources(node.children)
|
||||
})
|
||||
}
|
||||
collectResources(this.data.list)
|
||||
const currentIndex = Math.max(playlist.findIndex((item) => item.resourceUrl === data.resourceUrl), 0)
|
||||
wx.navigateTo({
|
||||
url: '/subpackage/teacher/resource-detail/resource-detail?data='+JSON.stringify(data),
|
||||
url: '/subpackage/teacher/resource-detail/resource-detail?data='+encodeURIComponent(JSON.stringify(data)),
|
||||
success(res) {
|
||||
res.eventChannel.emit('setPlaylist', { playlist, currentIndex })
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
@ -115,4 +126,4 @@ Page({
|
||||
onShareAppMessage() {
|
||||
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user