合并精品课

This commit is contained in:
haomingming
2025-08-21 09:59:04 +08:00
parent 72d63c5a80
commit 196ea43fad
19 changed files with 6308 additions and 18 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,638 @@
<template>
<view class="course-filter-page">
<!-- 自定义导航栏 -->
<view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-content">
<view class="nav-left" @click="goBack">
<uni-icons type="arrowleft" size="24" color="#FF4757"></uni-icons>
</view>
<view class="nav-title">肝病精品课</view>
<view class="nav-right" @click="goSearch">
<uni-icons type="search" size="24" color="#FF4757"></uni-icons>
</view>
</view>
</view>
<!-- 页面内容 -->
<view class="page-content" :style="{ paddingTop: navBarHeight + 'px' }">
<!-- 主横幅自动轮播 -->
<view class="main-banner">
<swiper
class="banner-swiper"
:autoplay="true"
:interval="4000"
:duration="500"
:circular="true"
:indicator-dots="true"
indicator-color="#d8d8d8"
indicator-active-color="#FF4757"
>
<swiper-item v-for="(banner, idx) in bannerList" :key="idx">
<view class="banner-content" :style="{ background: banner.background }">
<view class="main-logo">{{ banner.title }}</view>
<view class="sub-title">{{ banner.subTitle }}</view>
<view class="description">{{ banner.description }}</view>
</view>
</swiper-item>
</swiper>
</view>
<!-- 筛选栏 -->
<view class="filter-bar">
<view class="filter-item" @click="showCategoryFilter">
<text>全部课程</text>
<uni-icons type="arrowdown" size="16" color="#666"></uni-icons>
</view>
<view class="filter-item" @click="showSortFilter">
<text>默认排序</text>
<uni-icons type="arrowdown" size="16" color="#666"></uni-icons>
</view>
<view class="separator"></view>
<view class="filter-item" @click="toggleLimitedOffer">
<text :class="{ active: filters.limitedOffer }">限时优惠</text>
</view>
<view class="separator"></view>
<view class="filter-item" @click="toggleFreeCourses">
<text :class="{ active: filters.freeCourses }">免费课程</text>
</view>
</view>
<!-- 课程列表 -->
<view class="course-list">
<view
class="course-item"
v-for="(course, index) in courseList"
:key="index"
@click="goToCourseDetail(course)"
>
<view class="course-image">
<image :src="course.image" mode="aspectFill"></image>
<view class="learner-count" v-if="course.learnerCount">
{{ course.learnerCount }}人学
</view>
</view>
<view class="course-info">
<view class="course-title">{{ course.title }}</view>
<view class="course-instructor" v-if="course.instructor">
{{ course.instructor }}
</view>
<view class="course-price">
<text class="current-price">¥{{ course.price }}</text>
<text class="original-price" v-if="course.originalPrice">¥{{ course.originalPrice }}</text>
</view>
<view class="limited-offer" v-if="course.limitedOffer">限时优惠</view>
</view>
</view>
</view>
</view>
<!-- 底部导航栏 -->
<view class="bottom-nav">
<view class="nav-item active">
<uni-icons type="book" size="24" color="#FF4757"></uni-icons>
<text>精品课</text>
</view>
<view class="nav-item" @click="goToMyCourses">
<uni-icons type="play" size="24" color="#999"></uni-icons>
<text>我的课程</text>
</view>
</view>
<!-- 分类筛选弹窗 -->
<uni-popup ref="categoryPopup" type="bottom">
<view class="popup-content">
<view class="popup-header">
<text>选择课程分类</text>
<view class="close-btn" @click="closeCategoryPopup">
<uni-icons type="close" size="20" color="#666"></uni-icons>
</view>
</view>
<view class="popup-options">
<view
class="popup-option"
:class="{ active: selectedCategory === option.value }"
v-for="option in categoryOptions"
:key="option.value"
@click="selectCategory(option.value)"
>
{{ option.label }}
</view>
</view>
</view>
</uni-popup>
<!-- 排序筛选弹窗 -->
<uni-popup ref="sortPopup" type="bottom">
<view class="popup-content">
<view class="popup-header">
<text>选择排序方式</text>
<view class="close-btn" @click="closeSortPopup">
<uni-icons type="close" size="20" color="#666"></uni-icons>
</view>
</view>
<view class="popup-options">
<view
class="popup-option"
:class="{ active: selectedSort === option.value }"
v-for="option in sortOptions"
:key="option.value"
@click="selectSort(option.value)"
>
{{ option.label }}
</view>
</view>
</view>
</uni-popup>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
// 响应式数据
const statusBarHeight = ref(0)
const navBarHeight = ref(88)
// 轮播横幅
const bannerList = ref([
{
title: '辩以明思',
subTitle: '慢乙肝治疗新起点',
description: '肝胆相照·全国中青年医师 学术争鸣辩论赛',
background: 'linear-gradient(135deg, #87CEEB 0%, #E0F6FF 100%)'
},
{
title: '名医大讲堂',
subTitle: '临床思维系统课程',
description: '顶尖专家带你系统化掌握诊疗要点',
background: 'linear-gradient(135deg, #FFE8E8 0%, #FFF5F5 100%)'
},
{
title: '专科提升',
subTitle: '循证与实践结合',
description: '以真实病例训练临床路径与决策',
background: 'linear-gradient(135deg, #E6F7FF 0%, #F2FFFC 100%)'
}
])
// 筛选条件
const filters = ref({
limitedOffer: false,
freeCourses: false
})
// 选中的分类和排序
const selectedCategory = ref('all')
const selectedSort = ref('default')
// 分类选项
const categoryOptions = ref([
{ value: 'all', label: '全部课程' },
{ value: 'liver', label: '肝病相关' },
{ value: 'hepatitis', label: '肝炎治疗' },
{ value: 'cirrhosis', label: '肝硬化' },
{ value: 'cancer', label: '肝癌' }
])
// 排序选项
const sortOptions = ref([
{ value: 'default', label: '默认排序' },
{ value: 'newest', label: '最新发布' },
{ value: 'popular', label: '最受欢迎' },
{ value: 'price', label: '价格排序' }
])
// 课程列表
const courseList = ref([
{
id: 1,
title: '找到已购买课程即学习找到已购买课程即可学习',
instructor: '佑安医院 临床思维 陈煜',
price: 39,
image: '/static/placeholder_doctor.png',
learnerCount: 123
},
{
id: 2,
title: '找到已购买课程即学习找到已购买课程即可学习',
price: 39,
image: '/static/placeholder_doctor.png',
learnerCount: 89
},
{
id: 3,
title: '找到已购买课程即学习找到已购买课程即可学习',
price: 29,
originalPrice: 39,
image: '/static/placeholder_doctor.png',
limitedOffer: true,
learnerCount: 156
},
{
id: 4,
title: '找到已购买课程即学习找到已购买课程即可学习',
price: 29,
originalPrice: 39,
image: '/static/placeholder_doctor.png',
limitedOffer: true,
learnerCount: 78
},
{
id: 5,
title: '找到已购买课程即学习找到已购买课程即可学习',
price: 39,
image: '/static/placeholder_doctor.png',
learnerCount: 234
}
])
// 方法
const getSystemInfo = () => {
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight
// #ifdef MP-WEIXIN
navBarHeight.value = systemInfo.statusBarHeight + 44
// #endif
// #ifdef APP-PLUS
navBarHeight.value = systemInfo.statusBarHeight + 44
// #endif
}
const goBack = () => {
uni.navigateBack()
}
const goSearch = () => {
uni.navigateTo({
url: '/pages_course/search/search'
})
}
const goToMyCourses = () => {
uni.navigateTo({
url: '/pages_course/my_courses/my_courses'
})
}
const goToCourseDetail = (course) => {
uni.navigateTo({
url: `/pages_course/course_detail/course_detail?id=${course.id}`
})
}
// 显示分类筛选
const showCategoryFilter = () => {
// 这里需要获取弹窗引用,暂时注释
// this.$refs.categoryPopup.open()
console.log('显示分类筛选')
}
// 关闭分类筛选
const closeCategoryPopup = () => {
// this.$refs.categoryPopup.close()
console.log('关闭分类筛选')
}
// 选择分类
const selectCategory = (value) => {
selectedCategory.value = value
closeCategoryPopup()
filterCourses()
}
// 显示排序筛选
const showSortFilter = () => {
// this.$refs.sortPopup.open()
console.log('显示排序筛选')
}
// 关闭排序筛选
const closeSortPopup = () => {
// this.$refs.sortPopup.close()
console.log('关闭排序筛选')
}
// 选择排序
const selectSort = (value) => {
selectedSort.value = value
closeSortPopup()
sortCourses()
}
// 切换限时优惠
const toggleLimitedOffer = () => {
filters.value.limitedOffer = !filters.value.limitedOffer
filterCourses()
}
// 切换免费课程
const toggleFreeCourses = () => {
filters.value.freeCourses = !filters.value.freeCourses
filterCourses()
}
// 筛选课程
const filterCourses = () => {
// 这里实现课程筛选逻辑
console.log('筛选条件:', filters.value, selectedCategory.value)
}
// 排序课程
const sortCourses = () => {
// 这里实现课程排序逻辑
console.log('排序方式:', selectedSort.value)
}
// 生命周期
onMounted(() => {
getSystemInfo()
})
</script>
<style lang="scss">
.course-filter-page {
min-height: 100vh;
background-color: #fff;
padding-bottom: 120rpx;
}
// 导航栏
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background-color: #fff;
border-bottom: 1rpx solid #f0f0f0;
.nav-content {
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
.nav-left, .nav-right {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.nav-title {
font-size: 36rpx;
font-weight: 600;
color: #FF4757;
}
}
}
.page-content {
background-color: #fff;
}
// 主横幅
.main-banner {
margin: 0rpx;
overflow: hidden;
position: relative;
.banner-swiper {
height: 300rpx;
overflow: hidden;
}
.banner-content {
padding: 48rpx 32rpx;
text-align: center;
position: relative;
.main-logo {
font-size: 48rpx;
font-weight: bold;
color: #FF4757;
margin-bottom: 16rpx;
}
.sub-title {
background-color: #FF4757;
color: #fff;
padding: 8rpx 24rpx;
border-radius: 20rpx;
font-size: 24rpx;
display: inline-block;
margin-bottom: 16rpx;
}
.description {
color: #0066CC;
font-size: 26rpx;
margin-bottom: 8rpx;
}
}
}
// 筛选栏
.filter-bar {
display: flex;
align-items: center;
padding: 24rpx;
background-color: #fff;
border-bottom: 1rpx solid #f0f0f0;
.filter-item {
display: flex;
align-items: center;
gap: 8rpx;
padding: 16rpx 24rpx;
border-radius: 20rpx;
transition: all 0.3s;
text {
font-size: 26rpx;
color: #666;
&.active {
color: #FF4757;
}
}
&:active {
background-color: #f5f5f5;
}
}
.separator {
width: 2rpx;
height: 32rpx;
background-color: #e0e0e0;
margin: 0 16rpx;
}
}
// 课程列表
.course-list {
padding: 24rpx;
.course-item {
display: flex;
padding: 24rpx;
background-color: #fff;
border-radius: 16rpx;
margin-bottom: 24rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.08);
.course-image {
position: relative;
margin-right: 24rpx;
image {
width: 120rpx;
height: 120rpx;
border-radius: 12rpx;
}
.learner-count {
position: absolute;
bottom: 8rpx;
left: 8rpx;
background-color: #FF4757;
color: #fff;
font-size: 20rpx;
padding: 4rpx 8rpx;
border-radius: 8rpx;
}
}
.course-info {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.course-title {
font-size: 28rpx;
color: #333;
line-height: 1.4;
margin-bottom: 8rpx;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
}
.course-instructor {
font-size: 24rpx;
color: #999;
margin-bottom: 16rpx;
}
.course-price {
display: flex;
align-items: center;
gap: 16rpx;
margin-bottom: 8rpx;
.current-price {
font-size: 32rpx;
color: #FF4757;
font-weight: bold;
}
.original-price {
font-size: 24rpx;
color: #999;
text-decoration: line-through;
}
}
.limited-offer {
background-color: #FF4757;
color: #fff;
font-size: 20rpx;
padding: 4rpx 12rpx;
border-radius: 12rpx;
align-self: flex-start;
}
}
}
}
// 底部导航栏
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
background-color: #fff;
border-top: 1rpx solid #f0f0f0;
display: flex;
z-index: 999;
.nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 8rpx;
text {
font-size: 24rpx;
color: #999;
}
&.active {
text {
color: #FF4757;
}
}
}
}
// 弹窗样式
.popup-content {
background-color: #fff;
border-radius: 24rpx 24rpx 0 0;
padding: 32rpx;
.popup-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 32rpx;
text {
font-size: 32rpx;
font-weight: 500;
color: #333;
}
.close-btn {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
}
.popup-options {
display: flex;
flex-direction: column;
gap: 24rpx;
.popup-option {
padding: 24rpx;
text-align: center;
border-radius: 16rpx;
background-color: #f8f9fa;
font-size: 28rpx;
color: #666;
transition: all 0.3s;
&.active {
background-color: #FF4757;
color: #fff;
}
}
}
}
</style>
@@ -0,0 +1,585 @@
<template>
<view class="payment-page">
<!-- 自定义导航栏 -->
<view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-content">
<view class="nav-left" @click="goBack">
<uni-icons type="arrowleft" size="24" color="#333"></uni-icons>
</view>
<view class="nav-title">课程支付</view>
<view class="nav-right"></view>
</view>
</view>
<!-- 页面内容 -->
<view class="page-content" :style="{ paddingTop: navBarHeight + 'px' }">
<!-- 课程信息卡片 -->
<view class="course-card">
<view class="course-left">
<image :src="courseInfo.image" mode="aspectFill" class="course-image"></image>
</view>
<view class="course-right">
<text class="course-title">{{ courseInfo.title }}</text>
<view class="course-tags">
<view class="tag-item" v-for="(tag, index) in courseInfo.tags" :key="index">
<text class="tag-text">{{ tag }}</text>
</view>
</view>
<text class="course-price">¥{{ courseInfo.price.toFixed(2) }}</text>
</view>
</view>
<!-- 支付方式选择 -->
<view class="payment-methods">
<view class="method-item" :class="{ disabled: pointsValueYuan <= 0 }" @click="togglePaymentMethod('points')">
<view class="method-info">
<text class="method-label">账户积分{{ userInfo.points }}积分</text>
<text class="method-desc" v-if="userInfo.points > 0"> ¥{{ (userInfo.points / userInfo.pointsExchangeRate).toFixed(2) }} </text>
</view>
<view class="radio-button" :class="{ active: usePoints }">
<view class="radio-inner" v-if="usePoints"></view>
</view>
</view>
<view class="method-item" :class="{ disabled: balanceYuan <= 0 }" @click="togglePaymentMethod('balance')">
<view class="method-info">
<text class="method-label">账户余额¥{{ (userInfo.balance/100).toFixed(2) }} </text>
</view>
<view class="radio-button" :class="{ active: useBalance }">
<view class="radio-inner" v-if="useBalance"></view>
</view>
</view>
</view>
<!-- 支付明细 -->
<view class="payment-summary">
<view class="summary-item">
<text class="summary-label">课程总金额</text>
<text class="summary-value">¥{{ courseInfo.price.toFixed(2) }}</text>
</view>
<view class="summary-item" v-if="usePoints && pointsUsedYuan > 0">
<text class="summary-label">使用积分</text>
<text class="summary-value">-¥{{ pointsUsedYuan.toFixed(2) }}</text>
</view>
<view class="summary-item" v-if="useBalance && balanceUsedYuan > 0">
<text class="summary-label">使用余额</text>
<text class="summary-value">-¥{{ balanceUsedYuan.toFixed(2) }}</text>
</view>
<view class="summary-item total">
<text class="summary-label">合计:</text>
<text class="summary-value total-price">¥{{ finalPrice.toFixed(2) }}</text>
</view>
</view>
</view>
<!-- 底部支付栏 -->
<view class="bottom-payment">
<view class="payment-left">
<text class="final-price">¥{{ finalPrice.toFixed(2) }}</text>
<!-- <text class="payment-method" v-if="selectedMethod === 'points'">使用积分支付</text>
<text class="payment-method" v-if="selectedMethod === 'balance'">使用余额支付</text> -->
</view>
<view class="payment-right">
<button class="pay-button" @click="confirmPayment" :disabled="!canPay">实际支付</button>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted, computed } from 'vue'
import course_api from "@/api/course_api.js"
// 响应式数据
const statusBarHeight = ref(0)
const navBarHeight = ref(88)
const selectedMethod = ref('points') // 默认选择积分支付(兼容旧逻辑)
const usePoints = ref(true) // 是否使用积分
const useBalance = ref(false) // 是否使用余额
const courseInfo = ref({
title: '找到已购买课程即学习找到已购买课程即可学习',
price: 39.00,
tags: ['佑安医院', '临床思维', '陈煜'],
image: '/static/icon_home_my_patient.png'
})
const userInfo = ref({
points: 0,
balance: 0.00,
pointsExchangeRate: 50 // 积分兑换比例,50积分=1元
})
// 计算属性
// 金额与抵扣(单位元)
const pointsValueYuan = computed(() => {
const rate = userInfo.value.pointsExchangeRate || 50
if (!rate) return 0
return (userInfo.value.points || 0) / rate
})
const balanceYuan = computed(() => {
// 后端余额单位为分
return (userInfo.value.balance || 0) / 100
})
// 组合抵扣:先用积分再用余额
const pointsUsedYuan = computed(() => {
const price = courseInfo.value.price || 0
if (!usePoints.value || price <= 0) return 0
return Math.min(pointsValueYuan.value, price)
})
const balanceUsedYuan = computed(() => {
const price = courseInfo.value.price || 0
if (!useBalance.value || price <= 0) return 0
const remainAfterPoints = Math.max(0, price - pointsUsedYuan.value)
return Math.min(balanceYuan.value, remainAfterPoints)
})
const finalPrice = computed(() => {
const price = courseInfo.value.price || 0
const remain = Math.max(0, price - pointsUsedYuan.value - balanceUsedYuan.value)
return Number(remain.toFixed(2))
})
const canPay = computed(() => {
const price = courseInfo.value.price || 0
if (price === 0) return true // 免费
if (finalPrice.value > 0) return true // 第三方可支付剩余
return (usePoints.value || useBalance.value)
})
// 方法
const getSystemInfo = () => {
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight
// #ifdef MP-WEIXIN
navBarHeight.value = systemInfo.statusBarHeight + 44
// #endif
// #ifdef APP-PLUS
navBarHeight.value = systemInfo.statusBarHeight + 44
// #endif
}
const goBack = () => {
uni.navigateBack()
}
const togglePaymentMethod = (method) => {
if (method === 'points') {
usePoints.value = !usePoints.value
} else if (method === 'balance') {
useBalance.value = !useBalance.value
}
}
const order_pay_type = ref(0)
// 组合支付类型编码:
// 0免费;1余额;2积分;3余额+积分;4第三方;5余额+第三方;6积分+第三方;7余额+积分+第三方
const computeOrderPayType = () => {
const price = courseInfo.value.price || 0
// 免费
if (price === 0) return 0
const useP = usePoints.value && pointsUsedYuan.value > 0
const useB = useBalance.value && balanceUsedYuan.value > 0
const useThirdParty = finalPrice.value > 0
if (useThirdParty && useB && useP) return 7
if (useThirdParty && useB) return 5
if (useThirdParty && useP) return 6
if (useThirdParty) return 4
if (useB && useP) return 3
if (useB) return 1
if (useP) return 2
// 回退:如果都没选但也不免费,则第三方
return 4
}
const confirmPayment = () => {
if (!canPay.value) {
uni.showToast({
title: '积分/余额不足以完成支付',
icon: 'none'
})
return
}
order_pay_type.value = computeOrderPayType()
console.log('确认支付,支付方式:', { usePoints: usePoints.value, useBalance: useBalance.value, order_pay_type: order_pay_type.value })
course_api.createExcellencourseMixedOrder(courseId.value, order_pay_type.value).then(res => {
console.log('创建订单:', res)
if (res.code == 200) {
uni.redirectTo({
url: '/pages_course/course_detail/course_detail?id='+courseId.value
})
}else{
uni.showToast({
title: res.msg,
icon: 'none'
})
}
})
}
const courseId = ref(0)
const getCourseInfo = () => {
// 获取课程详情
course_api.excellencourseDetail(courseId.value).then(res => {
console.log('课程详情:', res)
if (res.code == 200) {
const data = res.data
// 根据优惠类型计算价格(单位:分)
const now = Date.now()
const beginTs = data.discount_begin_date ? new Date(data.discount_begin_date).getTime() : null
const endTs = data.discount_end_date ? new Date(data.discount_end_date).getTime() : null
let finalPriceCents = data.account || 0
if (data.discount_type === 1 && data.discount_price > 0) {
// 永久优惠
finalPriceCents = data.discount_price
} else if (data.discount_type === 2 && data.discount_price > 0) {
// 限时优惠:仅在有效期内生效
const inWindow = (!beginTs || beginTs <= now) && (!!endTs && endTs > now)
if (inWindow) finalPriceCents = data.discount_price
}
courseInfo.value = {
title: data.title || '课程标题',
price: (finalPriceCents || 0) / 100,
tags: [data.special_type_name || '精品课程'],
image: data.index_img || '/static/icon_home_my_patient.png'
}
}
}).catch(err => {
console.error('获取课程详情失败:', err)
})
// 获取用户账户信息
course_api.excellencoursePayPage(courseId.value).then(res => {
console.log('用户账户信息:', res)
if (res.code == 200) {
const data = res.data
userInfo.value = {
points: data.totalPoints || 0,
balance: data.availableBalance || 0,
pointsExchangeRate: parseInt(data.points_price_exchange) || 50
}
// 根据可用余额和积分自动选择支付方式
updatePaymentMethod()
}
}).catch(err => {
console.error('获取用户账户信息失败:', err)
})
}
// 更新支付方式选择
const updatePaymentMethod = () => {
const coursePrice = courseInfo.value.price
const pointsValue = userInfo.value.points / userInfo.value.pointsExchangeRate
const balance = userInfo.value.balance
// 如果积分足够,优先选择积分支付
if (pointsValue >= coursePrice) {
selectedMethod.value = 'points'
}
// 如果余额足够,选择余额支付
else if (balance >= coursePrice) {
selectedMethod.value = 'balance'
}
// 如果都不够,默认选择积分支付
else {
selectedMethod.value = 'points'
}
}
// 生命周期
onMounted(() => {
// 获取页面参数
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
const options = currentPage.options || {}
courseId.value = options.id
console.log('课程ID:', options.id)
getSystemInfo()
getCourseInfo()
})
</script>
<style lang="scss">
.payment-page {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 120rpx;
}
// 导航栏
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background-color: #fff;
border-bottom: 1rpx solid #e5e5e5;
.nav-content {
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
.nav-left, .nav-right {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.nav-title {
font-size: 36rpx;
font-weight: 600;
color: #FF4757; // 红色标题
}
}
}
.page-content {
padding: 24rpx;
}
// 课程信息卡片
.course-card {
background-color: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 24rpx;
display: flex;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
.course-left {
margin-right: 24rpx;
.course-image {
width: 120rpx;
height: 120rpx;
border-radius: 12rpx;
}
}
.course-right {
flex: 1;
display: flex;
flex-direction: column;
justify-content: space-between;
.course-title {
font-size: 28rpx;
color: #333;
font-weight: 500;
line-height: 1.4;
margin-bottom: 16rpx;
// 文字溢出处理
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.course-tags {
display: flex;
gap: 12rpx;
margin-bottom: 16rpx;
.tag-item {
.tag-text {
border: 1rpx solid #e0e0e0;
color: #333;
font-size: 20rpx;
padding: 6rpx 12rpx;
border-radius: 8rpx;
background-color: #fff;
}
}
}
.course-price {
font-size: 36rpx;
color: #FF4757;
font-weight: 600;
}
}
}
// 支付方式选择
.payment-methods {
background-color: #fff;
border-radius: 16rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
.method-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 24rpx;
border-bottom: 1rpx solid #f0f0f0;
transition: all 0.3s;
&:last-child {
border-bottom: none;
}
&.disabled {
opacity: 0.5;
background-color: #f8f9fa;
.method-label,
.method-value,
.method-desc {
color: #999;
}
}
.method-info {
display: flex;
flex-direction: column;
gap: 4rpx;
.method-label {
font-size: 28rpx;
color: #333;
font-weight: 500;
}
.method-value {
font-size: 24rpx;
color: #666;
}
.method-desc {
font-size: 20rpx;
color: #999;
}
}
.radio-button {
width: 40rpx;
height: 40rpx;
border: 2rpx solid #e0e0e0;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
transition: all 0.3s;
&.active {
border-color: #FF4757;
background-color: #FF4757;
}
.radio-inner {
width: 20rpx;
height: 20rpx;
background-color: #fff;
border-radius: 50%;
}
}
}
}
// 支付明细
.payment-summary {
background-color: #fff;
border-radius: 16rpx;
padding: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
.summary-item {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
&.total {
border-top: 1rpx solid #f0f0f0;
padding-top: 20rpx;
margin-top: 20rpx;
.summary-label {
font-weight: 600;
color: #333;
}
.total-price {
color: #FF4757;
font-weight: 600;
}
}
.summary-label {
font-size: 28rpx;
color: #666;
}
.summary-value {
font-size: 28rpx;
color: #333;
}
}
}
// 底部支付栏
.bottom-payment {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
background-color: #fff;
border-top: 1rpx solid #e5e5e5;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 24rpx;
z-index: 999;
.payment-left {
.final-price {
font-size: 40rpx;
color: #FF4757;
font-weight: 600;
margin-bottom: 8rpx;
}
.payment-method {
font-size: 24rpx;
color: #666;
}
}
.payment-right {
.pay-button {
background-color: #FF4757;
color: #fff;
font-size: 32rpx;
font-weight: 600;
padding: 20rpx 60rpx;
border-radius: 44rpx;
border: none;
box-shadow: 0 4rpx 16rpx rgba(255, 71, 87, 0.3);
transition: all 0.3s;
&:disabled {
background-color: #ccc;
box-shadow: none;
opacity: 0.6;
}
}
}
}
</style>
@@ -0,0 +1,284 @@
<template>
<view class="review-page">
<!-- 自定义导航栏 -->
<view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-content">
<view class="nav-left" @click="goBack">
<uni-icons type="arrowleft" size="24" color="#333"></uni-icons>
</view>
<view class="nav-title">评价课程</view>
<view class="nav-right"></view>
</view>
</view>
<!-- 页面内容 -->
<view class="page-content" :style="{ paddingTop: navBarHeight + 'px' }">
<!-- 星级评分区域 -->
<view class="rating-section">
<text class="rating-title">点击星星评分</text>
<view class="stars-container">
<view
class="star-item"
v-for="star in 5"
:key="star"
@click="setRating(star)"
>
<uni-icons
:type="star <= currentRating ? 'star-filled' : 'star'"
size="48"
:color="star <= currentRating ? '#FFB800' : '#E0E0E0'"
></uni-icons>
</view>
</view>
<text class="rating-feedback">{{ ratingFeedback }}</text>
</view>
<!-- 评价输入区域 -->
<view class="review-input-section">
<textarea
class="review-textarea"
v-model="reviewContent"
placeholder="在这里提交您对精品课的建议,帮助课程继续优化改进(300字以内)"
maxlength="300"
:show-confirm-bar="false"
></textarea>
<view class="word-count">
<text class="count-text">{{ reviewContent.length }}/300</text>
</view>
</view>
<!-- 提交按钮 -->
<view class="submit-section">
<button class="submit-btn" @click="submitReview">提交</button>
</view>
<!-- 奖励信息 -->
<view class="reward-info">
<text class="reward-text">参与评价,您将获得积分商城88积分,"精彩评价"将再额外获得88积分哦</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, onMounted } from 'vue'
// 响应式数据
const statusBarHeight = ref(0)
const navBarHeight = ref(88)
const currentRating = ref(0)
const reviewContent = ref('')
const ratingFeedback = ref('')
// 方法
const getSystemInfo = () => {
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight
// #ifdef MP-WEIXIN
navBarHeight.value = systemInfo.statusBarHeight + 44
// #endif
// #ifdef APP-PLUS
navBarHeight.value = systemInfo.statusBarHeight + 44
// #endif
}
const setRating = (rating) => {
currentRating.value = rating
updateRatingFeedback()
}
const updateRatingFeedback = () => {
const feedbacks = {
1: '差评,课程没有任何帮助',
2: '一般,课程内容一般',
3: '还行,课程有一定帮助',
4: '不错,课程内容很好',
5: '很好,课程非常棒'
}
ratingFeedback.value = feedbacks[currentRating.value] || ''
}
const submitReview = () => {
if (currentRating.value === 0) {
uni.showToast({
title: '请先选择评分',
icon: 'none'
})
return
}
if (!reviewContent.value.trim()) {
uni.showToast({
title: '请填写评价内容',
icon: 'none'
})
return
}
// 提交评价
uni.showLoading({
title: '提交中...'
})
// 模拟提交
setTimeout(() => {
uni.hideLoading()
uni.showToast({
title: '评价提交成功',
icon: 'success'
})
// 延迟返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
}, 1000)
}
const goBack = () => {
uni.navigateBack()
}
// 生命周期
onMounted(() => {
getSystemInfo()
})
</script>
<style lang="scss">
.review-page {
min-height: 100vh;
background-color: #fff;
}
// 导航栏
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background-color: #fff;
border-bottom: 1rpx solid #e5e5e5;
.nav-content {
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
.nav-left, .nav-right {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.nav-title {
font-size: 36rpx;
font-weight: 600;
color: #FF4757;
}
}
}
.page-content {
padding: 32rpx 24rpx;
}
// 星级评分区域
.rating-section {
text-align: center;
margin-bottom: 48rpx;
.rating-title {
display: block;
font-size: 28rpx;
color: #333;
margin-bottom: 32rpx;
}
.stars-container {
display: flex;
justify-content: center;
gap: 16rpx;
margin-bottom: 24rpx;
.star-item {
cursor: pointer;
}
}
.rating-feedback {
display: block;
font-size: 24rpx;
color: #333;
font-weight: 500;
}
}
// 评价输入区域
.review-input-section {
margin-bottom: 48rpx;
.review-textarea {
width: 100%;
height: 300rpx;
padding: 24rpx;
background-color: #f8f9fa;
border: 1rpx solid #e0e0e0;
border-radius: 16rpx;
font-size: 26rpx;
color: #333;
line-height: 1.6;
resize: none;
&::placeholder {
color: #999;
line-height: 1.8;
}
}
.word-count {
text-align: right;
margin-top: 16rpx;
.count-text {
font-size: 22rpx;
color: #999;
}
}
}
// 提交按钮
.submit-section {
margin-bottom: 48rpx;
.submit-btn {
width: 100%;
height: 88rpx;
background-color: #20B2AA;
color: #fff;
border: none;
border-radius: 16rpx;
font-size: 32rpx;
font-weight: 600;
display: flex;
align-items: center;
justify-content: center;
}
}
// 奖励信息
.reward-info {
text-align: center;
.reward-text {
font-size: 24rpx;
color: #333;
line-height: 1.5;
}
}
</style>
+130
View File
@@ -0,0 +1,130 @@
<template>
<view class="demo-page">
<view class="demo-header">
<text class="demo-title">课程页面演示</text>
</view>
<view class="demo-content">
<view class="demo-item" @click="goCourse">
<view class="demo-icon">📚</view>
<view class="demo-info">
<text class="demo-name">肝胆相照精品课</text>
<text class="demo-desc">100%还原设计图的课程页面</text>
</view>
<uni-icons type="arrowright" size="20" color="#999"></uni-icons>
</view>
<view class="demo-item" @click="goCourseDetail">
<view class="demo-icon">📖</view>
<view class="demo-info">
<text class="demo-name">课程详情页面</text>
<text class="demo-desc">肝硬化与重症肝病课程详情</text>
</view>
<uni-icons type="arrowright" size="20" color="#999"></uni-icons>
</view>
</view>
<view class="demo-tips">
<text class="tips-title">使用说明</text>
<text class="tips-text">1. 页面已完全按照设计图还原</text>
<text class="tips-text">2. 所有交互功能已实现</text>
<text class="tips-text">3. 需要添加对应的图片资源</text>
<text class="tips-text">4. 可根据需求连接后端API</text>
</view>
</view>
</template>
<script setup>
import { ref } from 'vue'
// 响应式数据
const goCourse = () => {
uni.navigateTo({
url: '/pages_course/course/course'
})
}
const goCourseDetail = () => {
uni.navigateTo({
url: '/pages_course/course_detail/course_detail'
})
}
</script>
<style lang="scss">
.demo-page {
padding: 40rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.demo-header {
text-align: center;
margin-bottom: 60rpx;
.demo-title {
font-size: 48rpx;
font-weight: bold;
color: #333;
}
}
.demo-content {
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
margin-bottom: 40rpx;
}
.demo-item {
padding: 40rpx;
display: flex;
align-items: center;
.demo-icon {
font-size: 48rpx;
margin-right: 24rpx;
}
.demo-info {
flex: 1;
.demo-name {
display: block;
font-size: 32rpx;
color: #333;
font-weight: 500;
margin-bottom: 8rpx;
}
.demo-desc {
font-size: 26rpx;
color: #666;
}
}
}
.demo-tips {
background-color: #fff;
border-radius: 16rpx;
padding: 40rpx;
box-shadow: 0 4rpx 12rpx rgba(0,0,0,0.1);
.tips-title {
display: block;
font-size: 32rpx;
color: #333;
font-weight: 500;
margin-bottom: 24rpx;
}
.tips-text {
display: block;
font-size: 28rpx;
color: #666;
line-height: 1.6;
margin-bottom: 16rpx;
}
}
</style>
+451
View File
@@ -0,0 +1,451 @@
<template>
<view class="my-courses-page">
<!-- 自定义导航栏 -->
<view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-content">
<view class="nav-left" @click="goBack">
<uni-icons type="arrowleft" size="24" color="#FF4757"></uni-icons>
</view>
<view class="nav-title">我的课程</view>
<view class="nav-right" @click="goSearch">
<uni-icons type="search" size="24" color="#FF4757"></uni-icons>
</view>
</view>
</view>
<!-- 页面内容 -->
<view class="page-content" :style="{ paddingTop: navBarHeight + 'px' }">
<!-- 标签导航 -->
<view class="tab-nav">
<view class="tab-item" :class="{ active: activeTab === 'learning' }" @click="switchTab('learning')">
<text class="tab-text">学习中</text>
</view>
<view class="tab-divider"></view>
<view class="tab-item" :class="{ active: activeTab === 'completed' }" @click="switchTab('completed')">
<text class="tab-text">已学完</text>
</view>
</view>
<!-- 课程列表 -->
<view class="course-list">
<view class="course-item" v-for="(course, index) in currentCourseList" :key="course.id" @click="goCourseDetail(course)">
<view class="course-left">
<image :src="course.image" mode="aspectFill" class="course-image"></image>
</view>
<view class="course-right">
<text class="course-title">{{ course.title }}</text>
<view class="course-meta">
<text class="meta-text">{{ course.lessonCount }}</text>
<text class="meta-separator"></text>
<text class="meta-text">{{ course.status }}</text>
<text class="meta-separator"></text>
<text class="meta-text">已学{{ course.learnedCount }}</text>
</view>
<view class="course-tags" v-if="course.tags && course.tags.length > 0">
<view class="tag-item" v-for="tag in course.tags" :key="tag.id">
<text class="tag-text">{{ tag.text }}</text>
</view>
</view>
</view>
</view>
</view>
<!-- 订单记录浮动按钮 -->
<view class="floating-order" @click="goOrderRecord">
<view class="order-icon">
<uni-icons type="list" size="24" color="#fff"></uni-icons>
</view>
<text class="order-text">订单记录</text>
</view>
</view>
<!-- 底部导航栏 -->
<view class="bottom-nav">
<view class="nav-item" @click="goPremiumCourses">
<uni-icons type="book" size="24" color="#999"></uni-icons>
<text class="nav-text">精品课</text>
</view>
<view class="nav-item active" @click="goMyCourses">
<view class="nav-icon-active">
<uni-icons type="play" size="24" color="#fff"></uni-icons>
</view>
<text class="nav-text active">我的课程</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
// 响应式数据
const statusBarHeight = ref(0)
const navBarHeight = ref(88)
const activeTab = ref('learning') // 默认激活"学习中"标签
// 课程数据
const courseList = ref({
learning: [
{
id: 1,
title: '找到已购买课程即学习找到已购买课程即可学习',
lessonCount: 26,
status: '更新10节',
learnedCount: 3,
image: '/static/icon_home_my_patient.png',
tags: []
},
{
id: 2,
title: '找到已购买课程即学习找到已购买课程即可学习',
lessonCount: 26,
status: '已完结',
learnedCount: 3,
image: '/static/icon_home_my_library.png',
tags: []
},
{
id: 3,
title: '找到已购买课程即学习找到已购买课程即可学习',
lessonCount: 26,
status: '已完结',
learnedCount: 3,
image: '/static/icon_home_my_patient.png',
tags: [
{ id: 1, text: '福利课堂' }
]
},
{
id: 4,
title: '找到已购买课程即学习找到已购买课程即可学习',
lessonCount: 26,
status: '已完结',
learnedCount: 3,
image: '/static/icon_home_my_patient.png',
tags: [
{ id: 2, text: '学完返现' }
]
}
],
completed: [
{
id: 5,
title: '肝硬化与重症肝病诊疗指南',
lessonCount: 15,
status: '已完结',
learnedCount: 15,
image: '/static/icon_home_my_library.png',
tags: [
{ id: 3, text: '学完返现' }
]
},
{
id: 6,
title: '慢性肝病营养治疗实践',
lessonCount: 12,
status: '已完结',
learnedCount: 12,
image: '/static/icon_home_video.png',
tags: []
}
]
})
// 计算属性
const currentCourseList = computed(() => {
return courseList.value[activeTab.value] || []
})
// 方法
const getSystemInfo = () => {
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight
// #ifdef MP-WEIXIN
navBarHeight.value = systemInfo.statusBarHeight + 44
// #endif
// #ifdef APP-PLUS
navBarHeight.value = systemInfo.statusBarHeight + 44
// #endif
}
const switchTab = (tab) => {
activeTab.value = tab
console.log('切换到标签:', tab)
}
const goCourseDetail = (course) => {
console.log('进入课程详情:', course.title)
uni.navigateTo({
url: '/pages_course/course_detail/course_detail'
})
}
const goOrderRecord = () => {
console.log('进入订单记录')
uni.navigateTo({
url: '/pages_course/order_record/order_record'
})
}
const goBack = () => {
uni.navigateBack()
}
const goSearch = () => {
console.log('进入筛选页面')
uni.navigateTo({
url: '/pages_course/course_filter/course_filter'
})
}
const goPremiumCourses = () => {
uni.navigateTo({
url: '/pages_course/course/course'
})
}
const goMyCourses = () => {
// 当前页面,无需跳转
console.log('当前已在我的课程页面')
}
// 生命周期
onMounted(() => {
getSystemInfo()
})
</script>
<style lang="scss">
.my-courses-page {
min-height: 100vh;
background-color: #f5f5f5;
padding-bottom: 120rpx;
}
// 导航栏
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background-color: #fff;
border-bottom: 1rpx solid #e5e5e5;
.nav-content {
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
.nav-left, .nav-right {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.nav-title {
font-size: 36rpx;
font-weight: 600;
color: #FF4757;
}
}
}
.page-content {
background-color: #f5f5f5;
}
// 标签导航
.tab-nav {
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx 0;
position: relative;
.tab-item {
padding: 16rpx 32rpx;
position: relative;
.tab-text {
font-size: 28rpx;
color: #999;
transition: color 0.3s;
}
&.active {
.tab-text {
color: #FF4757;
font-weight: 600;
}
}
}
.tab-divider {
width: 2rpx;
height: 32rpx;
background-color: #e0e0e0;
margin: 0 16rpx;
}
}
// 课程列表
.course-list {
padding: 24rpx;
.course-item {
background-color: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 24rpx;
display: flex;
align-items: flex-start;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
.course-left {
margin-right: 24rpx;
.course-image {
width: 120rpx;
height: 120rpx;
border-radius: 12rpx;
}
}
.course-right {
flex: 1;
.course-title {
display: block;
font-size: 28rpx;
color: #333;
font-weight: 500;
line-height: 1.4;
margin-bottom: 16rpx;
// 文字溢出处理
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.course-meta {
display: flex;
align-items: center;
margin-bottom: 16rpx;
.meta-text {
font-size: 24rpx;
color: #666;
}
.meta-separator {
font-size: 24rpx;
color: #999;
margin: 0 12rpx;
}
}
.course-tags {
display: flex;
gap: 12rpx;
.tag-item {
.tag-text {
background-color: #FF4757;
color: #fff;
font-size: 20rpx;
padding: 6rpx 12rpx;
border-radius: 8rpx;
}
}
}
}
}
}
// 订单记录浮动按钮
.floating-order {
position: fixed;
right: 24rpx;
bottom: 200rpx;
display: flex;
align-items: center;
gap: 12rpx;
background-color: #20B2AA;
padding: 16rpx 20rpx;
border-radius: 32rpx;
box-shadow: 0 4rpx 16rpx rgba(32, 178, 170, 0.3);
z-index: 999;
.order-icon {
width: 48rpx;
height: 48rpx;
background-color: rgba(255, 255, 255, 0.2);
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
.order-text {
font-size: 24rpx;
color: #fff;
font-weight: 500;
}
}
// 底部导航栏
.bottom-nav {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 120rpx;
background-color: #fff;
border-top: 1rpx solid #e5e5e5;
display: flex;
align-items: center;
z-index: 999;
.nav-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 16rpx 0;
.nav-text {
font-size: 22rpx;
color: #999;
margin-top: 8rpx;
&.active {
color: #FF4757;
}
}
.nav-icon-active {
width: 48rpx;
height: 48rpx;
background-color: #FF4757;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
}
&.active {
.nav-text {
color: #FF4757;
}
}
}
}
</style>
+580
View File
@@ -0,0 +1,580 @@
<template>
<view class="order-record-page">
<!-- 自定义导航栏 -->
<view class="navbar" :style="{ paddingTop: statusBarHeight + 'px' }">
<view class="nav-content">
<view class="nav-left" @click="goBack">
<uni-icons type="arrowleft" size="24" color="#333"></uni-icons>
</view>
<view class="nav-title">订单记录</view>
<view class="nav-right"></view>
</view>
</view>
<!-- 页面内容 -->
<view class="page-content" :style="{ paddingTop: navBarHeight + 'px' }">
<!-- 标签导航 -->
<view class="tab-nav">
<view class="tab-item" :class="{ active: activeTab === 'paid' }" @click="switchTab('paid')">
<text class="tab-text">已支付</text>
</view>
<view class="tab-divider"></view>
<view class="tab-item" :class="{ active: activeTab === 'unpaid' }" @click="switchTab('unpaid')">
<text class="tab-text">未支付</text>
</view>
</view>
<!-- 订单列表 -->
<view class="order-list">
<view v-if="currentOrderList.length > 0">
<view class="order-item" v-for="(order, index) in currentOrderList" :key="order.id">
<!-- 订单号 -->
<view class="order-header">
<text class="order-number">订单号: {{ order.orderNumber }}</text>
<!-- <text class="status-pill" :class="order.status">{{ order.status === 'paid' ? '已支付' : '未支付' }}</text> -->
<button class="copy-btn" @click="copyOrderNumber(order.orderNumber)">
<uni-icons type="copy" size="18" color="#666"></uni-icons>
<text class="btn-text">复制</text>
</button>
</view>
<!-- 课程名称 -->
<view class="course-name">
<text class="course-title">课程名称: {{ order.courseName }}</text>
</view>
<!-- 下单时间 -->
<view class="order-time">
<text class="time-text">下单时间: {{ order.orderTime }}</text>
</view>
<!-- 课程金额 -->
<view class="course-amount">
<text class="amount-text">课程金额: ¥{{ order.courseAmount }}</text>
</view>
<!-- 支付方式信息 -->
<view class="payment-info" v-if="order.balancePayment">
<text class="payment-text">余额支付: -¥{{ order.balancePayment }}</text>
</view>
<view class="payment-info" v-if="order.pointsPayment">
<text class="payment-text">积分支付: -{{ order.pointsPayment }}积分 </text>
</view>
<!-- 实付金额 -->
<view class="actual-payment">
<text class="actual-text">实付金额: ¥{{ order.actualPayment }}</text>
</view>
<!-- 剩余时间仅未支付订单显示 -->
<view class="remaining-time" v-if="order.remainingTime && activeTab === 'unpaid'">
<text class="time-text">剩余时间: {{ order.remainingTime }}</text>
</view>
<!-- 操作按钮 -->
<view class="action-buttons">
<button class="action-btn secondary" @click="handleSecondaryAction(order)">
<uni-icons type="eye" size="18" color="#555"></uni-icons>
<text>{{ getSecondaryButtonText(order.status) }}</text>
</button>
<button class="action-btn primary" @click="handlePrimaryAction(order)">
<uni-icons type="arrowright" size="18" color="#fff"></uni-icons>
<text>{{ getPrimaryButtonText(order.status) }}</text>
</button>
</view>
</view>
</view>
<view class="empty" v-else>
<uni-icons type="cart" size="56" color="#E0E0E0"></uni-icons>
<text class="empty-text">暂无订单</text>
</view>
</view>
</view>
</view>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue'
import course_api from "@/api/course_api.js"
// 响应式数据
const statusBarHeight = ref(0)
const navBarHeight = ref(88)
const activeTab = ref('paid') // 默认激活"已支付"标签
// 订单数据
const orderList = ref({
paid: [
{
id: 1,
orderNumber: 'XXXXXXXXXXXXXXXXXXXXXXXXXXX',
courseName: '找到已购买课程即可学习找到学习找到到到已到已购买',
orderTime: '2020.30.21 12:23:00',
courseAmount: '62.00',
balancePayment: '50.00',
actualPayment: '12.00',
status: 'paid'
},
{
id: 2,
orderNumber: 'XXXXXXXXXXXXXXXXXXXXXXXXXXX',
courseName: '找到已购买课程即可学习找到学习找到到到已到已购买',
orderTime: '2020.30.21 12:23:00',
courseAmount: '62.00',
balancePayment: '50.00',
actualPayment: '12.00',
status: 'paid'
}
],
unpaid: [
{
id: 3,
orderNumber: 'XXXXXXXXXXXXXXXXXXXXXXXXXXX',
courseName: '找到已购买课程即可学习找到学习找到到到已到已购买',
orderTime: '2020.30.21 12:23:00',
courseAmount: '62.00',
balancePayment: '50.00',
actualPayment: '12.00',
remainingTime: '12:23:00',
status: 'pending'
},
{
id: 4,
orderNumber: 'XXXXXXXXXXXXXXXXXXXXXXXXXXX',
courseName: '找到已购买课程即可学习找到学习找到到到已到已购买',
orderTime: '2020.30.21 12:23:00',
courseAmount: '62.00',
balancePayment: '50.00',
actualPayment: '12.00',
remainingTime: '12:23:00',
status: 'timeout'
},
{
id: 5,
orderNumber: 'XXXXXXXXXXXXXXXXXXXXXXXXXXX',
courseName: '找到已购买课程即可学习找到学习找到到到已到已购买',
orderTime: '2020.30.21 12:23:00',
courseAmount: '62.00',
pointsPayment: '200',
balancePayment: '42.00',
actualPayment: '0.00',
status: 'failed'
}
]
})
// 计算属性
const currentOrderList = computed(() => {
return orderList.value[activeTab.value] || []
})
// 方法
const getSystemInfo = () => {
const systemInfo = uni.getSystemInfoSync()
statusBarHeight.value = systemInfo.statusBarHeight
// #ifdef MP-WEIXIN
navBarHeight.value = systemInfo.statusBarHeight + 44
// #endif
// #ifdef APP-PLUS
navBarHeight.value = systemInfo.statusBarHeight + 44
// #endif
}
const goBack = () => {
uni.navigateBack()
}
const switchTab = (tab) => {
activeTab.value = tab
console.log('切换到标签:', tab)
getOrderList()
}
const copyOrderNumber = (orderNumber) => {
// 复制订单号到剪贴板
uni.setClipboardData({
data: orderNumber,
success: () => {
uni.showToast({
title: '订单号已复制',
icon: 'success'
})
}
})
}
const getSecondaryButtonText = (status) => {
switch (status) {
case 'pending':
return '取消订单'
case 'timeout':
return '支付超时'
case 'failed':
return '支付失败'
default:
return '查看订单'
}
}
const getPrimaryButtonText = (status) => {
switch (status) {
case 'pending':
return '继续支付'
case 'timeout':
case 'failed':
return '查看课程'
default:
return '进入学习'
}
}
const handleSecondaryAction = (order) => {
switch (order.status) {
case 'pending':
cancelOrder(order)
break
case 'timeout':
case 'failed':
viewOrder(order)
break
default:
viewOrder(order)
}
}
const handlePrimaryAction = (order) => {
switch (order.status) {
case 'pending':
continuePayment(order)
break
case 'timeout':
case 'failed':
viewCourse(order)
break
default:
enterLearning(order)
}
}
const cancelOrder = (order) => {
uni.showModal({
title: '确认取消',
content: '确定要取消这个订单吗?',
success: (res) => {
if (res.confirm) {
uni.showToast({
title: '订单已取消',
icon: 'success'
})
// 这里可以调用API取消订单
}
}
})
}
const continuePayment = (order) => {
console.log('继续支付订单:', order.id)
uni.navigateTo({
url: '/pages_course/course_payment/course_payment'
})
}
const viewCourse = (order) => {
console.log('查看课程:', order.id)
uni.navigateTo({
url: '/pages_course/course_detail/course_detail'
})
}
const enterLearning = (order) => {
console.log('进入学习:', order.id)
uni.navigateTo({
url: '/pages_course/course_detail/course_detail'
})
}
const viewOrder = (order) => {
console.log('查看订单详情:', order.id)
uni.showToast({
title: '订单详情功能',
icon: 'none'
})
}
const getOrderList = () => {
course_api.listExcellencourseOrder(activeTab.value, 1).then(res => {
console.log('订单列表:', res)
if (res.code === 200) {
const data = res.data || {}
const list = data.list || []
const toYuan = (v) => (Number(v || 0) / 100).toFixed(2)
const formatTs = (sec) => sec ? new Date(sec * 1000).toLocaleString('zh-CN') : ''
const includesBalance = (t) => [1,3,5,7].includes(Number(t))
const includesPoints = (t) => [2,3,6,7].includes(Number(t))
const mapped = list.map(item => {
let balanceCents = 0
let points = 0
;(item.childOrder || []).forEach(child => {
const t = Number(child.order_pay_type)
if (includesBalance(t)) balanceCents += Number(child.account || 0)
if (includesPoints(t)) points += Number(child.bonuspoints || 0)
})
return {
id: item.id,
orderNumber: item.order_id || item.uuid || '-',
courseName: item.excellencourse_title || '-',
orderTime: formatTs(item.create_date),
courseAmount: toYuan(item.account),
balancePayment: balanceCents > 0 ? toYuan(balanceCents) : null,
pointsPayment: points > 0 ? points : null,
actualPayment: toYuan(item.pay_account),
status: item.order_status === 'paid' ? 'paid' : 'created'
}
})
orderList.value[activeTab.value] = mapped
} else {
uni.showToast({ title: res.msg || '获取订单失败', icon: 'none' })
}
}).catch(() => {
uni.showToast({ title: '获取订单失败', icon: 'none' })
})
}
// 生命周期
onMounted(() => {
getSystemInfo()
getOrderList()
})
</script>
<style lang="scss">
.order-record-page {
min-height: 100vh;
background-color: #f5f5f5;
}
// 导航栏
.navbar {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 999;
background-color: #fff;
border-bottom: 1rpx solid #e5e5e5;
.nav-content {
height: 88rpx;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 32rpx;
.nav-left, .nav-right {
width: 60rpx;
height: 60rpx;
display: flex;
align-items: center;
justify-content: center;
}
.nav-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
}
}
.page-content {
background-color: #f5f5f5;
}
// 标签导航
.tab-nav {
background-color: #fff;
display: flex;
align-items: center;
justify-content: center;
padding: 24rpx 0;
position: relative;
.tab-item {
padding: 16rpx 32rpx;
position: relative;
.tab-text {
font-size: 28rpx;
color: #999;
transition: color 0.3s;
}
&.active {
.tab-text {
color: #333;
font-weight: 600;
}
}
}
.tab-divider {
width: 2rpx;
height: 32rpx;
background-color: #e0e0e0;
margin: 0 16rpx;
}
}
// 订单列表
.order-list {
padding: 24rpx;
}
.empty {
padding: 120rpx 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 16rpx;
.empty-text {
font-size: 26rpx;
color: #999;
}
}
.order-item {
background-color: #fff;
border-radius: 16rpx;
padding: 24rpx;
margin-bottom: 24rpx;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.1);
// 订单号
.order-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
.order-number {
font-size: 26rpx;
color: #333;
font-weight: 500;
}
.copy-btn {
background: transparent;
color: #666;
border: none;
padding: 8rpx 12rpx;
border-radius: 6rpx;
font-size: 24rpx;
line-height: 1;
transition: opacity 0.15s ease;
&:active { opacity: 0.6; }
}
}
// 课程名称
.course-name {
margin-bottom: 16rpx;
.course-title {
font-size: 26rpx;
color: #333;
line-height: 1.4;
}
}
// 下单时间
.order-time {
margin-bottom: 16rpx;
.time-text {
font-size: 24rpx;
color: #666;
}
}
// 课程金额
.course-amount {
margin-bottom: 16rpx;
.amount-text {
font-size: 24rpx;
color: #666;
}
}
// 支付方式信息
.payment-info {
margin-bottom: 16rpx;
.payment-text {
font-size: 24rpx;
color: #666;
}
}
// 实付金额
.actual-payment {
margin-bottom: 16rpx;
.actual-text {
font-size: 26rpx;
color: #FF4757;
font-weight: 600;
}
}
// 剩余时间
.remaining-time {
margin-bottom: 16rpx;
.time-text {
font-size: 24rpx;
color: #FF6B35;
font-weight: 500;
}
}
// 操作按钮
.action-buttons {
display: flex;
justify-content: flex-end;
gap: 16rpx;
margin-top: 24rpx;
.action-btn {
min-height: 64rpx;
padding: 0 28rpx;
border-radius: 12rpx;
font-size: 26rpx;
border: 1rpx solid transparent;
line-height: 64rpx;
transition: transform 0.08s ease, box-shadow 0.2s ease;
&:active { transform: scale(0.98); }
&.secondary {
background-color: #fff;
color: #555;
border-color: #e6e6e6;
box-shadow: 0 2rpx 8rpx rgba(0,0,0,0.04);
}
&.primary {
background-color: #FF4757;
color: #fff;
border-color: #FF4757;
box-shadow: 0 6rpx 14rpx rgba(255, 71, 87, 0.18);
}
}
}
}
</style>