feat: 陪练预览,陪练记录,陪练报告,陪练对话等联调
This commit is contained in:
@@ -10,3 +10,11 @@ export const queryTraStdyInfoPreviewPaging = (data) => {
|
||||
data
|
||||
});
|
||||
};
|
||||
export const queryTraListPreviewPaging = (data) => {
|
||||
return request({
|
||||
url: '/trapractice/traPartnerInfo/queryTraPartnerPreviewPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
import request from '@/api/request'
|
||||
const base_url = '/trapractice'
|
||||
|
||||
|
||||
/**练习*/
|
||||
// 练习列表页
|
||||
export const queryPracticeRecordPaging = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerExecuteHis/queryTraPartnerExecutePaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data:{
|
||||
traType:'01',
|
||||
...data
|
||||
}
|
||||
});
|
||||
};
|
||||
// 练习每一项列表
|
||||
export const queryPracticeHis = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerExecuteHis/queryTraPartnerExecuteHisPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
// 练习详情 通过 exrId 查询
|
||||
export const queryPracticeRecordByExrId = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traCrsPractice/queryPracticeRecordByExrId',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
/**考试*/
|
||||
export const queryCrsExamRecordPaging = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerExecuteHis/queryTraPartnerExecutePaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data:{
|
||||
traType:'02',
|
||||
...data
|
||||
}
|
||||
});
|
||||
};
|
||||
// 考试每一项列表
|
||||
export const queryCrsExamHisByExamId = (data) => {
|
||||
return request({
|
||||
url: base_url + '/traPartnerExecuteHis/queryTraPartnerExecuteHisPaging',
|
||||
method: 'post',
|
||||
toastErrors: true,
|
||||
data
|
||||
});
|
||||
};
|
||||
|
||||
@@ -510,6 +510,15 @@
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/traRecord/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "陪练记录页",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"tabBar": {
|
||||
|
||||
@@ -119,7 +119,6 @@
|
||||
dataY: dataY + '%'
|
||||
});
|
||||
}
|
||||
console.log(vertices)
|
||||
return vertices;
|
||||
},
|
||||
|
||||
|
||||
@@ -119,7 +119,6 @@
|
||||
dataY: dataY + '%'
|
||||
});
|
||||
}
|
||||
console.log(vertices)
|
||||
return vertices;
|
||||
},
|
||||
|
||||
|
||||
@@ -97,7 +97,7 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="scroll-view-item">
|
||||
<view class="item" @click="goTest()">
|
||||
<view class="item" @click="common.navigateTo('/pages/traRecord/index')">
|
||||
<image class="icon" src="@/static/images/me/report.png"></image>
|
||||
<view class="title">AI陪练记录</view>
|
||||
</view>
|
||||
|
||||
@@ -1,42 +1,67 @@
|
||||
<template>
|
||||
<view class="scroll_div">
|
||||
<scroll-view class="scroll-Y" :show-scrollbar="false">
|
||||
<view class="item">
|
||||
<image src="/src/static/images/common/default_img_2.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
</scroll-view>
|
||||
</view>
|
||||
<z-paging ref="pagingRef" v-model="data_list" @query="queryList" :auto="false" empty-view-text="暂无数据"
|
||||
paging-class="pagingClass">
|
||||
<view class="sparring-item-box" v-for="item in data_list">
|
||||
<SparringItem :itemInfo="item" :activeTag="[]" @clickItem="toSparringDetail(item)" />
|
||||
</view>
|
||||
</z-paging>
|
||||
</template>
|
||||
<script setup>
|
||||
|
||||
import common from '@/common/common';
|
||||
import {
|
||||
ref,
|
||||
reactive,
|
||||
onMounted,
|
||||
nextTick,
|
||||
watch
|
||||
} from 'vue';
|
||||
import {
|
||||
queryTraListPreviewPaging
|
||||
} from '@/api/preview.js';
|
||||
import useZpaging from '@/composables/useZpaging';
|
||||
import SparringItem from '@/pages/sparring/components/sparring-item.vue'
|
||||
const {
|
||||
pagingRef,
|
||||
queryList,
|
||||
data_list,
|
||||
reload,
|
||||
total
|
||||
} = useZpaging({
|
||||
fetchFunction: queryTraListPreviewPaging
|
||||
});
|
||||
const props = defineProps(['tabIndex', 'currentIndex']);
|
||||
const toSparringDetail = (item) => {
|
||||
if (item.traId) {
|
||||
common.navigateTo('/pages/sparring/detail?traId=' + item.traId + '&isPreview=1')
|
||||
}
|
||||
};
|
||||
onMounted(() => {
|
||||
watch(
|
||||
() => props.currentIndex,
|
||||
(v) => v === props.tabIndex && total.value === -1 && reload(), {
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scroll_div {
|
||||
/* 圆形渐变,颜色顺序已反转(原1和3交换) */
|
||||
background: radial-gradient(circle,
|
||||
#C3B9FF 0%,
|
||||
/* 原第三种颜色,现在作为起始颜色 */
|
||||
#DFF3FF 46%,
|
||||
/* 中间过渡色保持不变 */
|
||||
#BFEBFF 100%
|
||||
/* 原第一种颜色,现在作为结束颜色 */
|
||||
);
|
||||
/* 渐变中心点仍在左下角,向右侧和上方扩散 */
|
||||
background-position: 0% 100%;
|
||||
background-size: 200% 200%;
|
||||
.pagingClass {
|
||||
width: calc(100% - 44rpx);
|
||||
margin: 22rpx auto;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.scroll-Y {
|
||||
height: calc(100vh - var(--status-bar-height) - 54rpx - 88rpx - 20rpx);
|
||||
}
|
||||
|
||||
.scroll-Y .item {
|
||||
// background-color: red;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
margin-top: 300rpx;
|
||||
|
||||
.sparring-item-box {
|
||||
width: 50%;
|
||||
float: left;
|
||||
margin-top: 24rpx;
|
||||
margin-bottom: -24rpx;
|
||||
// background-color: #f1f1f1;
|
||||
// margin-bottom: 16rpx;
|
||||
&:nth-child(2n-1) {
|
||||
// margin-right: 16rpx;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -5,16 +5,19 @@
|
||||
</view>
|
||||
</view>
|
||||
<view class="answer-content" v-else>
|
||||
<view :class="['talking-gif', !voiceLoading&&(props.activeVoiceId === dataInfo.id) ? 'is-play' : '']">
|
||||
<CommonLoading color="#06f" v-show="voiceLoading" />
|
||||
</view>
|
||||
<view class="talking-cont">
|
||||
<MarkdownPreview :mdString="mdText" />
|
||||
</view>
|
||||
<view class="btns-cont" v-if="!props.dataInfo.isLoading">
|
||||
<view class="replay-btn" @click="reAnswer" v-if="!props.dataInfo.isHistory">
|
||||
<view class="btns-cont" v-if="!dataInfo.isLoading">
|
||||
<!-- <view class="replay-btn" @click="reAnswer" v-if="!props.dataInfo.isHistory">
|
||||
<image class="icon" src="@/static/images/dialog/answer-restart1.png" style="width: 100%; height: 100%"></image>
|
||||
</view>
|
||||
<view class="replay-btn" @click="stopAnswer" v-if="props.isAnswering && props.isLast">
|
||||
<image class="icon" src="@/static/images/dialog/answer-stop1.png" style="width: 100%; height: 100%"></image>
|
||||
</view>
|
||||
</view> -->
|
||||
<!-- 重新播放 -->
|
||||
<view class="replay-btn" @click.stop="restartPlay">
|
||||
<image class="icon" src="@/static/images/course/replay.png" style="width: 100%; height: 100%"></image>
|
||||
@@ -39,9 +42,20 @@
|
||||
watch,
|
||||
toRefs
|
||||
} from 'vue'
|
||||
import { onUnload } from '@dcloudio/uni-app';
|
||||
import {
|
||||
onUnload
|
||||
} from '@dcloudio/uni-app';
|
||||
import MarkdownPreview from '@/components/markdown-preview/markdown-preview.vue'
|
||||
import common from '@/common/common'
|
||||
import {
|
||||
useStorageStore
|
||||
} from '@/store/storage.js';
|
||||
import {
|
||||
downloadFile,
|
||||
downloadVoiceFile
|
||||
} from '@/api/common.js'
|
||||
|
||||
const storageStore = useStorageStore();
|
||||
|
||||
const emit = defineEmits(['handleEvents', 'changePlay'])
|
||||
const props = defineProps({
|
||||
@@ -49,6 +63,10 @@
|
||||
default: () => ({}),
|
||||
type: Object
|
||||
},
|
||||
roleInfo: {
|
||||
default: () => ({}),
|
||||
type: Object
|
||||
},
|
||||
isAnswering: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
@@ -56,24 +74,31 @@
|
||||
isLast: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
activeVoiceId: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
isStudy: {
|
||||
default: true,
|
||||
type: Boolean
|
||||
},
|
||||
})
|
||||
const { isLast, dataInfo } = toRefs(props)
|
||||
watch(
|
||||
() => props.dataInfo,
|
||||
() => {}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
|
||||
const {
|
||||
isLast,
|
||||
dataInfo,
|
||||
roleInfo,
|
||||
activeVoiceId,
|
||||
isStudy
|
||||
} = toRefs(props)
|
||||
const isPlaying = ref(false)
|
||||
const voiceIsPlaying = ref(false)
|
||||
const startPlay = ref(false)
|
||||
|
||||
const mdText = computed(() => {
|
||||
let _text = (props.dataInfo?.talkingText || '').replace(/\\n/g, `\n`);
|
||||
return _text
|
||||
})
|
||||
const fileList = computed(() => props.dataInfo?.talkingFiles || [])
|
||||
const crsList = computed(() => props.dataInfo?.talkingCrsList || [])
|
||||
const activeNames = ref([1, 2])
|
||||
|
||||
const changeActive = val => {
|
||||
@@ -102,6 +127,22 @@
|
||||
common.navigateTo('/pages/courseDetail/index?crsId=' + item.crsId)
|
||||
}
|
||||
const talkVoiceObj = ref(null)
|
||||
const voiceLoading = ref(false)
|
||||
const itemVoice = ref('')
|
||||
// 重播
|
||||
const restartPlay = () => {
|
||||
if (itemVoice.value) {
|
||||
emit('changePlay', '');
|
||||
emit('changePlay', unref(dataInfo)?.id);
|
||||
setTimeout(() => {
|
||||
talkVoiceObj.value.src = itemVoice.value;
|
||||
talkVoiceObj.value.seek(0);
|
||||
talkVoiceObj.value.play();
|
||||
}, 100);
|
||||
} else {
|
||||
playVoice();
|
||||
}
|
||||
};
|
||||
const playVoice = (readType = 'pgth') => {
|
||||
let _content = mdText.value;
|
||||
if (!_content) {
|
||||
@@ -109,41 +150,91 @@
|
||||
return;
|
||||
}
|
||||
isPlaying.value = true
|
||||
voiceLoading.value = true;
|
||||
setTimeout(() => {
|
||||
downloadFile(props.dataInfo?.talkingVoiceId)
|
||||
.then((res) => {
|
||||
if (res?.data) {
|
||||
common.msg(res.data.message);
|
||||
return;
|
||||
}
|
||||
voiceLoading.value = false;
|
||||
itemVoice.value = res.tempFilePath;
|
||||
talkVoiceObj.value.src = res.tempFilePath;
|
||||
emit('changePlay', unref(dataInfo)?.id);
|
||||
// readType 阅读内容类型pgrh段落/qns问题
|
||||
if (talkVoiceObj.value.src && talkVoiceObj.value.src === itemVoice.value) {
|
||||
if (voiceIsPlaying.value) return
|
||||
talkVoiceObj.value.play();
|
||||
voiceLoading.value = false;
|
||||
return;
|
||||
}
|
||||
talkVoiceObj.value.src = '';
|
||||
if (itemVoice.value) {
|
||||
setTimeout(() => {
|
||||
talkVoiceObj.value.src = itemVoice.value;
|
||||
if (voiceIsPlaying.value) return
|
||||
talkVoiceObj.value.play();
|
||||
})
|
||||
.catch((err) => {
|
||||
voiceLoading.value = false;
|
||||
}, 100);
|
||||
} else {
|
||||
voiceLoading.value = true;
|
||||
let _params = {
|
||||
chaId: unref(roleInfo)?.chaId || '',
|
||||
execLogId: unref(dataInfo)?.execLogId || '',
|
||||
};
|
||||
let _url =
|
||||
'/trapractice/traPartnerChat/readContent?' +
|
||||
Object.keys(_params)
|
||||
.map((t) => {
|
||||
return encodeURIComponent(t) + '=' + encodeURIComponent(_params[t]);
|
||||
})
|
||||
.join('&');
|
||||
storageStore.getStorageById('audio', _url + _params.teachNo, (url) => {
|
||||
if (url) {
|
||||
voiceLoading.value = false;
|
||||
itemVoice.value = url;
|
||||
talkVoiceObj.value.src = url;
|
||||
if (voiceIsPlaying.value) return
|
||||
talkVoiceObj.value.play();
|
||||
} else {
|
||||
downloadVoiceFile(_url)
|
||||
.then((res) => {
|
||||
if (res?.data) {
|
||||
common.msg(res.data.message);
|
||||
return;
|
||||
}
|
||||
voiceLoading.value = false;
|
||||
itemVoice.value = res.tempFilePath;
|
||||
talkVoiceObj.value.src = res.tempFilePath;
|
||||
if (voiceIsPlaying.value) return
|
||||
talkVoiceObj.value.play();
|
||||
storageStore.setStorage('audio', _url + _params.teachNo, itemVoice.value);
|
||||
})
|
||||
.catch((err) => {
|
||||
voiceLoading.value = false;
|
||||
emit('changePlay', '');
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}, 100);
|
||||
};
|
||||
|
||||
const initVoice = () => {
|
||||
talkVoiceObj.value = uni.createInnerAudioContext()
|
||||
talkVoiceObj.value.onEnded(()=>{
|
||||
talkVoiceObj.value.onEnded(() => {
|
||||
emit('changePlay', '')
|
||||
talkVoiceObj.value.src = ''
|
||||
voiceIsPlaying.value = false
|
||||
})
|
||||
talkVoiceObj.value.onError(()=>{
|
||||
talkVoiceObj.value.onPause(() => {
|
||||
voiceIsPlaying.value = false
|
||||
});
|
||||
talkVoiceObj.value.onPlay(() => {
|
||||
console.log('onPlay');
|
||||
startPlay.value = true
|
||||
voiceIsPlaying.value = true
|
||||
setTimeout(() => {
|
||||
startPlay.value = false
|
||||
}, 300)
|
||||
emit('changePlay', unref(dataInfo)?.id);
|
||||
});
|
||||
|
||||
talkVoiceObj.value.onError(() => {
|
||||
talkVoiceObj.value.src = ''
|
||||
voiceIsPlaying.value = false
|
||||
})
|
||||
if(unref(dataInfo).intrctWay === '02'){
|
||||
itemVoice.value = unref(dataInfo).talkingVoice
|
||||
talkVoiceObj.value.src = itemVoice.value
|
||||
setTimeout(()=>{
|
||||
setVoiceTime(0)
|
||||
},100)
|
||||
}
|
||||
}
|
||||
const pauseVoice = () => {
|
||||
if (!talkVoiceObj.value) return
|
||||
@@ -158,18 +249,45 @@
|
||||
pauseVoice()
|
||||
})
|
||||
watch(() => props.dataInfo.talkingVoice, (val) => {
|
||||
if (val) {
|
||||
if (val && isStudy.value) {
|
||||
initVoice()
|
||||
}
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
watch(
|
||||
() => props.dataInfo,
|
||||
(val) => {
|
||||
if (val.execLogId && isStudy.value) {
|
||||
playVoice()
|
||||
}
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
watch(
|
||||
() => props.activeVoiceId,
|
||||
(val) => {
|
||||
if (val !== props.dataInfo.id) {
|
||||
if (isPlaying.value) {
|
||||
isPlaying.value = false
|
||||
if (!talkVoiceObj.value) return
|
||||
talkVoiceObj.value.pause()
|
||||
}
|
||||
}
|
||||
}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
}
|
||||
)
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.answer-content {
|
||||
background-color: #f5f5f5;
|
||||
background-color: #fff;
|
||||
border: 2rpx solid #efefef;
|
||||
padding: 20rpx;
|
||||
border-radius: 15rpx;
|
||||
padding-bottom: 10rpx;
|
||||
@@ -179,6 +297,20 @@
|
||||
width: 100%;
|
||||
min-height: 80rpx;
|
||||
|
||||
.talking-gif {
|
||||
width: 200rpx;
|
||||
height: 38rpx;
|
||||
margin-bottom: 10rpx;
|
||||
background: url(@/static/images/course/voice-gray.png) no-repeat;
|
||||
background-size: contain;
|
||||
padding-left: 168rpx;
|
||||
|
||||
&.is-play {
|
||||
background: url(@/static/images/course/voice-gif.gif) no-repeat;
|
||||
background-size: auto 38rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.talking-cont {
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<view class="talking-info" v-if="dataInfo.intrctWay === '02'">
|
||||
<view :class="['talking-gif', !voiceLoading&&(props.activeVoiceId === dataInfo.id) ? 'is-play' : '']">
|
||||
<CommonLoading color="#f60" v-show="voiceLoading"/>
|
||||
</view>
|
||||
<view class="right-time">{{voiceTime}}</view>
|
||||
<view class="talking-cont">
|
||||
<text :class="['talking-text translate-text loading']" v-show="!translateLoading" v-html="showContent">
|
||||
</text>
|
||||
<view class="talking-text translate-text" v-if="translateLoading">
|
||||
<CommonLoading color="#f60"/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="btns-cont">
|
||||
<view class="play-btn">
|
||||
<image class="icon"
|
||||
v-show="!(!voiceLoading&&(props.activeVoiceId === dataInfo.id))"
|
||||
@click.stop="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> -->
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- <view class="talking-info talking-info-text" v-else-if="dataInfo.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-gif']">
|
||||
<CommonLoading color="#f60"/>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, unref, ref, toRefs, watch, onMounted } from "vue"
|
||||
import { onUnload } from '@dcloudio/uni-app';
|
||||
const props = defineProps({
|
||||
dataInfo: {
|
||||
default: () => ({}),
|
||||
type: Object
|
||||
},
|
||||
activeVoiceId:{
|
||||
default:'',
|
||||
type: String
|
||||
},
|
||||
})
|
||||
const { activeVoiceId, dataInfo } = toRefs(props)
|
||||
const emit = defineEmits(['changePlay'])
|
||||
// 声音播放初始化
|
||||
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(()=>{
|
||||
return dataInfo.value.talkingText || ''
|
||||
})
|
||||
onMounted(()=>{
|
||||
initVoice()
|
||||
})
|
||||
onUnload(()=>{
|
||||
pauseVoice()
|
||||
})
|
||||
const pauseVoice = () => {
|
||||
if(!talkVoiceObj.value) return
|
||||
talkVoiceObj.value.pause()
|
||||
isPlaying.value = false
|
||||
emit('changePlay', '')
|
||||
}
|
||||
const playVoice = () =>{
|
||||
emit('changePlay', unref(dataInfo)?.id)
|
||||
if(talkVoiceObj.value && talkVoiceObj.value.src) {
|
||||
setTimeout(()=>{
|
||||
talkVoiceObj.value.src = unref(dataInfo).talkingVoice
|
||||
talkVoiceObj.value.play()
|
||||
},300)
|
||||
return
|
||||
}else{
|
||||
talkVoiceObj.value.src = ''
|
||||
if(unref(dataInfo).talkingVoice){
|
||||
setTimeout(()=>{
|
||||
talkVoiceObj.value.src = unref(dataInfo).talkingVoice
|
||||
talkVoiceObj.value.play()
|
||||
isPlaying.value = true
|
||||
},100)
|
||||
}
|
||||
}
|
||||
}
|
||||
const initVoice = () => {
|
||||
talkVoiceObj.value = uni.createInnerAudioContext()
|
||||
talkVoiceObj.value.onPause(()=>{
|
||||
})
|
||||
talkVoiceObj.value.onPlay(()=>{
|
||||
if(isTesting.value){
|
||||
talkVoiceObj.value.pause()
|
||||
isTesting.value = false
|
||||
}
|
||||
})
|
||||
talkVoiceObj.value.onEnded(()=>{
|
||||
emit('changePlay', '')
|
||||
talkVoiceObj.value.src = ''
|
||||
})
|
||||
talkVoiceObj.value.onError(()=>{
|
||||
talkVoiceObj.value.src = ''
|
||||
})
|
||||
}
|
||||
const isTesting= ref(false)
|
||||
watch(() => props.dataInfo, () => {},{deep: true,immediate: true})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.talking-info{
|
||||
padding: 20rpx;
|
||||
border-radius: 15rpx;
|
||||
background-color: #f5f5f5;
|
||||
padding-bottom: 30rpx;
|
||||
position: relative;
|
||||
// color: #fff;
|
||||
width: 100%;
|
||||
float: right;
|
||||
.talking-gif{
|
||||
width: 200rpx;
|
||||
height: 38rpx;
|
||||
background: url(@/static/images/course/voice-gray.png) no-repeat;
|
||||
background-size: contain;
|
||||
padding-left: 168rpx;
|
||||
&.is-play{
|
||||
background: url(@/static/images/course/voice-gif.gif) no-repeat;
|
||||
background-size: contain;
|
||||
}
|
||||
}
|
||||
.right-time{
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 20rpx;
|
||||
font-size: 28rpx;
|
||||
height: 38rpx;
|
||||
line-height: 38rpx;
|
||||
}
|
||||
&.talking-info-loading{
|
||||
padding-bottom: 20rpx;
|
||||
}
|
||||
&.talking-info-text{
|
||||
padding-bottom: 20rpx;
|
||||
width: auto;
|
||||
float: right;
|
||||
.talking-cont{
|
||||
margin-top: 0rpx;
|
||||
}
|
||||
|
||||
}
|
||||
.talking-cont{
|
||||
margin-top: 16rpx;
|
||||
overflow: hidden;
|
||||
.talking-text{
|
||||
font-size: 28rpx;
|
||||
line-height: 48rpx;
|
||||
margin-bottom: 20rpx;
|
||||
word-break: break-all;
|
||||
white-space: pre-line;
|
||||
overflow: hidden;
|
||||
&.translate-text{
|
||||
min-height: 48rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
.btns-cont{
|
||||
overflow: hidden;
|
||||
border-top: 3rpx solid #eee;
|
||||
.replay-btn{
|
||||
float: left;
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
overflow: hidden;
|
||||
margin-right: 16rpx;
|
||||
margin-top: 25rpx;
|
||||
}
|
||||
.play-btn{
|
||||
float: left;
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
overflow: hidden;
|
||||
margin-right: 16rpx;
|
||||
margin-top: 25rpx;
|
||||
}
|
||||
.text-btn{
|
||||
float: left;
|
||||
width: 46rpx;
|
||||
height: 46rpx;
|
||||
overflow: hidden;
|
||||
line-height: 46rpx;
|
||||
text-align: center;
|
||||
// color: #fff;
|
||||
margin-right: 16rpx;
|
||||
margin-top: 25rpx;
|
||||
background-color: #67A4ff;
|
||||
font-size: 24rpx;
|
||||
font-weight: bold;
|
||||
border-radius: 20rpx;
|
||||
}
|
||||
.text-right-btn{
|
||||
float: right;
|
||||
padding: 0 10rpx;
|
||||
height: 46rpx;
|
||||
overflow: hidden;
|
||||
line-height: 46rpx;
|
||||
text-align: center;
|
||||
margin-left: 16rpx;
|
||||
margin-top: 25rpx;
|
||||
}
|
||||
.next-btn{
|
||||
float: right;
|
||||
padding: 0 60rpx 0 20rpx;
|
||||
height: 62rpx;
|
||||
line-height: 62rpx;
|
||||
font-size: 30rpx;
|
||||
background-color: #06f;
|
||||
// color: #fff;
|
||||
border-radius: 8rpx;
|
||||
margin-top: 16rpx;
|
||||
position: relative;
|
||||
.icon-iamge{
|
||||
width: 25rpx;
|
||||
height: 25rpx;
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
top: 50%;
|
||||
margin-top: -12rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -16,7 +16,7 @@
|
||||
<view class="play-btn">
|
||||
<image class="icon"
|
||||
v-show="!(!voiceLoading&&(props.activeVoiceId === dataInfo.id))"
|
||||
@click="playVoice"
|
||||
@click.stop="playVoice"
|
||||
src="@/static/images/course/play-blue.png"
|
||||
style="width: 100%;height: 100%;"
|
||||
></image>
|
||||
@@ -59,7 +59,6 @@ import { onUnload } from '@dcloudio/uni-app';
|
||||
})
|
||||
const { activeVoiceId, dataInfo } = toRefs(props)
|
||||
const emit = defineEmits(['changePlay'])
|
||||
watch(() => props.dataInfo, () => {},{deep: true,immediate: true})
|
||||
// 声音播放初始化
|
||||
const talkVoiceObj = ref(null)
|
||||
|
||||
@@ -78,14 +77,6 @@ import { onUnload } from '@dcloudio/uni-app';
|
||||
onUnload(()=>{
|
||||
pauseVoice()
|
||||
})
|
||||
watch(() => props.dataInfo.talkingVoice,(val)=>{
|
||||
if(val){
|
||||
initVoice()
|
||||
}
|
||||
},{
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
const pauseVoice = () => {
|
||||
if(!talkVoiceObj.value) return
|
||||
talkVoiceObj.value.pause()
|
||||
@@ -95,7 +86,10 @@ import { onUnload } from '@dcloudio/uni-app';
|
||||
const playVoice = () =>{
|
||||
emit('changePlay', unref(dataInfo)?.id)
|
||||
if(talkVoiceObj.value && talkVoiceObj.value.src) {
|
||||
talkVoiceObj.value.play()
|
||||
setTimeout(()=>{
|
||||
talkVoiceObj.value.src = unref(dataInfo).talkingVoice
|
||||
talkVoiceObj.value.play()
|
||||
},300)
|
||||
return
|
||||
}else{
|
||||
talkVoiceObj.value.src = ''
|
||||
@@ -110,6 +104,14 @@ import { onUnload } from '@dcloudio/uni-app';
|
||||
}
|
||||
const initVoice = () => {
|
||||
talkVoiceObj.value = uni.createInnerAudioContext()
|
||||
talkVoiceObj.value.onPause(()=>{
|
||||
})
|
||||
talkVoiceObj.value.onPlay(()=>{
|
||||
if(isTesting.value){
|
||||
talkVoiceObj.value.pause()
|
||||
isTesting.value = false
|
||||
}
|
||||
})
|
||||
talkVoiceObj.value.onEnded(()=>{
|
||||
emit('changePlay', '')
|
||||
talkVoiceObj.value.src = ''
|
||||
@@ -117,14 +119,16 @@ import { onUnload } from '@dcloudio/uni-app';
|
||||
talkVoiceObj.value.onError(()=>{
|
||||
talkVoiceObj.value.src = ''
|
||||
})
|
||||
if(unref(dataInfo).intrctWay === '02'){
|
||||
itemVoice.value = unref(dataInfo).talkingVoice
|
||||
talkVoiceObj.value.src = itemVoice.value
|
||||
setTimeout(()=>{
|
||||
setVoiceTime(0)
|
||||
},100)
|
||||
}
|
||||
itemVoice.value = unref(dataInfo).talkingVoice
|
||||
talkVoiceObj.value.src = itemVoice.value
|
||||
talkVoiceObj.value.play()
|
||||
isTesting.value = true
|
||||
talkVoiceObj.value.pause()
|
||||
setTimeout(()=>{
|
||||
setVoiceTime(0)
|
||||
},100)
|
||||
}
|
||||
const isTesting= ref(false)
|
||||
const setVoiceTime = (sum) => {
|
||||
sum += 1
|
||||
if(Math.ceil(talkVoiceObj.value.duration)>0){
|
||||
@@ -139,6 +143,15 @@ import { onUnload } from '@dcloudio/uni-app';
|
||||
},200)
|
||||
}
|
||||
}
|
||||
watch(() => props.dataInfo, () => {},{deep: true,immediate: true})
|
||||
watch(() => props.dataInfo.talkingVoice,(val)=>{
|
||||
if(val){
|
||||
initVoice()
|
||||
}
|
||||
},{
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
|
||||
@@ -10,10 +10,10 @@
|
||||
<!-- <view class="ai-name-desc">{{ roleInfo?.teacherDesc }}</view> -->
|
||||
</view>
|
||||
</view>
|
||||
<questionVue v-if="talkingType === 'question'" @changePlay="changePlay" :dataInfo="talkingInfo" :isLast="isLast"
|
||||
<questionVue v-if="talkingType === 'question'" @changePlay="changePlay" :dataInfo="talkingInfo" :isLast="isLast" :roleInfo="roleInfo"
|
||||
:activeVoiceId="activeVoiceId"></questionVue>
|
||||
<answerVue v-else-if="talkingType === 'answer'" @changePlay="changePlay" :dataInfo="props.talkingInfo" :isLast="isLast"
|
||||
:isAnswering="props.isAnswering" @handleEvents="handleEvents">
|
||||
<answerVue v-else-if="talkingType === 'answer'" @changePlay="changePlay" :dataInfo="talkingInfo" :isLast="isLast" :roleInfo="roleInfo"
|
||||
:isAnswering="props.isAnswering" @handleEvents="handleEvents" :activeVoiceId="activeVoiceId">
|
||||
</answerVue>
|
||||
<view v-else></view>
|
||||
<view class="talking-tips" v-if="talkingType === 'answer'">
|
||||
@@ -82,7 +82,8 @@
|
||||
})
|
||||
const {
|
||||
talkingType,
|
||||
talkingInfo
|
||||
talkingInfo,
|
||||
roleInfo
|
||||
} = toRefs(props)
|
||||
|
||||
const loadingTips = ref(false)
|
||||
@@ -97,6 +98,7 @@
|
||||
}
|
||||
const getTips = () => {
|
||||
loadingTips.value = true
|
||||
handleEvents('scrollToBottom')
|
||||
partnerChatPrompt({
|
||||
execId: props.execId,
|
||||
traId: props.traId,
|
||||
@@ -104,6 +106,7 @@
|
||||
}).then(res=>{
|
||||
tipsContent.value = res.body
|
||||
loadingTips.value = false
|
||||
handleEvents('scrollToBottom')
|
||||
}).catch(()=> {
|
||||
loadingTips.value =false
|
||||
})
|
||||
@@ -191,7 +194,7 @@
|
||||
.talking-tips-loading{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-height: 40rpx;
|
||||
min-height: 80rpx;
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
<template>
|
||||
<view class="talking-item">
|
||||
<!-- 头像信息 -->
|
||||
<view class="user-info" v-if="talkingType === 'answer'">
|
||||
<view class="avatar-box">
|
||||
<ImagePreview class="img-box" :src="roleInfo?.ossAddr"></ImagePreview>
|
||||
</view>
|
||||
<view class="ai-name">
|
||||
{{ roleInfo?.chaName }}
|
||||
<!-- <view class="ai-name-desc">{{ roleInfo?.teacherDesc }}</view> -->
|
||||
</view>
|
||||
</view>
|
||||
<questionVue v-if="talkingType === 'question'" @changePlay="changePlay" :dataInfo="talkingInfo" :isLast="isLast" :roleInfo="roleInfo"
|
||||
:activeVoiceId="activeVoiceId"></questionVue>
|
||||
<answerVue v-else-if="talkingType === 'answer'" @changePlay="changePlay" :dataInfo="talkingInfo" :isLast="isLast" :roleInfo="roleInfo"
|
||||
:isAnswering="props.isAnswering" @handleEvents="handleEvents" :activeVoiceId="activeVoiceId" :isStudy="false">
|
||||
</answerVue>
|
||||
<view v-else></view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import {
|
||||
toRefs,
|
||||
watch,
|
||||
ref
|
||||
} from 'vue'
|
||||
import questionVue from './question-preview.vue';
|
||||
import answerVue from './answer.vue';
|
||||
import ImagePreview from '@/components/image-preview/image-preview.vue';
|
||||
|
||||
const emit = defineEmits(['handleEvents'])
|
||||
const props = defineProps({
|
||||
talkingType: {
|
||||
default: 'question',
|
||||
type: String
|
||||
},
|
||||
traId: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
execId: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
talkingInfo: {
|
||||
default: () => ({}),
|
||||
type: Object
|
||||
},
|
||||
roleInfo:{
|
||||
default: () => ({}),
|
||||
type: Object
|
||||
},
|
||||
activeVoiceId: {
|
||||
default: '',
|
||||
type: String
|
||||
},
|
||||
isAnswering: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
isLast: {
|
||||
default: false,
|
||||
type: Boolean
|
||||
},
|
||||
})
|
||||
watch(() => props.activeVoiceId, () => {}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
watch(() => props.talkingInfo, () => {}, {
|
||||
deep: true,
|
||||
immediate: true
|
||||
})
|
||||
const {
|
||||
talkingType,
|
||||
talkingInfo,
|
||||
roleInfo
|
||||
} = toRefs(props)
|
||||
|
||||
const loadingTips = ref(false)
|
||||
const tipsContent = ref('')
|
||||
const handleEvents = (type, info = {}) => {
|
||||
emit('handleEvents', {
|
||||
type,
|
||||
data: props.talkingInfo,
|
||||
id: "",
|
||||
info
|
||||
})
|
||||
}
|
||||
const changePlay = (id) => {
|
||||
emit('handleEvents', {
|
||||
type: 'changePlay',
|
||||
data: props.talkingInfo,
|
||||
id,
|
||||
info: {}
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.talking-item {
|
||||
overflow: hidden;
|
||||
margin-top: 0rpx;
|
||||
position: relative;
|
||||
margin-bottom: 24rpx;
|
||||
&+.talking-item {
|
||||
margin-top: 30rpx;
|
||||
}
|
||||
.user-info {
|
||||
overflow: hidden;
|
||||
|
||||
.avatar-box {
|
||||
float: left;
|
||||
width: 76rpx;
|
||||
height: 76rpx;
|
||||
border: 3rpx solid #fff;
|
||||
// background-color: #f1f1f1;
|
||||
overflow: hidden;
|
||||
border-radius: 10rpx;
|
||||
margin-right: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.img-box {
|
||||
width: 76rpx;
|
||||
height: 76rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ai-name {
|
||||
float: left;
|
||||
color: #666;
|
||||
font-size: 25rpx;
|
||||
line-height: 35rpx;
|
||||
width: 140rpx;
|
||||
white-space: pre-wrap;
|
||||
.ai-name-desc {
|
||||
color: #999;
|
||||
margin-left: 16rpx;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.talking-tips{
|
||||
float: left;
|
||||
position: relative;
|
||||
padding-top: 60rpx;
|
||||
width: 100%;
|
||||
&-btn{
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 22rpx;
|
||||
height: 24rpx;
|
||||
line-height: 24rpx;
|
||||
font-size: 24rpx;
|
||||
color: #06f;
|
||||
transform: translateX(-50% );
|
||||
padding-left: 30rpx;
|
||||
background: url(@/static/images/sparring/ask-icon.png) no-repeat;
|
||||
background-position: left center;
|
||||
background-size: 24rpx 24rpx;
|
||||
}
|
||||
&-cont{
|
||||
width: 100%;
|
||||
font-size: 24rpx;
|
||||
line-height: 38rpx;
|
||||
color: #8191AA;
|
||||
}
|
||||
.talking-tips-loading{
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
min-height: 80rpx;
|
||||
padding-top: 20rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -60,7 +60,10 @@
|
||||
@click="previewFile(item)"> {{ item.dataName }}.{{ item.dataSuffix }} </view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="body-bottom">
|
||||
<view class="body-bottom" v-if="isPreview">
|
||||
<view class="body-bottom-btn" @click="toChooseRole('preview')">预览陪练</view>
|
||||
</view>
|
||||
<view class="body-bottom" v-else>
|
||||
<view class="body-bottom-btn" @click="toChooseRole('test')">去练习</view>
|
||||
<view class="body-bottom-btn" @click="toChooseRole('exam')">去考试</view>
|
||||
</view>
|
||||
@@ -117,8 +120,11 @@
|
||||
const toChooseRole = type => {
|
||||
common.navigateTo('/pages/sparring/chooseRole?traId=' + traId.value + '&type=' + type)
|
||||
}
|
||||
const isPreview = ref(false)
|
||||
onLoad(params => {
|
||||
traId.value = params.traId
|
||||
isPreview.value = params.isPreview === '1'
|
||||
console.log(params)
|
||||
})
|
||||
onMounted(() => {
|
||||
getDetailInfo()
|
||||
|
||||
+256
-111
@@ -23,36 +23,49 @@
|
||||
<view class="body-top-collepse-title">
|
||||
<view :class="['collepse-btn', showAllTalk?'show-all':'']" @click="showAllTalk = !showAllTalk"></view>
|
||||
<view class="collepse-tab">
|
||||
|
||||
<uv-tabs :list="tabList" @click="changeTab" :scrollable="false" active-style="font-weight:bold; color:#000;"
|
||||
class="font_pf my_tabs" :inactive-style="{ color: '#333333', fontSize: '28rpx', fontWeight: '500' }"
|
||||
:activeStyle="{ color: '#333333', fontSize: '28rpx', fontWeight: '600' }"></uv-tabs>
|
||||
|
||||
<uv-tabs :list="tabList" @click="changeTab" :scrollable="false" active-style="font-weight:bold; color:#000;"
|
||||
class="font_pf my_tabs" :inactive-style="{ color: '#333333', fontSize: '28rpx', fontWeight: '500' }"
|
||||
:activeStyle="{ color: '#333333', fontSize: '28rpx', fontWeight: '600' }"></uv-tabs>
|
||||
</view>
|
||||
<view class="talk-item"></view>
|
||||
<!-- 头像信息 -->
|
||||
<view class="user-info" v-if="showTalkList.length>0 && showTalkList[0].type==='question'">
|
||||
<!-- <view class="avatar-box">
|
||||
<ImagePreview class="img-box" :src="roleInfo?.ossAddr"></ImagePreview>
|
||||
</view>
|
||||
<view class="ai-name">
|
||||
{{ roleInfo?.chaName }}
|
||||
</view> -->
|
||||
</view>
|
||||
<TalkingPreviewItem class="talk-item" v-for="item in showTalkList" :talkingType="item.type"
|
||||
:talkingInfo="item" :roleInfo="roleInfo" :traId="traId" :execId="execId" :isAnswering="false"
|
||||
:activeVoiceId="activeVoiceTalkingItemId" @handleEvents="talkItemEvents"></TalkingPreviewItem>
|
||||
</view>
|
||||
<view :class="['body-top-collepse-cont', showAllTalk?'show-all':'']">
|
||||
<view class="talk-item"></view>
|
||||
<TalkingPreviewItem class="talk-item" v-for="item in hideTalkList" :talkingType="item.type"
|
||||
:talkingInfo="item" :roleInfo="roleInfo" :traId="traId" :execId="execId" :isAnswering="false"
|
||||
:activeVoiceId="activeVoiceTalkingItemId" @handleEvents="talkItemEvents"></TalkingPreviewItem>
|
||||
</view>
|
||||
</view>
|
||||
<view class="body-bottom">
|
||||
<view class="body-bottom-title">维度评价总览</view>
|
||||
<view class="body-bottom-item" v-for="item in evalDimens">
|
||||
<view class="item-title">{{item.gradeDimensName}}: {{ item.traGrade }}
|
||||
<!-- <image class="item-title-icon" src="@/static/images/sparring/info-icon.png"></image> -->
|
||||
<!-- <image class="item-title-icon" src="@/static/images/sparring/info-icon.png"></image> -->
|
||||
</view>
|
||||
<view class="item-cont">{{item.settDesc}}</view>
|
||||
</view>
|
||||
<view :class="['body-bottom-static', 'pass','fialed']">
|
||||
<view :class="['body-bottom-static', isPass?'pass':'fialed']">
|
||||
<view class="static-tip">得分</view>
|
||||
<view class="static-score">{{ totalScore }}</view>
|
||||
<view class="static-desc">{{1?'革命尚未成功,同志仍需努力!':'恭喜阁下过关斩将,顺利通关!'}}</view>
|
||||
|
||||
|
||||
<view class="static-charts">
|
||||
<ChartThree v-if="length === 3" fromType="tra" :dataList="evalDimens" />
|
||||
<ChartThree v-if="length === 3" fromType="tra" :dataList="evalDimens" />
|
||||
<ChartFour v-if="length === 4" fromType="tra" :dataList="evalDimens" />
|
||||
<ChartFive v-if="length === 5" fromType="tra" :dataList="evalDimens" />
|
||||
<ChartSix v-if="length === 6" fromType="tra" :dataList="evalDimens" />
|
||||
<ChartSeven v-if="length === 7" fromType="tra" :dataList="evalDimens"/>
|
||||
<ChartSix v-if="length === 6" fromType="tra" :dataList="evalDimens" />
|
||||
<ChartSeven v-if="length === 7" fromType="tra" :dataList="evalDimens" />
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
@@ -72,13 +85,18 @@
|
||||
onLoad,
|
||||
onUnload
|
||||
} from '@dcloudio/uni-app';
|
||||
import common from '@/common/common'
|
||||
import Feedback from './components/feedback.vue';
|
||||
import ChartFour from '@/pages/course/components/chartFour.vue';
|
||||
import ChartThree from '@/pages/course/components/chartThree.vue';
|
||||
import ChartFive from '@/pages/course/components/chartFive.vue';
|
||||
import ChartSix from '@/pages/course/components/chartSix.vue';
|
||||
import ChartSeven from '@/pages/course/components/chartSeven.vue';
|
||||
import { partnerChatReport } from "@/api/sparring.js"
|
||||
import TalkingPreviewItem from './components/talking-preview-item.vue'
|
||||
import ImagePreview from '@/components/image-preview/image-preview.vue';
|
||||
import {
|
||||
partnerChatReport
|
||||
} from "@/api/sparring.js"
|
||||
const traId = ref('')
|
||||
const execId = ref('')
|
||||
const feedbackRef = ref()
|
||||
@@ -87,8 +105,8 @@
|
||||
const length = ref(6)
|
||||
const traInfo = ref({})
|
||||
const traTm = ref('')
|
||||
const tabList = ref([
|
||||
{
|
||||
const activeVoiceTalkingItemId = ref('')
|
||||
const tabList = ref([{
|
||||
name: '全部',
|
||||
value: ''
|
||||
},
|
||||
@@ -101,60 +119,135 @@
|
||||
value: '02'
|
||||
}
|
||||
])
|
||||
|
||||
// const evalDimens = [{
|
||||
// gradeDimensName: '维度1',
|
||||
// anlyGrade: 60
|
||||
// }, {
|
||||
// gradeDimensName: '维度2',
|
||||
// anlyGrade: 80
|
||||
// }, {
|
||||
// gradeDimensName: '维度3',
|
||||
// anlyGrade: 30
|
||||
// }]
|
||||
const showTalkList = computed(() => {
|
||||
return allTalkList.value.slice(0, 2)
|
||||
})
|
||||
const hideTalkList = computed(() => {
|
||||
return allTalkList.value.slice(2)
|
||||
})
|
||||
const allTalkList = computed(() => {
|
||||
if (!filterType.value) {
|
||||
return traPartnerExecuteAnswerRecords.value
|
||||
} else if (filterType.value === '01') {
|
||||
return traPartnerExecuteAnswerRecords.value.filter((t, index) => {
|
||||
return t.talkingText.indexOf('#15b859') >= 0 || traPartnerExecuteAnswerRecords.value[index + 1]
|
||||
?.talkingText.indexOf('#15b859') >= 0
|
||||
})
|
||||
} else if (filterType.value === '02') {
|
||||
return traPartnerExecuteAnswerRecords.value.filter((t, index) => {
|
||||
return t.talkingText.indexOf('#d70c18') >= 0 || traPartnerExecuteAnswerRecords.value[index + 1]
|
||||
?.talkingText.indexOf('#d70c18') >= 0
|
||||
})
|
||||
}
|
||||
})
|
||||
// 关闭播放状态
|
||||
const changePlayItemId = (id = '') => {
|
||||
activeVoiceTalkingItemId.value = id
|
||||
}
|
||||
|
||||
const talkItemEvents = ({
|
||||
type,
|
||||
data,
|
||||
id,
|
||||
info = {}
|
||||
}) => {
|
||||
switch (type) {
|
||||
case 'changePlay':
|
||||
activeVoiceTalkingItemId.value = id;
|
||||
break;
|
||||
case 'scrollToBottom':
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
};
|
||||
const feedbackNow = () => {
|
||||
feedbackRef.value.open({
|
||||
traId: traId.value
|
||||
});
|
||||
}
|
||||
const filterType = ref('')
|
||||
const changeTab = (info) => {
|
||||
console.log(info)
|
||||
filterType.value = info.value
|
||||
}
|
||||
const evalDimens = ref([])
|
||||
const traPartnerExecuteAnswerRecords = ref([])
|
||||
const totalScore = ref(0)
|
||||
const showLoading = ref(true)
|
||||
const isPass = ref(false)
|
||||
const negativeList = ref([]) // 劣质话术
|
||||
const positiveList = ref([]) // 劣质话术
|
||||
const getData = () => {
|
||||
// execId=db2f275b-c04a-4572-83dd-74a95ff49d67&traId=PARTNERINFO02501812207220251103160409000007402
|
||||
|
||||
partnerChatReport({
|
||||
// traId: traId.value,
|
||||
// execId: execId.value
|
||||
traId: 'PARTNERINFO02501812207220251103160409000007402',
|
||||
execId: 'db2f275b-c04a-4572-83dd-74a95ff49d67'
|
||||
}).then(res=>{
|
||||
if(res.body.code === 200){
|
||||
traId: traId.value,
|
||||
execId: execId.value
|
||||
// traId: 'PARTNERINFO02501812207220251103160409000007402',
|
||||
// execId: 'db2f275b-c04a-4572-83dd-74a95ff49d67'
|
||||
}).then(res => {
|
||||
if (res.body.code === 200) {
|
||||
setTimeout(getData, 2000)
|
||||
}else{
|
||||
|
||||
traInfo.value = res.body.traPartnerInfo
|
||||
roleInfo.value = res.body.traPartnerCharacterInfoVo
|
||||
traTm.value = res.body.traTm
|
||||
evalDimens.value = res.body.traPartnerSettlement.map(t=>{
|
||||
} else {
|
||||
traInfo.value = res.body.traPartnerInfo || {}
|
||||
roleInfo.value = res.body.traPartnerCharacterInfoVo || {}
|
||||
traTm.value = res.body.traTm || '0'
|
||||
isPass.value = res.body.traPartnerExecuteHisVo?.traResult === 'P'
|
||||
totalScore.value = res.body.traPartnerExecuteHisVo?.traGrade || '0'
|
||||
positiveList.value = res.body.positiveList || []
|
||||
negativeList.value = res.body.negativeList || []
|
||||
let _evalDimens = res.body.traPartnerSettlement || []
|
||||
evalDimens.value = _evalDimens.map(t => {
|
||||
return {
|
||||
...t,
|
||||
anlyGrade: t.traGrade,
|
||||
gradeDimensName: t.gradeDimensName||'维度'
|
||||
|
||||
gradeDimensName: t.gradeDimensName || '维度'
|
||||
|
||||
}
|
||||
})
|
||||
traPartnerExecuteAnswerRecords.value = res.body.traPartnerExecuteAnswerRecords
|
||||
setTimeout(()=>{
|
||||
let _talkingList = res.body.traPartnerExecuteAnswerRecords || []
|
||||
traPartnerExecuteAnswerRecords.value = _talkingList.map(t => {
|
||||
let _content = setTextColor(t.content)
|
||||
if (t.openStyle === '02') {
|
||||
return {
|
||||
...t,
|
||||
type: 'question',
|
||||
talkingText: _content,
|
||||
isLoading: false,
|
||||
execLogId: t.execLogId || '',
|
||||
intrctWay: '02',
|
||||
id: t.execLogId,
|
||||
...t,
|
||||
}
|
||||
} else if (t.openStyle === '01') {
|
||||
return {
|
||||
...t,
|
||||
type: 'answer',
|
||||
talkingText: _content,
|
||||
isLoading: false,
|
||||
execLogId: t.execLogId || '',
|
||||
id: t.execLogId,
|
||||
}
|
||||
} else {
|
||||
return t
|
||||
}
|
||||
})
|
||||
setTimeout(() => {
|
||||
showLoading.value = false
|
||||
},800)
|
||||
}, 800)
|
||||
}
|
||||
})
|
||||
}
|
||||
const setTextColor = (str) => {
|
||||
let _str = str
|
||||
positiveList.value.forEach(t => {
|
||||
_str = _str.split(t).join(`<text style="color: #15b859;text-decoration:underline;">${t}</text>`)
|
||||
})
|
||||
negativeList.value.forEach(t => {
|
||||
_str = _str.split(t).join(`<text style="color: #d70c18;text-decoration:underline;">${t}</text>`)
|
||||
})
|
||||
console.log(_str)
|
||||
return _str
|
||||
}
|
||||
onLoad((params) => {
|
||||
traId.value = params.traId;
|
||||
execId.value = params.execId
|
||||
@@ -179,44 +272,46 @@
|
||||
padding: calc(var(--status-bar-height) + 90rpx) 0 0rpx;
|
||||
// background-image: url(@/static/images/sparring/bg.png);
|
||||
// background-size: cover;
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
.sparring-result-loading {
|
||||
position: fixed;
|
||||
z-index: 20;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-image: url(@/static/images/sparring/bg.png);
|
||||
|
||||
.loading-img {
|
||||
|
||||
.sparring-result-loading {
|
||||
position: fixed;
|
||||
z-index: 20;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
background-image: url(@/static/images/sparring/bg.png);
|
||||
|
||||
.loading-img {
|
||||
position: absolute;
|
||||
width: 380rpx;
|
||||
height: 540rpx;
|
||||
top: 40%;
|
||||
transform: translateY(-50%) translateX(-50%);
|
||||
left: 50%;
|
||||
padding-top: 540rpx;
|
||||
line-height: 50rpx;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
background-image: url(@/static/images/sparring/waiting.png);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 380rpx 590rpx;
|
||||
|
||||
&::after {
|
||||
content: '报告生成中,请耐心等待 ~ ';
|
||||
display: block;
|
||||
position: absolute;
|
||||
width: 380rpx;
|
||||
height: 540rpx;
|
||||
top: 40%;
|
||||
transform: translateY(-50%) translateX(-50%);
|
||||
left: 50%;
|
||||
padding-top: 540rpx;
|
||||
left: 0%;
|
||||
width: 100%;
|
||||
bottom: -50rpx;
|
||||
height: 50rpx;
|
||||
line-height: 50rpx;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
background-image: url(@/static/images/sparring/waiting.png);
|
||||
background-repeat: no-repeat;
|
||||
background-size: 380rpx 590rpx;
|
||||
&::after{
|
||||
content: '报告生成中,请耐心等待 ~ ';
|
||||
display: block;
|
||||
position: absolute;
|
||||
left: 0%;
|
||||
width: 100%;
|
||||
bottom: -50rpx;
|
||||
height: 50rpx;
|
||||
line-height: 50rpx;
|
||||
text-align: center;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
font-size: 28rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.top-box {
|
||||
height: 90rpx;
|
||||
@@ -325,13 +420,56 @@
|
||||
}
|
||||
|
||||
.body-top-collepse-title {
|
||||
padding-top: 10rpx;
|
||||
position: relative;
|
||||
.collepse-tab{
|
||||
position: absolute;
|
||||
width: 360rpx;
|
||||
right: 120rpx;
|
||||
top: 4rpx;
|
||||
min-height: 100rpx;
|
||||
.user-info {
|
||||
overflow: hidden;
|
||||
height: 92rpx;
|
||||
|
||||
.avatar-box {
|
||||
float: left;
|
||||
width: 76rpx;
|
||||
height: 76rpx;
|
||||
border: 3rpx solid #fff;
|
||||
// background-color: #f1f1f1;
|
||||
overflow: hidden;
|
||||
border-radius: 10rpx;
|
||||
margin-right: 16rpx;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
.img-box {
|
||||
width: 76rpx;
|
||||
height: 76rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.ai-name {
|
||||
float: left;
|
||||
color: #666;
|
||||
font-size: 25rpx;
|
||||
line-height: 35rpx;
|
||||
width: 140rpx;
|
||||
white-space: pre-wrap;
|
||||
|
||||
.ai-name-desc {
|
||||
color: #999;
|
||||
margin-left: 16rpx;
|
||||
display: inline-block;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.collepse-tab {
|
||||
position: absolute;
|
||||
width: 300rpx;
|
||||
right: 120rpx;
|
||||
top: 0rpx;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.collepse-btn {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
@@ -340,6 +478,7 @@
|
||||
height: 46rpx;
|
||||
border: 2rpx solid #d4dce8;
|
||||
border-radius: 23rpx;
|
||||
z-index: 10;
|
||||
|
||||
&.show-all {
|
||||
&::after {
|
||||
@@ -399,25 +538,25 @@
|
||||
// top: 2rpx;
|
||||
// left: 10rpx;
|
||||
// }
|
||||
|
||||
line-height: 40rpx;
|
||||
position: relative;
|
||||
padding: 0rpx 46rpx 0rpx 26rpx;
|
||||
line-height: 42rpx;
|
||||
font-size: 29rpx;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
background-color: #000;
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
left: 0rpx;
|
||||
top: 14rpx;
|
||||
}
|
||||
|
||||
line-height: 40rpx;
|
||||
position: relative;
|
||||
padding: 0rpx 46rpx 0rpx 26rpx;
|
||||
line-height: 42rpx;
|
||||
font-size: 29rpx;
|
||||
margin-bottom: 16rpx;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
display: block;
|
||||
background-color: #000;
|
||||
width: 12rpx;
|
||||
height: 12rpx;
|
||||
border-radius: 50%;
|
||||
position: absolute;
|
||||
left: 0rpx;
|
||||
top: 14rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.item-cont {
|
||||
@@ -425,32 +564,37 @@
|
||||
line-height: 50rpx;
|
||||
}
|
||||
}
|
||||
&-static{
|
||||
|
||||
&-static {
|
||||
border-top: 2px solid #eee;
|
||||
padding-top: 38rpx;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 270rpx 190rpx;
|
||||
background-position: right 40rpx;
|
||||
|
||||
&.fialed{
|
||||
|
||||
&.fialed {
|
||||
background-image: url(@/static/images/sparring/def-res.png);
|
||||
}
|
||||
&.pass{
|
||||
|
||||
&.pass {
|
||||
background-image: url(@/static/images/sparring/pass-res.png);
|
||||
}
|
||||
.static-tip{
|
||||
|
||||
.static-tip {
|
||||
line-height: 44rpx;
|
||||
font-size: 32rpx;
|
||||
margin-bottom: 24rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
.static-score{
|
||||
|
||||
.static-score {
|
||||
font-size: 66rpx;
|
||||
color: #06f;
|
||||
font-family: Arial, Helvetica, sans-serif;
|
||||
marign-bottom: 20rpx;
|
||||
}
|
||||
.static-desc{
|
||||
|
||||
.static-desc {
|
||||
color: #999;
|
||||
font-size: 25rpx;
|
||||
margin-bottom: 32rpx;
|
||||
@@ -460,14 +604,15 @@
|
||||
}
|
||||
|
||||
.talk-item {
|
||||
height: 200rpx;
|
||||
// height: 200rpx;
|
||||
overflow: hidden;
|
||||
background-color: #0f0;
|
||||
}
|
||||
|
||||
.my_tabs {
|
||||
|
||||
// 解决这个圆角的图片,开穿透
|
||||
:deep(.uv-tabs__wrapper__nav__line) {
|
||||
background: linear-gradient(90deg, #06f 0%, rgb(0,159,255) 50%, rgb(255,255,255) 100%) !important;
|
||||
background: linear-gradient(90deg, #06f 0%, rgb(0, 159, 255) 50%, rgb(255, 255, 255) 100%) !important;
|
||||
}
|
||||
|
||||
:deep(.uv-tabs__wrapper__nav__item) {
|
||||
|
||||
@@ -235,8 +235,8 @@
|
||||
case 'changePlay':
|
||||
activeVoiceTalkingItemId.value = id;
|
||||
break;
|
||||
case 'askTip':
|
||||
// 获取问答提示
|
||||
case 'scrollToBottom':
|
||||
scrollToBottomNow();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -249,7 +249,8 @@
|
||||
const answerText = ref('')
|
||||
const touchBtnCallBack = ref(null)
|
||||
const initsocketTask = () => {
|
||||
socketStore.creatSocket('/trapractice/partnersocket/open?summary=');
|
||||
let _src = talkingType.value === 'preview'?'/trapractice/partnersocket/open':'/trapractice/partnersocket/open'
|
||||
socketStore.creatSocket(_src + '?summary=');
|
||||
// unref(SocketObj).on('open', (res)=>{
|
||||
// if(!answerIsOver.value){
|
||||
// sendMessage({
|
||||
@@ -346,26 +347,31 @@
|
||||
console.log('开始回答!');
|
||||
answerIsOver.value = false;
|
||||
answerText.value = '';
|
||||
talkingList.value.pop(); detailInfo.chaName
|
||||
talkingList.value.pop();
|
||||
talkingList.value.push({
|
||||
type: 'answer',
|
||||
talkingText: answerText.value,
|
||||
isLoading: false,
|
||||
execLogId: body.execLogId||'',
|
||||
id: _id,
|
||||
...body,
|
||||
askData: {
|
||||
...askData.value
|
||||
},
|
||||
talkingFiles: [],
|
||||
talkingCrsList: []
|
||||
});
|
||||
} else if (body.event === 'token') {
|
||||
answerIsOver.value = false;
|
||||
answerText.value += body.data;
|
||||
console.log(answerText.value)
|
||||
talkingList.value[talkingList.value.length - 1].talkingText = answerText.value;
|
||||
} else if (body.event === 'end') {
|
||||
console.log('完成回答!');
|
||||
answerIsOver.value = true;
|
||||
} else if (body.event === 'recordAi') {
|
||||
console.log('推送声音id!');
|
||||
talkingList.value[talkingList.value.length - 1].execLogId = body.execLogId
|
||||
}
|
||||
scrollToBottomNow();
|
||||
return;
|
||||
}
|
||||
if (commond === 'ASK-RE-ANSWER') {
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<view class="item">
|
||||
<view class="details_div">
|
||||
<view class="img_div">
|
||||
<image-preview class="img" :src="item.ossAddr"></image-preview>
|
||||
</view>
|
||||
<view class="right_div">
|
||||
<view class="title ellipsis-text">
|
||||
{{ item.traName }}
|
||||
</view>
|
||||
<view class="prompt_div">
|
||||
<view class="prompt_div_left">
|
||||
<view class="top">{{ item.maxGrade || '0' }}</view>
|
||||
<view class="bottom">历史最高分</view>
|
||||
</view>
|
||||
<view class="prompt_div_line"></view>
|
||||
<view class="prompt_div_right" @click="show_list_click(item.examCnt)">
|
||||
<view class="click_text">
|
||||
{{ item.executeHisNum || 0 }}
|
||||
<uv-icon
|
||||
class="click_text_icon"
|
||||
:class="{ click_text_icon_action: show_list_state }"
|
||||
name="arrow-right"
|
||||
color="#0066FF"
|
||||
size="12"
|
||||
:bold="true"
|
||||
></uv-icon>
|
||||
</view>
|
||||
<view class="bottom">考试记录</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list_div" :style="{ height: list_div_height }">
|
||||
<view class="list_item" v-for="item_list in data_list" @click="click_list_item(item_list)">
|
||||
<view class="list_content">
|
||||
<view class="list_small_item">
|
||||
<image src="@/static/images/courseRecord/time.png" class="icon_img"></image>
|
||||
<view class="prompt">考试时间:{{ item_list.startPracticeTm }}</view>
|
||||
</view>
|
||||
<view class="list_small_item">
|
||||
<image src="@/static/images/courseRecord/duration.png" class="icon_img"></image>
|
||||
<view class="prompt">考试得分:{{ item_list.traGrade }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="fl1"></view>
|
||||
<uv-icon name="arrow-right" color="#979797" size="17" :bold="true"></uv-icon>
|
||||
</view>
|
||||
<uv-load-more
|
||||
v-if="status !== 'nomore'"
|
||||
@click="getData()"
|
||||
:status="status"
|
||||
loadmore-text="点击加载更多"
|
||||
:height="30"
|
||||
></uv-load-more>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, reactive } from 'vue';
|
||||
import common from '@/common/common';
|
||||
import { queryCrsExamHisByExamId } from '@/api/courseRecord.js';
|
||||
import { useItemList } from '../useItemList.js';
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
});
|
||||
const item = computed(() => props.item);
|
||||
const fetchFunction = (params) => queryCrsExamHisByExamId({ traId: item.value.traId, ...params });
|
||||
const { status, data_list, show_list_state, list_div_height, show_list_click, getData, clear } =
|
||||
useItemList(fetchFunction);
|
||||
// 跳转到详情
|
||||
const click_list_item = (list_item) => {
|
||||
common.navigateTo('/pages/sparring/result?traId=' + list_item.traId + '&execId=' + list_item.execId)
|
||||
};
|
||||
defineExpose({ clear });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.icon_img {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
}
|
||||
|
||||
.list_div {
|
||||
overflow: hidden; /* 确保内容不会溢出容器 */
|
||||
transition: height 0.2s ease-out; /* 高度动画 */
|
||||
.list_item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 18rpx 32rpx 18rpx 28rpx;
|
||||
background-color: #f9fbff;
|
||||
border-radius: 8rpx;
|
||||
margin: 20rpx 0 0 0;
|
||||
.list_content {
|
||||
.list_small_item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 400;
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
line-height: 40rpx;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
|
||||
.prompt {
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.item {
|
||||
margin: 17rpx 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx 22rpx;
|
||||
.details_div {
|
||||
display: flex;
|
||||
|
||||
.img_div {
|
||||
position: relative;
|
||||
width: 224rpx;
|
||||
height: 168rpx;
|
||||
overflow: hidden;
|
||||
border-radius: 22rpx;
|
||||
|
||||
.img {
|
||||
width: 224rpx;
|
||||
height: 168rpx;
|
||||
}
|
||||
|
||||
.subscript {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 92rpx;
|
||||
height: 46rpx;
|
||||
border-radius: 0px 0px 0px 22rpx;
|
||||
font-family: PingFangSC;
|
||||
font-weight: 400;
|
||||
font-size: 22rpx;
|
||||
line-height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.right_div {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
margin-left: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
font-family: PingFangSC;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 28rpx; /* 字体大小30rpx */
|
||||
overflow: hidden; /* 超出部分隐藏 */
|
||||
margin-bottom: 8rpx;
|
||||
min-height: 40rpx;
|
||||
}
|
||||
|
||||
.prompt_div {
|
||||
font-family: PingFangSC;
|
||||
font-weight: 400;
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
line-height: 34rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
height: 116rpx;
|
||||
background-color: #f9fbff;
|
||||
border-radius: 8rpx;
|
||||
justify-content: space-around;
|
||||
.prompt_div_line {
|
||||
width: 4rpx;
|
||||
height: 30rpx;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
RGBA(242, 244, 248, 1) 0%,
|
||||
RGBA(230, 232, 238, 1) 30%,
|
||||
RGBA(230, 232, 238, 1) 70%,
|
||||
RGBA(242, 244, 248, 1) 100%
|
||||
);
|
||||
}
|
||||
.prompt_div_left,
|
||||
.prompt_div_right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.top,
|
||||
.click_text {
|
||||
font-family: Arial;
|
||||
font-weight: 700;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
line-height: 34rpx;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
.bottom {
|
||||
}
|
||||
.click_text {
|
||||
position: relative;
|
||||
.click_text_icon {
|
||||
position: absolute;
|
||||
margin-left: 18rpx;
|
||||
top: calc(50% - 12rpx);
|
||||
left: calc(100% - 14rpx);
|
||||
/* 添加过渡动画,包含延迟效果 */
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
.click_text_icon_action {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.people_progress_div {
|
||||
display: flex;
|
||||
font-family: PingFangSC;
|
||||
font-weight: 400;
|
||||
font-size: 23rpx;
|
||||
color: #666;
|
||||
margin-bottom: 23rpx;
|
||||
|
||||
line-height: 23rpx;
|
||||
flex-wrap: nowrap;
|
||||
height: 23rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<z-paging
|
||||
ref="pagingRef"
|
||||
v-model="data_list"
|
||||
@query="queryList"
|
||||
class="scroll_div"
|
||||
:auto="false"
|
||||
empty-view-text="暂无记录"
|
||||
@refresherTouchend="refresherTouchend"
|
||||
>
|
||||
<view class="scroll-Y">
|
||||
<itemVue ref="itemVueRef" :item="item" v-for="item in data_list"></itemVue>
|
||||
</view>
|
||||
</z-paging>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import itemVue from './examRecordItem.vue';
|
||||
import { ref, reactive, onMounted, watch } from 'vue';
|
||||
import { queryCrsExamRecordPaging } from '@/api/traRecord.js';
|
||||
import useZpaging from '@/composables/useZpaging';
|
||||
const itemVueRef = ref([]);
|
||||
const { pagingRef, queryList, data_list, reload, total } = useZpaging({
|
||||
fetchFunction: queryCrsExamRecordPaging
|
||||
});
|
||||
const clearAll = () => {
|
||||
itemVueRef.value.forEach((child) => {
|
||||
if (child && typeof child.clear === 'function') {
|
||||
child.clear();
|
||||
}
|
||||
});
|
||||
};
|
||||
const refresherTouchend = () => {
|
||||
clearAll();
|
||||
};
|
||||
const props = defineProps<{
|
||||
tabIndex: number;
|
||||
currentIndex: number;
|
||||
}>();
|
||||
onMounted(() => {
|
||||
watch(
|
||||
() => props.currentIndex,
|
||||
(v) => v === props.tabIndex && total.value === -1 && reload(),
|
||||
{ immediate: true }
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scroll-Y {
|
||||
background-color: #f1f5fa;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,250 @@
|
||||
<template>
|
||||
<view class="item">
|
||||
<view class="details_div">
|
||||
<view class="img_div">
|
||||
<image-preview class="img" :src="item.ossAddr"></image-preview>
|
||||
</view>
|
||||
<view class="right_div">
|
||||
<view class="title ellipsis-text">
|
||||
{{ item.traName }}
|
||||
</view>
|
||||
<view class="prompt_div">
|
||||
<view class="prompt_div_left">
|
||||
<view class="top">{{ item.maxGrade || '0' }}</view>
|
||||
<view class="bottom">历史最高分</view>
|
||||
</view>
|
||||
<view class="prompt_div_line"></view>
|
||||
<view class="prompt_div_right" @click="show_list_click(item.exrCnt)">
|
||||
<view class="click_text">
|
||||
{{ item.executeHisNum || '0' }}
|
||||
<uv-icon
|
||||
class="click_text_icon"
|
||||
:class="{ click_text_icon_action: show_list_state }"
|
||||
name="arrow-right"
|
||||
color="#0066FF"
|
||||
size="12"
|
||||
:bold="true"
|
||||
></uv-icon>
|
||||
</view>
|
||||
<view class="bottom">练习记录</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="list_div" :style="{ height: list_div_height }">
|
||||
<view class="list_item" v-for="item_list in data_list" @click="click_list_item(item_list)">
|
||||
<view class="list_content">
|
||||
<view class="list_small_item">
|
||||
<image src="@/static/images/courseRecord/time.png" class="icon_img"></image>
|
||||
<view class="prompt">练习时间:{{ item_list.startPracticeTm }}</view>
|
||||
</view>
|
||||
<view class="list_small_item">
|
||||
<image src="@/static/images/courseRecord/duration.png" class="icon_img"></image>
|
||||
<view class="prompt">练习得分:{{item_list.traGrade }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="fl1"></view>
|
||||
<uv-icon name="arrow-right" color="#979797" size="17" :bold="true"></uv-icon>
|
||||
</view>
|
||||
<uv-load-more
|
||||
v-if="status !== 'nomore'"
|
||||
@click="getData()"
|
||||
:status="status"
|
||||
loadmore-text="点击加载更多"
|
||||
:height="30"
|
||||
></uv-load-more>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref, reactive } from 'vue';
|
||||
import common from '@/common/common';
|
||||
import { queryPracticeHis } from '@/api/traRecord.js';
|
||||
import { useItemList, formatDuration } from '../useItemList.js';
|
||||
|
||||
const props = defineProps({
|
||||
item: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
}
|
||||
});
|
||||
const item = computed(() => props.item);
|
||||
const fetchFunction = (params) => queryPracticeHis({ traId: item.value.traId, ...params });
|
||||
const { status, data_list, show_list_state, list_div_height, show_list_click, getData, clear } =
|
||||
useItemList(fetchFunction);
|
||||
|
||||
// 跳转到详情
|
||||
const click_list_item = (list_item) => {
|
||||
common.navigateTo('/pages/sparring/result?traId=' + list_item.traId + '&execId=' + list_item.execId)
|
||||
};
|
||||
defineExpose({ clear });
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.icon_img {
|
||||
width: 24rpx;
|
||||
height: 24rpx;
|
||||
}
|
||||
|
||||
.list_div {
|
||||
overflow: hidden; /* 确保内容不会溢出容器 */
|
||||
transition: height 0.2s ease-out; /* 高度动画 */
|
||||
.list_item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 18rpx 32rpx 18rpx 28rpx;
|
||||
background-color: #f9fbff;
|
||||
border-radius: 8rpx;
|
||||
margin: 20rpx 0 0 0;
|
||||
.list_content {
|
||||
.list_small_item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-weight: 400;
|
||||
font-size: 24rpx;
|
||||
color: #666666;
|
||||
line-height: 40rpx;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
|
||||
.prompt {
|
||||
margin-left: 16rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.item {
|
||||
margin: 17rpx 20rpx;
|
||||
background-color: #fff;
|
||||
border-radius: 16rpx;
|
||||
padding: 32rpx 22rpx;
|
||||
.details_div {
|
||||
display: flex;
|
||||
|
||||
.img_div {
|
||||
position: relative;
|
||||
width: 224rpx;
|
||||
height: 168rpx;
|
||||
overflow: hidden;
|
||||
border-radius: 22rpx;
|
||||
|
||||
.img {
|
||||
width: 224rpx;
|
||||
height: 168rpx;
|
||||
}
|
||||
|
||||
.subscript {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
width: 92rpx;
|
||||
height: 46rpx;
|
||||
border-radius: 0px 0px 0px 22rpx;
|
||||
font-family: PingFangSC;
|
||||
font-weight: 400;
|
||||
font-size: 22rpx;
|
||||
line-height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.right_div {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
margin-left: 24rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.title {
|
||||
font-family: PingFangSC;
|
||||
color: #333333;
|
||||
text-align: left;
|
||||
font-style: normal;
|
||||
font-weight: 600;
|
||||
font-size: 28rpx; /* 字体大小30rpx */
|
||||
overflow: hidden; /* 超出部分隐藏 */
|
||||
margin-bottom: 8rpx;
|
||||
min-height: 40rpx;
|
||||
}
|
||||
|
||||
.prompt_div {
|
||||
font-family: PingFangSC;
|
||||
font-weight: 400;
|
||||
font-size: 22rpx;
|
||||
color: #666;
|
||||
line-height: 34rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
overflow: hidden;
|
||||
height: 116rpx;
|
||||
background-color: #f9fbff;
|
||||
border-radius: 8rpx;
|
||||
justify-content: space-around;
|
||||
.prompt_div_line {
|
||||
width: 4rpx;
|
||||
height: 30rpx;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
RGBA(242, 244, 248, 1) 0%,
|
||||
RGBA(230, 232, 238, 1) 30%,
|
||||
RGBA(230, 232, 238, 1) 70%,
|
||||
RGBA(242, 244, 248, 1) 100%
|
||||
);
|
||||
}
|
||||
.prompt_div_left,
|
||||
.prompt_div_right {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
.top,
|
||||
.click_text {
|
||||
font-family: Arial;
|
||||
font-weight: 700;
|
||||
font-size: 30rpx;
|
||||
color: #333333;
|
||||
line-height: 34rpx;
|
||||
text-align: center;
|
||||
font-style: normal;
|
||||
}
|
||||
.bottom {
|
||||
}
|
||||
.click_text {
|
||||
position: relative;
|
||||
.click_text_icon {
|
||||
position: absolute;
|
||||
margin-left: 18rpx;
|
||||
top: calc(50% - 12rpx);
|
||||
left: calc(100% - 14rpx);
|
||||
/* 添加过渡动画,包含延迟效果 */
|
||||
transition: transform 0.2s ease;
|
||||
}
|
||||
.click_text_icon_action {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.people_progress_div {
|
||||
display: flex;
|
||||
font-family: PingFangSC;
|
||||
font-weight: 400;
|
||||
font-size: 23rpx;
|
||||
color: #666;
|
||||
margin-bottom: 23rpx;
|
||||
|
||||
line-height: 23rpx;
|
||||
flex-wrap: nowrap;
|
||||
height: 23rpx;
|
||||
overflow: hidden;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<z-paging
|
||||
ref="pagingRef"
|
||||
v-model="data_list"
|
||||
@query="queryList"
|
||||
class="scroll_div"
|
||||
:auto="false"
|
||||
empty-view-text="暂无记录"
|
||||
@refresherTouchend="refresherTouchend"
|
||||
>
|
||||
<view class="scroll-Y">
|
||||
<itemVue ref="itemVueRef" :item="item" v-for="item in data_list"></itemVue>
|
||||
</view>
|
||||
</z-paging>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import itemVue from './practiceRecordItem.vue';
|
||||
import { ref, reactive, onMounted, watch } from 'vue';
|
||||
import useZpaging from '@/composables/useZpaging';
|
||||
import { queryPracticeRecordPaging } from '@/api/traRecord.js';
|
||||
const itemVueRef = ref([]);
|
||||
const { pagingRef, queryList, data_list, reload, total } = useZpaging({
|
||||
fetchFunction: queryPracticeRecordPaging
|
||||
});
|
||||
const clearAll = () => {
|
||||
itemVueRef.value.forEach((child) => {
|
||||
if (child && typeof child.clear === 'function') {
|
||||
child.clear();
|
||||
}
|
||||
});
|
||||
};
|
||||
const refresherTouchend = () => {
|
||||
clearAll();
|
||||
};
|
||||
const props = defineProps<{
|
||||
tabIndex: number;
|
||||
currentIndex: number;
|
||||
}>();
|
||||
onMounted(() => {
|
||||
watch(
|
||||
() => props.currentIndex,
|
||||
(v) => v === props.tabIndex && total.value === -1 && reload(),
|
||||
{ immediate: true }
|
||||
);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.scroll-Y {
|
||||
background-color: #f1f5fa;
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,161 @@
|
||||
<template>
|
||||
<z-paging-swiper>
|
||||
<template #top>
|
||||
<view class="detail-main">
|
||||
<nav-bar :is_seat="false"></nav-bar>
|
||||
<view class="fixed_search_div">
|
||||
<view class="back_div" @click="common.navigateBack()">
|
||||
<view class="back-icon"></view>
|
||||
</view>
|
||||
<view class="title font_pf">陪练记录</view>
|
||||
</view>
|
||||
<view class="detail-body">
|
||||
<view class="body-tab">
|
||||
<uv-tabs
|
||||
class="font_pf my_tabs"
|
||||
:current="tabAct"
|
||||
:list="tabList"
|
||||
@change="tabChange"
|
||||
:scrollable="false"
|
||||
line-color="#0066FF"
|
||||
:inactive-style="{ color: '#333333', fontSize: '30rpx', fontWeight: '600' }"
|
||||
:activeStyle="{ color: '#0066FF', fontSize: '30rpx', fontWeight: '600' }"
|
||||
:lineColor="`url(${LineBg}) 10% 10%`"
|
||||
></uv-tabs>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
<swiper class="swiper" :current="tabAct" :indicator-dots="false" @change="swiperChange">
|
||||
<swiper-item class="swiper-item">
|
||||
<practiceRecordList
|
||||
:ref="(el) => (listItemRefs[0] = el)"
|
||||
:tabIndex="0"
|
||||
:currentIndex="tabAct"
|
||||
></practiceRecordList>
|
||||
</swiper-item>
|
||||
<swiper-item class="swiper-item">
|
||||
<examRecordList :ref="(el) => (listItemRefs[1] = el)" :tabIndex="1" :currentIndex="tabAct"></examRecordList>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
</z-paging-swiper>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { LineBg } from '@/enum.js';
|
||||
import practiceRecordList from './components/practiceRecordList.vue';
|
||||
import examRecordList from './components/examRecordList.vue';
|
||||
import { ref, onMounted, watch } from 'vue';
|
||||
import common from '@/common/common.js';
|
||||
import useIndexList from '@/composables/useIndexList.js';
|
||||
const { tabAct, listItemRefs, tabChange, swiperChange } = useIndexList();
|
||||
|
||||
const tabList = ref([
|
||||
{
|
||||
name: '练习记录'
|
||||
},
|
||||
{
|
||||
name: '考试记录'
|
||||
}
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
$bg-margin-left: 50rpx;
|
||||
.my_tabs {
|
||||
// 解决这个圆角的图片,开穿透
|
||||
:deep(.uv-tabs__wrapper__nav__line) {
|
||||
height: 26rpx !important;
|
||||
left: 60rpx;
|
||||
background-size: 28rpx !important;
|
||||
background-repeat: no-repeat !important;
|
||||
}
|
||||
}
|
||||
|
||||
.swiper {
|
||||
height: 100%;
|
||||
}
|
||||
.swiper-item {
|
||||
display: block;
|
||||
height: 100%;
|
||||
background-color: #f1f5fa;
|
||||
}
|
||||
|
||||
.detail-body {
|
||||
background: #ffffff;
|
||||
border-radius: 15rpx;
|
||||
z-index: 101;
|
||||
overflow: hidden;
|
||||
|
||||
.body-tab {
|
||||
height: 88rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.detail-main {
|
||||
background-color: #f6f8ff;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #000000;
|
||||
font-size: 34rpx;
|
||||
height: 34rpx;
|
||||
font-weight: 600;
|
||||
line-height: 34rpx;
|
||||
}
|
||||
.fixed_search_input_div {
|
||||
width: 100%;
|
||||
height: 80rpx;
|
||||
padding: 0 22rpx;
|
||||
// background-color: red;
|
||||
background-color: #fff;
|
||||
:deep(.uv-input.uv-input--radius) {
|
||||
border-radius: 16rpx !important;
|
||||
}
|
||||
}
|
||||
.fixed_search_div {
|
||||
padding: calc(20rpx + var(--status-bar-height)) 15rpx 34rpx 30rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
background-color: #fff;
|
||||
// background-color: red;
|
||||
// height: 180rpx;
|
||||
position: relative;
|
||||
|
||||
.back_div {
|
||||
position: absolute;
|
||||
left: 20rpx;
|
||||
top: var(--status-bar-height);
|
||||
padding: 10rpx;
|
||||
|
||||
.back-icon {
|
||||
position: relative;
|
||||
width: 50rpx;
|
||||
height: 50rpx;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.back-icon::before,
|
||||
.back-icon::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.back-icon::after {
|
||||
top: 25%;
|
||||
left: 25%;
|
||||
width: 40%;
|
||||
height: 40%;
|
||||
border-top: 4rpx solid #000000;
|
||||
border-left: 4rpx solid #000000;
|
||||
transform: rotate(-45deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,112 @@
|
||||
import {
|
||||
computed,
|
||||
reactive,
|
||||
nextTick,
|
||||
ref
|
||||
} from 'vue';
|
||||
|
||||
|
||||
// 计算两个标准时间的时间差值
|
||||
export const formatDuration = (startTm, endTm) => {
|
||||
if (endTm === null || startTm === null) return "00:00:00";
|
||||
// 解析时间字符串为时间戳(毫秒)
|
||||
const startTime = new Date(startTm).getTime();
|
||||
const endTime = new Date(endTm).getTime();
|
||||
|
||||
// 计算时间差(秒),确保为正数
|
||||
const seconds = Math.abs(Math.floor((endTime - startTime) / 1000));
|
||||
// 计算时、分、秒
|
||||
const hours = Math.floor(seconds / 3600);
|
||||
const minutes = Math.floor((seconds % 3600) / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
|
||||
// 补零格式化函数(统一处理所有单位)
|
||||
const formatNumber = (num) => num.toString().padStart(2, '0');
|
||||
|
||||
// 小时、分钟、秒均保持两位数格式
|
||||
return `${formatNumber(hours)}:${formatNumber(minutes)}:${formatNumber(remainingSeconds)}`;
|
||||
};
|
||||
|
||||
// 秒数转换成小时,带小数点的小时
|
||||
export const secondsToHours = (seconds) => {
|
||||
if (typeof seconds === 'number') {
|
||||
// 转换为小时并保留一位小数
|
||||
return (seconds / 3600).toFixed(1);
|
||||
}
|
||||
return '0.0';
|
||||
};
|
||||
|
||||
export function useItemList(fetchFunction, item) {
|
||||
const status = ref('loadmore'); // loadmore - 加载前,loading - 加载中,nomore - 没有数据
|
||||
const limit = 5;
|
||||
const page = ref(0);
|
||||
const total = ref(-1);
|
||||
const data_list = reactive([]);
|
||||
const show_list_state = ref(false);
|
||||
|
||||
const list_div_height = computed(() => {
|
||||
const statusHeight = status.value === 'nomore' ? 0 : 100;
|
||||
if (!show_list_state.value) {
|
||||
return '0';
|
||||
} else if (status.value === 'loading') {
|
||||
return data_list.length * 136 + statusHeight + 'rpx';
|
||||
} else {
|
||||
return data_list.length * 136 + statusHeight + 'rpx';
|
||||
}
|
||||
});
|
||||
// 点击获取详情
|
||||
const show_list_click = (num = -1) => {
|
||||
if (num === 0) return;
|
||||
show_list_state.value = !show_list_state.value;
|
||||
if (show_list_state.value) {
|
||||
nextTick(() => {
|
||||
getData('first');
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const getData = (from = '') => {
|
||||
if (from === 'first') {
|
||||
if (total.value !== -1) {
|
||||
return;
|
||||
}
|
||||
status.value = 'loading';
|
||||
total.value = -1;
|
||||
page.value = 1;
|
||||
data_list.length = 0;
|
||||
} else {
|
||||
if (status.value !== 'loadmore' && status.value !== '') return;
|
||||
status.value = 'loading';
|
||||
page.value++;
|
||||
}
|
||||
fetchFunction({
|
||||
page: page.value,
|
||||
limit: limit
|
||||
})
|
||||
.then((res) => {
|
||||
total.value = res.total;
|
||||
data_list.push(...res.body);
|
||||
})
|
||||
.finally(() => {
|
||||
if (data_list.length >= total.value) {
|
||||
status.value = 'nomore';
|
||||
} else {
|
||||
status.value = 'loadmore';
|
||||
}
|
||||
});
|
||||
};
|
||||
const clear = () => {
|
||||
total.value = -1
|
||||
data_list.length = 0
|
||||
show_list_state.value = false
|
||||
}
|
||||
return {
|
||||
status,
|
||||
data_list,
|
||||
show_list_state,
|
||||
list_div_height,
|
||||
show_list_click,
|
||||
getData,
|
||||
clear
|
||||
};
|
||||
}
|
||||
+10
-1
@@ -116,6 +116,15 @@
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"path": "pages/traRecord/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "陪练记录页",
|
||||
"app-plus": {
|
||||
"titleNView": false
|
||||
}
|
||||
}
|
||||
},
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user