finish
This commit is contained in:
@@ -1,74 +1,256 @@
|
||||
.page-container {
|
||||
padding: 20px;
|
||||
background-color: #000;
|
||||
min-height: 100vh;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.title {
|
||||
color: #fff;
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.meta-info {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 20px;
|
||||
margin-bottom: 30px;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.tag {
|
||||
background: rgba(0, 240, 255, 0.2);
|
||||
color: #00f0ff;
|
||||
padding: 6px 16px;
|
||||
border-radius: 4px;
|
||||
font-size: 24px;
|
||||
border: 1px solid #00f0ff;
|
||||
.scroll-content {
|
||||
flex: 1;
|
||||
height: calc(100vh - 100px); /* 留出底部栏高度 */
|
||||
}
|
||||
|
||||
.cover-image {
|
||||
width: 100%;
|
||||
height: 420px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
padding: 30px;
|
||||
background: #000;
|
||||
border-radius: 30px 30px 0 0;
|
||||
margin-top: -30px;
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.header-section {
|
||||
margin-bottom: 40px;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
padding-bottom: 30px;
|
||||
|
||||
.title {
|
||||
color: #fff;
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.info {
|
||||
color: #888;
|
||||
font-size: 26px;
|
||||
.tags-row {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 20px;
|
||||
|
||||
.tag {
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
color: #aaa;
|
||||
padding: 6px 16px;
|
||||
border-radius: 8px;
|
||||
font-size: 24px;
|
||||
|
||||
&.highlight {
|
||||
background: rgba(0, 240, 255, 0.2);
|
||||
color: #00f0ff;
|
||||
border: 1px solid rgba(0, 240, 255, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.price {
|
||||
font-size: 48px;
|
||||
color: #00f0ff;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.desc {
|
||||
.section {
|
||||
margin-bottom: 50px;
|
||||
|
||||
.section-title {
|
||||
color: #fff;
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 24px;
|
||||
display: block;
|
||||
position: relative;
|
||||
padding-left: 20px;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 8px;
|
||||
bottom: 8px;
|
||||
width: 6px;
|
||||
background: #00f0ff;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.instructor-section {
|
||||
.instructor-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background: #111;
|
||||
padding: 20px;
|
||||
border-radius: 16px;
|
||||
|
||||
.avatar-placeholder {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
background: #333;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-right: 20px;
|
||||
flex-shrink: 0;
|
||||
|
||||
text {
|
||||
color: #666;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
border-radius: 50%;
|
||||
margin-right: 20px;
|
||||
border: 2px solid #333;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.instructor-info {
|
||||
flex: 1;
|
||||
.name {
|
||||
color: #fff;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.title-tag {
|
||||
font-size: 20px;
|
||||
color: #000;
|
||||
background: #00f0ff;
|
||||
padding: 2px 10px;
|
||||
border-radius: 8px;
|
||||
margin-left: 10px;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
.desc {
|
||||
color: #888;
|
||||
font-size: 24px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-grid {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
background: #111;
|
||||
padding: 30px;
|
||||
border-radius: 16px;
|
||||
|
||||
.grid-item {
|
||||
text-align: center;
|
||||
flex: 1;
|
||||
border-right: 1px solid rgba(255, 255, 255, 0.1);
|
||||
|
||||
&:last-child {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: #666;
|
||||
font-size: 24px;
|
||||
margin-bottom: 10px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.value {
|
||||
color: #fff;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.desc-text {
|
||||
color: #aaa;
|
||||
font-size: 28px;
|
||||
margin-bottom: 40px;
|
||||
display: block;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.course-placeholder {
|
||||
width: 100%;
|
||||
height: 500px;
|
||||
.detail-images {
|
||||
.detail-long-image {
|
||||
width: 100%;
|
||||
border-radius: 16px;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.placeholder-box {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
background: #111;
|
||||
border: 2px dashed #333;
|
||||
border-radius: 16px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #666;
|
||||
}
|
||||
}
|
||||
|
||||
.bottom-bar {
|
||||
height: 120px;
|
||||
background: #111;
|
||||
border: 2px dashed #333;
|
||||
border-radius: 16px;
|
||||
border-top: 1px solid #222;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
padding: 0 30px;
|
||||
padding-bottom: constant(safe-area-inset-bottom);
|
||||
padding-bottom: env(safe-area-inset-bottom);
|
||||
|
||||
.icon {
|
||||
font-size: 80px;
|
||||
color: #444;
|
||||
margin-bottom: 20px;
|
||||
.price-container {
|
||||
flex: 1;
|
||||
|
||||
.label {
|
||||
color: #aaa;
|
||||
font-size: 24px;
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
||||
.amount {
|
||||
color: #00f0ff;
|
||||
font-size: 40px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.text {
|
||||
color: #666;
|
||||
font-size: 28px;
|
||||
.btn-buy {
|
||||
width: 240px;
|
||||
height: 80px;
|
||||
line-height: 80px;
|
||||
background: linear-gradient(90deg, #00f0ff, #0099ff);
|
||||
color: #000;
|
||||
font-size: 30px;
|
||||
font-weight: bold;
|
||||
border-radius: 40px;
|
||||
border: none;
|
||||
margin: 0;
|
||||
|
||||
&::after {
|
||||
border: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.btn-launch {
|
||||
margin-top: 60px;
|
||||
background: #00f0ff;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
border-radius: 45px;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { View, Text, Button, Image } from '@tarojs/components'
|
||||
import { View, Text, Button, Image, ScrollView } from '@tarojs/components'
|
||||
import Taro, { useLoad } from '@tarojs/taro'
|
||||
import { useState } from 'react'
|
||||
import { getVBCourseDetail } from '../../api'
|
||||
@@ -31,9 +31,9 @@ export default function CourseDetail() {
|
||||
}
|
||||
|
||||
const handleLaunch = () => {
|
||||
Taro.showToast({
|
||||
title: '课程内容准备中',
|
||||
icon: 'none'
|
||||
if (!detail) return
|
||||
Taro.navigateTo({
|
||||
url: `/pages/order/checkout?id=${detail.id}&type=course`
|
||||
})
|
||||
}
|
||||
|
||||
@@ -42,29 +42,89 @@ export default function CourseDetail() {
|
||||
|
||||
return (
|
||||
<View className='page-container'>
|
||||
<Text className='title'>{detail.title}</Text>
|
||||
|
||||
<View className='meta-info'>
|
||||
<Text className='tag'>{typeMap[detail.course_type] || '软件课程'}</Text>
|
||||
<Text className='info'>讲师: {detail.instructor}</Text>
|
||||
<Text className='info'>时长: {detail.duration}</Text>
|
||||
<Text className='info'>课时: {detail.lesson_count}</Text>
|
||||
</View>
|
||||
|
||||
<Text className='desc'>{detail.description}</Text>
|
||||
|
||||
<View className='course-placeholder'>
|
||||
{detail.display_detail_image ? (
|
||||
<Image src={detail.display_detail_image} style={{ width: '100%', height: '100%', borderRadius: '16px' }} mode='widthFix' />
|
||||
) : (
|
||||
<>
|
||||
<Text className='icon'>📚</Text>
|
||||
<Text className='text'>课程大纲与视频内容加载区域</Text>
|
||||
</>
|
||||
<ScrollView scrollY className='scroll-content'>
|
||||
{/* 封面图 */}
|
||||
{detail.cover_image_url && (
|
||||
<Image src={detail.cover_image_url} className='cover-image' mode='aspectFill' />
|
||||
)}
|
||||
</View>
|
||||
|
||||
<Button className='btn-launch' onClick={handleLaunch}>开始学习</Button>
|
||||
<View className='content-wrapper'>
|
||||
{/* 标题区 */}
|
||||
<View className='header-section'>
|
||||
<Text className='title'>{detail.title}</Text>
|
||||
<View className='tags-row'>
|
||||
<Text className='tag'>{typeMap[detail.course_type] || 'VB课程'}</Text>
|
||||
{detail.tag && <Text className='tag highlight'>{detail.tag}</Text>}
|
||||
</View>
|
||||
<Text className='price'>¥{detail.price}</Text>
|
||||
</View>
|
||||
|
||||
{/* 讲师信息 */}
|
||||
<View className='section instructor-section'>
|
||||
<Text className='section-title'>讲师介绍</Text>
|
||||
<View className='instructor-row'>
|
||||
{detail.instructor_avatar_url ? (
|
||||
<Image src={detail.instructor_avatar_url} className='avatar' mode='aspectFill' />
|
||||
) : (
|
||||
<View className='avatar-placeholder'>
|
||||
<Text>讲师</Text>
|
||||
</View>
|
||||
)}
|
||||
<View className='instructor-info'>
|
||||
<Text className='name'>{detail.instructor} <Text className='title-tag'>{detail.instructor_title}</Text></Text>
|
||||
<Text className='desc'>{detail.instructor_desc}</Text>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 课程信息 */}
|
||||
<View className='section info-grid'>
|
||||
<View className='grid-item'>
|
||||
<Text className='label'>时长</Text>
|
||||
<Text className='value'>{detail.duration}</Text>
|
||||
</View>
|
||||
<View className='grid-item'>
|
||||
<Text className='label'>课时</Text>
|
||||
<Text className='value'>{detail.lesson_count}节</Text>
|
||||
</View>
|
||||
<View className='grid-item'>
|
||||
<Text className='label'>难度</Text>
|
||||
<Text className='value'>中级</Text>
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{/* 课程简介 */}
|
||||
<View className='section'>
|
||||
<Text className='section-title'>课程简介</Text>
|
||||
<Text className='desc-text'>{detail.description}</Text>
|
||||
</View>
|
||||
|
||||
{/* 详情长图 */}
|
||||
<View className='section detail-images'>
|
||||
<Text className='section-title'>课程详情</Text>
|
||||
{detail.display_detail_image || detail.detail_image_url ? (
|
||||
<Image
|
||||
src={detail.detail_image_url || detail.display_detail_image}
|
||||
className='detail-long-image'
|
||||
mode='widthFix'
|
||||
/>
|
||||
) : (
|
||||
<View className='placeholder-box'>
|
||||
<Text>暂无详情长图</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
</ScrollView>
|
||||
|
||||
{/* 底部栏 */}
|
||||
<View className='bottom-bar'>
|
||||
<View className='price-container'>
|
||||
<Text className='label'>总价:</Text>
|
||||
<Text className='amount'>¥{detail.price}</Text>
|
||||
</View>
|
||||
<Button className='btn-buy' onClick={handleLaunch}>立即报名</Button>
|
||||
</View>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ export default function CourseIndex() {
|
||||
<View className='content'>
|
||||
<Text className='item-title'>{item.title}</Text>
|
||||
<Text className='item-desc'>{item.description}</Text>
|
||||
<Button className='btn-start'>开始学习</Button>
|
||||
<Button className='btn-start' onClick={(e) => { e.stopPropagation(); goDetail(item.id) }}>报名课程</Button>
|
||||
</View>
|
||||
</View>
|
||||
))
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { View, Text, Image, ScrollView, Button } from '@tarojs/components'
|
||||
import Taro, { useRouter, useLoad } from '@tarojs/taro'
|
||||
import { useState, useMemo } from 'react'
|
||||
import { getConfigDetail, createOrder } from '../../api'
|
||||
import { getConfigDetail, createOrder, getVBCourseDetail } from '../../api'
|
||||
import { getSelectedItems, removeItem } from '../../utils/cart'
|
||||
import './checkout.scss'
|
||||
|
||||
@@ -34,15 +34,28 @@ export default function Checkout() {
|
||||
setLoading(false)
|
||||
} else if (params.id) {
|
||||
try {
|
||||
const res = await getConfigDetail(params.id)
|
||||
setItems([{
|
||||
id: res.id,
|
||||
name: res.name,
|
||||
price: res.price,
|
||||
image: res.static_image_url || res.detail_image_url,
|
||||
quantity: Number(params.quantity) || 1,
|
||||
description: res.description
|
||||
}])
|
||||
let res: any = null
|
||||
if (params.type === 'course') {
|
||||
res = await getVBCourseDetail(Number(params.id))
|
||||
setItems([{
|
||||
id: res.id,
|
||||
name: res.title,
|
||||
price: res.price,
|
||||
image: res.cover_image_url || res.detail_image_url,
|
||||
quantity: 1,
|
||||
description: res.description
|
||||
}])
|
||||
} else {
|
||||
res = await getConfigDetail(params.id)
|
||||
setItems([{
|
||||
id: res.id,
|
||||
name: res.name,
|
||||
price: res.price,
|
||||
image: res.static_image_url || res.detail_image_url,
|
||||
quantity: Number(params.quantity) || 1,
|
||||
description: res.description
|
||||
}])
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
Taro.showToast({ title: '商品加载失败', icon: 'none' })
|
||||
@@ -93,7 +106,8 @@ export default function Checkout() {
|
||||
quantity: item.quantity,
|
||||
customer_name: address.userName,
|
||||
phone_number: address.telNumber,
|
||||
shipping_address: `${address.provinceName}${address.cityName}${address.countyName}${address.detailInfo}`
|
||||
shipping_address: `${address.provinceName}${address.cityName}${address.countyName}${address.detailInfo}`,
|
||||
type: params.type || 'config'
|
||||
}
|
||||
return createOrder(orderData)
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user