智慧教务系统
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

1614 lines
39 KiB

<template>
<view class="course-detail">
<!-- 加载状态 -->
<view v-if="loading" class="loading-container">
<view class="loading-spinner"></view>
<text class="loading-text">加载中...</text>
</view>
<!-- 课程详情内容 -->
<view v-else-if="courseDetail" class="detail-container">
<!-- 课程基本信息卡片 -->
<view class="info-card">
<view class="card-header">
<text class="card-title">课程基本信息</text>
<view :class="['status-badge', getStatusClass(courseDetail.student_course_info.status)]">
{{ getStatusText(courseDetail.student_course_info.status) }}
</view>
</view>
<view class="info-grid">
<view class="info-item">
<text class="info-label">课程名称:</text>
<text class="info-value">{{ courseDetail.student_course_info.course_name }}</text>
</view>
<view class="info-item">
<text class="info-label">学员姓名:</text>
<text class="info-value">{{ courseDetail.student_course_info.student_name }}</text>
</view>
<view class="info-item">
<text class="info-label">开始日期:</text>
<text class="info-value">{{ courseDetail.student_course_info.start_date }}</text>
</view>
<view class="info-item">
<text class="info-label">结束日期:</text>
<text class="info-value">{{ courseDetail.student_course_info.end_date }}</text>
</view>
</view>
</view>
<!-- 课时信息卡片 -->
<view class="info-card">
<view class="card-header">
<text class="card-title">课时统计</text>
</view>
<view class="hours-summary">
<view class="summary-item total">
<text class="summary-label">总课时</text>
<text class="summary-value">{{ courseDetail.student_course_info.total_class_hours }}节</text>
</view>
<view class="summary-item used">
<text class="summary-label">已用课时</text>
<text class="summary-value">{{ courseDetail.student_course_info.used_class_hours }}节</text>
</view>
<view class="summary-item remaining">
<text class="summary-label">剩余课时</text>
<text class="summary-value">{{ courseDetail.student_course_info.remaining_class_hours }}节</text>
</view>
</view>
<!-- 课时详细信息 -->
<view class="hours-details">
<view class="hours-item">
<text class="hours-label">正式课时</text>
<view class="hours-info">
<text class="hours-total">{{ courseDetail.student_course_info.total_hours }}节</text>
<text class="hours-used">已用{{ courseDetail.student_course_info.use_total_hours }}节</text>
</view>
</view>
<view class="hours-item" v-if="courseDetail.student_course_info.gift_hours > 0">
<text class="hours-label">赠送课时</text>
<view class="hours-info">
<text class="hours-total">{{ courseDetail.student_course_info.gift_hours }}节</text>
<text class="hours-used">已用{{ courseDetail.student_course_info.use_gift_hours }}节</text>
</view>
</view>
</view>
<!-- 课时进度条 -->
<view class="progress-section">
<view class="progress-item">
<text class="progress-label">总课时进度</text>
<view class="progress-bar">
<view
class="progress-fill"
:style="'width:' + totalProgress + '%'"
></view>
</view>
<text class="progress-text">{{ courseDetail.student_course_info.used_class_hours }}/{{ courseDetail.student_course_info.total_class_hours }}</text>
</view>
</view>
</view>
<!-- 教练团队卡片 -->
<view class="info-card" v-if="hasCoachInfo">
<view class="card-header">
<text class="card-title">教练团队</text>
</view>
<view class="coach-list">
<view class="coach-item" v-if="courseDetail.student_course_info.coach_details.main_coach">
<view class="coach-avatar">
<text class="coach-initial">{{ getInitial(courseDetail.student_course_info.coach_details.main_coach.name) }}</text>
</view>
<view class="coach-info">
<text class="coach-role">主教练</text>
<text class="coach-name">{{ courseDetail.student_course_info.coach_details.main_coach.name }}</text>
</view>
</view>
<view class="coach-item" v-if="courseDetail.student_course_info.coach_details.education">
<view class="coach-avatar education">
<text class="coach-initial">{{ getInitial(courseDetail.student_course_info.coach_details.education.name) }}</text>
</view>
<view class="coach-info">
<text class="coach-role">教务</text>
<text class="coach-name">{{ courseDetail.student_course_info.coach_details.education.name }}</text>
</view>
</view>
<view class="coach-item" v-for="assistant in courseDetail.student_course_info.coach_details.assistants" :key="assistant.id">
<view class="coach-avatar assistant">
<text class="coach-initial">{{ getInitial(assistant.name) }}</text>
</view>
<view class="coach-info">
<text class="coach-role">助教</text>
<text class="coach-name">{{ assistant.name }}</text>
</view>
</view>
</view>
</view>
<!-- 课时使用记录卡片 -->
<view class="info-card">
<view class="card-header">
<text class="card-title">使用记录</text>
<text class="record-count" v-if="courseDetail.usage_records.length > 0">
共{{ courseDetail.statistics.total_usage_records }}条记录
</text>
</view>
<view v-if="courseDetail.usage_records.length > 0" class="usage-records">
<view class="record-item" v-for="record in courseDetail.usage_records" :key="record.id">
<view class="record-date">
<text class="date-text">{{ formatDate(record.usage_date) }}</text>
</view>
<view class="record-hours">
<text class="hours-text">-{{ record.used_hours }}节</text>
</view>
</view>
</view>
<view v-else class="no-records">
<text class="no-records-text">暂无使用记录</text>
</view>
</view>
<!-- 订单信息卡片 -->
<view class="info-card" v-if="courseDetail.order_info">
<view class="card-header">
<text class="card-title">订单信息</text>
</view>
<view class="order-info">
<view class="order-header">
<view class="order-id">
<text class="order-label">订单号:</text>
<text class="order-value">{{ courseDetail.order_info.payment_id }}</text>
</view>
<view :class="['order-status', getOrderStatusClass(courseDetail.order_info.order_status)]">
{{ getOrderStatusText(courseDetail.order_info.order_status) }}
</view>
</view>
<view class="order-details">
<view class="order-item">
<text class="order-label">订单金额:</text>
<text class="order-value amount">¥{{ courseDetail.order_info.order_amount }}</text>
</view>
<view class="order-item">
<text class="order-label">支付方式:</text>
<text class="order-value">{{ getPaymentTypeText(courseDetail.order_info.payment_type) }}</text>
</view>
<view class="order-item">
<text class="order-label">创建时间:</text>
<text class="order-value">{{ formatDateTime(courseDetail.order_info.created_at) }}</text>
</view>
<view class="order-item" v-if="courseDetail.order_info.payment_time">
<text class="order-label">支付时间:</text>
<text class="order-value">{{ formatDateTime(courseDetail.order_info.payment_time) }}</text>
</view>
<view class="order-item">
<text class="order-label">销售员工:</text>
<text class="order-value">{{ courseDetail.order_info.staff_name }}</text>
</view>
<view class="order-item">
<text class="order-label">所属校区:</text>
<text class="order-value">{{ courseDetail.order_info.campus_name }}</text>
</view>
</view>
<view v-if="courseDetail.order_info.remark" class="order-remark">
<text class="remark-label">备注:</text>
<text class="remark-text">{{ courseDetail.order_info.remark }}</text>
</view>
</view>
</view>
<!-- 无订单信息提示 -->
<view class="info-card" v-else>
<view class="card-header">
<text class="card-title">订单信息</text>
</view>
<view class="no-order">
<text class="no-order-text">暂无关联订单信息</text>
</view>
</view>
</view>
<!-- 错误状态 -->
<view v-else class="error-container">
<view class="error-icon">❌</view>
<text class="error-text">课程信息加载失败</text>
<button class="retry-btn" @tap="loadCourseDetail">重试</button>
</view>
<!-- 操作按钮 -->
<view class="action-buttons" v-if="courseDetail">
<button class="action-btn primary" @tap="openEditModal">
<text class="btn-icon">✏️</text>
<text class="btn-text">课程编辑</text>
</button>
<button class="action-btn secondary" @tap="openTransferModal">
<text class="btn-icon">🔄</text>
<text class="btn-text">课程转移</text>
</button>
</view>
<!-- 课程编辑弹窗 -->
<view v-if="showEditModal" class="modal-overlay" @tap="closeEditModal">
<view class="modal-content" @tap.stop>
<view class="modal-header">
<text class="modal-title">编辑课程信息</text>
<view class="close-btn" @tap="closeEditModal">×</view>
</view>
<view class="modal-body">
<view class="form-group">
<text class="form-label">正式课时数</text>
<input
class="form-input"
type="number"
v-model="editForm.total_hours"
placeholder="请输入正式课时数"
/>
</view>
<view class="form-group">
<text class="form-label">赠送课时数</text>
<input
class="form-input"
type="number"
v-model="editForm.gift_hours"
placeholder="请输入赠送课时数"
/>
</view>
<view class="form-group">
<text class="form-label">开始日期</text>
<picker
mode="date"
:value="editForm.start_date"
@change="onStartDateChange"
>
<view class="picker-input">
{{ editForm.start_date || '请选择开始日期' }}
<text class="picker-arrow">▼</text>
</view>
</picker>
</view>
<view class="form-group">
<text class="form-label">结束日期</text>
<picker
mode="date"
:value="editForm.end_date"
@change="onEndDateChange"
>
<view class="picker-input">
{{ editForm.end_date || '请选择结束日期' }}
<text class="picker-arrow">▼</text>
</view>
</picker>
</view>
<view class="form-group">
<text class="form-label">单次消课数量</text>
<input
class="form-input"
type="number"
v-model="editForm.single_session_count"
placeholder="请输入单次消课数量"
/>
</view>
<view class="form-group">
<text class="form-label">主教练</text>
<picker
:range="coachList"
range-key="name"
:value="selectedCoachIndex"
@change="onCoachChange"
>
<view class="picker-input">
{{ selectedCoachName || '请选择主教练' }}
<text class="picker-arrow">▼</text>
</view>
</picker>
</view>
<view class="form-group">
<text class="form-label">助教</text>
<picker
mode="multiSelector"
:range="[coachList]"
:value="selectedAssistantIndexes"
@change="onAssistantChange"
>
<view class="picker-input">
{{ selectedAssistantNames || '请选择助教' }}
<text class="picker-arrow">▼</text>
</view>
</picker>
</view>
<view class="form-group">
<text class="form-label">教务</text>
<picker
:range="educationList"
range-key="name"
:value="selectedEducationIndex"
@change="onEducationChange"
>
<view class="picker-input">
{{ selectedEducationName || '请选择教务' }}
<text class="picker-arrow">▼</text>
</view>
</picker>
</view>
</view>
<view class="modal-footer">
<button class="btn btn-cancel" @tap="closeEditModal">取消</button>
<button class="btn btn-confirm" @tap="saveCourseEdit" :loading="saving">保存</button>
</view>
</view>
</view>
<!-- 课程转移弹窗 -->
<view v-if="showTransferModal" class="modal-overlay" @tap="closeTransferModal">
<view class="modal-content" @tap.stop>
<view class="modal-header">
<text class="modal-title">课程转移</text>
<view class="close-btn" @tap="closeTransferModal">×</view>
</view>
<view class="modal-body">
<view class="transfer-info">
<text class="info-text">将课程转移给其他学员</text>
<text class="course-name">课程名称: {{ courseDetail.student_course_info.course_name }}</text>
</view>
<view class="form-group">
<text class="form-label">选择新学员</text>
<picker
:range="studentList"
range-key="name"
:value="selectedStudentIndex"
@change="onStudentChange"
>
<view class="picker-input">
{{ selectedStudentName || '请选择学员' }}
<text class="picker-arrow">▼</text>
</view>
</picker>
</view>
<view class="form-group">
<text class="form-label">转移原因</text>
<textarea
class="form-textarea"
v-model="transferForm.reason"
placeholder="请输入转移原因"
maxlength="200"
></textarea>
</view>
</view>
<view class="modal-footer">
<button class="btn btn-cancel" @tap="closeTransferModal">取消</button>
<button class="btn btn-confirm" @tap="confirmTransfer" :loading="transferring">确认转移</button>
</view>
</view>
</view>
</view>
</template>
<script>
import apiRoute from '@/api/apiRoute.js'
export default {
name: 'CourseDetail',
computed: {
// 检查是否有教练信息
hasCoachInfo() {
return this.courseDetail && this.courseDetail.student_course_info.coach_details && (
this.courseDetail.student_course_info.coach_details.main_coach ||
this.courseDetail.student_course_info.coach_details.education ||
(this.courseDetail.student_course_info.coach_details.assistants && this.courseDetail.student_course_info.coach_details.assistants.length > 0)
)
},
// 计算总课时进度
totalProgress() {
if (!this.courseDetail || !this.courseDetail.student_course_info.total_class_hours) return 0
return Math.round((this.courseDetail.student_course_info.used_class_hours / this.courseDetail.student_course_info.total_class_hours) * 100)
}
},
data() {
return {
// 页面参数
studentCourseId: null,
// 数据状态
loading: false,
courseDetail: null,
// 列表数据
coachList: [],
educationList: [],
studentList: [],
// 编辑弹窗
showEditModal: false,
saving: false,
editForm: {
total_hours: '',
gift_hours: '',
start_date: '',
end_date: '',
single_session_count: '',
main_coach_id: '',
assistant_ids: '',
education_id: ''
},
selectedCoachIndex: 0,
selectedAssistantIndexes: [0],
selectedEducationIndex: 0,
selectedCoachName: '',
selectedAssistantNames: '',
selectedEducationName: '',
// 转移弹窗
showTransferModal: false,
transferring: false,
transferForm: {
new_student_id: '',
reason: ''
},
selectedStudentIndex: 0,
selectedStudentName: ''
}
},
onLoad(options) {
console.log('页面参数:', options)
this.studentCourseId = options.student_course_id || options.id
if (!this.studentCourseId) {
uni.showToast({
title: '缺少学员课程ID参数',
icon: 'none'
})
return
}
this.loadCourseDetail()
},
methods: {
// 加载课程详情
async loadCourseDetail() {
if (!this.studentCourseId) return
this.loading = true
try {
console.log('加载学员课程详情,ID:', this.studentCourseId)
// 调用API接口
const res = await apiRoute.getStudentCourseDetail({
student_course_id: parseInt(this.studentCourseId)
})
console.log('API响应:', res)
if (res.code === 1) {
this.courseDetail = res.data
console.log('课程详情加载完成:', this.courseDetail)
// 加载编辑所需的选项数据
await this.loadEditOptions()
} else {
throw new Error(res.msg || '获取课程详情失败')
}
} catch (error) {
console.error('加载课程详情失败:', error)
uni.showToast({
title: error.message || '加载失败,请重试',
icon: 'none'
})
} finally {
this.loading = false
}
},
// 获取姓名首字母
getInitial(name) {
if (!name) return '?'
return name.charAt(0).toUpperCase()
},
// 状态相关方法
getStatusClass(status) {
const statusMap = {
'active': 'status-active',
'expired': 'status-expired',
'waiting': 'status-pending',
'delayed': 'status-extended',
'completed': 'status-completed'
}
return statusMap[status] || 'status-default'
},
getStatusText(status) {
const statusMap = {
'active': '有效进行中',
'expired': '已过期',
'waiting': '等待期',
'delayed': '延期',
'completed': '已完成'
}
return statusMap[status] || '未知状态'
},
// 订单状态相关方法
getOrderStatusClass(status) {
const statusMap = {
'pending': 'status-pending',
'paid': 'status-success',
'signed': 'status-success',
'completed': 'status-success',
'transfer': 'status-info'
}
return statusMap[status] || 'status-default'
},
getOrderStatusText(status) {
const statusMap = {
'pending': '待支付',
'paid': '已支付',
'signed': '已签约',
'completed': '已完成',
'transfer': '已转移'
}
return statusMap[status] || '未知状态'
},
// 支付方式文本
getPaymentTypeText(type) {
const typeMap = {
'cash': '现金',
'scan_code': '扫码支付',
'subscription': '订阅',
'wxpay_online': '微信在线支付'
}
return typeMap[type] || type || '未知'
},
// 日期格式化方法
formatDate(dateStr) {
if (!dateStr) return ''
try {
const date = new Date(dateStr)
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`
} catch (e) {
return dateStr
}
},
formatDateTime(dateTimeStr) {
if (!dateTimeStr) return ''
try {
const date = new Date(dateTimeStr)
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`
} catch (e) {
return dateTimeStr
}
},
// 加载编辑所需的选项数据
async loadEditOptions() {
try {
// 加载教练列表
const coachResponse = await apiRoute.common_getCoachList()
if (coachResponse.code === 1) {
// API返回的数据结构包含 coach_list、education_list 等字段
const data = coachResponse.data
this.coachList = data.coach_list || []
this.educationList = data.education_list || []
console.log('教练列表加载完成:', this.coachList)
console.log('教务列表加载完成:', this.educationList)
}
} catch (error) {
console.error('加载选项数据失败:', error)
// 设置空数组作为默认值
this.coachList = []
this.educationList = []
}
},
// 加载学员列表
async loadStudentList() {
try {
// 使用搜索学员接口获取学员列表
const response = await apiRoute.searchStudents({ limit: 100 })
if (response.code === 1) {
this.studentList = response.data || []
}
} catch (error) {
console.error('加载学员列表失败:', error)
}
},
// 打开编辑弹窗
openEditModal() {
if (!this.courseDetail) return
// 检查选项数据是否已加载
if (!this.coachList || !Array.isArray(this.coachList) || this.coachList.length === 0) {
uni.showToast({
title: '数据加载中,请稍候',
icon: 'none'
})
return
}
// 初始化编辑表单
const studentCourse = this.courseDetail.student_course_info
this.editForm = {
total_hours: studentCourse.total_hours || '',
gift_hours: studentCourse.gift_hours || 0,
start_date: studentCourse.start_date || '',
end_date: studentCourse.end_date || '',
single_session_count: studentCourse.single_session_count || '',
main_coach_id: studentCourse.main_coach_id || '',
assistant_ids: studentCourse.assistant_ids || '',
education_id: studentCourse.education_id || ''
}
// 设置选择器索引
this.setPickerIndexes()
this.showEditModal = true
},
// 设置选择器索引
setPickerIndexes() {
// 确保教练列表已加载
if (!this.coachList || !Array.isArray(this.coachList)) {
console.warn('教练列表未加载完成')
return
}
// 主教练
if (this.editForm.main_coach_id) {
const index = this.coachList.findIndex(c => c.id === parseInt(this.editForm.main_coach_id))
if (index >= 0) {
this.selectedCoachIndex = index
this.selectedCoachName = this.coachList[index].name
}
}
// 助教(多选)
if (this.editForm.assistant_ids) {
const assistantIds = this.editForm.assistant_ids.split(',').map(id => parseInt(id)).filter(id => !isNaN(id))
const indexes = assistantIds.map(id => {
const index = this.coachList.findIndex(c => c.id === id)
return index >= 0 ? index : 0
})
this.selectedAssistantIndexes = indexes.length > 0 ? indexes : [0]
const names = indexes.map(index => {
return this.coachList[index] ? this.coachList[index].name : ''
}).filter(name => name)
this.selectedAssistantNames = names.join('、')
}
// 教务
if (this.editForm.education_id && this.educationList && Array.isArray(this.educationList)) {
const index = this.educationList.findIndex(e => e.id === parseInt(this.editForm.education_id))
if (index >= 0) {
this.selectedEducationIndex = index
this.selectedEducationName = this.educationList[index].name
}
}
},
// 保存课程编辑
async saveCourseEdit() {
if (this.saving) return
// 验证表单
if (!this.editForm.total_hours || this.editForm.total_hours <= 0) {
uni.showToast({
title: '请输入有效的正式课时数',
icon: 'none'
})
return
}
if (!this.editForm.start_date || !this.editForm.end_date) {
uni.showToast({
title: '请选择开始和结束日期',
icon: 'none'
})
return
}
if (new Date(this.editForm.end_date) <= new Date(this.editForm.start_date)) {
uni.showToast({
title: '结束日期必须晚于开始日期',
icon: 'none'
})
return
}
this.saving = true
try {
console.log('保存课程编辑:', this.editForm)
// TODO: 调用保存API
// 这里需要根据实际API调整
// 模拟保存成功
setTimeout(() => {
// 更新本地数据
const studentCourse = this.courseDetail.student_course_info
Object.assign(studentCourse, this.editForm)
this.saving = false
this.showEditModal = false
uni.showToast({
title: '保存成功',
icon: 'success'
})
}, 1000)
} catch (error) {
console.error('保存失败:', error)
this.saving = false
uni.showToast({
title: '保存失败,请重试',
icon: 'none'
})
}
},
// 打开转移弹窗
openTransferModal() {
if (!this.courseDetail) return
// 初始化转移表单
this.transferForm = {
new_student_id: '',
reason: ''
}
this.selectedStudentIndex = 0
this.selectedStudentName = ''
// 加载学员列表
this.loadStudentList()
this.showTransferModal = true
},
// 确认转移
async confirmTransfer() {
if (this.transferring) return
if (!this.transferForm.new_student_id) {
uni.showToast({
title: '请选择新学员',
icon: 'none'
})
return
}
if (this.transferForm.new_student_id === this.courseDetail.student_course_info.student_id) {
uni.showToast({
title: '不能转移给同一学员',
icon: 'none'
})
return
}
this.transferring = true
try {
console.log('确认课程转移:', this.transferForm)
// TODO: 调用转移API
// 这里需要根据实际API调整
// 模拟转移成功
setTimeout(() => {
this.transferring = false
this.showTransferModal = false
uni.showToast({
title: '转移成功',
icon: 'success'
})
// 转移成功后返回上一页
setTimeout(() => {
uni.navigateBack()
}, 1500)
}, 1000)
} catch (error) {
console.error('转移失败:', error)
this.transferring = false
uni.showToast({
title: '转移失败,请重试',
icon: 'none'
})
}
},
// 关闭编辑弹窗
closeEditModal() {
this.showEditModal = false
},
// 关闭转移弹窗
closeTransferModal() {
this.showTransferModal = false
},
// 选择器事件处理
onStartDateChange(e) {
this.editForm.start_date = e.detail.value
},
onEndDateChange(e) {
this.editForm.end_date = e.detail.value
},
onCoachChange(e) {
const index = e.detail.value
this.selectedCoachIndex = index
if (this.coachList && this.coachList[index]) {
const coach = this.coachList[index]
this.editForm.main_coach_id = coach.id
this.selectedCoachName = coach.name
}
},
onAssistantChange(e) {
const indexes = e.detail.value
this.selectedAssistantIndexes = indexes
if (this.coachList && Array.isArray(this.coachList)) {
const assistants = indexes.map(index => this.coachList[index]).filter(Boolean)
this.editForm.assistant_ids = assistants.map(item => item.id).join(',')
this.selectedAssistantNames = assistants.map(item => item.name).join('、')
}
},
onEducationChange(e) {
const index = e.detail.value
this.selectedEducationIndex = index
if (this.educationList && this.educationList[index]) {
const education = this.educationList[index]
this.editForm.education_id = education.id
this.selectedEducationName = education.name
}
},
onStudentChange(e) {
const index = e.detail.value
this.selectedStudentIndex = index
if (this.studentList && this.studentList[index]) {
const student = this.studentList[index]
this.transferForm.new_student_id = student.id
this.selectedStudentName = student.name
}
}
}
}
</script>
<style lang="scss" scoped>
.course-detail {
min-height: 100vh;
background: #1a1a1a;
padding: 24rpx;
padding-bottom: 120rpx;
}
.loading-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 60vh;
}
.loading-spinner {
width: 60rpx;
height: 60rpx;
border: 4rpx solid #404040;
border-top: 4rpx solid #29D3B4;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-bottom: 24rpx;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
color: #999;
font-size: 28rpx;
}
.detail-container {
display: flex;
flex-direction: column;
gap: 24rpx;
}
.info-card {
background: #2a2a2a;
border-radius: 16rpx;
padding: 32rpx;
border: 1px solid #404040;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24rpx;
}
.card-title {
font-size: 32rpx;
font-weight: 600;
color: #fff;
}
.status-badge {
padding: 8rpx 16rpx;
border-radius: 20rpx;
font-size: 24rpx;
font-weight: 500;
&.status-active {
background: rgba(41, 211, 180, 0.2);
color: #29D3B4;
}
&.status-expired {
background: rgba(244, 67, 54, 0.2);
color: #F44336;
}
&.status-pending {
background: rgba(255, 193, 7, 0.2);
color: #FFC107;
}
&.status-extended {
background: rgba(33, 150, 243, 0.2);
color: #2196F3;
}
&.status-completed {
background: rgba(156, 39, 176, 0.2);
color: #9C27B0;
}
}
.info-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20rpx;
}
.info-item {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.info-label {
font-size: 26rpx;
color: #999;
}
.info-value {
font-size: 28rpx;
color: #fff;
font-weight: 500;
}
// 课时统计样式
.hours-summary {
display: flex;
justify-content: space-between;
margin-bottom: 24rpx;
background: #333;
border-radius: 12rpx;
padding: 24rpx;
}
.summary-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8rpx;
&.total {
.summary-value {
color: #29D3B4;
}
}
&.used {
.summary-value {
color: #FF6B35;
}
}
&.remaining {
.summary-value {
color: #2196F3;
}
}
}
.summary-label {
font-size: 24rpx;
color: #999;
}
.summary-value {
font-size: 32rpx;
font-weight: 600;
}
.hours-details {
margin-bottom: 24rpx;
}
.hours-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx 0;
border-bottom: 1px solid #404040;
&:last-child {
border-bottom: none;
}
}
.hours-label {
font-size: 28rpx;
color: #fff;
}
.hours-info {
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 4rpx;
}
.hours-total {
font-size: 28rpx;
color: #fff;
font-weight: 500;
}
.hours-used {
font-size: 24rpx;
color: #FF6B35;
}
// 教练团队样式
.coach-list {
display: flex;
flex-direction: column;
gap: 16rpx;
}
.coach-item {
display: flex;
align-items: center;
gap: 16rpx;
padding: 16rpx;
background: #333;
border-radius: 12rpx;
}
.coach-avatar {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
background: #29D3B4;
display: flex;
align-items: center;
justify-content: center;
&.education {
background: #2196F3;
}
&.assistant {
background: #FF6B35;
}
}
.coach-initial {
font-size: 24rpx;
color: #fff;
font-weight: 600;
}
.coach-info {
flex: 1;
display: flex;
flex-direction: column;
gap: 4rpx;
}
.coach-role {
font-size: 24rpx;
color: #999;
}
.coach-name {
font-size: 28rpx;
color: #fff;
font-weight: 500;
}
// 使用记录样式
.record-count {
font-size: 24rpx;
color: #999;
}
.usage-records {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.record-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16rpx;
background: #333;
border-radius: 8rpx;
}
.date-text {
font-size: 28rpx;
color: #fff;
}
.hours-text {
font-size: 28rpx;
color: #FF6B35;
font-weight: 500;
}
.no-records, .no-order {
text-align: center;
padding: 40rpx;
color: #999;
font-size: 28rpx;
}
// 订单信息样式
.order-info {
display: flex;
flex-direction: column;
gap: 20rpx;
}
.order-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding-bottom: 16rpx;
border-bottom: 1px solid #404040;
}
.order-id {
display: flex;
flex-direction: column;
gap: 8rpx;
}
.order-label {
font-size: 24rpx;
color: #999;
}
.order-value {
font-size: 28rpx;
color: #fff;
font-weight: 500;
&.amount {
color: #29D3B4;
font-size: 32rpx;
font-weight: 600;
}
}
.order-status {
padding: 6rpx 12rpx;
border-radius: 16rpx;
font-size: 22rpx;
font-weight: 500;
&.status-success {
background: rgba(41, 211, 180, 0.2);
color: #29D3B4;
}
&.status-pending {
background: rgba(255, 193, 7, 0.2);
color: #FFC107;
}
&.status-info {
background: rgba(33, 150, 243, 0.2);
color: #2196F3;
}
}
.order-details {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.order-item {
display: flex;
justify-content: space-between;
align-items: center;
}
.order-remark {
padding: 16rpx;
background: #333;
border-radius: 8rpx;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.remark-label {
font-size: 24rpx;
color: #999;
}
.remark-text {
font-size: 26rpx;
color: #fff;
line-height: 1.4;
}
.progress-section {
margin-top: 24rpx;
display: flex;
flex-direction: column;
gap: 16rpx;
}
.progress-item {
display: flex;
align-items: center;
gap: 16rpx;
}
.progress-label {
font-size: 26rpx;
color: #fff;
min-width: 140rpx;
}
.progress-bar {
flex: 1;
height: 12rpx;
background: #404040;
border-radius: 6rpx;
overflow: hidden;
}
.progress-fill {
height: 100%;
background: linear-gradient(90deg, #29D3B4 0%, #4ECDC4 100%);
border-radius: 6rpx;
transition: width 0.3s ease;
&.gift {
background: linear-gradient(90deg, #FF6B35 0%, #FFB74D 100%);
}
}
.progress-text {
font-size: 24rpx;
color: #fff;
min-width: 80rpx;
text-align: right;
}
// 错误页面样式
.error-container {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 60vh;
text-align: center;
}
.error-icon {
font-size: 120rpx;
margin-bottom: 32rpx;
}
.error-text {
font-size: 32rpx;
color: #999;
margin-bottom: 32rpx;
}
.retry-btn {
padding: 20rpx 40rpx;
background: #29D3B4;
color: #fff;
border: none;
border-radius: 8rpx;
font-size: 28rpx;
}
// 操作按钮样式
.action-buttons {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #2a2a2a;
padding: 16rpx 24rpx;
display: flex;
gap: 16rpx;
border-top: 1px solid #404040;
}
.action-btn {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
gap: 8rpx;
padding: 16rpx 20rpx;
border-radius: 8rpx;
border: none;
font-size: 26rpx;
font-weight: 500;
&.primary {
background: #29D3B4;
color: #fff;
}
&.secondary {
background: #404040;
color: #fff;
}
&:active {
opacity: 0.8;
}
}
.btn-icon {
font-size: 24rpx;
}
.btn-text {
font-size: 26rpx;
}
// 弹窗样式
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.6);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: #2a2a2a;
border-radius: 16rpx;
width: 90%;
max-width: 650rpx;
max-height: 80vh;
overflow: hidden;
border: 1px solid #404040;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 32rpx;
border-bottom: 1px solid #404040;
}
.modal-title {
font-size: 36rpx;
font-weight: 600;
color: #fff;
}
.close-btn {
width: 48rpx;
height: 48rpx;
display: flex;
align-items: center;
justify-content: center;
font-size: 32rpx;
color: #999;
border-radius: 50%;
&:active {
background: rgba(255, 255, 255, 0.1);
}
}
.modal-body {
padding: 32rpx;
max-height: 50vh;
overflow-y: auto;
}
.transfer-info {
background: #3a3a3a;
padding: 24rpx;
border-radius: 8rpx;
margin-bottom: 24rpx;
}
.info-text {
display: block;
font-size: 28rpx;
color: #fff;
margin-bottom: 12rpx;
}
.course-name {
display: block;
font-size: 24rpx;
color: #29D3B4;
}
.form-group {
margin-bottom: 24rpx;
&:last-child {
margin-bottom: 0;
}
}
.form-label {
display: block;
font-size: 28rpx;
color: #fff;
margin-bottom: 16rpx;
}
.form-input {
width: 100%;
padding: 0 24rpx;
background: #3a3a3a;
border: 1px solid #404040;
border-radius: 8rpx;
color: #fff;
font-size: 28rpx;
height: 80rpx;
box-sizing: border-box;
display: flex;
align-items: center;
&:focus {
border-color: #29D3B4;
}
}
.form-textarea {
width: 100%;
padding: 20rpx 24rpx;
background: #3a3a3a;
border: 1px solid #404040;
border-radius: 8rpx;
color: #fff;
font-size: 28rpx;
min-height: 120rpx;
resize: none;
height: auto;
box-sizing: border-box;
&:focus {
border-color: #29D3B4;
}
}
.picker-input {
width: 100%;
padding: 20rpx 24rpx;
background: #3a3a3a;
border: 1px solid #404040;
border-radius: 8rpx;
color: #fff;
font-size: 28rpx;
display: flex;
justify-content: space-between;
align-items: center;
height: 80rpx;
box-sizing: border-box;
&:active {
border-color: #29D3B4;
}
}
.picker-arrow {
color: #999;
font-size: 24rpx;
}
.modal-footer {
display: flex;
gap: 20rpx;
padding: 32rpx;
border-top: 1px solid #404040;
}
.btn {
flex: 1;
padding: 20rpx;
border-radius: 8rpx;
font-size: 28rpx;
font-weight: 500;
border: none;
&.btn-cancel {
background: #404040;
color: #fff;
}
&.btn-confirm {
background: #29D3B4;
color: #fff;
}
&:disabled {
opacity: 0.6;
}
}
</style>