更新:增加“精彩试听”列表;将课程列表元素提取公共组件

This commit is contained in:
2025-11-14 09:13:53 +08:00
parent 8ed3680e5d
commit e7e0597026
7 changed files with 214 additions and 202 deletions

View File

@@ -127,6 +127,12 @@
"navigationStyle": "custom", "navigationStyle": "custom",
"navigationBarTitleText": "%courseSearch.title%" "navigationBarTitleText": "%courseSearch.title%"
} }
}, {
"path": "pages/course/list/tryListen",
"style": {
"navigationStyle": "custom",
"navigationBarTitleText": "%courseHome.tryListen%"
}
} }
], ],
"tabBar": { "tabBar": {

View File

@@ -195,9 +195,7 @@
</view> </view>
</view> </view>
<view class="moreBox shiting"> <view class="moreBox shiting">
<text @click="onPageJump('/pages/course/tryListen', 1, $t('courseHome.tryListen'))" <text @click="onPageJump('/pages/course/list/tryListen', 1, $t('courseHome.tryListen'))">{{ $t('courseHome.moreTryListen') }}</text>
>{{ $t('courseHome.moreTryListen') }}</text
>
</view> </view>
</view> </view>
</view> </view>

View File

View File

@@ -0,0 +1,141 @@
<template>
<wd-card type="rectangle" custom-class="list-item-card" @click="goCourseDetail(data.id)">
<view class="content">
<image :src="data[cover]" mode="aspectFill" class="course-cover-image"/>
<view class="course-info">
<view class="course-name">{{ data[title] }}</view>
<view class="course-desc" v-html="data[desc]"></view>
<!-- 课程标签 -->
<view class="course-tags">
<wd-tag v-if="data.level && data.level !== 0" type="primary">
{{ data.level === 1 ? $t('courseSearch.levelBeginner') : $t('courseSearch.levelAdvanced') }}
</wd-tag>
<wd-tag v-if="data.selective === 1" type="warning">
{{ $t('courseSearch.required') }}
</wd-tag>
<wd-tag v-if="data.selective === 2" type="success">
{{ $t('courseSearch.elective') }}
</wd-tag>
</view>
</view>
</view>
<template #footer>
<!-- 课程价格 -->
<view v-if="data.courseCatalogueEntityList" class="text-[red]">
<text v-if="data.courseCatalogueEntityList.length === 1">
{{
data.courseCatalogueEntityList[0].halfFee === 0
? $t('courseSearch.free')
: `¥${data.courseCatalogueEntityList[0].halfFee}/${data.courseCatalogueEntityList[0].fee}`
}}
</text>
<text v-if="data.courseCatalogueEntityList.length > 1">
<text v-for="(v, i) in data.courseCatalogueEntityList" :key="i">
{{ formatCatalogueTitle(v.title) }}
<text v-if="i !== data.courseCatalogueEntityList.length - 1">/</text>
</text>
{{
data.courseCatalogueEntityList[0].halfFee === 0
? $t('courseSearch.free')
: ` ${$t('courseSearch.each')}${data.courseCatalogueEntityList[0].halfFee}/${data.courseCatalogueEntityList[0].fee}`
}}
</text>
</view>
<wd-button v-if="showToDetail" size="small" custom-class="course-detail-btn">课程详情</wd-button>
</template>
</wd-card>
</template>
<script lang="ts" setup>
import { defineProps } from 'vue'
const props = defineProps({
data: {
type: Object,
default: () => ({})
},
title: {
type: String,
default: 'title'
},
desc: {
type: String,
default: 'desc'
},
cover: {
type: String,
default: 'squareImage'
},
showToDetail: {
type: Boolean,
default: true
}
})
/**
* 格式化课程目录标题
* 提取"上/中/下"
*/
const formatCatalogueTitle = (title: string): string => {
const keywords = ['上部', '中部', '下部']
const result: string[] = []
keywords.forEach((keyword) => {
if (title.includes(keyword)) {
result.push(keyword.substring(0, 1))
}
})
return result.join('')
}
/**
* 跳转到课程详情
*/
const goCourseDetail = (courseId: number) => {
uni.navigateTo({
url: `/pages/course/courseDetail?id=${courseId}`
})
}
</script>
<style lang="scss" scoped>
.list-item-card {
box-shadow: 0px 0px 10px 0px #a7bbe4 !important;
border-radius: 10px !important;
}
.content {
display: flex;
flex-direction: row;
justify-content: flex-start;
.course-cover-image {
width: 124px;
height: 100px;
}
.course-info {
flex: 1;
margin-left: 10px;
.course-name {
color: rgba(0,0,0,0.85);
font-size: 16px;
}
.course-desc {
color: rgba(0,0,0,0.4);
font-size: 12px;
line-height: 1.5;
max-height: 4.5em;
overflow: hidden;
display: -webkit-box;
-webkit-line-clamp: 3;
-webkit-box-orient: vertical;
text-overflow: ellipsis;
}
}
}
.course-detail-btn {
background-image: linear-gradient(90deg, #258feb 0%, #00e1ec 100%) !important;
color: #fff !important;
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<view>
<!-- 自定义导航栏 -->
<nav-bar :title="$t('courseHome.tryListen')" />
<!-- 课程列表 -->
<view class="course-list">
<list-item v-for="item in tryListenList" :key="item.id" :data="item" desc="content" />
</view>
</view>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import { onMounted } from 'vue'
import { useI18n } from 'vue-i18n'
import listItem from './components/list-item.vue'
import { courseApi } from '@/api/modules/course'
const { t } = useI18n()
// 精彩试听
const tryListenList = ref<ICourse[]>([]) // 试听课程列表
/**
* 获取试听课程列表
*/
const getTryListenList = async () => {
try {
const res = await courseApi.getMarketCourseList(1, 6, 1)
if (res && res.code === 0) {
if (res.courseList && res.courseList.records && res.courseList.records.length > 0) {
tryListenList.value = res.courseList.records
} else {
tryListenList.value = []
}
}
} catch (error) {
console.error('获取试听课程失败:', error)
}
}
// 页面加载时获取试听课程列表
onMounted(() => {
getTryListenList()
})
</script>
<style lang="scss" scoped>
body {
background-color: #f5f7fa;
}
.course-list {
padding: 10px;
}
</style>

View File

@@ -40,51 +40,13 @@
<!-- 课程列表 --> <!-- 课程列表 -->
<view v-if="!loading && courseList.length > 0" class="course-section"> <view v-if="!loading && courseList.length > 0" class="course-section">
<view class="course-list"> <view class="course-list">
<view <list-item
v-for="(item, index) in courseList" v-for="(item, index) in courseList"
:key="index" :key="index"
class="course-item" :data="item"
@click="goCourseDetail(item.id)" :showToDetail="false"
> desc="content"
<image :src="item.squareImage" class="course-image" mode="aspectFit" /> />
<view class="course-info">
<view class="course-title">{{ item.title }}</view>
<view class="course-content" v-html="item.content"></view>
<!-- 课程标签 -->
<view class="course-tags">
<wd-tag v-if="item.level && item.level !== 0" type="primary">
{{ item.level === 1 ? $t('courseSearch.levelBeginner') : $t('courseSearch.levelAdvanced') }}
</wd-tag>
<wd-tag v-if="item.selective === 1" type="warning">
{{ $t('courseSearch.required') }}
</wd-tag>
<wd-tag v-if="item.selective === 2" type="success">
{{ $t('courseSearch.elective') }}
</wd-tag>
</view>
</view>
<!-- 课程价格 -->
<view class="course-price">
<text v-if="item.courseCatalogueEntityList.length === 1">
{{
item.courseCatalogueEntityList[0].halfFee === 0
? $t('courseSearch.free')
: `¥${item.courseCatalogueEntityList[0].halfFee}/${item.courseCatalogueEntityList[0].fee}`
}}
</text>
<text v-if="item.courseCatalogueEntityList.length > 1">
<text v-for="(v, i) in item.courseCatalogueEntityList" :key="i">
{{ formatCatalogueTitle(v.title) }}
<text v-if="i !== item.courseCatalogueEntityList.length - 1">/</text>
</text>
{{
item.courseCatalogueEntityList[0].halfFee === 0
? $t('courseSearch.free')
: ` ${$t('courseSearch.each')}${item.courseCatalogueEntityList[0].halfFee}/${item.courseCatalogueEntityList[0].fee}`
}}
</text>
</view>
</view>
</view> </view>
</view> </view>
@@ -102,6 +64,7 @@ import { onShow } from '@dcloudio/uni-app'
import { useI18n } from 'vue-i18n' import { useI18n } from 'vue-i18n'
import { courseApi } from '@/api/modules/course' import { courseApi } from '@/api/modules/course'
import type { ICourse } from '@/types/search' import type { ICourse } from '@/types/search'
import listItem from './list/components/list-item.vue'
const { t } = useI18n() const { t } = useI18n()
@@ -207,32 +170,6 @@ const handleClear = () => {
getHistory() getHistory()
} }
/**
* 跳转到课程详情
*/
const goCourseDetail = (courseId: number) => {
uni.navigateTo({
url: `/pages/course/courseDetail?id=${courseId}`
})
}
/**
* 格式化课程目录标题
* 提取"上/中/下"
*/
const formatCatalogueTitle = (title: string): string => {
const keywords = ['上部', '中部', '下部']
const result: string[] = []
keywords.forEach((keyword) => {
if (title.includes(keyword)) {
result.push(keyword.substring(0, 1))
}
})
return result.join('')
}
/** /**
* 页面挂载 * 页面挂载
*/ */
@@ -313,78 +250,6 @@ onShow(() => {
text-shadow: 0 2rpx 4rpx rgba(0, 122, 255, 0.6); text-shadow: 0 2rpx 4rpx rgba(0, 122, 255, 0.6);
} }
// 商品网格样式
.product-section {
margin-bottom: 40rpx;
}
.product-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20rpx;
}
.product-item {
background: #fff;
border-radius: 15rpx;
padding: 14rpx;
position: relative;
overflow: hidden;
.vip-badge {
position: absolute;
top: 10rpx;
left: 10rpx;
z-index: 10;
background: #f94f04;
color: #fff;
font-size: 22rpx;
font-weight: bold;
padding: 4rpx 8rpx;
border-radius: 4rpx;
}
.product-image {
width: 100%;
height: 220rpx;
border-radius: 10rpx;
background: #f0f0f0;
}
.product-name {
font-size: 24rpx;
font-weight: bold;
margin-top: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.product-price {
font-size: 26rpx;
margin-top: 10rpx;
.price-vip {
color: #e97512;
font-size: 26rpx;
font-weight: bold;
}
.price-original {
color: #8a8a8a;
font-size: 22rpx;
margin-left: 8rpx;
text-decoration: line-through;
}
.price-normal {
color: #e97512;
font-size: 26rpx;
font-weight: bold;
}
}
}
// 课程列表样式 // 课程列表样式
.course-section { .course-section {
margin-bottom: 40rpx; margin-bottom: 40rpx;
@@ -395,62 +260,4 @@ onShow(() => {
flex-direction: column; flex-direction: column;
gap: 20rpx; gap: 20rpx;
} }
.course-item {
display: flex;
background: #fff;
border-radius: 20rpx;
padding: 20rpx;
position: relative;
box-shadow: 0 0 10rpx rgba(0, 0, 0, 0.1);
.course-image {
width: 250rpx;
height: 250rpx;
margin-right: 20rpx;
border-radius: 10rpx;
background: #f0f0f0;
flex-shrink: 0;
}
.course-info {
flex: 1;
display: flex;
flex-direction: column;
.course-title {
font-size: 30rpx;
font-weight: bold;
color: #000;
margin-bottom: 10rpx;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.course-content {
font-size: 24rpx;
color: #9c9c9c;
line-height: 30rpx;
height: 60rpx;
overflow: hidden;
margin-bottom: 10rpx;
}
.course-tags {
display: flex;
gap: 10rpx;
flex-wrap: wrap;
}
}
.course-price {
position: absolute;
bottom: 20rpx;
right: 20rpx;
font-size: 32rpx;
font-weight: 500;
color: red;
}
}
</style> </style>

View File

@@ -267,6 +267,9 @@
.text-\[\#fff\] { .text-\[\#fff\] {
color: #fff; color: #fff;
} }
.text-\[red\] {
color: red;
}
.lowercase { .lowercase {
text-transform: lowercase; text-transform: lowercase;
} }