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.
611 lines
16 KiB
611 lines
16 KiB
<template>
|
|
<view class="detail-root">
|
|
<view class="header">
|
|
<view class="title">课程安排详情</view>
|
|
<view class="date">日期:{{ course_info.course_date }} {{course_info.time_slot}}</view>
|
|
</view>
|
|
<!-- 正式学员列表 -->
|
|
<view class="section">
|
|
<view class="section-title">正式学员</view>
|
|
<view class="student-list" v-if="course_info">
|
|
<!-- 显示已安排的正式学员 -->
|
|
<view v-for="(stu, idx) in formalStudents" :key="idx" class="student-item"
|
|
@tap="viewStudent(stu)">
|
|
<view class="avatar">{{ stu.name && stu.name.charAt(0) }}</view>
|
|
<view class="info">
|
|
<view class="name">{{ stu.name }}</view>
|
|
<view class="desc">{{ getCourseTypeText(stu.course_type) }} | {{ getStatusText(stu.status) }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 显示正式学员空位 -->
|
|
<view v-for="index in formalEmptySeats" :key="index" class="student-item empty"
|
|
@tap="addStudent($event, index, 'formal')">
|
|
<view class="avatar empty-avatar">+</view>
|
|
<view class="info">
|
|
<view class="name">空位</view>
|
|
<view class="desc">点击添加学员</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
<!-- 没有数据时的占位区域 -->
|
|
<view class="empty-placeholder" v-else>
|
|
<image src="/static/icon-img/empty.png" mode="aspectFit"></image>
|
|
<text>暂无课程数据</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 等待位学员列表 -->
|
|
<view class="section">
|
|
<view class="section-title waiting-title">等待位</view>
|
|
<view class="student-list">
|
|
<!-- 显示等待位学员 -->
|
|
<view v-for="(stu, idx) in waitingStudents" :key="idx" class="student-item waiting"
|
|
@tap="viewStudent(stu)">
|
|
<view class="avatar waiting-avatar">{{ stu.name && stu.name.charAt(0) }}</view>
|
|
<view class="info">
|
|
<view class="name">{{ stu.name }}</view>
|
|
<view class="desc">{{ getCourseTypeText(stu.course_type) }} | {{ getStatusText(stu.status) }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 等待位空位(固定2个) -->
|
|
<view v-for="index in waitingEmptySeats" :key="index"
|
|
class="student-item empty waiting" @tap="addStudent($event, index, 'waiting')">
|
|
<view class="avatar empty-avatar waiting-avatar">+</view>
|
|
<view class="info">
|
|
<view class="name">等待位</view>
|
|
<view class="desc">点击添加学员</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 请假原因弹窗 -->
|
|
<fui-modal ref="leaveReasonModal" :buttons="[]" width="600" title="请假申请">
|
|
<view class="leave-form">
|
|
<view class="leave-label">请假原因</view>
|
|
<view class="leave-input">
|
|
<fui-textarea v-model="leaveReason" placeholder="请输入请假原因" :isCounter="true" :maxlength="200"
|
|
:minHeight="200" :isAutoHeight="true"></fui-textarea>
|
|
</view>
|
|
<view class="leave-buttons">
|
|
<fui-button background="#434544" color="#fff" borderColor="#666" btnSize="medium"
|
|
@tap="$refs.leaveReasonModal.close()">取消</fui-button>
|
|
<fui-button background="#29d3b4" color="#fff" btnSize="medium"
|
|
@tap="submitLeaveRequest">提交</fui-button>
|
|
</view>
|
|
</view>
|
|
</fui-modal>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import apiRoute from '@/api/apiRoute.js';
|
|
export default {
|
|
data() {
|
|
return {
|
|
course_id: '',
|
|
course_info: [],
|
|
date: '',
|
|
students: [], // 所有学员数据
|
|
formalStudents: [], // 正式学员
|
|
waitingStudents: [], // 等待位学员
|
|
resource_id: '',
|
|
leaveReason: '', // 请假原因
|
|
currentStudent: null, // 当前操作的学生
|
|
formalEmptySeats: [], // 正式学员空位数组
|
|
waitingEmptySeats: [1, 2] // 等待位固定2个空位
|
|
};
|
|
},
|
|
onLoad(query) {
|
|
console.log('onLoad 参数:', query);
|
|
this.course_id = query.id || '';
|
|
this.resource_id = query.resource_id
|
|
console.log('初始化参数 - course_id:', this.course_id, 'resource_id:', this.resource_id);
|
|
this.courseInfo();
|
|
},
|
|
methods: {
|
|
viewStudent(stu) {
|
|
console.log(stu, this.course_info);
|
|
|
|
// 显示操作选项弹窗
|
|
uni.showActionSheet({
|
|
title: `学员: ${stu.name}`,
|
|
itemList: stu.person_type === 'customer_resource' ? ['取消课程'] : ['申请请假'],
|
|
success: (res) => {
|
|
if (res.tapIndex === 0) {
|
|
// 判断学员类型
|
|
if (stu.person_type === 'customer_resource') {
|
|
// 如果是客户资源,弹出确认取消课程的弹窗
|
|
uni.showModal({
|
|
title: '取消课程',
|
|
content: `是否取消学员 ${stu.name} 的课程?`,
|
|
success: async (res) => {
|
|
if (res.confirm) {
|
|
// 用户点击确定,调用schedule_del接口
|
|
try {
|
|
uni.showLoading({
|
|
title: '处理中...'
|
|
});
|
|
|
|
const params = {
|
|
resources_id: stu.resources_id,
|
|
id: this.course_info.id
|
|
};
|
|
|
|
const result = await apiRoute.schedule_del(params);
|
|
|
|
uni.hideLoading();
|
|
|
|
if (result.code === 1) {
|
|
uni.showToast({
|
|
title: '取消课程成功',
|
|
icon: 'success'
|
|
});
|
|
|
|
// 刷新数据
|
|
await this.courseInfo();
|
|
} else {
|
|
uni.showToast({
|
|
title: result.msg || '取消课程失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
} catch (error) {
|
|
uni.hideLoading();
|
|
uni.showToast({
|
|
title: '操作失败,请重试',
|
|
icon: 'none'
|
|
});
|
|
console.error('取消课程失败:', error);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} else if (stu.person_type === 'student') {
|
|
// 如果是学生,弹出请假原因输入框
|
|
this.$refs.leaveReasonModal.open();
|
|
this.currentStudent = stu; // 保存当前操作的学生信息
|
|
}
|
|
}
|
|
}
|
|
});
|
|
},
|
|
getStatusText(status) {
|
|
const statusMap = {
|
|
0: '待上课',
|
|
1: '已上课',
|
|
2: '请假'
|
|
};
|
|
return statusMap[status] || status;
|
|
},
|
|
// 获取课程类型文本
|
|
getCourseTypeText(courseType) {
|
|
const courseTypeMap = {
|
|
1: '正式课',
|
|
2: '体验课',
|
|
3: '补课',
|
|
4: '试听课'
|
|
};
|
|
return courseTypeMap[courseType] || '未知';
|
|
},
|
|
// 分类学员数据
|
|
categorizeStudents() {
|
|
// 根据schedule_type分类学员
|
|
// schedule_type: 1=正式位, 2=等待位
|
|
this.formalStudents = this.students.filter(stu => stu.schedule_type === 1 || stu.schedule_type === null);
|
|
this.waitingStudents = this.students.filter(stu => stu.schedule_type === 2);
|
|
|
|
// 确保学员名称存在,如果不存在则返回未知学员
|
|
this.formalStudents.forEach(stu => {
|
|
if (!stu.name) stu.name = '未知学员';
|
|
});
|
|
this.waitingStudents.forEach(stu => {
|
|
if (!stu.name) stu.name = '未知学员';
|
|
});
|
|
|
|
console.log('学员分类完成:', {
|
|
formal: this.formalStudents.length,
|
|
waiting: this.waitingStudents.length
|
|
});
|
|
},
|
|
async scheduleList() {
|
|
try {
|
|
console.log('开始获取学员列表, schedule_id:', this.course_id);
|
|
let res = await apiRoute.scheduleList({
|
|
'schedule_id': this.course_id
|
|
})
|
|
|
|
console.log('学员列表响应:', res);
|
|
|
|
if (res.code === 1 && Array.isArray(res.data)) {
|
|
// 确保学员列表按照位置排序
|
|
this.students = res.data.sort((a, b) => {
|
|
// 如果有position字段,按position排序
|
|
if (a.position !== undefined && b.position !== undefined) {
|
|
return parseInt(a.position) - parseInt(b.position);
|
|
}
|
|
// 否则按照ID排序
|
|
return a.id - b.id;
|
|
});
|
|
|
|
// 分类学员
|
|
this.categorizeStudents();
|
|
} else {
|
|
this.students = [];
|
|
this.formalStudents = [];
|
|
this.waitingStudents = [];
|
|
}
|
|
|
|
// 更新可用容量
|
|
this.updateAvailableCapacity();
|
|
|
|
return this.students;
|
|
} catch (error) {
|
|
console.error('获取学员列表失败:', error);
|
|
this.students = [];
|
|
this.formalStudents = [];
|
|
this.waitingStudents = [];
|
|
return [];
|
|
}
|
|
},
|
|
async courseInfo() {
|
|
try {
|
|
console.log('开始获取课程信息, id:', this.course_id);
|
|
let res = await apiRoute.courseInfo({
|
|
'id': this.course_id
|
|
})
|
|
console.log('课程信息响应:', res);
|
|
|
|
if (res.code === 1 && res.data) {
|
|
this.course_info = res.data;
|
|
|
|
|
|
// 获取学员列表
|
|
await this.scheduleList();
|
|
|
|
// 计算可用容量
|
|
this.updateAvailableCapacity();
|
|
} else {
|
|
uni.showToast({
|
|
title: res.msg || '获取课程信息失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('获取课程信息失败:', error);
|
|
uni.showToast({
|
|
title: '获取课程信息失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
},
|
|
// 更新可用容量
|
|
updateAvailableCapacity() {
|
|
// 确保course_info已加载
|
|
if (!this.course_info) return;
|
|
|
|
console.log('更新可用容量 - 课程总容量:', this.course_info.available_capacity, '正式学员数量:', this.formalStudents.length);
|
|
|
|
// 计算正式位已占用的位置数
|
|
const occupiedFormalSeats = this.formalStudents.length;
|
|
|
|
// 获取课程总容量(正式位)
|
|
const totalCapacity = parseInt(this.course_info.available_capacity) || 0;
|
|
|
|
// 计算正式位剩余空位
|
|
const remainingFormalSeats = Math.max(0, totalCapacity - occupiedFormalSeats);
|
|
|
|
console.log('计算后的正式位剩余容量:', remainingFormalSeats);
|
|
|
|
// 初始化正式位空位数组
|
|
this.formalEmptySeats = [];
|
|
for (let i = 1; i <= remainingFormalSeats; i++) {
|
|
this.formalEmptySeats.push(i);
|
|
}
|
|
|
|
// 更新等待位空位(减去已有的等待位学员)
|
|
const remainingWaitingSeats = Math.max(0, 2 - this.waitingStudents.length);
|
|
this.waitingEmptySeats = [];
|
|
for (let i = 1; i <= remainingWaitingSeats; i++) {
|
|
this.waitingEmptySeats.push(i);
|
|
}
|
|
|
|
console.log('等待位剩余空位:', remainingWaitingSeats);
|
|
},
|
|
async addStudent(e, index, type = 'formal') {
|
|
console.log('添加学员到位置:', index, '类型:', type);
|
|
|
|
const data = {
|
|
'resources_id': this.resource_id,
|
|
'person_type': 'customer_resource',
|
|
'schedule_id': this.course_id,
|
|
'course_date': this.course_info.course_date,
|
|
'time_slot': this.course_info.time_slot,
|
|
'position': index, // 位置信息
|
|
'schedule_type': type === 'waiting' ? 2 : 1, // 1=正式位, 2=等待位
|
|
'course_type': type === 'waiting' ? 2 : 1 // 1=正式课, 2=体验课等
|
|
};
|
|
|
|
try {
|
|
uni.showLoading({
|
|
title: '添加中...'
|
|
});
|
|
|
|
let res = await apiRoute.addSchedule(data)
|
|
|
|
uni.hideLoading();
|
|
|
|
if (res.code == 1) {
|
|
uni.showToast({
|
|
title: '添加成功',
|
|
icon: 'success'
|
|
});
|
|
|
|
// 刷新数据并更新可用容量
|
|
await this.courseInfo();
|
|
} else {
|
|
uni.showToast({
|
|
title: res.msg || '添加失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
|
|
} catch (error) {
|
|
uni.hideLoading();
|
|
uni.showToast({
|
|
title: '添加失败',
|
|
icon: 'none'
|
|
});
|
|
console.error('添加学员失败:', error);
|
|
}
|
|
},
|
|
// 提交请假申请
|
|
submitLeaveRequest() {
|
|
if (!this.leaveReason.trim()) {
|
|
uni.showToast({
|
|
title: '请填写请假原因',
|
|
icon: 'none'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 调用请假接口
|
|
uni.showLoading({
|
|
title: '提交中...'
|
|
});
|
|
|
|
// 构建请假参数
|
|
const params = {
|
|
resources_id: this.currentStudent.resources_id,
|
|
id: this.course_info.id,
|
|
remark: this.leaveReason
|
|
};
|
|
|
|
// 调用请假接口
|
|
apiRoute.schedule_del(params)
|
|
.then(res => {
|
|
uni.hideLoading();
|
|
if (res.code === 1) {
|
|
uni.showToast({
|
|
title: '请假申请提交成功',
|
|
icon: 'success'
|
|
});
|
|
this.$refs.leaveReasonModal.close();
|
|
this.leaveReason = ''; // 清空请假原因
|
|
|
|
// 刷新数据并更新可用容量
|
|
this.courseInfo().then(() => {
|
|
// 确保数据刷新后更新可用容量
|
|
this.updateAvailableCapacity();
|
|
});
|
|
} else {
|
|
uni.showToast({
|
|
title: res.msg || '请假申请提交失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
})
|
|
.catch(err => {
|
|
uni.hideLoading();
|
|
uni.showToast({
|
|
title: '请假申请提交失败,请重试',
|
|
icon: 'none'
|
|
});
|
|
console.error('请假申请提交失败:', err);
|
|
});
|
|
},
|
|
},
|
|
};
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.detail-root {
|
|
background: #232323;
|
|
min-height: 100vh;
|
|
padding-bottom: 30rpx;
|
|
}
|
|
|
|
.header {
|
|
padding: 40rpx 30rpx 20rpx 30rpx;
|
|
|
|
.title {
|
|
color: #fff;
|
|
font-size: 36rpx;
|
|
font-weight: bold;
|
|
}
|
|
|
|
.date {
|
|
color: #29d3b4;
|
|
font-size: 26rpx;
|
|
margin-top: 10rpx;
|
|
}
|
|
}
|
|
|
|
.section {
|
|
margin: 30rpx;
|
|
background: #434544;
|
|
border-radius: 16rpx;
|
|
padding: 30rpx 20rpx;
|
|
}
|
|
|
|
.section-title {
|
|
color: #ffd86b;
|
|
font-size: 28rpx;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.student-list {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 20rpx;
|
|
}
|
|
|
|
.student-item {
|
|
background: #333;
|
|
border-radius: 12rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 18rpx 24rpx;
|
|
width: calc(50% - 10rpx);
|
|
/* 一行两个,减去gap的一半 */
|
|
box-sizing: border-box;
|
|
|
|
.avatar {
|
|
width: 60rpx;
|
|
height: 60rpx;
|
|
border-radius: 50%;
|
|
background: #29d3b4;
|
|
color: #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 32rpx;
|
|
margin-right: 18rpx;
|
|
}
|
|
|
|
.info {
|
|
flex: 1;
|
|
overflow: hidden;
|
|
|
|
.name {
|
|
color: #fff;
|
|
font-size: 28rpx;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.desc {
|
|
color: #bdbdbd;
|
|
font-size: 22rpx;
|
|
margin-top: 4rpx;
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
}
|
|
|
|
&.empty {
|
|
border: 2rpx dashed #ffd86b;
|
|
background: #232323;
|
|
|
|
.avatar.empty-avatar {
|
|
background: #ffd86b;
|
|
color: #232323;
|
|
font-size: 36rpx;
|
|
}
|
|
|
|
.info .name {
|
|
color: #ffd86b;
|
|
}
|
|
|
|
.info .desc {
|
|
color: #ffd86b;
|
|
}
|
|
}
|
|
|
|
// 等待位样式
|
|
&.waiting {
|
|
background: #2a2a3a; // 不同的背景色
|
|
border: 1rpx solid #8a7fff;
|
|
|
|
.avatar,
|
|
.avatar.waiting-avatar {
|
|
background: #8a7fff;
|
|
color: #fff;
|
|
}
|
|
|
|
&.empty {
|
|
border: 2rpx dashed #8a7fff;
|
|
background: #1a1a2a;
|
|
|
|
.avatar.empty-avatar {
|
|
background: #8a7fff;
|
|
color: #1a1a2a;
|
|
}
|
|
|
|
.info .name {
|
|
color: #8a7fff;
|
|
}
|
|
|
|
.info .desc {
|
|
color: #8a7fff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 等待位标题样式
|
|
.waiting-title {
|
|
color: #8a7fff !important;
|
|
}
|
|
|
|
// 请假弹窗样式
|
|
.leave-form {
|
|
padding: 20rpx;
|
|
|
|
.leave-label {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.leave-input {
|
|
margin-bottom: 30rpx;
|
|
}
|
|
|
|
.leave-buttons {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
gap: 20rpx;
|
|
|
|
.fui-button {
|
|
flex: 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 空数据占位区域样式 */
|
|
.empty-placeholder {
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 60rpx 0;
|
|
|
|
image {
|
|
width: 200rpx;
|
|
height: 200rpx;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
text {
|
|
color: #999;
|
|
font-size: 28rpx;
|
|
}
|
|
}
|
|
</style>
|