peixue-dev/peidu/uniapp/user-package/pages/timecard/purchase.vue

477 lines
11 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="purchase-page">
<!-- 时卡套餐列表 -->
<view class="package-list">
<view class="section-title">选择时卡套餐</view>
<view
v-for="pkg in packages"
:key="pkg.type"
class="package-item"
:class="{ active: selectedPackage === pkg.type }"
@click="selectPackage(pkg)">
<view class="package-header">
<view class="package-name">{{ pkg.name }}</view>
<view class="package-badge" v-if="pkg.recommended">推荐</view>
</view>
<view class="package-hours">{{ pkg.hours }}小时</view>
<view class="package-price">
<text class="price-symbol">¥</text>
<text class="price-value">{{ pkg.price }}</text>
<text class="price-unit">/套餐</text>
</view>
<view class="package-desc">{{ pkg.description }}</view>
<view class="package-features">
<view v-for="(feature, index) in pkg.features" :key="index" class="feature-item">
<text class="feature-icon">✓</text>
<text class="feature-text">{{ feature }}</text>
</view>
</view>
</view>
</view>
<!-- 服务配置 -->
<view class="config-section">
<view class="section-title">服务配置</view>
<view class="config-item">
<text class="config-label">服务类型</text>
<picker mode="multiSelector" :range="serviceTypes" @change="onServiceTypeChange">
<view class="config-value">
{{ selectedServiceTypes.length > 0 ? selectedServiceTypes.join('、') : '请选择' }}
<text class="arrow">></text>
</view>
</picker>
</view>
<view class="config-item">
<text class="config-label">教师等级</text>
<picker :range="teacherLevels" range-key="label" @change="onTeacherLevelChange">
<view class="config-value">
{{ selectedTeacherLevel.label || '请选择' }}
<text class="arrow">></text>
</view>
</picker>
</view>
<view class="config-item">
<text class="config-label">服务方式</text>
<picker :range="serviceModes" range-key="label" @change="onServiceModeChange">
<view class="config-value">
{{ selectedServiceMode.label || '请选择' }}
<text class="arrow">></text>
</view>
</picker>
</view>
</view>
<!-- 价格明细 -->
<view class="price-detail">
<view class="detail-row">
<text class="detail-label">套餐价格</text>
<text class="detail-value">¥{{ currentPackage.price }}</text>
</view>
<view class="detail-row total">
<text class="detail-label">应付金额</text>
<text class="detail-value price">¥{{ currentPackage.price }}</text>
</view>
</view>
<!-- 底部按钮 -->
<view class="bottom-bar">
<view class="total-price">
<text class="label">合计:</text>
<text class="price">¥{{ currentPackage.price }}</text>
</view>
<button class="btn-purchase" @click="handlePurchase">立即购买</button>
</view>
</view>
</template>
<script>
export default {
data() {
return {
packages: [
{
type: 'basic',
name: '基准式陪伴',
hours: 100,
price: 12900,
description: '适合日常陪伴学习',
recommended: false,
features: ['专业教师陪伴', '作业辅导', '学习习惯培养']
},
{
type: 'warm',
name: '暖心式陪伴',
hours: 200,
price: 24900,
description: '更多时长,更优惠',
recommended: true,
features: ['专业教师陪伴', '作业辅导', '学习习惯培养', '心理疏导']
},
{
type: 'partner',
name: '伙伴式陪伴',
hours: 300,
price: 35900,
description: '长期陪伴,效果更佳',
recommended: false,
features: ['专业教师陪伴', '作业辅导', '学习习惯培养', '心理疏导', '成长规划']
},
{
type: 'butler',
name: '管家式陪伴',
hours: 500,
price: 58900,
description: 'VIP服务全方位陪伴',
recommended: false,
features: ['金牌教师陪伴', '全科辅导', '学习规划', '心理疏导', '家庭教育指导']
}
],
selectedPackage: 'warm',
serviceTypes: [['陪伴', '辅导', '接送', '托管', '心理疏导', '兴趣培养']],
selectedServiceTypes: [],
teacherLevels: [
{ label: '普通教师', value: 'normal' },
{ label: '金牌教师', value: 'gold' },
{ label: '学霸教师', value: 'scholar' }
],
selectedTeacherLevel: { label: '普通教师', value: 'normal' },
serviceModes: [
{ label: '入户服务', value: 'home' },
{ label: '到店服务', value: 'store' }
],
selectedServiceMode: { label: '入户服务', value: 'home' }
}
},
computed: {
currentPackage() {
return this.packages.find(p => p.type === this.selectedPackage) || this.packages[0]
}
},
methods: {
selectPackage(pkg) {
this.selectedPackage = pkg.type
},
onServiceTypeChange(e) {
const indices = e.detail.value
this.selectedServiceTypes = indices.map(i => this.serviceTypes[0][i])
},
onTeacherLevelChange(e) {
this.selectedTeacherLevel = this.teacherLevels[e.detail.value]
},
onServiceModeChange(e) {
this.selectedServiceMode = this.serviceModes[e.detail.value]
},
async handlePurchase() {
if (this.selectedServiceTypes.length === 0) {
uni.showToast({
title: '请选择服务类型',
icon: 'none'
})
return
}
const data = {
cardName: this.currentPackage.name + this.currentPackage.hours + '小时',
cardType: this.currentPackage.type,
totalHours: this.currentPackage.hours,
price: this.currentPackage.price,
expireDate: this.getExpireDate(),
serviceType: this.selectedServiceTypes.join(','),
teacherLevel: this.selectedTeacherLevel.value,
serviceMode: this.selectedServiceMode.value
}
try {
uni.showLoading({ title: '处理中...' })
const res = await this.$http.post('/api/timecard/purchase', data)
if (res.code === 200) {
// 这里应该跳转到支付页面
// 暂时直接提示成功
uni.showToast({
title: '购买成功',
icon: 'success'
})
setTimeout(() => {
uni.navigateBack()
}, 1500)
} else {
uni.showToast({
title: res.message || '购买失败',
icon: 'none'
})
}
} catch (e) {
console.error('购买失败', e)
uni.showToast({
title: '购买失败',
icon: 'none'
})
} finally {
uni.hideLoading()
}
},
getExpireDate() {
const date = new Date()
date.setFullYear(date.getFullYear() + 1)
return date.toISOString().split('T')[0]
}
}
}
</script>
<style lang="scss" scoped>
@import '@/static/css/common.scss';
.purchase-page {
min-height: 100vh;
background: $bg-color;
padding-bottom: 160rpx;
}
.section-title {
font-size: 32rpx;
font-weight: bold;
color: #333;
padding: 30rpx;
}
.package-list {
padding: 0 30rpx 20rpx;
}
.package-item {
background: #fff;
border-radius: 16rpx;
padding: 30rpx;
margin-bottom: 20rpx;
border: 2rpx solid transparent;
transition: all 0.3s;
&.active {
border-color: #4a9b9f;
box-shadow: 0 4rpx 20rpx rgba(74, 155, 159, 0.2);
}
.package-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16rpx;
.package-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.package-badge {
background: linear-gradient(135deg, #ff6b6b 0%, #ff8e53 100%);
color: #fff;
padding: 4rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
}
}
.package-hours {
font-size: 28rpx;
color: #4a9b9f;
margin-bottom: 16rpx;
}
.package-price {
margin-bottom: 16rpx;
.price-symbol {
font-size: 32rpx;
color: #ff4d4f;
}
.price-value {
font-size: 48rpx;
font-weight: bold;
color: #ff4d4f;
}
.price-unit {
font-size: 24rpx;
color: #999;
margin-left: 8rpx;
}
}
.package-desc {
font-size: 28rpx;
color: #666;
margin-bottom: 20rpx;
}
.package-features {
.feature-item {
display: flex;
align-items: center;
margin-bottom: 12rpx;
&:last-child {
margin-bottom: 0;
}
.feature-icon {
width: 32rpx;
height: 32rpx;
line-height: 32rpx;
text-align: center;
background: rgba(74, 155, 159, 0.1);
color: #4a9b9f;
border-radius: 50%;
font-size: 20rpx;
margin-right: 12rpx;
}
.feature-text {
font-size: 26rpx;
color: #666;
}
}
}
}
.config-section {
background: #fff;
margin: 20rpx 30rpx;
border-radius: 16rpx;
padding: 30rpx;
.config-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx 0;
border-bottom: 1rpx solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.config-label {
font-size: 28rpx;
color: #333;
}
.config-value {
font-size: 28rpx;
color: #666;
.arrow {
margin-left: 8rpx;
color: #ccc;
}
}
}
}
.price-detail {
background: #fff;
margin: 20rpx 30rpx;
border-radius: 16rpx;
padding: 30rpx;
.detail-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
&.total {
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
.detail-label {
font-size: 32rpx;
font-weight: bold;
}
.detail-value {
font-size: 36rpx;
}
}
.detail-label {
font-size: 28rpx;
color: #666;
}
.detail-value {
font-size: 28rpx;
color: #333;
&.price {
color: #ff4d4f;
font-weight: bold;
}
}
}
}
.bottom-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
padding: 20rpx 30rpx;
box-shadow: 0 -4rpx 12rpx rgba(0, 0, 0, 0.05);
display: flex;
align-items: center;
.total-price {
flex: 1;
.label {
font-size: 28rpx;
color: #666;
}
.price {
font-size: 40rpx;
font-weight: bold;
color: #ff4d4f;
}
}
.btn-purchase {
width: 280rpx;
height: 88rpx;
line-height: 88rpx;
background: linear-gradient(135deg, #4a9b9f 0%, #3d8185 100%);
color: #fff;
border-radius: 44rpx;
font-size: 32rpx;
font-weight: bold;
border: none;
}
}
</style>