feat: 陪练详情展示页面联调

This commit is contained in:
杨航
2025-11-03 17:40:48 +08:00
parent 9c72b4dc9b
commit cec5a746d3
12 changed files with 530 additions and 56 deletions
+1 -2
View File
@@ -4,8 +4,7 @@ ENV = 'development.yh'
VITE_APP_BASE_API_Url = 'http://25.18.122.65:7001'
# VITE_APP_BASE_H5_API_Url = 'http://25.18.122.78:9786'
# VITE_APP_BASE_H5_API_Url_TRAEXAM = 'http://25.64.32.154:9604'
# VITE_APP_BASE_H5_API_Url_TRAPRACTICE = 'http://25.64.32.154:9603'
+14 -6
View File
@@ -1,11 +1,11 @@
import request from '@/api/request'
const course_base_url = '/trapractice'
const base_url = '/trapractice'
// 获取课程分类树接口
export const getCourseTypeListApi = (data) => {
// 获取分类树接口
export const queryTraPartnerCatelogList = (data) => {
return request({
url: course_base_url + '/traPartnerCatelog/queryTraPartnerCatelog',
url: base_url + '/traPartnerCatelog/queryTraPartnerCatelogList',
method: 'post',
data
});
@@ -13,7 +13,7 @@ export const getCourseTypeListApi = (data) => {
// 获取标签分类树 接口
export const getTraTagListApi = (data) => {
return request({
url: course_base_url + '/traPartnerTag/queryTraTagCatalogInfoList',
url: base_url + '/traPartnerTag/queryTraTagCatalogInfoList',
method: 'post',
data
});
@@ -21,7 +21,15 @@ export const getTraTagListApi = (data) => {
// 全部模块
export const queryCrsInfoByCatalogApi = (data) => {
return request({
url: course_course_base_url + '/traPartnerInfo/queryTraPartnerInfoPaging',
url: base_url + '/traPartnerInfo/queryTraPartnerInfoPaging',
method: 'post',
data
});
};
// id 查详情/trapractice/traPartnerInfo/queryTraPartnerInfoById
export const queryTraPartnerInfoById = (data) => {
return request({
url: base_url + '/traPartnerInfo/queryTraPartnerInfoById',
method: 'post',
data
});
+9
View File
@@ -447,6 +447,15 @@
"titleNView": false
}
}
},
{
"path": "pages/sparring/detail",
"style": {
"navigationBarTitleText": "AI陪练详情",
"app-plus": {
"titleNView": false
}
}
}
],
"tabBar": {
@@ -5,31 +5,31 @@
<image
class="type-icon"
src="@/static/images/sparring/video-icon.png"
v-if="Number(itemInfo.accmStdyCnt)%3 === 0"
v-if="itemInfo.traInterTyp === '03'"
></image>
<image
class="type-icon"
src="@/static/images/sparring/phone-icon.png"
v-if="Number(itemInfo.accmStdyCnt)%3 === 1"
v-if="itemInfo.traInterTyp === '02'"
></image>
<image
class="type-icon"
src="@/static/images/sparring/talk-icon.png"
v-if="Number(itemInfo.accmStdyCnt)%3 === 2"
v-if="itemInfo.traInterTyp === '01'"
></image>
{{'视频对话'}}
{{itemInfo.traInterName}}
</view>
<ImagePreview class="img-box" :imgId="itemInfo.imgId" :isCache="true" width="225" height="170" mode="scaleToFill">
<ImagePreview class="img-box" :src="itemInfo.ossAddr" :isCache="true" width="225" height="170" mode="scaleToFill">
</ImagePreview>
<view class="item-title">{{itemInfo.crsName}}</view>
<view class="item-title">{{itemInfo.traName}}</view>
<scroll-view class="item-tags" :scroll-x="true">
<view class="item-tag-view">
<view :class="['item-tag', this.activeTag.includes(item.tagId)?'active-tag':'']" v-for="item in tagsShowList">
{{item.tagName}}</view>
</view>
</scroll-view>
<view class="item-publish">截至时间{{itemInfo.mtTime}}</view>
<view class="item-num">陪练次数{{itemInfo.accmStdyCnt}}人次</view>
<view class="item-publish">截至时间{{itemInfo.endTm}}</view>
<view class="item-num">陪练次数{{itemInfo.practiceTimes }}人次</view>
</view>
</view>
</template>
+387
View File
@@ -0,0 +1,387 @@
<template>
<view class="sparring-detail max_page"><view class="sparring-detail-body">
<view class="top-box">
<view class="top-title">
<view class="back-icon" @click="common.navigateBack()"></view>
<!-- <uv-icon class="arrow-icon" name="arrow-left" @click="common.switchTab('/pages/index/index')" /> -->
陪练详情
</view>
<ImagePreview class="img-box" :src="detailInfo.ossAddr" :isCache="true" width="704" height="350" />
</view>
<view class="body-top">
<view class="body-top-title">
{{detailInfo.traName}}
</view>
<view class="body-top-item">
已练习次数{{detailInfo.practiceTimes}}
</view>
<view class="body-top-item">
类型{{detailInfo.traInterTypName}}
</view>
<view class="body-top-item">
截止时间{{detailInfo.endTm}}
</view>
<scroll-view class="body-top-tags" :scroll-x="true">
<view class="item-tag-view">
<view class="item-tag active-tag" v-for="item in tagsShowList">
{{item.tagName}}
</view>
</view>
</scroll-view>
</view>
<view class="body-item">
<view class="body-item-title">
<image class="title-icon" src="@/static/images/sparring/detail-icon-1.png"></image>
陪练介绍
</view>
<view class="body-item-detail">
{{detailInfo.introduction}}
</view>
</view>
<view class="body-item">
<view class="body-item-title">
<image class="title-icon" src="@/static/images/sparring/detail-icon-2.png"></image>
陪练目标
</view>
<view class="body-item-detail">
{{detailInfo.traTarget}}
</view>
</view>
<view class="body-item">
<view class="body-item-title">
<image class="title-icon" src="@/static/images/sparring/detail-icon-3.png"></image>
陪练预计时长
</view>
<view class="body-item-detail">
{{detailInfo.traStartLimit+ ' ~ ' + detailInfo.traEndLimit}} 分钟
</view>
</view>
<view class="body-item">
<view class="body-item-title">
<image class="title-icon" src="@/static/images/sparring/detail-icon-4.png"></image>
陪练参考资料
</view>
<view class="body-item-detail">
<view class="body-item-detail-file" v-for="item in 6" @click="previewFile(item)">
文件名称{{item}}.docx
</view>
</view>
</view>
<view class="body-bottom">
<view class="body-bottom-btn">AI陪练</view>
</view>
</view>
</view>
</template>
<script setup>
import {
ref,
onMounted
} from 'vue'
import { onShow, onLoad, onUnload } from '@dcloudio/uni-app';
import common from '@/common/common';
import ImagePreview from '@/components/image-preview/image-preview.vue';
import { queryTraPartnerInfoById } from "@/api/sparring.js"
import { getPhoneEnvBool } from '@/common/common';
const traId = ref('')
const detailInfo = ref({})
const isDownloading = ref(false)
const tagsShowList = ref([{
tagName: '公金业务'
}, {
tagName: '初级'
}, {
tagName: '销售类'
}, {
tagName: '公金业务'
}, {
tagName: '初级'
}, {
tagName: '销售类'
}, {
tagName: '公金业务'
}, {
tagName: '初级'
}, {
tagName: '销售类'
}])
const getDetailInfo = () => {
queryTraPartnerInfoById({
id: traId.value
}).then(res=> {
console.log(res)
detailInfo.value = res.body||{}
})
}
const previewFile = (info) => {
if (getPhoneEnvBool('AND')) {
common.navigateTo('/pages/index/preview?fileId=' + info.docId);
} else {
if(isDownloading.value) return
const base_url = get_base_url('/traask/traFilePreview/');
let fileUrl = base_url + '/traask/traFilePreview/' + info.docId + userParams;
const localFilePath = '_doc/pdf_view/' + info.docName + '.pdf';
isDownloading.value = true
downloadFileFnc(fileUrl, localFilePath).then((res) => {
uni.openDocument({
filePath: res,
showMenu: true,
success: function () {
console.log('打开文档成功');
}
})
}).finally(()=>{
setTimeout(()=>{
isDownloading.value = false
},1000)
});
}
}
const downloadFileFnc = async (fileUrl, localFilePath) => {
try {
//#ifdef APP-PLUS
return new Promise((resolve, reject) => {
console.log('开始下载:', fileUrl);
const dtask = plus.downloader.createDownload(
fileUrl,
{
filename: localFilePath
},
(d, status) => {
if (status === 200) {
console.log('保存路径:', d.filename);
resolve(d.filename);
} else {
console.error('文件下载失败:', status);
reject(new Error(`下载失败,状态码: ${status}`));
}
}
);
dtask.start();
});
// #endif
//#ifdef H5
return new Promise((resolve, reject) => {
resolve();
});
// #endif
} catch (error) {
console.error('创建目录失败:', error);
throw error;
}
};
onLoad((params) => {
traId.value = params.traId;
});
onMounted(()=> {
getDetailInfo()
})
</script>
<style lang="scss" scoped>
.sparring-detail {
position: relative;
overflow-x: hidden;
overflow-y: auto;
.sparring-detail-body {
position: absolute;
overflow-x: hidden;
overflow-y: hidden;
left: 0;
top: 0;
min-height: 100%;
width: 100%;
box-sizing: border-box;
background-color: #f6f8ff;
z-index: 1;
padding: calc(var(--status-bar-height) + 336rpx) 23rpx 180rpx;
.top-box {
background-image: url(@/static/images/sparring/top-bg.png);
background-repeat: no-repeat;
background-size: cover;
height: 436rpx;
clip-path: ellipse(650rpx 327rpx at 375rpx 109rpx);
width: 100%;
position: absolute;
top: 0;
left: 0;
z-index: 2;
.img-box {
width: 704rpx;
height: 350rpx;
margin: 0 auto;
border-radius: 23rpx;
overflow: hidden;
}
.top-title {
height: 90rpx;
// background-color: red;
text-align: center;
font-size: 33rpx;
padding-top: 10rpx;
line-height: 46rpx;
box-sizing: border-box;
font-weight: 500;
color: #fff;
position: relative;
// margin-bottom: 23rpx;
.arrow-icon {
position: absolute;
left: 36rpx;
top: 20rpx;
color: #fff !important;
}
.back-icon {
position: absolute;
width: 40rpx;
height: 40rpx;
cursor: pointer;
left: 50rpx;
top: 33rpx;
transform: translateY(-50%);
}
.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 #ffffff;
border-left: 4rpx solid #ffffff;
transform: rotate(-45deg);
}
}
}
.body-top {
background-color: #fff;
border-radius: 23rpx;
padding: 120rpx 28rpx 20rpx;
margin-bottom: 30rpx;
&-title {
min-height: 50rpx;
padding: 4rpx 0;
line-height: 42rpx;
font-size: 32rpx;
margin-bottom: 12rpx;
font-weight: 500;
}
&-item {
height: 35rpx;
white-space: nowrap;
line-height: 35rpx;
font-size: 25rpx;
color: #666;
display: inline-block;
width: 50%;
margin-bottom: 15rpx;
}
&-tags {
margin-bottom: 0rpx;
box-sizing: border-box;
padding-top: 5rpx;
.item-tag-view {
white-space: nowrap;
height: 50rpx;
.item-tag {
height: 38rpx;
line-height: 34rpx;
box-sizing: border-box;
font-size: 20rpx;
background-color: #fff;
color: #06f;
border: 2rpx solid #06f;
padding: 0 20rpx;
border-radius: 8rpx;
margin-right: 20rpx;
display: inline-block;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 200rpx;
box-sizing: border-box;
&.active-tag {
color: #ffa150;
border: 2rpx solid #ffa150;
}
}
}
}
}
.body-bottom{
height: 180rpx;
background-color: #fff;
padding: 36rpx 23rpx 0;
position: fixed;
width: 100vw;
bottom: 0;
left: 0;
&-btn{
width: 100%;
height: 92rpx;
line-height: 92rpx;
background-color: #06f;
text-align: center;
color: #fff;
font-size: 29rpx;
border-radius: 23rpx;
}
}
.body-item {
padding: 28rpx;
background-color: #fff;
margin-bottom: 30rpx;
border-radius: 23rpx;
&-title {
position: relative;
font-size: 29rpx;
line-height: 40rpx;
padding-left: 56rpx;
margin-bottom: 23rpx;
.title-icon {
position: absolute;
width: 38rpx;
height: 38rpx;
left: 0;
top: 1rpx;
}
}
&-detail {
font-size: 28rpx;
line-height: 52rpx;
color: #333;
&-file{
color: #06f;
}
}
}
}
}
</style>
+102 -40
View File
@@ -14,13 +14,14 @@
</uv-input>
</view>
<view class="tabs-view">
<uv-tabs :list="tabList" @click="changeTab" :scrollable="false" active-style="font-weight:bold; color:#000;"
<uv-tabs :list="tabList" @click="changeTab" :scrollable="true" active-style="font-weight:bold; color:#000;"
class="font_pf my_tabs" :inactive-style="{ color: '#333333', fontSize: '30rpx', fontWeight: '500' }"
:activeStyle="{ color: '#333333', fontSize: '30rpx', fontWeight: '600' }"></uv-tabs>
<uv-tabs :list="tabList" @click="changeTabSec" :scrollable="true" active-style="font-weight:bold; color:#000;"
<uv-tabs :list="tabSecList" @click="changeTabSec" :scrollable="true" active-style="font-weight:bold; color:#000;"
class="font_pf my_tabs2" :inactive-style="{ color: '#333333', fontSize: '24rpx', fontWeight: '500' }"
:activeStyle="{ color: '#0066FF', fontSize: '24rpx', fontWeight: '500' }"
:lineColor="`url(${LineBg}) 2% 2%`"></uv-tabs>
:activeStyle="{ color: '#0066FF', fontSize: '24rpx', fontWeight: '500' }" :lineColor="`#06f`"
></uv-tabs>
<!-- :lineColor="`url(${LineBg}) 2% 2%`" -->
</view>
<view class="list-view">
<view class="filter-btn" @click="
@@ -68,7 +69,7 @@
<scroll-view :class="['right-sparring-list', getActiveItemClass()]" :scroll-y="true"
@scrolltolower="getMoreList()">
<view class="sparring-item-box" v-for="item in sparringList">
<SparringItem :itemInfo="item" :activeTag="activeTag" @clickItem="toCourseDetail(item)" />
<SparringItem :itemInfo="item" :activeTag="activeTag" @clickItem="toSparringDetail(item)" />
</view>
<view class="loading-box" v-show="loading">
<CommonLoading color="#06f" class="loading-val" />
@@ -89,7 +90,7 @@
} from '@/enum.js';
import {
getTraTagListApi,
getCourseTypeListApi,
queryTraPartnerCatelogList,
queryCrsInfoByCatalogApi
} from '@/api/sparring.js'
import {
@@ -123,19 +124,52 @@
backgroundColor: '#ffffff',
borderRadius: '23rpx'
}
const tabList = [{
name: '全部'
}, {
name: '热门'
}, {
name: '最新'
}, {
name: '推荐'
}, {
name: '生产线'
}]
const catelogList = ref([])
const tabList = computed(()=>{
return [{
name: '全 部',
value: ''
}, ...catelogList.value.map(t=>{
return {
name: t.label,
value: t.value
}
})]
})
const tabSecList = computed(()=> {
let _arr = []
if(!activeTabId.value){
catelogList.value.forEach(t=>{
let _children = (t.children||[])
_arr = _arr.concat(_children.map(k=>{
return {
name: k.label,
value: k.value
}
}))
})
}else{
let _obj = catelogList.value.find(t=>t.value === activeTabId.value)
if(_obj){
let _children = (_obj.children||[])
_arr = _children.map(k=>{
return {
name: k.label,
value: k.value
}
})
}
}
return [{
name: '全 部',
value: ''
}, ..._arr]
})
const activeTab = ref('全部')
const activeCourseType = ref('')
const activeTabId= ref('')
const activeTabSec = ref('全部')
const activeTabSecId= ref('')
const activeCourseType = computed(()=> (activeTabSecId.value || activeTabId.value || ''))
const activePrdlineType = ref('')
const activeTag = ref([])
const sparringTypeList = ref([])
@@ -197,15 +231,6 @@
getMoreList(1)
}
// 点击课程分类
const changeCourseType = (item, index) => {
activeCourseType.value = item.value === activeCourseType.value ? '' : item.value
activeLeftIndex.value = index
getMoreList(1)
}
const showTypeName = computed(() => {
return (activeTab.value === '全部' && activeCourseType.value === '') || (activeTab.value === '生产线' &&
activePrdlineType.value === '')
})
const tagsList = computed(() => {
let _arr = []
tagsTree.value.forEach(t => {
@@ -213,7 +238,7 @@
_arr = _arr.concat(_tarr)
})
let _reqList = _arr.filter(t => t.required === 'Y')
let _reqListN = _arr.filter(t => t.required === 'N')
let _reqListN = _arr.filter(t => t.required !== 'N')
return _reqList.concat(_reqListN)
})
const changeChooseTagsList = () => {
@@ -262,16 +287,19 @@
if (!item) return
sparringListBase.value = []
activeTab.value = item.name
activeTabId.value = item.value || ''
activeLeftIndex.value = -1
activeTag.value = []
pageObj.value.page = 1
pageObj.value.total = 0
// activeTag.value = ''
activeCourseType.value = ''
activePrdlineType.value = ''
loading.value = true
queryCrsInfoByCatalogApi(pageObj.value)
queryCrsInfoByCatalogApi({
...pageObj.value,
catalogId: activeCourseType.value
})
.then(res => {
sparringListBase.value = res.body || []
pageObj.value.total = res.total
@@ -280,21 +308,41 @@
loading.value = false
})
}
const changeTabSec = () => {
const changeTabSec = (item) => {
sparringListBase.value = []
activeLeftIndex.value = -1
activeTabSec.value = item.name
activeTabSecId.value = item.value || ''
activeTag.value = []
pageObj.value.page = 1
pageObj.value.total = 0
// activeTag.value = ''
activePrdlineType.value = ''
loading.value = true
queryCrsInfoByCatalogApi({
...pageObj.value,
catalogId: activeCourseType.value
})
.then(res => {
sparringListBase.value = res.body || []
pageObj.value.total = res.total
})
.finally(() => {
loading.value = false
})
}
// 跳转搜索页
const toSearchPage = () => {
common.navigateTo('/pages/search/search?from=sparring')
}
// 跳转 课程详情
const toCourseDetail = item => {
common.navigateTo('/pages/sparringDetail/index?crsNum=' + item.crsNum + '&crsId=' + item.crsId)
// common.navigateTo('/pages/sparring/study?crsNum=' + item.crsNum)
const toSparringDetail = item => {
common.navigateTo('/pages/sparring/detail?traId=' + item.traId)
}
onMounted(() => {
getCourseTypeListApi().then(res => {
sparringTypeList.value = res.body || []
queryTraPartnerCatelogList().then(res => {
catelogList.value = res.body || []
})
getTraTagListApi().then(res => {
tagsTree.value = res.body || []
@@ -329,14 +377,28 @@
</script>
<style lang="scss" scoped>
.my_tabs2 {
.my_tabs {
// 解决这个圆角的图片,开穿透
:deep(.uv-tabs__wrapper__nav__line) {
height: 30rpx !important;
left: 42rpx;
// height: 30rpx !important;
// left: 60rpx;
// background-size: 24rpx !important;
// background-repeat: no-repeat !important;
background: linear-gradient(90deg, #06f 0%, rgb(0,159,255) 50%, rgb(255,255,255) 100%) !important;
}
:deep(.uv-tabs__wrapper__nav__item) {
padding: 0 30rpx !important;
}
}
.my_tabs2 {
// 解决这个圆角的图片,开穿透
:deep(.uv-tabs__wrapper__nav__line) {
// display: none;
// height: 30rpx !important;
background-size: 24rpx !important;
background-repeat: no-repeat !important;
// background: linear-gradient(90deg, #06f 0%, rgb(0,159,255) 50%, rgb(255,255,255) 100%) !important;
}
:deep(.uv-tabs__wrapper__nav__item) {
+9
View File
@@ -63,5 +63,14 @@
}
}
},
{
"path": "pages/sparring/detail",
"style": {
"navigationBarTitleText": "AI陪练详情",
"app-plus": {
"titleNView": false
}
}
},
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 736 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 515 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 231 KiB