Files
tra-app/src/pages/course/study.vue
T

1926 lines
48 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<view class="course-study max_page">
<view class="top-bg"></view>
<view class="bot-bg"></view>
<view class="course-study-body" v-show="step === 1">
<view class="seek-setting">
<view class="inline-block" @click="showChangeSeekModelNow">{{ chooseRate }}</view>
</view>
<view class="body-title">
<uv-icon class="arrow-icon" name="arrow-left" @click="backRouter()" />
语言选择开启学习之旅
</view>
<view class="voice-list">
<uv-radio-group v-model="choiceTeacher">
<view class="voice-item" v-for="item in teacherList" :key="item.teacherNo"
@click="choiceTeacher = item.teacherNo">
<view class="avatar-box">
<ImagePreview class="img-box" :src="item.imgAddr" :isCache="true" :width="120" :height="160">
</ImagePreview>
</view>
<view class="right-cont">
<view class="right-name">{{ item.teacherName }}</view>
<view class="right-desc">描述{{ item.voiceDesc || '--' }}</view>
<view class="audio-box">
<view
:class="['talking-gif', activeVoiceTeachNo === item.teacherNo && !loadingTeacherVoice ? 'is-play' : '']">
</view>
<CommonLoading color="#06f" v-show="activeVoiceTeachNo === item.teacherNo && loadingTeacherVoice"
style="float: left" />
<view class="play-btn" @click="playTeacherVoice(item)">
<image class="icon" v-if="activeVoiceTeachNo !== item.teacherNo"
src="@/static/images/course/play-blue.png" style="width: 100%; height: 100%"></image>
<image class="icon" v-else src="@/static/images/course/stop-blue.png"
style="width: 100%; height: 100%"></image>
</view>
</view>
</view>
<uv-radio :name="item.teacherNo" class="radio-box" @click.stop />
</view>
</uv-radio-group>
</view>
<view class="start-btns" @click="nextStep"> 开启学习之旅</view>
</view>
<view :class="['course-study-body', !IsAndEnv ? 'isIos' : 'isAnd']" v-show="step === 2">
<view class="seek-setting">
<view class="inline-block" @click="showChangeSeekModelNow">{{ chooseRate }}</view>
<view class="image-icon" @click="showChangeBgModelNow">
<image src="@/static/images/course/setting.png" alt="" class="image-icon-img"></image>
</view>
</view>
<view class="body-title" @click="scrollToBottomNow">
<uv-icon class="arrow-icon" name="arrow-left" @click="backRouter()" />
课程学习
</view>
<view class="study-num">
<view class="color-black ml-40">学习中<text class="color-gray"><text
class="color-black">{{ showOrder }}</text>/{{ pgrphNum }}</text></view>
<view class="bg-block">
<view class="bg-gray">
<view class="bg-blue" :style="`width: ${showOrder/pgrphNum * 100}%;`"></view>
</view>
</view>
</view>
<scroll-view class="talking-list" :scroll-y="true" :scroll-top="scrollTop" :show-scrollbar="false"
:scroll-with-animation="true">
<!-- <view class="talking-list-cont" id="scroll-box" ref="scrollBoxRef"> -->
<TalkingItem class="talking-item" v-for="(item, index) in talkingList" :key="'item' + item.id" :type="item.type"
:dataInfo="item" :activeVoiceId="activeVoiceTalkingItemId" :isPlaying="activeVoiceTalkingItemId === item.id"
:voiceRate="chooseRate" :lastQuestionInfo="lastTalkingInfo" :teacherInfo="choiceTeacherInfo"
:answerMethod="answerMethod" @changePlay="changePlayItemId" @nextQuestion="nextQuestionSuccess"
@withdraw="withdrawNow" @answerQuestion="answerQuestionNow" @finishQuestion="finishQuestionNow"
:id="'item' + item.id" :isLast="talkingList.length === index + 1"></TalkingItem>
<!-- <video v-if="videoUrlShow" :src="videoUrlShow" controls></video> -->
<!-- </view> -->
</scroll-view>
<!-- <TouchBtn
v-if="answerMethod !== '03'"
class="talk-btns"
@uploadVoice="uploadRecordNow"
@submitAnswer="submitAnswerNow"
@startRecord="changePlayItemId('')"
:isLoading="isUploadingVoice"
:onlyVoice="answerOnlyVoice"
:canAnswer="testAnswer"
:adjustPosition="true"
:canAnswerText="canAnswerText"
:answerMethod="answerMethod"
:isDisabled="(lastTalkingInfo.type || 0) !== 4 || ['SN', 'MU', 'JD'].includes(lastTalkingInfo.qnsTyp)" />
<TouchBtnOnlyVoice
v-else
class="talk-btns"
@uploadVoice="uploadRecordNow"
@submitAnswer="submitAnswerNow"
@startRecord="changePlayItemId('')"
:isLoading="isUploadingVoice"
:onlyVoice="answerOnlyVoice"
:canAnswer="testAnswer"
:adjustPosition="true"
:canAnswerText="canAnswerText"
:answerMethod="answerMethod"
:isDisabled="(lastTalkingInfo.type || 0) !== 4 || ['SN', 'MU', 'JD'].includes(lastTalkingInfo.qnsTyp)" /> -->
<touch-btn class="talk-btns" ref="touchBtnRef" @sendLoading="sendLoading" @submitVoice="submitVoice"
@submitAnswer="submitAnswerNow" @startRecord="changePlayItemId('')" :isLoading="isUploadingVoice"
:adjustPosition="true" :canAnswer="!testAnswer" :canAnswerText="canAnswerText"
:isDisabled="(lastTalkingInfo.type || 0) !== 4 || ['SN', 'MU', 'JD'].includes(lastTalkingInfo.qnsTyp)"
loadingText="只能进行一条回答" :answerMethod="answerMethod" />
</view>
<uv-overlay :show="showSeekModel" opacity=".6" @click="showSeekModel = false">
<view class="overlay-box">
<view class="white-bg-box">
<view class="box-title">倍速</view>
<view class="box-btns">
<view v-for="item in rateList" :class="{ 'is-active': item === chooseRate }" @click="chooseRateNow(item)">
{{ item }}
</view>
</view>
</view>
</view>
</uv-overlay>
<uv-overlay :show="showBgModel" opacity=".6" @click="showBgModel = false">
<view class="overlay-box">
<view class="white-bg-box-setting white-bg-box">
<view class="box-title">设置</view>
<view class="box-items" @click.stop>
<view class="box-item">
<view class="box-item-title">音色</view>
<uv-radio-group v-model="choiceTeacher" class="box-item-cont">
<view class="overlay-voice-item" v-for="item in teacherList" :key="item.teacherNo"
@click="changeTeacherVoiceNow(item)">
<view class="overlay-avatar-box">
<ImagePreview class="overlay-img-box" :src="item.imgAddr" :isCache="true" :width="120"
:height="160"></ImagePreview>
</view>
<view class="overlay-teacher-name">{{ item.teacherName }}</view>
<uv-radio :name="item.teacherNo" class="overlay-radio-box" @click.stop />
</view>
</uv-radio-group>
</view>
</view>
</view>
</view>
</uv-overlay>
<PreviewDetail />
<badge ref="badgeRef"></badge>
<points ref="pointsRef"></points>
</view>
</template>
<script setup>
import {
reactive,
ref,
computed,
unref,
onMounted,
onUnmounted,
getCurrentInstance,
nextTick
} from 'vue'
import {
onLoad,
onShow,
onUnload,
onHide
} from '@dcloudio/uni-app'
import {
previewFile,
downloadFile,
commonUploadVoiceFile,
downloadVoiceFile,
uploadVoiceFile
} from '@/api/common.js'
import {
getToken,
getPhoneEnvBool
} from '@/common/common.js'
import {
get_base_url,
goto_login_fun
} from '@/api/request'
import {
getCourseStudyInfoApi,
afreshStudyCourseApi,
queryQuestionEvalateApi,
textChartApi,
setStudyTeacherApi,
setPreviewTeacherApi,
textChartPreviewApi,
previewAfreshStudyCourse,
studyEndApi,
studyStartApi
} from '@/api/study.js'
import common from '@/common/common'
import TalkingItem from '@/pages/course/components/talkingItem.vue'
import ImagePreview from '@/components/image-preview/image-preview.vue'
import PreviewDetail from '@/components/image-preview/preview-detail.vue'
// import TouchBtn from '@/pages/course/components/touchBtn.vue'
// import TouchBtnOnlyVoice from '@/pages/course/components/touchBtnOnlyVoice.vue'
import badge from '@/components/points-and-badge/badge';
import points from '@/components/points-and-badge/points';
import usePointsAndBadge from '@/components/points-and-badge/usePointsAndBadge';
const {
pointAndBadgesRun,
badgeRef,
pointsRef
} = usePointsAndBadge();
import {
useSocketStore
} from '@/store/socket.js'
import {
useStorageStore
} from '@/store/storage.js'
import {
storeToRefs
} from 'pinia'
const socketStore = useSocketStore()
const storageStore = useStorageStore()
const {
SocketObj
} = storeToRefs(socketStore)
const audioCacheObj = computed(() => storageStore.audioObj)
const {
proxy
} = getCurrentInstance()
const crsNum = ref('')
const crsId = ref('')
const step = ref(0)
const teacherList = ref([])
const choiceTeacher = ref('')
const videoUrlShow = ref('')
const talkingList = ref([])
const activeVoiceTalkingItemId = ref('')
const answerValue = ref('')
const inputRef = ref('')
const flagVal = ref('1')
const isUploadingVoice = ref(false)
const isPreview = ref(false)
const IsAndEnv = ref(getPhoneEnvBool('AND'))
// const badgeRef = ref(null);
// const pointsRef = ref(null);
const ajaxApi = computed(() => (isPreview.value ? textChartPreviewApi : textChartApi))
const {
statusBarHeight
} = uni.getWindowInfo()
const pageTopPadding = computed(() => statusBarHeight + 'px')
const isContinue = ref('N')
// 初始化数据
const getData = async flag => {
try {
const {
body
} = await getCourseStudyInfoApi({
crsId: crsId.value
})
teacherList.value = body.teacheList.map(t => ({
...t,
voicePath: '',
voiceObj: null
}))
if (isPreview.value) {
resStartStudyNow(body.teacheList)
} else {
if (body.isStdyFinish === 'Y') {
// 重新学习
resStartStudyNow(body.teacheList)
return
}
if (body.stdyProgressFlag === 'N') {
step.value = 1
} else {
choiceTeacher.value = body.choiceTeacher
if (startPgrphId.value) {
sendMessage({
commond: 'STDY',
body: {
crsId: crsId.value,
teacherNo: choiceTeacher.value,
studyType: isPreview.value ? 'P' : 'S',
isContinue: isContinue.value
}
})
return
}
common
.show('提示', '系统检测到您已存在学习记录,要继续学习吗?', true, '重新学习', '继续学习')
.then(res => {
if (res) {
// 继续学习 获取学习历史接口
isContinue.value = 'Y'
// 滚动到最下面
scrollToBottomNow()
sendMessage({
commond: 'STDY',
body: {
crsId: crsId.value,
teacherNo: choiceTeacher.value,
studyType: isPreview.value ? 'P' : 'S',
isContinue: isContinue.value
}
})
step.value = 2
} else {
// 重新学习
resStartStudyNow(body.teacheList)
}
})
.finally(() => {})
}
}
} catch {} finally {
watchFlag.value += 1
}
}
const answerMethod = ref('01')
const startStudy = data => {
studyStartApi(data)
.then(res => {
const {
body
} = res
stdyId.value = body.stdyId || ''
stdyBatch.value = body.stdyBatch || ''
oldStdyId.value = body.oldStdyId || ''
logId.value = body.logId || ''
getGraphInfoUnStudy(1)
step.value = 2
console.log(body)
answerMethod.value = body.answerMethod || '01'
})
.catch(err => {})
}
// 重新学习
const resStartStudyNow = ($list = []) => {
let _ajax = isPreview.value ? previewAfreshStudyCourse : afreshStudyCourseApi
_ajax({
crsId: crsId.value
})
.then(res => {
step.value = 1
teacherList.value = $list.map(t => ({
...t,
voicePath: '',
voiceObj: null
}))
})
.catch(e => {
step.value = 1
teacherList.value = $list.map(t => ({
...t,
voicePath: '',
voiceObj: null
}))
})
}
const showChangeSeekModelNow = () => {
showSeekModel.value = true
}
const showBgModel = ref(false)
const showChangeBgModelNow = () => {
console.log(222)
changePlayItemId()
showBgModel.value = true
}
// 关闭播放状态
const changePlayItemId = (id = '') => {
activeVoiceTalkingItemId.value = id
if (id == '') {
storageStore.setGettingVoiceId(id)
}
}
// 选择的教师对象信息
const choiceTeacherInfo = computed(() => {
return teacherList.value.find(t => t.teacherNo === choiceTeacher.value)
})
const changeTeacherVoiceNow = async item => {
if (item.teacherNo === choiceTeacher.value) return
try {
let _ajax = isPreview.value ? setPreviewTeacherApi : setStudyTeacherApi
const res = await _ajax({
stdyId: stdyId.value,
teacherNo: item.teacherNo
})
if (res.rtnCode === '0000') {
choiceTeacher.value = item.teacherNo
showBgModel.value = false
}
} catch {} finally {}
}
// 选择教师语音对象
const activeVoiceTeachNo = ref('')
let teacherVoiceObj = uni.createInnerAudioContext()
teacherVoiceObj.onEnded(() => {
activeVoiceTeachNo.value = ''
teacherVoiceObj.src = ''
})
const loadingTeacherVoice = ref(false)
const playTeacherVoice = item => {
teacherVoiceObj.stop()
teacherVoiceObj.src = ''
let _isStop = item.teacherNo === activeVoiceTeachNo.value
activeVoiceTeachNo.value = _isStop ? '' : item.teacherNo
if (_isStop) return
if (item.voicePath) {
teacherVoiceObj.src = item.voicePath
teacherVoiceObj.play()
} else {
loadingTeacherVoice.value = true
storageStore.getStorageById('audio', item.teacherNo, url => {
if (url) {
teacherVoiceObj.src = url
teacherVoiceObj.play()
loadingTeacherVoice.value = false
} else {
downloadVoiceFile('/trastudy/traStdyInfo/trialListening?teachNo=' + item.teacherNo).then(res => {
loadingTeacherVoice.value = false
item.voicePath = res.tempFilePath
teacherVoiceObj.src = item.voicePath
teacherVoiceObj.play()
storageStore.setStorage('audio', item.teacherNo, item.voicePath)
})
}
})
}
}
var socketTask = null
const nextStep = () => {
if (choiceTeacher.value) {
teacherVoiceObj.destroy()
sendMessage({
commond: 'STDY',
body: {
crsId: crsId.value,
teacherNo: choiceTeacher.value,
studyType: isPreview.value ? 'P' : 'S',
isContinue: isContinue.value
}
})
step.value = 2
} else {
uni.showToast({
title: '请选择一位教师!',
icon: 'none',
duration: 1000
})
}
}
const pgrphNum = ref(1)
const showOrder = ref(0)
const isLastPgrph = ref(false)
const backRouter = () => {
// common.redirectTo(`/pages/courseDetail/index?crsId=${crsId.value}&crsNum=${crsNum.value}&from=study`)
// uni.$emit('refresh_course_list') // yh0903让去除
common.navigateBack()
}
const answerOnlyVoice = computed(() => {
return lastTalkingInfo.value.answerWay === 'V' || (answerMethod.value === '03')
})
const canAnswerText = computed(() => {
return '每个问题最多连续发送五次答案!'
})
const oldStdyId = ref('')
// 获取段落
const getGraphInfoUnStudy = (flag = 0, lastPgrphId = '') => {
isGetting.value = true
let _id = new Date().getTime() + 'id'
let _data = {
crsId: crsId.value,
stdyId: unref(stdyId),
stdyBatch: unref(stdyBatch),
oldStdyId: unref(oldStdyId),
isContinue: isContinue.value,
lastPgrphId
}
if (flag === 1 && !!startPgrphId.value) {
_data.pgrphId = startPgrphId.value
}
unref(ajaxApi)({
processType: 'PRGH-STDY',
requestBody: JSON.stringify(_data)
})
.then(res => {
let _body = res.body || {}
if (!_body.chatBody) {
return
}
if (Number(_body.bodyTyp) === 7) {
uni.$emit('refresh_course_list', crsId.value)
if (isPreview.value) {
common
.show('提示', '您已学完全部知识点', false)
.then(res => {
common.navigateBack()
// common.redirectTo(`/pages/courseDetail/index?crsId=${crsId.value}&crsNum=${crsNum.value}&from=study&isPreview=true`)
})
.finally(() => {})
} else {
pointAndBadgesRun('02', crsId.value, () => {
common
.show('恭喜你已学完全部知识点', '有什么心得跟我们分享吗?', true, '取消', '去评论')
.then(res => {
if (res) {
// 去评论
common.redirectTo(
`/pages/courseDetail/index?tabAct=2&crsId=${crsId.value}&crsNum=${crsNum.value}&from=study&isPreview=true`
)
} else {
// 取消
common.navigateBack()
// common.redirectTo(`/pages/courseDetail/index?crsId=${crsId.value}&crsNum=${crsNum.value}&from=study`)
}
})
.finally(() => {})
})
}
return
}
lastTalkingInfo.value = {
crsId: crsId.value,
qstLogId: _body.chatBody,
pgrphId: _body.pgrphId,
logId: _body.chatBody.logId || '',
chatContent: _body.chatContent,
userInfo: {
...choiceTeacherInfo.value
},
..._body.chatBody,
type: Number(_body.bodyTyp) || 0,
id: _id
}
isLastPgrph.value = _body.chatBody.haveNext !== 'Y'
talkingList.value.push(lastTalkingInfo.value)
pgrphNum.value = _body.chatBody.pgrphNum
showOrder.value = _body.chatBody.showOrder
logId.value = _body.chatBody.logId || ''
scrollToBottomNow()
})
.finally(() => {
isGetting.value = false
})
}
const lastTalkingInfo = ref({})
const qstLogId = ref('')
// 获取问题
const getQuestionInfoUnStudy = pgrphId => {
isGetting.value = true
unref(ajaxApi)({
processType: 'QNS-ANSER',
requestBody: JSON.stringify({
crsId: crsId.value,
stdyId: unref(stdyId),
stdyBatch: unref(stdyBatch),
logId: unref(logId),
pgrphId: pgrphId
})
})
.then(res => {
let _id = new Date().getTime() + 'id'
let _body = res.body || {}
console.log(_body)
if (_body.bodyTyp === '4') {
lastTalkingInfo.value = {
crsId: crsId.value,
stdyId: unref(stdyId),
stdyBatch: unref(stdyBatch),
logId: unref(logId),
pgrphId,
chatContent: _body.chatContent || '',
...(_body.chatBody || {}),
type: 4,
userInfo: {
...choiceTeacherInfo.value
},
id: _id,
isQuestion: true,
my_answer: [],
backend_answer: []
}
talkingList.value.push(JSON.parse(JSON.stringify(lastTalkingInfo.value)))
qstLogId.value = _body.chatBody?.qstLogId
scrollToBottomNow(800)
} else {
lastTalkingInfo.value = {
crsId: crsId.value,
stdyId: unref(stdyId),
stdyBatch: unref(stdyBatch),
logId: unref(logId),
chatContent: _body.chatContent || '',
stdyStat: _body.chatBody.stdyStat,
type: 9,
pgrphId,
userInfo: {
...choiceTeacherInfo.value
},
id: _id
}
talkingList.value.push(JSON.parse(JSON.stringify(lastTalkingInfo.value)))
}
})
.finally(() => {
isGetting.value = false
})
}
const isGetting = ref(false)
// 继续 执行方法
const nextQuestionSuccess = (info, type = 'next') => {
if (isGetting.value) return
isGetting.value = true
let _id = new Date().getTime() + 'id'
if (type === 'next') {
talkingList.value.push({
crsId: info.crsId,
pgrphId: info.pgrphId,
talkingContent: '继续',
type: 0,
id: _id
})
scrollToBottomNow()
getQuestionInfoUnStudy(info.pgrphId)
} else if (type === 'overGraph') {
overPgrhNow(info, getQuestionInfoUnStudy, _id)
} else if (type === 'overNextGraph') {
overPgrhNow(info, ($id) => {
getGraphInfoUnStudy(0, $id)
}, _id)
} else if (type === 'nextGraph') {
talkingList.value.push({
crsId: info.crsId,
pgrphId: info.pgrphId,
talkingContent: '继续学',
type: 0,
id: _id
})
// 下个段落
getGraphInfoUnStudy(0, info.pgrphId)
} else if (type === 'reStart') {
talkingList.value.push({
crsId: info.crsId,
pgrphId: info.pgrphId,
talkingContent: '重新答',
type: 0,
id: _id
})
// 重新回答
unref(ajaxApi)({
processType: 'NEW-ANSER', //NEW-KNOW
requestBody: JSON.stringify({
stdyId: unref(stdyId),
stdyPgrphId: info.stdyPgrphId,
qnsId: info.qnsId
})
})
.then(res => {
let _body = res.body || {}
if (!_body.chatBody) {
return
}
lastTalkingInfo.value = {
crsId: info.crsId,
qstLogId: _body.chatBody,
chatContent: _body.chatContent,
userInfo: {
...choiceTeacherInfo.value
},
..._body.chatBody,
pgrphId: info.pgrphId,
type: Number(_body.bodyTyp) || 0,
id: _id,
isQuestion: true,
my_answer: [],
backend_answer: []
}
talkingList.value.push(JSON.parse(JSON.stringify(lastTalkingInfo.value)))
qstLogId.value = _body.chatBody.qstLogId
scrollToBottomNow()
})
.finally(() => {
isGetting.value = false
})
} else if (type === 'reStartPrgh') {
talkingList.value.push({
crsId: info.crsId,
pgrphId: info.pgrphId,
talkingContent: '重新学',
type: 0,
id: _id
})
unref(ajaxApi)({
processType: 'NEW-KNOW',
requestBody: JSON.stringify({
logId: unref(logId),
pgrphId: info.pgrphId,
stdyId: unref(stdyId)
})
})
.then(res => {
let _body = res.body || {}
if (!_body.chatBody) {
return
}
lastTalkingInfo.value = {
crsId: info.crsId,
qstLogId: _body.chatBody,
pgrphId: info.pgrphId,
chatContent: _body.chatContent,
userInfo: {
...choiceTeacherInfo.value
},
..._body.chatBody,
type: Number(_body.bodyTyp) || 0,
id: _id,
isQuestion: true
}
talkingList.value.push(JSON.parse(JSON.stringify(lastTalkingInfo.value)))
logId.value = _body.chatBody.logId
qstLogId.value = _body.chatBody.qstLogId || qstLogId.value
scrollToBottomNow()
})
.finally(() => {
isGetting.value = false
})
}
}
const overPgrhNow = (info, cb = () => {}, _id) => {
unref(ajaxApi)({
processType: 'PRGH-OVER',
requestBody: JSON.stringify({
stdyId: unref(stdyId),
pgrphId: info?.pgrphId,
logId: info?.logId || logId.value,
oldStdyId: unref(oldStdyId),
isContinue: isContinue.value
})
})
.then(res => {
talkingList.value.push({
crsId: info.crsId,
pgrphId: info.pgrphId,
talkingContent: '继续',
type: 0,
id: _id
})
scrollToBottomNow(300)
setTimeout(() => {
cb(info.pgrphId)
}, 500)
// cb(info.pgrphId)
})
.finally(() => {
isGetting.value = false
})
}
const scrollBoxRef = ref()
const scrollTop = ref(99999)
const scrollToBottomNow = (time = 500) => {
nextTick(() => {
setTimeout(() => {
scrollTop.value += 1
scrollTop.value += 1
let _arr = talkingList.value || []
if (_arr.length && _arr.length > 0) {
let _last = talkingList.value[talkingList.value.length - 1]
uni.pageScrollTo({
selector: '#item' + _last.id,
duration: 300
})
}
}, time)
})
}
const reconnectTimer = null // 重连timer
const isManualClose = ref(false) // 是否手动关闭
const stdyId = ref('')
const stdyBatch = ref('')
const logId = ref('')
// ws 初始化逻辑
const initsocketTask = () => {
socketStore.creatSocket()
unref(SocketObj).on('message', res => {
const {
rtnCode,
body,
commond
} = JSON.parse(res.data)
if (rtnCode === '0000') {
if (commond === 'STDYBTH' && !stdyId.value) {
stdyId.value = body.stdyId || ''
stdyBatch.value = body.stdyBatch || ''
oldStdyId.value = body.oldStdyId || ''
logId.value = body.logId || ''
getGraphInfoUnStudy(1)
step.value = 2
}
} else {
// if(['0005', 'QQ0005'].includes(res.rtnCode)) {
// goto_login_fun('socket');
// }
}
})
}
const reconnect = () => {
initsocketTask()
}
const sendMessage = data => {
// unref(SocketObj).send(data)
startStudy(data.body)
}
// ws 结束
const startPgrphId = ref('')
const imageId = ref('')
onLoad(params => {
changeAppHeightBottom()
watchFlag.value = 1
isPreview.value = params.isPreview === 'preview'
crsId.value = params.crsId || ''
crsNum.value = params.crsNum || ''
imageId.value = params.imgId || ''
startPgrphId.value = params.pgrphId || ''
})
const showTalkingModel = ref(false)
// 录音
onMounted(() => {
// initsocketTask()
getData('1')
})
const keyboardHeight = ref('0px')
const setHeight = res => {
keyboardHeight.value = res.height + 'px'
// scrollToBottomNow()
}
const keyBoardIsHide = computed(() => {
return parseInt(keyboardHeight.value) == 0
})
const bottomHeight = ref('0px')
const totalBotHeight = computed(() => {
return keyBoardIsHide.value ? 0 + 'px' : parseInt(keyboardHeight.value) - parseInt(bottomHeight.value) + 'px'
})
const changeAppHeightBottom = height => {
//#ifdef APP-PLUS
// 获取系统信息
const systemInfo = uni.getSystemInfoSync()
// 计算安全区域底部高度
bottomHeight.value = systemInfo.screenHeight - systemInfo.safeArea.bottom + 'px'
// #endif
}
const closeStudy = () => {
return studyEndApi({
stdyId: stdyId.value
})
}
onHide(() => {
changePlayItemId('')
if (!isPreview.value) {
closeStudy()
}
// socketStore.closeSocket()
//#ifdef APP-PLUS
uni.offKeyboardHeightChange(setHeight)
// #endif
try {
teacherVoiceObj?.stop()
teacherVoiceObj.src = ''
} catch {}
storageStore.setGettingVoiceId('')
// common.show('提示信息', '检测到您已离开,即将退出学习页面。',false, '','确认').then((res) => {
// common.redirectTo(`/pages/courseDetail/index?crsId=${crsId.value}&crsNum=${crsNum.value}&from=study`)
// }).finally(() => {})
})
onShow(() => {
console.log('onShow')
//#ifdef APP-PLUS
uni.onKeyboardHeightChange(setHeight)
// #endif
if (watchFlag.value === 1) return
// getData('2');
// initsocketTask()
// 注释显示时开始新纪录
// if (!isPreview.value) {
// studyStartApi({
// crsId: crsId.value,
// teacherNo: choiceTeacher.value,
// studyType: isPreview.value ? 'P' : 'S',
// isContinue: 'Y',
// stdyId: stdyId.value
// }).then(res => {
// const {
// body
// } = res
// stdyId.value = body.stdyId || ''
// stdyBatch.value = body.stdyBatch || ''
// oldStdyId.value = body.oldStdyId || ''
// logId.value = body.logId || ''
// step.value = 2
// }).catch(err => {})
// }
})
const watchFlag = ref(1)
onUnload(() => {
console.log('onUnload')
if (!isPreview.value) {
closeStudy()
}
// socketStore.closeSocket()
changePlayItemId('')
console.log(typeof teacherVoiceObj)
try {
teacherVoiceObj?.stop()
teacherVoiceObj.src = ''
// 0910杨航叫我这么改的
} catch {}
})
const testAnswer = computed(() => {
if (talkingList.value.length < 5) return true
let _arr = talkingList.value.slice(-5)
let _num = _arr.map(t => [1, 2, 6].indexOf(Number(t.type)) >= 0).filter(t => !!t)
return _num.length < 5
})
const sendLoading = () => {
if (!testAnswer.value) {
common.msg('每个问题最多连续发送五次答案!')
return
}
isUploadingVoice.value = true
let _data = {
stdyId: unref(stdyId),
qnsId: unref(lastTalkingInfo).qnsId, // 问题ID 必输项:true 类型:String
pgrphId: unref(lastTalkingInfo).pgrphId, // 段落ID 必输项:true 类型:String
qstLogId: unref(qstLogId), // 段落ID 必输项:true 类型:String
crsId: crsId.value, // 课程ID 必输项:true 类型:String
stdyBatch: stdyBatch.value, // 学习批次 必输项:true 类型:String
ansterContent: '', // 回答文本内容 必输项:false 类型:String
asrStartTm: unref(lastTalkingInfo).asrStartTm
}
let _id = new Date().getTime() + 'id'
//#ifndef APP-PLUS
// h5 假数据
talkingList.value.push({
crsId: crsId.value,
pgrphId: unref(lastTalkingInfo).pgrphId,
qnsId: unref(lastTalkingInfo).qnsId,
talkingContent: '',
talkingVoice: voicePath,
type: 1,
userInfo: {
...choiceTeacherInfo.value
},
id: _id
})
setIsUploadingVoice()
scrollToBottomNow()
// #endif
}
// 新录音组件语音提交
const submitVoice = ({
ossKey,
text
}) => {
if (!ossKey) {
common.msg('系统异常,请联系管理员');
} else if (text === '') {
common.msg('未识别到文字');
} else {
let _data = {
stdyId: unref(stdyId),
qnsId: unref(lastTalkingInfo).qnsId, // 问题ID 必输项:true 类型:String
pgrphId: unref(lastTalkingInfo).pgrphId, // 段落ID 必输项:true 类型:String
qstLogId: unref(qstLogId), // 段落ID 必输项:true 类型:String
crsId: crsId.value, // 课程ID 必输项:true 类型:String
stdyBatch: stdyBatch.value, // 学习批次 必输项:true 类型:String
ansterContent: '', // 回答文本内容 必输项:false 类型:String
asrStartTm: unref(lastTalkingInfo).asrStartTm,
ossKey,
text
}
let _id = new Date().getTime() + 'id'
unref(ajaxApi)({
processType: 'ANSER-VOICE',
requestBody: JSON.stringify(_data)
})
.then(res => {
let _obj = {
crsId: crsId.value,
pgrphId: _data.pgrphId,
qstLogId: _data.qstLogId,
qnsId: _data.qnsId,
talkingContent: text,
bucketKey: ossKey,
text,
talkingVoice: '',
recordId: res.body.chatBody.recordId,
asrStartTm: unref(lastTalkingInfo).asrStartTm,
stdyPgrphId: res.body.chatBody.stdyPgrphId,
type: 1,
isLoading: false,
userInfo: {
...choiceTeacherInfo.value
},
id: _id,
voiceTime: 0
}
talkingList.value.push(_obj)
scrollToBottomNow()
setIsUploadingVoice()
})
.catch((err) => {
setIsUploadingVoice()
})
}
}
const uploadRecordNow = (voicePath, voiceTime) => {
if (!testAnswer.value) {
common.msg('每个问题最多连续发送五次答案!')
return
}
isUploadingVoice.value = true
let _data = {
stdyId: unref(stdyId),
qnsId: unref(lastTalkingInfo).qnsId, // 问题ID 必输项:true 类型:String
pgrphId: unref(lastTalkingInfo).pgrphId, // 段落ID 必输项:true 类型:String
qstLogId: unref(qstLogId), // 段落ID 必输项:true 类型:String
crsId: crsId.value, // 课程ID 必输项:true 类型:String
stdyBatch: stdyBatch.value, // 学习批次 必输项:true 类型:String
ansterContent: '', // 回答文本内容 必输项:false 类型:String
asrStartTm: unref(lastTalkingInfo).asrStartTm
}
let _id = new Date().getTime() + 'id'
//#ifndef APP-PLUS
// h5 假数据
talkingList.value.push({
crsId: crsId.value,
pgrphId: unref(lastTalkingInfo).pgrphId,
qnsId: unref(lastTalkingInfo).qnsId,
talkingContent: '',
talkingVoice: voicePath,
type: 1,
userInfo: {
...choiceTeacherInfo.value
},
id: _id
})
setIsUploadingVoice()
scrollToBottomNow()
// #endif
//#ifdef APP-PLUS
talkingList.value.push({
crsId: crsId.value,
pgrphId: unref(lastTalkingInfo).pgrphId,
qnsId: unref(lastTalkingInfo).qnsId,
talkingContent: '',
talkingVoice: voicePath,
intrctId: '',
isLoading: true,
type: 1,
userInfo: {
...choiceTeacherInfo.value
},
id: _id
})
scrollToBottomNow()
let _path = isPreview.value ? '/trastudy/traStdychat/previewVoiceChart' : '/trastudy/traStdychat/voiceChart'
commonUploadVoiceFile(voicePath, _path, {
processType: 'ANSER-VOICE',
requestBody: JSON.stringify(_data)
})
.then(_res => {
talkingList.value = talkingList.value.map(t => {
if (t.id === _id) {
return {
crsId: crsId.value,
pgrphId: _data.pgrphId,
qstLogId: _data.qstLogId,
qnsId: _data.qnsId,
talkingContent: '',
talkingVoice: voicePath,
recordId: _res.body.chatBody.recordId,
asrStartTm: unref(lastTalkingInfo).asrStartTm,
stdyPgrphId: _res.body.chatBody.stdyPgrphId,
type: 1,
userInfo: {
...choiceTeacherInfo.value
},
id: _id,
voiceTime: voiceTime
}
} else {
return t
}
})
console.log(11111, _res.body.chatBody)
scrollToBottomNow()
setIsUploadingVoice()
})
.catch(err => {
console.log('err', err)
setIsUploadingVoice()
talkingList.value = talkingList.value.filter(t => t.id !== _id)
uni.showToast({
title: err.message || '系统异常,请联系管理员',
icon: 'none',
duration: 1000
})
})
// #endif
}
const setIsUploadingVoice = (flag = false) => {
if (flag) {
isUploadingVoice.value = true
} else {
setTimeout(() => {
isUploadingVoice.value = false
}, 100)
}
}
// 发送文本
const submitAnswerNow = (val, cb) => {
if (!testAnswer.value) {
common.msg('每个问题最多连续发送五次答案!')
return
}
isUploadingVoice.value = true
let _data = {
stdyId: unref(stdyId),
qnsId: unref(lastTalkingInfo).qnsId, // 问题ID 必输项:true 类型:String
pgrphId: unref(lastTalkingInfo).pgrphId, // 段落ID 必输项:true 类型:String
qstLogId: unref(qstLogId), // 段落ID 必输项:true 类型:String
crsId: crsId.value, // 课程ID 必输项:true 类型:String
stdyBatch: stdyBatch.value, // 学习批次 必输项:true 类型:String
ansterContent: val, // 回答文本内容 必输项:false 类型:String
asrStartTm: unref(lastTalkingInfo).asrStartTm
}
unref(ajaxApi)({
processType: 'ANSER-TEXT',
requestBody: JSON.stringify(_data)
})
.then(res => {
setIsUploadingVoice()
let _id = new Date().getTime() + 'id'
let _body = res.body || {}
if (!_body.chatBody) {
return
}
talkingList.value.push({
..._data,
chatContent: _body.chatContent,
recordId: _body.chatBody.recordId,
stdyPgrphId: _body.chatBody.stdyPgrphId,
talkingContent: val,
talkingVoice: '',
type: 2,
userInfo: {
...choiceTeacherInfo.value
},
qstLogId: qstLogId.value,
id: _id
})
scrollToBottomNow()
cb && cb()
})
.catch(() => {
setIsUploadingVoice()
})
}
// 撤回
const withdrawNow = data => {
if (isGetting.value) return
isGetting.value = true
unref(ajaxApi)({
processType: 'ANSER-WITHDRAW',
requestBody: JSON.stringify({
recordId: data.recordId
})
})
.then(res => {
// 删除撤回的数据item
common.msg('撤回成功!')
talkingList.value = talkingList.value.filter(t => t.recordId !== data.recordId)
})
.finally(() => {
isGetting.value = false
})
}
const loadingExecLogId = ref('')
const answerQuestionNow = data => {
if (isGetting.value) return
isGetting.value = true
let _data = {
stdyId: unref(stdyId),
qnsId: data.qnsId, // 问题ID 必输项:true 类型:String
pgrphId: data.pgrphId, // 段落ID 必输项:true 类型:String
crsId: crsId.value, // 课程ID 必输项:true 类型:String
stdyBatch: stdyBatch.value, // 学习批次 必输项:true 类型:String
ansterContent: data.answer.join(','), // 回答文本内容 必输项:false 类型:String
asrStartTm: data.asrStartTm
}
// 发出答案
let _answer = data.answerItems.map(t => t.itemNo).join('')
let _id1 = new Date().getTime() + '3d'
talkingList.value.push({
id: _id1,
talkingContent: _answer,
userInfo: {
...choiceTeacherInfo.value
},
type: 0
})
scrollToBottomNow(0)
unref(ajaxApi)({
processType: 'ANSER-CHOICE',
requestBody: JSON.stringify(_data)
})
.then(res => {
let _id = new Date().getTime() + 'id'
let _body = res.body
lastTalkingInfo.value = {
crsId: crsId.value,
stdyId: unref(stdyId),
stdyBatch: unref(stdyBatch),
logId: unref(logId),
pgrphId: data.pgrphId,
qnsId: data.qnsId,
chatContent: _body.chatContent || '',
...(_body.chatBody || {}),
type: Number(_body.bodyTyp || 8),
userInfo: {
...choiceTeacherInfo.value
},
id: _id,
isQuestion: true,
my_answer: [],
backend_answer: []
}
talkingList.value.push(JSON.parse(JSON.stringify(lastTalkingInfo.value)))
scrollToBottomNow()
})
.finally(() => {
isGetting.value = false
})
}
// 完成
const finishQuestionNow = data => {
if (isGetting.value) return
isGetting.value = true
unref(ajaxApi)({
processType: 'ANSER-OVER',
requestBody: JSON.stringify({
qnsId: data.qnsId,
stdyPgrphId: data.stdyPgrphId,
asrStartTm: data.asrStartTm
})
})
.then(res => {
// common.msg('完成成功,开始获取评分维度数据!')
let _state = res.body.chatBody.isWait
if (_state !== 'Y') {
common.msg('提交答案失败!')
return
}
let _id1 = new Date().getTime() + '3d'
talkingList.value.push({
id: _id1,
talkingContent: '完成',
userInfo: {
...choiceTeacherInfo.value
},
type: 0
})
loadingExecLogId.value = res.body.chatBody.execLogId
qstLogId.value = loadingExecLogId.value
let _id = new Date().getTime() + 'id'
lastTalkingInfo.value = {
isLoading: true,
id: _id,
userInfo: {
...choiceTeacherInfo.value
},
type: 5
}
talkingList.value.push(JSON.parse(JSON.stringify(lastTalkingInfo.value)))
scrollToBottomNow(0)
getQuestionEvalateRequest(_id, data, 1)
})
.catch(err => {
console.log(err)
})
.finally(() => {
isGetting.value = false
})
}
// 查询问题回答评分
const getQuestionEvalateRequest = (id, data, num) => {
if (loadingExecLogId.value) {
unref(ajaxApi)({
processType: 'ANSER-RESULT',
requestBody: JSON.stringify({
execLogId: loadingExecLogId.value,
pgrphId: data.pgrphId,
stdyId: stdyId.value
})
}).then(res => {
let _body = res.body || {}
if (!_body.chatBody) {
return
}
if (_body.chatBody && _body.chatBody.evalOver === 'Y') {
talkingList.value = talkingList.value.map(t => {
if (t.id === id) {
lastTalkingInfo.value = {
..._body.chatBody,
crsId: crsId.value,
pgrphId: data.pgrphId,
qstLogId: loadingExecLogId.value,
id,
userInfo: {
...choiceTeacherInfo.value
},
evalDimens: (_body.chatBody.essayEvalVo || []).map(t => ({
...t,
gradeDimensName: t.gradeDimens
})),
type: 5
}
return lastTalkingInfo.value
} else {
return t
}
})
scrollToBottomNow()
} else {
// 1分钟之后取消获取报错
if (num === 30) {
common.msg('获取评分数据失败,请重新提交获取!')
talkingList.value.pop() //删除loading会话
talkingList.value.pop() //删除完成会话
return
}
setTimeout(() => {
getQuestionEvalateRequest(id, data, num + 1)
}, 2 * 1000)
}
})
}
}
const showSeekModel = ref(false)
const rateList = ref(['0.5', '0.8', '1.0', '1.5', '2.0'])
const chooseRate = ref('1.0')
const chooseRateNow = item => {
chooseRate.value = item
showSeekModel.value = false
teacherVoiceObj.playbackRate = Number(item)
}
</script>
<style lang="scss" scoped>
.course-study {
position: relative;
display: flex;
flex-direction: column;
height: 100vh;
overflow: hidden;
bottom: 0;
.top-bg {
background: linear-gradient(16deg, rgba(234, 246, 255, 0) 0%, #e6eafd 100%);
height: 480rpx;
width: 750rpx;
}
.bot-bg {
// background-color: #F6F8FF;
background: linear-gradient(136deg, rgba(234, 246, 255, 0) 0%, #e6eafd 100%);
width: 750rpx;
flex: 1;
}
.seek-setting {
position: absolute;
z-index: 3;
right: 35rpx;
top: calc(v-bind(pageTopPadding) - 12rpx);
font-size: 26rpx;
width: 140rpx;
height: 92rpx;
padding: 30rpx 0;
overflow: hidden;
line-height: 32rpx;
text-align: right;
padding-right: 68rpx;
.image-icon {
width: 30rpx;
height: 32rpx;
position: absolute;
right: 10rpx;
top: 30rpx;
.image-icon-img {
width: 100%;
height: 100%;
}
}
}
.course-study-body {
position: fixed;
overflow: hidden;
left: 0;
bottom: 0;
height: 100%;
// height: calc(100% - v-bind(totalBotHeight));
width: 100%;
padding: 0 0rpx;
box-sizing: border-box;
display: flex;
flex-direction: column;
padding-top: v-bind(pageTopPadding);
&.isAnd {
height: calc(100% - v-bind(totalBotHeight));
}
.body-title {
height: 90rpx;
text-align: center;
font-size: 33rpx;
padding-top: 10rpx;
line-height: 46rpx;
box-sizing: border-box;
font-weight: 500;
color: #000;
position: sticky;
top: 0;
.top-desc {
position: absolute;
bottom: -10rpx;
font-size: 18rpx;
color: #999;
left: 50%;
transform: translateX(-50%);
}
// margin-bottom: 23rpx;
.arrow-icon {
position: absolute;
left: 21rpx;
top: 5rpx;
color: #000;
padding: 15rpx;
// background-color: red;
}
}
.study-num {
height: 110rpx;
line-height: 46rpx;
font-size: 36rpx;
text-align: left;
padding-left: 30rpx;
padding-right: 30rpx;
padding-top: 20rpx;
box-sizing: border-box;
background: url(@/static/images/course/study-bg.png) no-repeat;
border-radius: 30rpx 30rpx 0 0;
font-size: 29rpx;
.color-blue {
color: #06f;
display: inline;
}
.color-black {
color: #333;
position: relative;
height: 44rpx;
line-height: 44rpx;
&.ml-40 {
padding-left: 24rpx;
&::before {
content: '';
display: block;
width: 12rpx;
height: 12rpx;
overflow: hidden;
position: absolute;
border-radius: 50%;
background-color: #06f;
top: 50%;
left: 0;
transform: translateY(-50%);
}
}
}
.color-gray {
color: #999;
}
.bg-block {
display: block;
width: 100%;
height: 56rpx;
// padding: 4rpx 0 12rpx;
position: relative;
// margin: 0 30rpx;
}
.bg-gray {
width: 100%;
background-color: #ededed;
overflow: hidden;
border-radius: 15rpx;
position: absolute;
top: 13rpx;
left: 0;
height: 16rpx;
.bg-blue {
height: 100%;
float: left;
background-color: #06f;
}
}
}
.talking-list {
flex: 1;
overflow-y: auto;
padding: 10rpx 30rpx;
background: url(@/static/images/course/study-bg.png) no-repeat;
background-size: cover;
background-position: center bottom;
box-sizing: border-box;
width: 100%;
.talking-list-cont {
padding: 0;
}
.talking-item {
margin-bottom: 46rpx;
}
}
.talk-btns {
height: 150rpx;
background-color: #fff;
padding: 25rpx 10rpx 0;
width: 100%;
position: relative;
.touch-btn {
height: 92rpx;
box-shadow: 0 8rpx 20rpx 0 rgba(0, 0, 0, 0.06);
text-align: center;
line-height: 92rpx;
color: #000;
font-weight: 400;
position: relative;
.input-box {
width: 100%;
height: 92rpx;
line-height: 92rpx;
text-align: left;
box-sizing: border-box;
padding: 0 40rpx 0 40rpx;
::v-deep .uv-input__content {
padding-right: 50rpx;
}
}
}
.talk-btn-box {
position: absolute;
height: 100%;
width: 120rpx;
right: 0;
top: 0;
.talk-btn-icon {
width: 40rpx;
height: 40rpx;
position: absolute;
right: 60rpx;
top: 50%;
margin-top: -20rpx;
}
}
}
.voice-list {
flex: 1;
overflow-y: auto;
padding: 10rpx 40rpx;
.voice-item {
min-height: 246rpx;
width: 100%;
overflow: hidden;
background-color: #fff;
border-radius: 16rpx;
display: flex;
padding: 25rpx;
position: relative;
&:not(:last-child) {
margin-bottom: 30rpx;
}
.avatar-box {
width: 160rpx;
height: 207rpx;
margin-right: 20rpx;
border-radius: 16rpx;
overflow: hidden;
background-color: #ccc;
.img-box {
width: 100%;
height: 100%;
}
}
.right-cont {
flex: 1;
.right-name {
width: 380rpx;
line-height: 40rpx;
font-size: 28rpx;
font-weight: 500;
margin-bottom: 25rpx;
word-break: break-all;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.right-desc {
width: 380rpx;
line-height: 38rpx;
font-size: 25rpx;
color: #999;
margin-bottom: 12rpx;
word-break: break-all;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.audio-box {
width: 90%;
height: 70rpx;
line-height: 70rpx;
background-color: #eaf2ff;
padding: 15rpx 25rpx;
overflow: hidden;
.talking-gif {
width: 140rpx;
height: 40rpx;
margin-right: 20rpx;
// background-color: #ccc;
background: url(@/static/images/course/voice-gray.png) no-repeat;
background-size: 100%;
box-sizing: border-box;
float: left;
&.is-play {
background: url(@/static/images/course/voice-gif.gif) no-repeat;
background-size: 100%;
}
}
.talking-time {
float: right;
text-align: right;
line-height: 40rpx;
font-size: 23rpx;
color: #b1bbd3;
}
.play-btn {
float: right;
width: 35rpx;
height: 40rpx;
padding-top: 2rpx;
line-height: 35rpx;
margin-left: 20rpx;
}
}
}
.radio-box {
position: absolute;
right: 20rpx;
top: 20rpx;
}
}
}
.start-btns {
margin: 20rpx 30rpx 30rpx;
height: 80rpx;
width: calc(100% - 60rpx);
background-color: #06f;
color: #fff;
text-align: center;
line-height: 80rpx;
}
}
}
.overlay-box {
display: flex;
flex-direction: column-reverse;
// justify-content: flex-end;
width: 100%;
height: 100%;
.circle-bg-box {
height: 234rpx;
background: url(@/static/images/course/talking.png) no-repeat;
background-size: cover;
position: relative;
.icon-box {
position: absolute;
left: 50%;
top: 50%;
margin-left: -17rpx;
margin-top: -24rpx;
width: 34rpx;
height: 48rpx;
image {
width: 100%;
height: 100%;
}
}
}
.tips-box {
height: 40rpx;
line-height: 40rpx;
color: #c0c0c0;
text-align: center;
margin: 12rpx 0 24rpx;
}
.btns-box {
height: 270rpx;
// padding: 0 120rpx;
width: 154rpx;
margin: 0 auto 100rpx;
overflow: hidden;
font-size: 28rpx;
text-align: center;
line-height: 116rpx;
.close-btn {
float: left;
height: 100%;
width: 154rpx;
overflow: hidden;
color: #999;
&.is-out {
color: #fff;
}
}
.translate-btn {
float: right;
height: 100%;
width: 154rpx;
overflow: hidden;
color: #999;
}
.btn-box {
width: 100%;
height: 154rpx;
}
}
.talk-dialog {
width: 506rpx;
height: 234rpx;
margin: 0 auto;
background-color: #06f;
}
.white-bg-box {
height: 365rpx;
background-color: #fff;
overflow: hidden;
border-radius: 32rpx 32rpx 0 0;
padding: 58rpx 36rpx;
&.white-bg-box-setting {
height: auto;
min-height: 400rpx;
}
.box-title {
font-size: 32rpx;
font-weight: bold;
line-height: 46rpx;
margin-bottom: 46rpx;
}
.box-btns {
height: 46rpx;
line-height: 46rpx;
display: flex;
justify-content: space-between;
color: #999;
.is-active {
color: #06f;
}
}
.box-items {
.box-item {
margin-bottom: 20rpx;
overflow: hidden;
.box-item-title {
width: 81rpx;
height: 100%;
float: left;
overflow: hidden;
color: #666;
font-size: 28rpx;
}
.box-item-cont {
display: block;
float: left;
width: calc(100% - 86rpx);
overflow: hidden;
padding-left: 5rpx;
padding-top: 5rpx;
.overlay-voice-item {
width: 123rpx;
float: left;
position: relative;
margin-bottom: 30rpx;
&:not(:nth-child(3n)) {
margin-right: 100rpx;
}
.overlay-avatar-box {
width: 123rpx;
height: 123rpx;
border-radius: 50%;
overflow: hidden;
box-shadow: 1rpx 0rpx 5rpx #ccc;
.overlay-img-box {
width: 100%;
height: 100%;
}
}
.overlay-teacher-name {
width: 123rpx;
text-align: center;
padding: 10rpx 0;
min-height: 58rpx;
line-height: 38rpx;
font-size: 28rpx;
}
.overlay-radio-box {
position: absolute;
left: 100rpx;
top: 5rpx;
pointer-events: none;
}
}
}
}
}
}
}
</style>