This commit is contained in:
jeremygan2021
2026-02-11 04:06:51 +08:00
parent 96d5598fb5
commit 1100143a6e
36 changed files with 1223 additions and 401 deletions

View File

@@ -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;
}

View File

@@ -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>
)
}

View File

@@ -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>
))

View File

@@ -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)
})