一些操作

This commit is contained in:
田岩
2025-08-19 13:45:39 +08:00
parent 1f31ce6d17
commit c77b7b3188
16 changed files with 723 additions and 437 deletions
+4 -1
View File
@@ -17,4 +17,7 @@ VITE_APP_BASE_API_Url = 'https://aitstest.jlbank.com.cn:7001'
# 王洋洋
#VITE_APP_BASE_H5_API_Url = 'http://25.64.16.140:9604'
VITE_APP_BASE_H5_API_Url = 'https://aitstest.jlbank.com.cn:7001'
VITE_APP_BASE_H5_API_Url = 'https://aitstest.jlbank.com.cn:7001'
#岳世凯
#VITE_APP_BASE_H5_API_Url = 'http://25.64.16.141:9701'
+16
View File
@@ -31,3 +31,19 @@ export const deleteTraPersonalCollectionById = (data) => {
});
};
/*
新增任务收藏
入参:
collectTyp 收藏类型 必须 String 01--课程,02--任务
collectBusId 收藏业务ID 必须 String
**/
export const insertTraPersonalCollectionTask = (data) => {
return request({
url: base_url + '/traPersonalCollection/insertTraPersonalCollectionTask',
method: 'post',
toastErrors: true,
data
});
};
+13
View File
@@ -0,0 +1,13 @@
import request from '@/api/request'
const base_url = '/trastudy'
// 查询所有、未完成、已完成的任务(分页)
export const queryAllTraTaskInfoByUserId = (data) => {
return request({
url: base_url + '/traTaskInfo/queryAllTraTaskInfoByUserId',
method: 'post',
toastErrors: true,
data
});
};
+34
View File
@@ -0,0 +1,34 @@
import request from '@/api/request'
const base_url = '/trastudy'
// 查询当前用户的所有消息通知 (分页)
export const queryTraPersonalMessageNoticePaging = (data) => {
return request({
url: base_url + '/traPersonalMessageNotice/queryTraPersonalMessageNoticePaging',
method: 'post',
toastErrors: true,
data
});
};
// 读取单一消息
export const readNotice = (data) => {
return request({
url: base_url + '/traPersonalMessageNotice/readNotice',
method: 'post',
toastErrors: true,
data
});
};
// 一键读取所有消息(不展示)
export const oneTimeRead = (data) => {
return request({
url: base_url + '/traPersonalMessageNotice/oneTimeRead',
method: 'post',
toastErrors: true,
data
});
};
@@ -25,7 +25,6 @@
<view class="option_div">
<questionVue @changePlay="changePlay" :dataInfo="talkingInfo" :activeVoiceId="questionValue"></questionVue>
</view>
</view>
</template>
<script setup>
@@ -69,7 +68,7 @@
const emit = defineEmits(['subject_click'])
const talkingInfo = ref({
id: '3',
intrctWay: "",
intrctWay: "02",
intrctContent: "中的i id一二三。",
talkingText: '中的i id一二三。',
talkingVoice: ''
+99 -73
View File
@@ -1,7 +1,7 @@
<template>
<view class="talking-info" v-if="dataInfo.intrctWay === '02'">
<view :class="['talking-gif', !voiceLoading&&(props.activeVoiceId === dataInfo.id) ? 'is-play' : '']">
<CommonLoading color="#fff" v-show="voiceLoading"/>
<view class="talking-info" v-if="intrctWay === '02'">
<view :class="['talking-gif', (props.activeVoiceId === dataInfo.id) ? 'is-play' : '']">
<CommonLoading color="#fff" v-show="voiceLoading" />
</view>
<view class="right-time">{{voiceTime}}</view>
<view class="talking-cont">
@@ -9,141 +9,151 @@
{{ showContent }}
</text>
<view class="talking-text translate-text" v-if="translateLoading">
<CommonLoading color="#fff"/>
<CommonLoading color="#fff" />
</view>
</view>
<view class="btns-cont">
<view class="play-btn">
<image class="icon"
v-show="!(!voiceLoading&&(props.activeVoiceId === dataInfo.id))"
@click="playVoice"
src="@/static/images/course/play-blue.png"
style="width: 100%;height: 100%;"
></image>
<image class="icon"
v-show="!voiceLoading&&(props.activeVoiceId === dataInfo.id)"
@click.stop="pauseVoice"
src="@/static/images/course/stop-blue.png"
style="width: 100%;height: 100%;"
></image>
<!-- <view class="icon text-btn" v-if="type === 1" @click.stop="translateVoice(dataInfo)"></view> -->
<image class="icon" v-show="!(props.activeVoiceId === dataInfo.id)" @click="playVoice"
src="@/static/images/course/play-blue.png" style="width: 100%;height: 100%;"></image>
<image class="icon" v-show="props.activeVoiceId === dataInfo.id"
@click.stop="pauseVoice" src="@/static/images/course/stop-blue.png"
style="width: 100%;height: 100%;"></image>
<!-- <view class="icon text-btn" v-if="type === 1" @click.stop="translateVoice(dataInfo)"></view> -->
</view>
</view>
</view>
<view class="talking-info talking-info-text" v-else-if="dataInfo.intrctWay === '01'">
<!-- 01纯文字 -->
<view class="talking-info talking-info-text" v-else-if="intrctWay === '01'">
<view class="talking-cont">
<text :class="['talking-text translate-text loading']">
{{ showContent }}
</text>
</view>
</view>
<view class="talking-info talking-info-loading" v-else>
<view class="talking-info talking-info-loading" v-else>
<view :class="['talking-gif']">
<CommonLoading color="#fff"/>
<CommonLoading color="#fff" />
</view>
</view>
</template>
<script setup>
import { computed, unref, ref, toRefs, watch, onMounted } from "vue"
import { onUnload } from '@dcloudio/uni-app';
const props = defineProps({
import {
computed,
unref,
ref,
toRefs,
watch,
onMounted
} from "vue"
import {
onUnload
} from '@dcloudio/uni-app';
const props = defineProps({
dataInfo: {
default: () => ({}),
type: Object
},
activeVoiceId:{
default:'',
activeVoiceId: {
default: '',
type: String
},
})
const { activeVoiceId, dataInfo } = toRefs(props)
const {
activeVoiceId,
dataInfo
} = toRefs(props)
const intrctWay = ref('02')
const emit = defineEmits(['changePlay'])
watch(() => props.dataInfo, () => {},{deep: true,immediate: true})
watch(() => props.dataInfo, () => {}, {
deep: true,
immediate: true
})
// 声音播放初始化
const talkVoiceObj = ref(null)
const isPlaying = ref(false)
const voiceLoading = ref(false)
const translateLoading = ref(false)
const voiceTime = ref('')
const itemVoice = ref('')
const showContent = computed(()=>{
const showContent = computed(() => {
return dataInfo.value.talkingText || ''
})
onMounted(()=>{
onMounted(() => {
initVoice()
})
onUnload(()=>{
onUnload(() => {
pauseVoice()
})
watch(() => props.dataInfo.talkingVoice,(val)=>{
if(val){
watch(() => props.dataInfo.talkingVoice, (val) => {
if (val) {
initVoice()
}
},{
}, {
deep: true,
immediate: true
})
const pauseVoice = () => {
if(!talkVoiceObj.value) return
if (!talkVoiceObj.value) return
talkVoiceObj.value.pause()
isPlaying.value = false
emit('changePlay', '')
}
const playVoice = () =>{
const playVoice = () => {
emit('changePlay', unref(dataInfo)?.id)
if(talkVoiceObj.value && talkVoiceObj.value.src) {
talkVoiceObj.value.play()
if (talkVoiceObj.value && talkVoiceObj.value.src) {
talkVoiceObj.value.play()
return
}else{
} else {
talkVoiceObj.value.src = ''
if(unref(dataInfo).talkingVoice){
setTimeout(()=>{
if (unref(dataInfo).talkingVoice) {
setTimeout(() => {
talkVoiceObj.value.src = unref(dataInfo).talkingVoice
talkVoiceObj.value.play()
isPlaying.value = true
},100)
}, 100)
}
}
}
const initVoice = () => {
talkVoiceObj.value = uni.createInnerAudioContext()
talkVoiceObj.value.onEnded(()=>{
talkVoiceObj.value.onEnded(() => {
emit('changePlay', '')
talkVoiceObj.value.src = ''
})
talkVoiceObj.value.onError(()=>{
talkVoiceObj.value.onError(() => {
talkVoiceObj.value.src = ''
})
if(unref(dataInfo).intrctWay === '02'){
if (unref(dataInfo).intrctWay === '02') {
itemVoice.value = unref(dataInfo).talkingVoice
talkVoiceObj.value.src = itemVoice.value
console.log(talkVoiceObj.value.src,1111)
setTimeout(()=>{
setTimeout(() => {
setVoiceTime(0)
},100)
}, 100)
}
}
const setVoiceTime = (sum) => {
sum += 1
if(Math.ceil(talkVoiceObj.value.duration)>0){
voiceTime.value = (Math.ceil(talkVoiceObj.value.duration) || 1 ) + 's'
}else{
if(sum === 20) {
if (Math.ceil(talkVoiceObj.value.duration) > 0) {
voiceTime.value = (Math.ceil(talkVoiceObj.value.duration) || 1) + 's'
} else {
if (sum === 20) {
voiceTime.value = 0 + 's'
return
}
setTimeout(()=>{
setTimeout(() => {
setVoiceTime(sum)
},200)
}, 200)
}
}
</script>
<style scoped lang="scss">
.talking-info{
.talking-info {
padding: 20rpx;
border-radius: 15rpx;
background-color: #06f;
@@ -152,18 +162,21 @@ import { onUnload } from '@dcloudio/uni-app';
color: #fff;
width: 100%;
float: right;
.talking-gif{
.talking-gif {
width: 200rpx;
height: 38rpx;
background: url(@/static/images/course/voice-gray.png) no-repeat;
background-size: contain;
padding-left: 168rpx;
&.is-play{
&.is-play {
background: url(@/static/images/course/voice-white.gif) no-repeat;
background-size: contain;
}
}
.right-time{
.right-time {
position: absolute;
right: 20rpx;
top: 20rpx;
@@ -171,37 +184,45 @@ import { onUnload } from '@dcloudio/uni-app';
height: 38rpx;
line-height: 38rpx;
}
&.talking-info-loading{
&.talking-info-loading {
padding-bottom: 20rpx;
}
&.talking-info-text{
&.talking-info-text {
padding-bottom: 20rpx;
width: auto;
float: right;
.talking-cont{
.talking-cont {
margin-top: 0rpx;
}
}
.talking-cont{
.talking-cont {
margin-top: 16rpx;
overflow: hidden;
.talking-text{
.talking-text {
font-size: 28rpx;
line-height: 48rpx;
margin-bottom: 20rpx;
word-break: break-all;
white-space: pre-line;
overflow: hidden;
&.translate-text{
&.translate-text {
min-height: 48rpx;
}
}
}
.btns-cont{
.btns-cont {
overflow: hidden;
border-top: 3rpx solid #eee;
.replay-btn{
.replay-btn {
float: left;
width: 46rpx;
height: 46rpx;
@@ -209,7 +230,8 @@ import { onUnload } from '@dcloudio/uni-app';
margin-right: 16rpx;
margin-top: 25rpx;
}
.play-btn{
.play-btn {
float: left;
width: 46rpx;
height: 46rpx;
@@ -217,7 +239,8 @@ import { onUnload } from '@dcloudio/uni-app';
margin-right: 16rpx;
margin-top: 25rpx;
}
.text-btn{
.text-btn {
float: left;
width: 46rpx;
height: 46rpx;
@@ -232,7 +255,8 @@ import { onUnload } from '@dcloudio/uni-app';
font-weight: bold;
border-radius: 20rpx;
}
.text-right-btn{
.text-right-btn {
float: right;
padding: 0 10rpx;
height: 46rpx;
@@ -242,7 +266,8 @@ import { onUnload } from '@dcloudio/uni-app';
margin-left: 16rpx;
margin-top: 25rpx;
}
.next-btn{
.next-btn {
float: right;
padding: 0 60rpx 0 20rpx;
height: 62rpx;
@@ -253,7 +278,8 @@ import { onUnload } from '@dcloudio/uni-app';
border-radius: 8rpx;
margin-top: 16rpx;
position: relative;
.icon-iamge{
.icon-iamge {
width: 25rpx;
height: 25rpx;
position: absolute;
+412
View File
@@ -0,0 +1,412 @@
<template>
<view class="talk-btns">
<uv-button class="touch-btn" @touchstart="startRecord" @touchend="stopRecord" @touchmove="handleMove"
v-if="!keyboardIsShow" :customStyle="{height:'92rpx'}">按住说话
</uv-button>
<view class="touch-btn" v-if="keyboardIsShow">
<uv-input class="input-box" ref="inputRef" v-model="inputText" :maxlength="props.textValueLength"
:adjustPosition="false" placeholderStyle='color:#999999;font-size:26rpx' placeholder="请输入文字">
</uv-input>
</view>
<view class="talk-btn-box">
<image class="talk-btn-icon" v-show="!keyboardIsShow" src="@/static/images/course/jianpan.png" mode=""
@click="changeBtn"></image>
<image class="talk-btn-icon" v-show="keyboardIsShow && !inputText" src="@/static/images/course/record.png"
mode="" @click="changeBtn"></image>
<image class="talk-btn-icon" v-show="keyboardIsShow && !!inputText" src="@/static/images/course/send.png"
mode="" @click.stop="showKeyboard"></image>
</view>
<uni-popup ref="popupRef" class="popup">
<view class="overlay-box">
<view class="circle-bg-box">
<view class="icon-box">
<image src="@/static/images/course/talk-icon.png"></image>
</view>
</view>
<view class="tips-box">松开发送</view>
<view class="btns-box">
<view :class="['close-btn',isOut?'is-out':'']" id="circleBox">取消
<image class="btn-box" v-if="!isOut" src="@/static/images/course/close.png" mode=""></image>
<image class="btn-box" v-else src="@/static/images/course/close-active.png" mode=""></image>
</view>
</view>
<view :class="['talk-dialog',isOut?'is-out':'']">
<image src="@/static/images/course/voice-white.gif" mode="" class="gif-box"></image>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import {
toRefs,
ref,
computed,
unref,
onMounted,
onUnmounted,
nextTick,
watch,
reactive
} from 'vue';
import common from '@/common/common';
import permission from '@/common/permission.js'
import {
getPhoneEnvBool
} from '@/common/common.js'
const emit = defineEmits(['submit'])
const props = defineProps({
isDisabled: {
default: true,
type: Boolean
},
isLoading: {
default: false,
type: Boolean
},
loadingText: {
default: '发送回答中,请稍后再试!',
type: String
},
canAnswer: {
default: true,
type: Boolean
},
canAnswerText: {
default: '当前状态不能发送内容!',
type: String
},
textValueLength: {
type: Number,
default: 500
},
minRecordDuration: { // 最小录音间隔
type: Number,
default: 1000
},
})
const {
isDisabled,
} = toRefs(props)
// 键盘还是语音
const keyboardIsShow = ref(false)
const popupRef = ref(null)
watch(() => props.isLoading, () => {}, {
deep: true,
immediate: true
})
watch(() => props.canAnswer, () => {}, {
deep: true,
immediate: true
})
watch(() => props.isDisabled, (val) => {
if (val) {
keyboardIsShow.value = false
}
}, {
deep: true,
immediate: true
})
const inputText = ref('')
let startTime = 0;
// not_started 未开始, preparing 准备中, in_recording 录音中,ending 结束中
const recordingStatus = ref('not_started')
// 获取全局唯一的录音管理器 recorderManager
const recorderManager = uni.getRecorderManager();
// const innerAudioContext = uni.createInnerAudioContext();
const startTimer = ref(null); // 延迟启动的定时器ID
const startFlag = ref(1)
const startRecord = (obj) => { // 开始录音
console.log('开始录音', recordingStatus.value);
if (recordingStatus.value !== 'not_started') return;
touchY.value = obj.changedTouches[0].pageY
startTimer.value = setTimeout(() => {
console.log('延迟开始');
recordingStatus.value = 'preparing'
isMoving.value = true
recorderManager.start();
}, 80);
}
// 结束录音
const stopRecord = (obj) => {
console.log('结束录音', obj);
if (startTimer.value) {
clearTimeout(startTimer.value);
startTimer.value = null;
}
recorderManager.stop();
popupRef.value.close()
nextTick(() => {
setTimeout(() => {
recordingStatus.value = 'not_started'
}, 300)
})
}
onMounted(async () => {
recorderManager.onStop(function(res) {
console.log('recorder stop' + JSON.stringify(res));
console.log('录音文件临时位置', res.tempFilePath);
if (isOut) return; // 被划掉的语音
const endTime = Date.now();
const recordDuration = (endTime - startTime);
if (props.minRecordDuration > recordDuration) {
return common.msg('说话时间太短,没有听清!')
}
console.log('录音结束,时长:', recordDuration, '秒');
emit('submit', 'voice', res.tempFilePath)
});
recorderManager.onStart(function(res) {
// 检测是否已经抬手结束了
console.log('检测是否已经抬手结束了', recordingStatus.value);
if (recordingStatus.value !== 'preparing') {
stopRecord()
}
startTime = Date.now();
popupRef.value.open()
recordingStatus.value = 'in_recording'
});
})
const touchY = ref(0)
const windowInfo = uni.getWindowInfo()
// 安全区域信息
const safeArea = windowInfo.safeArea;
// 计算是否超出按下盒子位置
const isOut = computed(() => {
const btnHeight = windowInfo.screenWidth / 750 * 234;
return touchY.value < safeArea.bottom - btnHeight;
});
const activeNum = ref(1)
const isMoving = ref(false)
const handleMove = (obj) => {
touchY.value = obj.changedTouches[0].pageY
}
// 发送文本
const showKeyboard = () => {
if (keyboardIsShow.value) {
const inputTextTram = inputText.value.trim()
if (inputTextTram) {
emit('submit', 'text', inputTextTram)
} else {
common.msg("不能发送空白消息!")
}
} else {
keyboardIsShow.value = !keyboardIsShow.value
}
}
// 切换键盘语音
const changeBtn = () => {
if (props.isDisabled) return
keyboardIsShow.value = !keyboardIsShow.value
inputText.value = ''
}
</script>
<style lang="scss" scoped>
.talk-btns {
height: 150rpx;
background-color: #fff;
padding: 25rpx 30rpx 0;
position: relative;
.touch-btn {
height: 92rpx;
box-shadow: 0 8rpx 20rpx 0 rgba(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;
}
}
.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;
}
}
}
.overlay-box {
display: flex;
flex-direction: column-reverse;
// justify-content: flex-end;
width: 100vw;
height: 100vh;
background-color: rgba(0, 0, 0, 0.9);
.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;
border-radius: 32rpx;
position: relative;
&.is-out {
background-color: #ff3e49;
&::after {
background-color: #ff3e49;
}
}
.gif-box {
position: absolute;
width: 200rpx;
height: 80rpx;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
}
&::after {
content: '';
width: 44rpx;
height: 44rpx;
clip-path: polygon(50% 0%, 100% 50%, 50% 100%, 0% 50%);
background-color: #06f;
overflow: hidden;
position: absolute;
bottom: -22rpx;
left: 50%;
margin-left: -22rpx;
}
}
.white-bg-box {
height: 365rpx;
background-color: #fff;
overflow: hidden;
border-radius: 32rpx 32rpx 0 0;
padding: 58rpx 36rpx;
.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;
}
}
}
}
::v-deep .uv-input__content__field-wrapper__field {
padding-right: 60rpx;
}
</style>
+84 -90
View File
@@ -15,40 +15,45 @@
</view>
<view class="title-num">
<view class="num-item">
<image class="title-icon" src="/src/static/images/courseDetail/zhishidian.png"
mode="heightFix"></image>
<image class="title-icon" src="/src/static/images/courseDetail/zhishidian.png" mode="heightFix"></image>
<view class="mr-10">知识点</view>
<view class="c-000">{{ form.pgrphNum || 0 }}</view>
</view>
<view class="num-item">
<image class="title-icon" src="/src/static/images/courseDetail/yixue.png" mode="heightFix">
</image>
<image class="title-icon" src="/src/static/images/courseDetail/yixue.png" mode="heightFix"></image>
<view class="mr-10">已学</view>
<view class="c-000">{{ form.styParaGraphNum || 0 }}</view>
</view>
<view class="num-item examination">
<image class="title-icon" src="/src/static/images/courseDetail/charts.png" mode="heightFix">
</image>
<image class="title-icon" src="/src/static/images/courseDetail/charts.png" mode="heightFix"></image>
<view>考试排行榜</view>
<view class="back-icon"></view>
</view>
<view class="fl1">
</view>
<view class="num-item" @click="test=!test">
<image v-show="test" class="collect-icon"
src="/src/static/images/courseDetail/in_collect.png" mode="widthFix"></image>
<image v-show="!test" class="collect-icon"
src="/src/static/images/courseDetail/out_collect.png" mode="widthFix"></image>
<view class="fl1"></view>
<view class="num-item" @click="collect_click(form.collectId)">
<image
v-if="form.collectId"
class="collect-icon"
src="/src/static/images/courseDetail/out_collect.png"
mode="widthFix"
></image>
<image v-else class="collect-icon" src="/src/static/images/courseDetail/in_collect.png" mode="widthFix"></image>
</view>
</view>
</view>
<view class="body-box">
<uv-sticky :customNavHeight="statusBarHeight" id='tabsDiv' class="sticky">
<uv-sticky :customNavHeight="statusBarHeight" id="tabsDiv" class="sticky">
<view class="body-tab">
<uv-tabs class="font_pf" :current="tabAct" :list="tabList" @change="tabSwitch"
:scrollable="false" line-color="#0066FF"
<uv-tabs
class="font_pf"
:current="tabAct"
:list="tabList"
@change="tabSwitch"
:scrollable="false"
line-color="#0066FF"
:active-style="{ color: '#323E57', 'font-weight': 600, 'font-size': '30rpx' }"
:inactive-style="{ color: '#666666', 'font-size': '30rpx' }"></uv-tabs>
:inactive-style="{ color: '#666666', 'font-size': '30rpx' }"
></uv-tabs>
</view>
</uv-sticky>
<!-- <swiper class="body-swiper" @change="swiper_change" :current="tabAct"><swiper-item> -->
@@ -61,66 +66,46 @@
</view>
<!--</swiper-item><swiper-item>-->
<view class="evaluate" v-if="tabAct == 2 && form">
<comment ref="commentRef" :crsNum="form.crsNum" :crsId="form.crsId" :imgId="form.imgId">
</comment>
<comment ref="commentRef" :crsNum="form.crsNum" :crsId="form.crsId" :imgId="form.imgId"></comment>
</view>
<!--</swiper-item></swiper>-->
</view>
</view>
</scroll-view>
<view class="btn-box btn-box-height font_pf">
<template v-if="[0,1].includes(tabAct)">
<uv-button type="primary"
@click="common.navigateTo('/pages/course/study?crsNum='+crsNum+'&crsId='+form.crsId + '&imgId=' + form.imgId)">去学习</uv-button>
<template v-if="[0, 1].includes(tabAct)">
<uv-button type="primary" @click="common.navigateTo('/pages/course/study?crsNum=' + crsNum + '&crsId=' + form.crsId + '&imgId=' + form.imgId)">去学习</uv-button>
<uv-button type="primary">去练习</uv-button>
<uv-button type="primary" @click="goto_examination()">去考试</uv-button>
</template>
<template v-if="tabAct == 2">
<uv-button type="primary" class="send_evaluate"
@click="common.navigateTo('/pages/courseDetail/submit?crsNum='+crsNum+'&imgId='+form.imgId)">发布评价</uv-button>
<uv-button type="primary" class="send_evaluate" @click="common.navigateTo('/pages/courseDetail/submit?crsNum=' + crsNum + '&imgId=' + form.imgId)">
发布评价
</uv-button>
</template>
</view>
</view>
</template>
<script setup>
import {
reactive,
ref,
computed,
onMounted
} from 'vue';
import {
onShow,
onLoad,
onUnload
} from '@dcloudio/uni-app'
import {
getCourseDetailApi
} from '@/api/courseDetail.js';
import {
startExamByCrs
} from '@/api/examination.js'
import { reactive, ref, computed, onMounted } from 'vue';
import { onShow, onLoad, onUnload } from '@dcloudio/uni-app';
import { getCourseDetailApi } from '@/api/courseDetail.js';
import { startExamByCrs } from '@/api/examination.js';
import { insertTraPersonalCollectionTask, deleteTraPersonalCollectionById } from '@/api/favorites.js';
import common from '@/common/common';
import introduceVue from './components/introduce.vue';
import knowledge from './components/knowledge.vue';
import comment from './components/comment.vue';
const statusBarHeight = uni.getSystemInfoSync().statusBarHeight;
// tab栏
const tabList = ref([{
name: '课程介绍'
}, {
name: '知识点'
}, {
name: '评价'
}]);
const tabList = ref([{ name: '课程介绍' }, { name: '知识点' }, { name: '评价' }]);
const test = ref(false);
const commentRef = ref(null);
const tabsDiv = ref(null);
const form = ref({});
const crsNum = ref('');
const crsId = ref('');
const tabAct = ref(0);
const scrollTop = ref(0);
const tabSwitch = (item) => {
@@ -134,17 +119,14 @@
};
const getData = async () => {
const {
body
} = await getCourseDetailApi({
crsNum: crsNum.value
const { body } = await getCourseDetailApi({
crsId: crsId.value
});
form.value = body;
};
const toStudyPrgh = (info) => {
common.navigateTo('/pages/course/study?crsNum=' + crsNum.value + '&crsId=' + form.value.crsId + '&pgrphId=' +
info.pgrphId + '&imgId=' + form.imgId)
}
common.navigateTo('/pages/course/study?crsNum=' + form.value.value + '&crsId=' + crsId.value + '&pgrphId=' + info.pgrphId + '&imgId=' + form.imgId);
};
const bgOpacity = computed(() => {
if (scrollTop.value <= 20) return `rgba(255,255,255,0)`;
@@ -152,48 +134,65 @@
return `rgba(255,255,255,${opacity})`;
});
// 子组件滑动开启状态
const childScrollStatus = ref(false)
const childScrollStatus = ref(false);
const scroll_fun = (e) => {
scrollTop.value = e.detail.scrollTop
scrollTop.value = e.detail.scrollTop;
// getElementPosition().then(res => {
// childScrollStatus.value = statusBarHeight === res.top
// })
}
};
const swiper_change = (res) => {
tabAct.value = res.detail.current
}
tabAct.value = res.detail.current;
};
// 去考试
const goto_examination = async () => {
common.loading()
common.loading();
startExamByCrs({
// crsId : form.value.crsId
crsId : 'CRS0250181220722025070408313900000018729'
}).then(res => {
setPageCache('examination', {...res.body, examId:detailData.examId})
common.navigateTo('/pages/examination/index')
}).finally(() => {
common.hideLoading()
crsId: 'CRS0250181220722025070408313900000018729'
})
}
.then((res) => {
setPageCache('examination', {
...res.body,
examId: detailData.examId
});
common.navigateTo('/pages/examination/index');
})
.finally(() => {
common.hideLoading();
});
};
const collect_click = async (collectId) => {
console.log(collectId);
if (!collectId) {
await insertTraPersonalCollectionTask({
collectTyp: '01',
collectBusId: crsId.value
});
} else {
await deleteTraPersonalCollectionById({
collectId: collectId
});
}
};
onMounted(async () => {
uni.$on('refresh_comment_data', () => {
console.log('发评论了刷新');
commentRef.value?.getData('submit');
tabAct.value = 2
})
})
tabAct.value = 2;
});
});
onLoad((params) => {
crsNum.value = params.crsNum;
crsId.value = params.crsId;
});
onShow(() => {
getData();
})
});
onUnload(() => {
uni.$off('refresh_comment_data')
uni.$off('refresh_comment_data');
});
</script>
@@ -227,8 +226,8 @@
left: 25%;
width: 40%;
height: 40%;
border-top: 4rpx solid #FFFFFF;
border-left: 4rpx solid #FFFFFF;
border-top: 4rpx solid #ffffff;
border-left: 4rpx solid #ffffff;
transform: rotate(-45deg);
}
}
@@ -254,7 +253,6 @@
height: 100%;
display: flex;
flex-direction: column;
}
.body-head {
@@ -273,7 +271,6 @@
}
}
.head-title {
border-radius: 24rpx 24rpx 0rpx 0rpx;
// height: 162rpx;
@@ -307,7 +304,7 @@
display: flex;
.examination {
color: #0066FF;
color: #0066ff;
.back-icon {
position: relative;
@@ -328,8 +325,8 @@
left: 25%;
width: 40%;
height: 40%;
border-top: 4rpx solid #0066FF;
border-left: 4rpx solid #0066FF;
border-top: 4rpx solid #0066ff;
border-left: 4rpx solid #0066ff;
transform: rotate(135deg);
}
}
@@ -342,7 +339,6 @@
padding-right: 16rpx;
padding-top: 20rpx;
padding-bottom: 245rpx;
}
.body-tab {
@@ -371,7 +367,7 @@
padding-top: 37rpx;
justify-content: space-around;
background-color: #ffffff;
border: 2rpx solid #E5E5E5;
border: 2rpx solid #e5e5e5;
z-index: 1000;
:deep(.uv-button-wrapper) {
@@ -402,8 +398,6 @@
height: 30rpx;
}
.learn {
color: #1dd17a;
}
@@ -416,4 +410,4 @@
font-weight: 600;
color: #000000;
}
</style>
</style>
-201
View File
@@ -1,201 +0,0 @@
<template>
<view class="main_div max_page">
<nav-bar :is_seat="true"></nav-bar>
<view class="nav-div">
<view class="title ellipsis-text">
这里是练习名称
</view>
<view class="back_div"></view>
</view>
<view class="countdown-and-question-number-div">
<view class="top">
<view class="countdown">
<text class="grey">答题进度</text> <text class="bold">2</text><text class="grey">/10</text>
</view>
<view class="question">
<view class="grey">剩余时间</view>
<uv-count-down :time="30 * 60 * 60 * 1000" format="HH:mm:ss"></uv-count-down>
</view>
</view>
<view class="progress">
<uv-line-progress :percentage="30" :showText="false" activeColor="#FFFFFF" inactiveColor="#89B8FF"
:height="8"></uv-line-progress>
</view>
</view>
<view class="scroll_div">
<swiper class="swiper" :current="tabAct" :indicator-dots="false" :disable-touch="true">
<swiper-item>
<view class="content">
<view class="expose_shadow"></view>
<scroll-view :scroll-y="true" class="scroll-Y" :show-scrollbar="false" :refresher-threshold="0">
<RadioSubject></RadioSubject>
</scroll-view>
</view>
</swiper-item>
</swiper>
</view>
</view>
</template>
<script setup>
import RadioSubject from '@/components/examination/radio-subject.vue'
import {
ref,
computed,
onMounted,
nextTick,
getCurrentInstance
} from 'vue';
const {
proxy
} = getCurrentInstance() // 获取当前组件实例
// 用于存储尺寸信息的响应式变量
const dimensions = ref({
containerHeight: 0,
contentHeight: 0
});
// 检查尺寸并更新
const checkDimensions = async () => {
await nextTick(); // 确保DOM已更新
// 创建查询
const query = uni.createSelectorQuery().in(proxy);
// 添加内容高度查询
query.select('.content').boundingClientRect(data => {
dimensions.value.contentHeight = data?.height || 0;
});
// 添加容器高度查询(注意这里没有调用.exec())
query.select('.scroll_div').boundingClientRect(data => {
dimensions.value.containerHeight = data?.height || 0;
});
// 执行所有查询(只调用一次exec())
query.exec();
};
// 计算属性:判断是否需要开启滚动
const isScrollable = computed(() => {
return dimensions.value.contentHeight > dimensions.value.containerHeight - 20;
});
// 初始化时检查
onMounted(() => {
checkDimensions();
// 监听窗口尺寸变化(可选)
uni.onWindowResize(() => {
checkDimensions();
});
});
</script>
<style scoped lang="scss">
$bg-margin-left: 30rpx;
.scroll-Y {
max-height: calc(100vh - var(--status-bar-height) - 70rpx - 70rpx - 40rpx - 64rpx - 68rpx - 60rpx);
min-height: calc(100vh - var(--status-bar-height) - 70rpx - 70rpx - 40rpx - 64rpx - 68rpx - 60rpx - 380rpx);
}
.content {
position: relative;
margin: 32rpx $bg-margin-left;
border-radius: 16rpx;
padding: 34rpx 30rpx 34rpx 36rpx;
background-color: #fff;
.expose_shadow {
margin-left: 36rpx;
position: absolute;
bottom: -26rpx;
left: 0%;
width: calc(100% - 66rpx);
height: 28rpx;
background-color: #FFFFFF;
border-radius: 0 0 14rpx 14rpx;
opacity: 0.4;
z-index: 0;
}
}
.countdown-and-question-number-div {
margin: 20rpx $bg-margin-left;
height: 70rpx;
// background-color: red;
display: flex;
flex-direction: column;
justify-content: space-between;
z-index: 10;
.top {
display: flex;
align-items: center;
justify-content: space-between;
font-family: PingFangSC;
font-size: 26rpx;
color: #FFFFFF;
text-align: left;
font-style: normal;
}
.question {
display: flex;
align-items: center;
// background-color: red;
width: 242rpx;
color: red;
.grey {
margin-right: 8rpx;
}
:deep(.uv-count-down__text) {
color: #FFFFFF !important;
font-weight: 600;
font-size: 26rpx !important;
}
}
.grey {
color: #B6E2FF;
}
.bold {
font-weight: 600;
}
}
.nav-div {
position: relative;
.title {
// padding-top:var(--status-bar-height);
// padding: 0 30rpx;
margin: 0 calc($bg-margin-left + 70rpx);
height: 70rpx;
font-family: PingFangSC;
font-weight: 500;
font-size: 34rpx;
color: #FFFFFF;
text-align: center;
font-style: normal;
line-height: 70rpx;
}
.back_div {
position: absolute;
right: $bg-margin-left;
top: 0;
width: 70rpx;
height: 70rpx;
// background-color: red;
}
}
.main_div {
display: flex;
flex-direction: column;
background: linear-gradient(to top, #3480FF, #0066FF);
/* 确保背景占满整个视口高度 */
min-height: 100vh;
margin: 0;
}
</style>
+36 -44
View File
@@ -43,7 +43,7 @@
</swiper>
</view>
<view class="fixed-box font_pf">
<view class="fixed-box font_pf" :style="{marginBottom:popup_input_bottom}">
<view class="fixed-btn-box">
<view class="sheet_button" @click="openSheet(true)">
<image :src="examinationSheetIcon()" class="img"></image>
@@ -60,13 +60,13 @@
</view>
</view>
<view class="touch-btn-div" :class="{'show':showTouchBtn}">
<TouchBtn class="talk-btns" @uploadVoice="uploadRecordNow" @submitAnswer="submitAnswerNow"
:loadingText="sendLoadingText" :isLoading="!answerIsOver" :isDisabled="false" />
<TouchBtn class="talk-btns" @submit="touchBtnSubmit"
loadingText="问题回答中,请在问题回答结束后重试!" :isLoading="!answerIsOver" :isDisabled="false" />
</view>
</view>
<uni-popup ref="popupRef" class="popup">
<view class="sheet_popup_div">
<view class="sheet_popup_div" >
<view class="title_div">
<view class="text">
答题卡
@@ -107,7 +107,7 @@
import Dialog from './components/dialog.vue'
import RadioSubject from '@/components/examination/radio-subject.vue'
import CharactersSubject from '@/components/examination/characters-subject.vue'
import TouchBtn from '@/pages/course/components/touchBtn.vue'
import TouchBtn from '@/components/examination/touchBtn.vue'
import {
ref,
reactive,
@@ -134,7 +134,7 @@
queryPaperExamScore
} from '@/api/examination.js'
const sendLoadingText = ref('问题回答中,请在问题回答结束后重试!')
const answerIsOver = ref(true)
const feedbackRef = ref(null)
const dialogRef = ref(null)
@@ -149,9 +149,9 @@
papersName: '', // 试卷名称
}) // 试卷信息
const topicList = reactive([]) // 问题列表
const popup_input_bottom = ref('0px')
const questionNumber = ref(0) // 题号
const systemInfo = uni.getSystemInfoSync();
const showTouchBtn = computed(() => {
return topicList[questionNumber.value]['qnsTyp'] === 'ES'
})
@@ -207,53 +207,32 @@
}
const voicePathValue = ref('')
// 录音按钮提交
const touchBtnSubmit = (type, submitObj) => {
if (type === 'voice') {
uploadRecordNow(submitObj)
}else if (type === 'text') {
}
}
// 发送语音
const uploadRecordNow = (voicePath) => {
//#ifdef APP-PLUS
commonUploadVoiceFile(voicePath, '/traask/traAskchat/voiceTrans', {}).then(res => {
let _res = JSON.parse(res.data)
voicePathValue.value = voicePath
console.log('_res', _res, voicePath);
if (_res.rtnCode === '0000') {
// "chatId": "CHAT025018122068202508081537410000000000000000000000000000000088",
// "fileId": "S3F0250181220722025080815374100000456565",
// "ossFileAddr": "http://25.18.122.66::9786/traoss/show//S3F0250181220722025080815374100000456565",
// "transContent": "下面那几个跟。"
// activeVoiceId.value =
// if (!!(_res.body.transContent || '').trim()) {
// askData.value = {
// reqBatch: reqBatch.value,
// intrctWay: '02', //01-文本;02-语音;03-图片
// intrctContent: _res.body.transContent,
// bucketKey: _res.body.fileId,
// ossFileAddr: _res.body.ossFileAddr,
// talkingVoice: voicePath
// }
// sendMessage({
// "commond": 'ASK',
// "body": askData.value
// })
// } else {
// // talkingList.value.pop()
// uni.showToast({
// title: '未识别到文字!',
// icon: "none",
// duration: 1000
// });
// }
} else {}
}
}).catch(err => {
// talkingList.value.pop()
uni.showToast({
title: '系统异常,请联系管理员',
icon: "none",
duration: 1000
});
})
// #endif
}
// 发送文本
const submitAnswerNow = (val, cb = null) => {
@@ -277,6 +256,18 @@
my_answer: [],
backend_answer: [],
})))
// 安全区域底部到屏幕底部的高度(即底部安全区域高度)
const safeAreaBottomHeight = systemInfo.screenHeight - systemInfo.safeArea.bottom;
uni.onKeyboardHeightChange(res => {
let keyboardHeight = res.height;
// 仅在iOS端补偿安全区域高度
if (systemInfo.platform === 'ios') {
keyboardHeight = Math.max(0, keyboardHeight - safeAreaBottomHeight); // 避免负数
}
popup_input_bottom.value = `${keyboardHeight}px`;
console.log('端补偿安全区域高度', popup_input_bottom.value);
});
});
const result_click = ({
@@ -366,7 +357,7 @@
}
}
})
console.log('answerDtoList', answerDtoList);
dialogRef.value.close()
common.loading('正在提交')
@@ -382,7 +373,7 @@
} else { // 不需要查分
common.navigateBack()
}
// const execId = res.body.execId
// let timer = setInterval(() => {
// queryPaperExamScore({
@@ -413,6 +404,7 @@
padding: 0 38rpx;
.sheet_popup_div {
width: 100%;
height: 100%;
background-color: #FFFFFF;
@@ -535,7 +527,7 @@
justify-content: space-between;
align-items: center;
padding: 0 20rpx 20rpx 20rpx;
.sheet_button {
display: flex;
flex-direction: column;
+5 -5
View File
@@ -130,7 +130,7 @@
<view class="scroll-view-item" v-for="(item,index) in hot_class_list" :key="index">
<view class="item_div">
<uv-skeleton :loading="!item.crsNum" :rows="3" avatar avatar-shape="square" :title="false">
<view class="item" @click="goto_course_detail(item.crsNum)">
<view class="item" @click="goto_course_detail(item.crsId)">
<view class="left">
<image-preview class="img-box img" :imgId="item.imgId" mode="aspectFill"
:isCache="true" :width="240" :height="240"></image-preview>
@@ -164,7 +164,7 @@
<view class="scroll-view-item" v-for="(item,index) in recommend_class_list" :key="index">
<view class="item_div">
<uv-skeleton :loading="!item.crsNum" :rows="3" avatar avatar-shape="square" :title="false">
<view class="item" @click="goto_course_detail(item.crsNum)">
<view class="item" @click="goto_course_detail(item.crsId)">
<view class="left">
<image-preview class="img-box img" :imgId="item.imgId" mode="aspectFill"
:isCache="true" :width="240" :height="240"></image-preview>
@@ -197,7 +197,7 @@
<view class="scroll-view-item" v-for="(item,index) in new_class_list" :key="index">
<view class="item_div">
<uv-skeleton :loading="!item.crsNum" :rows="3" avatar avatar-shape="square" :title="false">
<view class="item" @click="goto_course_detail(item.crsNum)">
<view class="item" @click="goto_course_detail(item.crsId)">
<view class="left">
<image-preview class="img-box img" :imgId="item.imgId" mode="aspectFill"
:isCache="true" :width="240" :height="240"></image-preview>
@@ -389,8 +389,8 @@
common.navigateTo('/pages/search/search')
}
// 进入课程详情页
const goto_course_detail = (crsNum) => {
common.navigateTo('/pages/courseDetail/index?crsNum=' + crsNum)
const goto_course_detail = (crsId) => {
common.navigateTo('/pages/courseDetail/index?crsId=' + crsId)
}
const scrolltolower = () => {
common.navigateTo('/pages/index/dialog', {
@@ -16,10 +16,10 @@
</view>
<view class="right_div">
<view class="title ellipsis-text">
JBank银行服务礼仪与投诉...
{{item.taskName}}
</view>
<view class="time_div">
起止时间<text class="blod_color">2025-05-13至2025-05-13</text>
<view class="time_div" v-if="item.limitTyp==='02'">
起止时间<text class="blod_color">{{item.startTm?.substr(0, 10)}}{{item.endTm?.substr(0, 10)}}</text>
</view>
<view class="people_progress_div">
<view class="mr-10">
@@ -49,8 +49,8 @@
} from 'vue'
import {
queryTraCrsBbsInfoByCrsId,
} from '@/api/courseDetail.js';
queryAllTraTaskInfoByUserId,
} from '@/api/learningTasks.js';
const getData = (from = '') => {
if (from == 'first') {
if (total.value !== -1) {
@@ -66,8 +66,8 @@
page.value++
}
setTimeout(() => {
queryTraCrsBbsInfoByCrsId({
crsId: 'CRS0250181220722025070408313900000018729',
queryAllTraTaskInfoByUserId({
state: '01',
page: page.value,
limit: limit
}).then(res => {
+12 -14
View File
@@ -15,36 +15,34 @@
suffixIcon="search" :customStyle="customStyle" :suffixIconStyle="suffixIconStyle">
</uv-input>
</view>
<scroll-view ref="scrollRef" scroll-y="true" class="scroll-Y" :refresher-enabled="true"
@refresherrefresh="onRefresh" @refresherrestore="triggered = 'restore'" :refresher-triggered="triggered"
refresher-default-style="none">
<!-- 主体 -->
<view class="content">
<view style="position: absolute;top: -90rpx;width: 100%;height: 90rpx;background-color: red;"> 下拉显示</view>
<view style="position: absolute;top: -90rpx;width: 100%;height: 90rpx;"> </view>
<uv-swipe-action>
<uv-swipe-action-item :options="options" :threshold="40" :index="index"
v-for="(index,item) in data_list">
v-for="(item, index) in data_list">
<view class="item">
<view class="icon">
<image class="img_100" :src="'/static/images/message_notification/type_1.png'"
mode="widthFix"></image>
<image class="img_100" :src="`@/static/images/messageNotification/type_${item.noticeTyp}.png`" mode="widthFix"></image>
</view>
<view class="right">
<view class="top">
<view class="title">
新任务提醒
{{item.noticeTitle}}
</view>
<view class="time">
2025-05-05
{{item.ctTime.substr(0, 16)}}
</view>
</view>
<view class="bottom">
<view class="note">
您的学习任务
<view class="note ellipsis-text">
{{item.noticeContent}}
</view>
<view class="num_div">
{{item}}
</view>
</view>
</view>
@@ -70,8 +68,9 @@
onLoad
} from '@dcloudio/uni-app'
import {
queryTraCrsBbsInfoByCrsId
} from '@/api/courseDetail.js';
queryTraPersonalMessageNoticePaging
} from '@/api/messageNotification.js';
const customStyle = {
backgroundColor: "#ffffff",
paddingLeft: "40rpx",
@@ -135,8 +134,7 @@
console.log(status.value, '阿斯顿撒');
setTimeout(() => {
queryTraCrsBbsInfoByCrsId({
crsId: 'CRS0250181220722025070408313900000018729',
queryTraPersonalMessageNoticePaging({
page: page.value,
limit: limit
}).then(res => {

Before

Width:  |  Height:  |  Size: 1.9 KiB

After

Width:  |  Height:  |  Size: 1.9 KiB

Before

Width:  |  Height:  |  Size: 2.4 KiB

After

Width:  |  Height:  |  Size: 2.4 KiB

Before

Width:  |  Height:  |  Size: 2.2 KiB

After

Width:  |  Height:  |  Size: 2.2 KiB