24 changed files with 2384 additions and 482 deletions
@ -0,0 +1,79 @@ |
|||||
|
<?php |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Niucloud-admin 企业快速开发的多应用管理平台 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | 官方网址:https://www.niucloud.com |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | niucloud团队 版权所有 开源版本可自由商用 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Author: Niucloud Team |
||||
|
// +---------------------------------------------------------------------- |
||||
|
|
||||
|
namespace app\api\controller\apiController; |
||||
|
|
||||
|
use app\Request; |
||||
|
use app\service\api\apiService\StudentCourseService; |
||||
|
use core\base\BaseApiService; |
||||
|
|
||||
|
/** |
||||
|
* 学员课程相关接口 |
||||
|
* Class StudentCourse |
||||
|
* @package app\api\controller\apiController |
||||
|
*/ |
||||
|
class StudentCourse extends BaseApiService |
||||
|
{ |
||||
|
/** |
||||
|
* 获取课程详情 |
||||
|
* @param Request $request |
||||
|
* @return \think\Response |
||||
|
*/ |
||||
|
public function courseDetail(Request $request) |
||||
|
{ |
||||
|
$course_id = $request->param('course_id', ''); |
||||
|
$resource_id = $request->param('resource_id', ''); |
||||
|
|
||||
|
if (empty($course_id)) { |
||||
|
return fail('课程ID不能为空'); |
||||
|
} |
||||
|
|
||||
|
// 如果没有传resource_id,尝试从当前登录用户获取 |
||||
|
if (empty($resource_id)) { |
||||
|
// 这里需要根据实际情况获取当前学员的resource_id |
||||
|
// 可能需要从member_id获取对应的resource_id |
||||
|
$resource_id = $this->getResourceIdByMemberId($this->member_id); |
||||
|
} |
||||
|
|
||||
|
if (empty($resource_id)) { |
||||
|
return fail('资源ID不能为空'); |
||||
|
} |
||||
|
|
||||
|
$where = [ |
||||
|
'course_id' => $course_id, |
||||
|
'resource_id' => $resource_id |
||||
|
]; |
||||
|
|
||||
|
try { |
||||
|
$res = (new StudentCourseService())->getCourseDetail($where); |
||||
|
if (!$res['code']) { |
||||
|
return fail($res['msg']); |
||||
|
} |
||||
|
|
||||
|
return success($res['data']); |
||||
|
} catch (\Exception $e) { |
||||
|
return fail('获取课程详情失败:' . $e->getMessage()); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 根据会员ID获取资源ID |
||||
|
* @param int $member_id |
||||
|
* @return int|string |
||||
|
*/ |
||||
|
private function getResourceIdByMemberId($member_id) |
||||
|
{ |
||||
|
// 这里根据实际业务逻辑实现 |
||||
|
// 从customer_resources表中根据member_id获取resource_id |
||||
|
$customerResource = \app\model\customer_resources\CustomerResources::where('member_id', $member_id)->find(); |
||||
|
return $customerResource ? $customerResource->id : ''; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,220 @@ |
|||||
|
<?php |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Niucloud-admin 企业快速开发的多应用管理平台 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | 官方网址:https://www.niucloud.com |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | niucloud团队 版权所有 开源版本可自由商用 |
||||
|
// +---------------------------------------------------------------------- |
||||
|
// | Author: Niucloud Team |
||||
|
// +---------------------------------------------------------------------- |
||||
|
|
||||
|
namespace app\service\api\apiService; |
||||
|
|
||||
|
use app\model\customer_resources\CustomerResources; |
||||
|
use app\model\student_courses\StudentCourses; |
||||
|
use app\model\person_course_schedule\PersonCourseSchedule; |
||||
|
use app\model\student_course_usage\StudentCourseUsage; |
||||
|
use app\model\course\Course; |
||||
|
use app\model\campus\Campus; |
||||
|
use app\model\venue\Venue; |
||||
|
use app\model\personnel\Personnel; |
||||
|
use core\base\BaseApiService; |
||||
|
use think\facade\Log; |
||||
|
|
||||
|
/** |
||||
|
* 学员课程服务类 |
||||
|
* Class StudentCourseService |
||||
|
* @package app\service\api\apiService |
||||
|
*/ |
||||
|
class StudentCourseService extends BaseApiService |
||||
|
{ |
||||
|
/** |
||||
|
* 获取课程详情 |
||||
|
* @param array $where |
||||
|
* @return array |
||||
|
*/ |
||||
|
public function getCourseDetail(array $where) |
||||
|
{ |
||||
|
try { |
||||
|
Log::debug('StudentCourseService::getCourseDetail - 查询条件: ' . json_encode($where)); |
||||
|
|
||||
|
$course_id = $where['course_id'] ?? ''; |
||||
|
$resource_id = $where['resource_id'] ?? ''; |
||||
|
|
||||
|
if (empty($course_id) || empty($resource_id)) { |
||||
|
return ['code' => 0, 'msg' => '参数不完整']; |
||||
|
} |
||||
|
|
||||
|
// 1. 获取学员课程基本信息 |
||||
|
$courseInfo = $this->getCourseInfo($course_id, $resource_id); |
||||
|
if (!$courseInfo) { |
||||
|
return ['code' => 0, 'msg' => '课程信息不存在']; |
||||
|
} |
||||
|
|
||||
|
// 2. 获取课程安排列表 |
||||
|
$scheduleList = $this->getScheduleList($course_id, $resource_id); |
||||
|
|
||||
|
// 3. 获取课程使用记录 |
||||
|
$usageList = $this->getUsageList($course_id, $resource_id); |
||||
|
|
||||
|
$result = [ |
||||
|
'course_info' => $courseInfo, |
||||
|
'schedule_list' => $scheduleList, |
||||
|
'usage_list' => $usageList |
||||
|
]; |
||||
|
|
||||
|
Log::debug('StudentCourseService::getCourseDetail - 返回数据: ' . json_encode($result)); |
||||
|
|
||||
|
return ['code' => 1, 'data' => $result]; |
||||
|
|
||||
|
} catch (\Exception $e) { |
||||
|
Log::error('StudentCourseService::getCourseDetail - 异常: ' . $e->getMessage()); |
||||
|
return ['code' => 0, 'msg' => '获取课程详情失败: ' . $e->getMessage()]; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取课程基本信息 |
||||
|
* @param int $course_id |
||||
|
* @param int $resource_id |
||||
|
* @return array|null |
||||
|
*/ |
||||
|
private function getCourseInfo($course_id, $resource_id) |
||||
|
{ |
||||
|
$studentCourse = new StudentCourses(); |
||||
|
|
||||
|
$info = $studentCourse |
||||
|
->alias('sc') |
||||
|
->join(['school_course' => 'c'], 'sc.course_id = c.id', 'left') |
||||
|
->join(['school_campus' => 'campus'], 'sc.campus_id = campus.id', 'left') |
||||
|
->join(['school_personnel' => 'coach'], 'sc.main_coach_id = coach.id', 'left') |
||||
|
->join(['school_personnel' => 'education'], 'sc.education_id = education.id', 'left') |
||||
|
->where([ |
||||
|
'sc.course_id' => $course_id, |
||||
|
'sc.resource_id' => $resource_id |
||||
|
]) |
||||
|
->field([ |
||||
|
'sc.id', |
||||
|
'sc.course_id', |
||||
|
'sc.resource_id', |
||||
|
'sc.total_hours', |
||||
|
'sc.gift_hours', |
||||
|
'sc.start_date', |
||||
|
'sc.end_date', |
||||
|
'sc.use_total_hours', |
||||
|
'sc.use_gift_hours', |
||||
|
'sc.status', |
||||
|
'sc.single_session_count', |
||||
|
'c.course_name', |
||||
|
'campus.campus_name', |
||||
|
'coach.name as main_coach_name', |
||||
|
'education.name as education_name' |
||||
|
]) |
||||
|
->find(); |
||||
|
|
||||
|
return $info ? $info->toArray() : null; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取课程安排列表 |
||||
|
* @param int $course_id |
||||
|
* @param int $resource_id |
||||
|
* @return array |
||||
|
*/ |
||||
|
private function getScheduleList($course_id, $resource_id) |
||||
|
{ |
||||
|
// 首先获取学员课程ID |
||||
|
$studentCourseId = $this->getStudentCourseId($course_id, $resource_id); |
||||
|
if (!$studentCourseId) { |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
$personCourseSchedule = new PersonCourseSchedule(); |
||||
|
|
||||
|
$list = $personCourseSchedule |
||||
|
->alias('pcs') |
||||
|
->join(['school_course_schedule' => 'cs'], 'pcs.schedule_id = cs.id', 'left') |
||||
|
->join(['school_venue' => 'v'], 'cs.venue_id = v.id', 'left') |
||||
|
->join(['school_personnel' => 'coach'], 'cs.coach_id = coach.id', 'left') |
||||
|
->where([ |
||||
|
'pcs.student_course_id' => $studentCourseId, |
||||
|
'pcs.resources_id' => $resource_id |
||||
|
]) |
||||
|
->field([ |
||||
|
'pcs.id', |
||||
|
'pcs.schedule_id', |
||||
|
'pcs.course_date', |
||||
|
'pcs.time_slot', |
||||
|
'pcs.schedule_type', |
||||
|
'pcs.course_type', |
||||
|
'pcs.status', |
||||
|
'pcs.remark', |
||||
|
'v.venue_name', |
||||
|
'coach.name as coach_name' |
||||
|
]) |
||||
|
->order('pcs.course_date desc, pcs.time_slot desc') |
||||
|
->select(); |
||||
|
|
||||
|
return $list ? $list->toArray() : []; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取课程使用记录 |
||||
|
* @param int $course_id |
||||
|
* @param int $resource_id |
||||
|
* @return array |
||||
|
*/ |
||||
|
private function getUsageList($course_id, $resource_id) |
||||
|
{ |
||||
|
// 首先获取学员课程ID |
||||
|
$studentCourseId = $this->getStudentCourseId($course_id, $resource_id); |
||||
|
if (!$studentCourseId) { |
||||
|
return []; |
||||
|
} |
||||
|
|
||||
|
$studentCourseUsage = new StudentCourseUsage(); |
||||
|
|
||||
|
$list = $studentCourseUsage |
||||
|
->alias('scu') |
||||
|
->join(['school_course_schedule' => 'cs'], 'scu.schedule_id = cs.id', 'left') |
||||
|
->join(['school_venue' => 'v'], 'cs.venue_id = v.id', 'left') |
||||
|
->where([ |
||||
|
'scu.student_course_id' => $studentCourseId, |
||||
|
'scu.resource_id' => $resource_id |
||||
|
]) |
||||
|
->field([ |
||||
|
'scu.id', |
||||
|
'scu.usage_date', |
||||
|
'scu.hours_used', |
||||
|
'scu.usage_type', |
||||
|
'scu.remark', |
||||
|
'cs.time_slot', |
||||
|
'v.venue_name' |
||||
|
]) |
||||
|
->order('scu.usage_date desc') |
||||
|
->select(); |
||||
|
|
||||
|
return $list ? $list->toArray() : []; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 获取学员课程ID |
||||
|
* @param int $course_id |
||||
|
* @param int $resource_id |
||||
|
* @return int|null |
||||
|
*/ |
||||
|
private function getStudentCourseId($course_id, $resource_id) |
||||
|
{ |
||||
|
$studentCourse = new StudentCourses(); |
||||
|
|
||||
|
$info = $studentCourse |
||||
|
->where([ |
||||
|
'course_id' => $course_id, |
||||
|
'resource_id' => $resource_id |
||||
|
]) |
||||
|
->find(); |
||||
|
|
||||
|
return $info ? $info->id : null; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,514 @@ |
|||||
|
<template> |
||||
|
<view class="course-detail-container"> |
||||
|
<!-- 课程基本信息 --> |
||||
|
<view class="course-info-card"> |
||||
|
<view class="course-header"> |
||||
|
<text class="course-title">{{ courseInfo.course_name || '课程详情' }}</text> |
||||
|
<view class="course-status" :class="getStatusClass(courseInfo.status)"> |
||||
|
{{ getStatusText(courseInfo.status) }} |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="course-stats"> |
||||
|
<view class="stat-item"> |
||||
|
<text class="stat-label">总课时</text> |
||||
|
<text class="stat-value">{{ courseInfo.total_hours || 0 }}</text> |
||||
|
</view> |
||||
|
<view class="stat-item"> |
||||
|
<text class="stat-label">赠送课时</text> |
||||
|
<text class="stat-value">{{ courseInfo.gift_hours || 0 }}</text> |
||||
|
</view> |
||||
|
<view class="stat-item"> |
||||
|
<text class="stat-label">已用课时</text> |
||||
|
<text class="stat-value">{{ (courseInfo.use_total_hours || 0) + (courseInfo.use_gift_hours || 0) }}</text> |
||||
|
</view> |
||||
|
<view class="stat-item"> |
||||
|
<text class="stat-label">剩余课时</text> |
||||
|
<text class="stat-value remaining">{{ getRemainingHours() }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="course-dates"> |
||||
|
<text class="date-item">开始日期:{{ courseInfo.start_date || '--' }}</text> |
||||
|
<text class="date-item">结束日期:{{ courseInfo.end_date || '--' }}</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 课程安排列表 --> |
||||
|
<view class="section-title"> |
||||
|
<text class="title-text">课程安排</text> |
||||
|
<text class="title-count">({{ scheduleList.length }})</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="schedule-list"> |
||||
|
<view |
||||
|
v-for="(item, index) in scheduleList" |
||||
|
:key="item.id" |
||||
|
class="schedule-item" |
||||
|
@tap="showScheduleDetail(item)" |
||||
|
> |
||||
|
<view class="schedule-date"> |
||||
|
<text class="date-text">{{ formatDate(item.course_date) }}</text> |
||||
|
<text class="time-text">{{ item.time_slot }}</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="schedule-info"> |
||||
|
<view class="schedule-type"> |
||||
|
<text class="type-tag" :class="getScheduleTypeClass(item.schedule_type)"> |
||||
|
{{ getScheduleTypeText(item.schedule_type) }} |
||||
|
</text> |
||||
|
<text class="course-type-tag" :class="getCourseTypeClass(item.course_type)"> |
||||
|
{{ getCourseTypeText(item.course_type) }} |
||||
|
</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="schedule-status"> |
||||
|
<text class="status-text" :class="getScheduleStatusClass(item.status)"> |
||||
|
{{ getScheduleStatusText(item.status) }} |
||||
|
</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<view class="schedule-arrow"> |
||||
|
<text class="arrow-icon">></text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 课程使用记录 --> |
||||
|
<view class="section-title"> |
||||
|
<text class="title-text">使用记录</text> |
||||
|
<text class="title-count">({{ usageList.length }})</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="usage-list"> |
||||
|
<view |
||||
|
v-for="(item, index) in usageList" |
||||
|
:key="item.id" |
||||
|
class="usage-item" |
||||
|
> |
||||
|
<view class="usage-date"> |
||||
|
<text class="date-text">{{ formatDate(item.usage_date) }}</text> |
||||
|
<text class="time-text">{{ item.time_slot || '--' }}</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="usage-info"> |
||||
|
<text class="usage-hours">消耗课时:{{ item.hours_used || 0 }}</text> |
||||
|
<text class="usage-type">{{ item.usage_type || '正常上课' }}</text> |
||||
|
</view> |
||||
|
|
||||
|
<view class="usage-status"> |
||||
|
<text class="status-text confirmed">已确认</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</view> |
||||
|
|
||||
|
<!-- 无数据提示 --> |
||||
|
<view v-if="scheduleList.length === 0 && usageList.length === 0" class="no-data"> |
||||
|
<text class="no-data-text">暂无课程安排和使用记录</text> |
||||
|
</view> |
||||
|
</view> |
||||
|
</template> |
||||
|
|
||||
|
<script> |
||||
|
export default { |
||||
|
data() { |
||||
|
return { |
||||
|
courseId: '', |
||||
|
courseInfo: {}, |
||||
|
scheduleList: [], |
||||
|
usageList: [] |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
onLoad(options) { |
||||
|
this.courseId = options.courseId || '' |
||||
|
if (this.courseId) { |
||||
|
this.loadCourseDetail() |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
methods: { |
||||
|
// 加载课程详情 |
||||
|
async loadCourseDetail() { |
||||
|
try { |
||||
|
uni.showLoading({ title: '加载中...' }) |
||||
|
|
||||
|
const res = await this.$http.get('/xy/course/detail', { |
||||
|
course_id: this.courseId |
||||
|
}) |
||||
|
|
||||
|
if (res.data.code === 1) { |
||||
|
this.courseInfo = res.data.data.course_info || {} |
||||
|
this.scheduleList = res.data.data.schedule_list || [] |
||||
|
this.usageList = res.data.data.usage_list || [] |
||||
|
} else { |
||||
|
uni.showToast({ |
||||
|
title: res.data.msg || '加载失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} |
||||
|
} catch (error) { |
||||
|
console.error('加载课程详情失败:', error) |
||||
|
uni.showToast({ |
||||
|
title: '加载失败', |
||||
|
icon: 'none' |
||||
|
}) |
||||
|
} finally { |
||||
|
uni.hideLoading() |
||||
|
} |
||||
|
}, |
||||
|
|
||||
|
// 计算剩余课时 |
||||
|
getRemainingHours() { |
||||
|
const total = (this.courseInfo.total_hours || 0) + (this.courseInfo.gift_hours || 0) |
||||
|
const used = (this.courseInfo.use_total_hours || 0) + (this.courseInfo.use_gift_hours || 0) |
||||
|
return Math.max(0, total - used) |
||||
|
}, |
||||
|
|
||||
|
// 获取课程状态文本 |
||||
|
getStatusText(status) { |
||||
|
const statusMap = { |
||||
|
1: '有效', |
||||
|
2: '过期', |
||||
|
3: '等待期', |
||||
|
4: '延期' |
||||
|
} |
||||
|
return statusMap[status] || '未知' |
||||
|
}, |
||||
|
|
||||
|
// 获取课程状态样式 |
||||
|
getStatusClass(status) { |
||||
|
const classMap = { |
||||
|
1: 'status-active', |
||||
|
2: 'status-expired', |
||||
|
3: 'status-waiting', |
||||
|
4: 'status-delayed' |
||||
|
} |
||||
|
return classMap[status] || 'status-unknown' |
||||
|
}, |
||||
|
|
||||
|
// 获取课程安排类型文本 |
||||
|
getScheduleTypeText(type) { |
||||
|
const typeMap = { |
||||
|
1: '临时课', |
||||
|
2: '固定课' |
||||
|
} |
||||
|
return typeMap[type] || '未知' |
||||
|
}, |
||||
|
|
||||
|
// 获取课程安排类型样式 |
||||
|
getScheduleTypeClass(type) { |
||||
|
const classMap = { |
||||
|
1: 'type-temp', |
||||
|
2: 'type-fixed' |
||||
|
} |
||||
|
return classMap[type] || 'type-unknown' |
||||
|
}, |
||||
|
|
||||
|
// 获取课程类型文本 |
||||
|
getCourseTypeText(type) { |
||||
|
const typeMap = { |
||||
|
1: '加课', |
||||
|
2: '补课', |
||||
|
3: '等待位' |
||||
|
} |
||||
|
return typeMap[type] || '正常课' |
||||
|
}, |
||||
|
|
||||
|
// 获取课程类型样式 |
||||
|
getCourseTypeClass(type) { |
||||
|
const classMap = { |
||||
|
1: 'course-add', |
||||
|
2: 'course-makeup', |
||||
|
3: 'course-waiting' |
||||
|
} |
||||
|
return classMap[type] || 'course-normal' |
||||
|
}, |
||||
|
|
||||
|
// 获取课程安排状态文本 |
||||
|
getScheduleStatusText(status) { |
||||
|
const statusMap = { |
||||
|
0: '待上课', |
||||
|
1: '已上课', |
||||
|
2: '请假' |
||||
|
} |
||||
|
return statusMap[status] || '未知' |
||||
|
}, |
||||
|
|
||||
|
// 获取课程安排状态样式 |
||||
|
getScheduleStatusClass(status) { |
||||
|
const classMap = { |
||||
|
0: 'schedule-pending', |
||||
|
1: 'schedule-completed', |
||||
|
2: 'schedule-leave' |
||||
|
} |
||||
|
return classMap[status] || 'schedule-unknown' |
||||
|
}, |
||||
|
|
||||
|
// 格式化日期 |
||||
|
formatDate(dateStr) { |
||||
|
if (!dateStr) return '--' |
||||
|
const date = new Date(dateStr) |
||||
|
const month = (date.getMonth() + 1).toString().padStart(2, '0') |
||||
|
const day = date.getDate().toString().padStart(2, '0') |
||||
|
return `${month}-${day}` |
||||
|
}, |
||||
|
|
||||
|
// 显示课程安排详情 |
||||
|
showScheduleDetail(item) { |
||||
|
// 可以跳转到课程安排详情页面 |
||||
|
console.log('查看课程安排详情:', item) |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
</script> |
||||
|
|
||||
|
<style lang="scss" scoped> |
||||
|
.course-detail-container { |
||||
|
padding: 20rpx; |
||||
|
background-color: #f5f5f5; |
||||
|
min-height: 100vh; |
||||
|
} |
||||
|
|
||||
|
.course-info-card { |
||||
|
background: white; |
||||
|
border-radius: 16rpx; |
||||
|
padding: 24rpx; |
||||
|
margin-bottom: 20rpx; |
||||
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1); |
||||
|
} |
||||
|
|
||||
|
.course-header { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
align-items: center; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.course-title { |
||||
|
font-size: 32rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.course-status { |
||||
|
padding: 8rpx 16rpx; |
||||
|
border-radius: 20rpx; |
||||
|
font-size: 24rpx; |
||||
|
font-weight: 500; |
||||
|
|
||||
|
&.status-active { |
||||
|
background: #e8f5e8; |
||||
|
color: #52c41a; |
||||
|
} |
||||
|
|
||||
|
&.status-expired { |
||||
|
background: #fff2f0; |
||||
|
color: #ff4d4f; |
||||
|
} |
||||
|
|
||||
|
&.status-waiting { |
||||
|
background: #f6ffed; |
||||
|
color: #faad14; |
||||
|
} |
||||
|
|
||||
|
&.status-delayed { |
||||
|
background: #f0f5ff; |
||||
|
color: #1890ff; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.course-stats { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.stat-item { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
align-items: center; |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.stat-label { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
margin-bottom: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.stat-value { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
|
||||
|
&.remaining { |
||||
|
color: #1890ff; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.course-dates { |
||||
|
display: flex; |
||||
|
justify-content: space-between; |
||||
|
padding-top: 20rpx; |
||||
|
border-top: 1rpx solid #f0f0f0; |
||||
|
} |
||||
|
|
||||
|
.date-item { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.section-title { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
margin: 30rpx 0 16rpx; |
||||
|
} |
||||
|
|
||||
|
.title-text { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
} |
||||
|
|
||||
|
.title-count { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
margin-left: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.schedule-list, .usage-list { |
||||
|
background: white; |
||||
|
border-radius: 16rpx; |
||||
|
overflow: hidden; |
||||
|
margin-bottom: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.schedule-item, .usage-item { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
padding: 24rpx; |
||||
|
border-bottom: 1rpx solid #f0f0f0; |
||||
|
|
||||
|
&:last-child { |
||||
|
border-bottom: none; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.schedule-date, .usage-date { |
||||
|
display: flex; |
||||
|
flex-direction: column; |
||||
|
width: 140rpx; |
||||
|
margin-right: 20rpx; |
||||
|
} |
||||
|
|
||||
|
.date-text { |
||||
|
font-size: 28rpx; |
||||
|
font-weight: 600; |
||||
|
color: #333; |
||||
|
margin-bottom: 4rpx; |
||||
|
} |
||||
|
|
||||
|
.time-text { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.schedule-info, .usage-info { |
||||
|
flex: 1; |
||||
|
} |
||||
|
|
||||
|
.schedule-type { |
||||
|
display: flex; |
||||
|
gap: 8rpx; |
||||
|
margin-bottom: 8rpx; |
||||
|
} |
||||
|
|
||||
|
.type-tag, .course-type-tag { |
||||
|
padding: 4rpx 8rpx; |
||||
|
border-radius: 8rpx; |
||||
|
font-size: 20rpx; |
||||
|
|
||||
|
&.type-temp { |
||||
|
background: #fff7e6; |
||||
|
color: #fa8c16; |
||||
|
} |
||||
|
|
||||
|
&.type-fixed { |
||||
|
background: #f6ffed; |
||||
|
color: #52c41a; |
||||
|
} |
||||
|
|
||||
|
&.course-add { |
||||
|
background: #e6f7ff; |
||||
|
color: #1890ff; |
||||
|
} |
||||
|
|
||||
|
&.course-makeup { |
||||
|
background: #f9f0ff; |
||||
|
color: #722ed1; |
||||
|
} |
||||
|
|
||||
|
&.course-waiting { |
||||
|
background: #fff2f0; |
||||
|
color: #ff4d4f; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.schedule-status, .usage-status { |
||||
|
display: flex; |
||||
|
align-items: center; |
||||
|
} |
||||
|
|
||||
|
.status-text { |
||||
|
font-size: 24rpx; |
||||
|
font-weight: 500; |
||||
|
|
||||
|
&.schedule-pending { |
||||
|
color: #faad14; |
||||
|
} |
||||
|
|
||||
|
&.schedule-completed { |
||||
|
color: #52c41a; |
||||
|
} |
||||
|
|
||||
|
&.schedule-leave { |
||||
|
color: #ff4d4f; |
||||
|
} |
||||
|
|
||||
|
&.confirmed { |
||||
|
color: #52c41a; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
.schedule-arrow { |
||||
|
margin-left: 16rpx; |
||||
|
} |
||||
|
|
||||
|
.arrow-icon { |
||||
|
font-size: 24rpx; |
||||
|
color: #ccc; |
||||
|
} |
||||
|
|
||||
|
.usage-hours { |
||||
|
font-size: 26rpx; |
||||
|
color: #333; |
||||
|
margin-bottom: 4rpx; |
||||
|
} |
||||
|
|
||||
|
.usage-type { |
||||
|
font-size: 24rpx; |
||||
|
color: #666; |
||||
|
} |
||||
|
|
||||
|
.no-data { |
||||
|
text-align: center; |
||||
|
padding: 100rpx 0; |
||||
|
} |
||||
|
|
||||
|
.no-data-text { |
||||
|
font-size: 24rpx; |
||||
|
color: #999; |
||||
|
} |
||||
|
</style> |
||||
Loading…
Reference in new issue