11 changed files with 1740 additions and 388 deletions
@ -0,0 +1,474 @@ |
|||
<template> |
|||
<view class="add-schedule-container"> |
|||
<uni-nav-bar |
|||
title="添加课程安排" |
|||
left-icon="left" |
|||
fixed="true" |
|||
background-color="#292929" |
|||
color="#FFFFFF" |
|||
@clickLeft="goBack" |
|||
></uni-nav-bar> |
|||
|
|||
<view class="form-container"> |
|||
<fui-form> |
|||
<!-- 课程选择 --> |
|||
<fui-form-item label="课程" required> |
|||
<view class="selector-input" @click="showCoursePicker = true"> |
|||
<text>{{ selectedCourse ? selectedCourse.course_name : '请选择课程' }}</text> |
|||
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon> |
|||
</view> |
|||
<fui-picker |
|||
:show="showCoursePicker" |
|||
:options="courseOptions" |
|||
valueKey="id" |
|||
textKey="course_name" |
|||
@confirm="onCourseSelect" |
|||
@cancel="showCoursePicker = false" |
|||
></fui-picker> |
|||
</fui-form-item> |
|||
|
|||
<!-- 班级选择 --> |
|||
<fui-form-item label="班级"> |
|||
<view class="selector-input" @click="showClassPicker = true"> |
|||
<text>{{ selectedClass ? selectedClass.class_name : '请选择班级(可选)' }}</text> |
|||
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon> |
|||
</view> |
|||
<fui-picker |
|||
:show="showClassPicker" |
|||
:options="classOptions" |
|||
valueKey="id" |
|||
textKey="class_name" |
|||
@confirm="onClassSelect" |
|||
@cancel="showClassPicker = false" |
|||
></fui-picker> |
|||
</fui-form-item> |
|||
|
|||
<!-- 教练选择 --> |
|||
<fui-form-item label="授课教练" required> |
|||
<view class="selector-input" @click="showCoachPicker = true"> |
|||
<text>{{ selectedCoach ? selectedCoach.name : '请选择教练' }}</text> |
|||
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon> |
|||
</view> |
|||
<fui-picker |
|||
:show="showCoachPicker" |
|||
:options="coachOptions" |
|||
valueKey="id" |
|||
textKey="name" |
|||
@confirm="onCoachSelect" |
|||
@cancel="showCoachPicker = false" |
|||
></fui-picker> |
|||
</fui-form-item> |
|||
|
|||
<!-- 场地选择 --> |
|||
<fui-form-item label="上课场地" required> |
|||
<view class="selector-input" @click="showVenuePicker = true"> |
|||
<text>{{ selectedVenue ? selectedVenue.venue_name : '请选择场地' }}</text> |
|||
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon> |
|||
</view> |
|||
<fui-picker |
|||
:show="showVenuePicker" |
|||
:options="venueOptions" |
|||
valueKey="id" |
|||
textKey="venue_name" |
|||
@confirm="onVenueSelect" |
|||
@cancel="showVenuePicker = false" |
|||
></fui-picker> |
|||
</fui-form-item> |
|||
|
|||
<!-- 日期选择 --> |
|||
<fui-form-item label="上课日期" required> |
|||
<view class="selector-input" @click="showDatePicker = true"> |
|||
<text>{{ formData.course_date || '请选择日期' }}</text> |
|||
<fui-icon name="calendar" :size="32" color="#CCCCCC"></fui-icon> |
|||
</view> |
|||
<fui-date-picker |
|||
:show="showDatePicker" |
|||
@confirm="onDateSelect" |
|||
@cancel="showDatePicker = false" |
|||
:value="formData.course_date" |
|||
></fui-date-picker> |
|||
</fui-form-item> |
|||
|
|||
<!-- 时间选择 --> |
|||
<fui-form-item label="上课时间" required> |
|||
<view class="selector-input" @click="showTimePicker = true"> |
|||
<text>{{ formData.time_slot || '请选择时间段' }}</text> |
|||
<fui-icon name="time" :size="32" color="#CCCCCC"></fui-icon> |
|||
</view> |
|||
<fui-picker |
|||
:show="showTimePicker" |
|||
:options="timeSlotOptions" |
|||
valueKey="value" |
|||
textKey="text" |
|||
@confirm="onTimeSelect" |
|||
@cancel="showTimePicker = false" |
|||
></fui-picker> |
|||
</fui-form-item> |
|||
|
|||
<!-- 容量设置 --> |
|||
<fui-form-item label="课程容量" required> |
|||
<fui-input |
|||
type="number" |
|||
:value="formData.available_capacity" |
|||
placeholder="请输入课程容量" |
|||
@input="formData.available_capacity = $event" |
|||
></fui-input> |
|||
</fui-form-item> |
|||
|
|||
<!-- 备注信息 --> |
|||
<fui-form-item label="备注"> |
|||
<fui-textarea |
|||
:value="formData.remark" |
|||
placeholder="请输入备注信息(可选)" |
|||
@input="formData.remark = $event" |
|||
maxlength="200" |
|||
></fui-textarea> |
|||
</fui-form-item> |
|||
</fui-form> |
|||
|
|||
<!-- 提交按钮 --> |
|||
<view class="btn-container"> |
|||
<fui-button type="primary" @click="submitForm" :loading="submitting">创建课程安排</fui-button> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import api from '@/api/apiRoute.js'; |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
// 表单数据 |
|||
formData: { |
|||
course_id: '', |
|||
class_id: '', |
|||
coach_id: '', |
|||
venue_id: '', |
|||
course_date: '', |
|||
time_slot: '', |
|||
available_capacity: '', |
|||
remark: '' |
|||
}, |
|||
|
|||
// 选择器数据 |
|||
showCoursePicker: false, |
|||
showClassPicker: false, |
|||
showCoachPicker: false, |
|||
showVenuePicker: false, |
|||
showDatePicker: false, |
|||
showTimePicker: false, |
|||
|
|||
// 选项数据 |
|||
courseOptions: [], |
|||
classOptions: [], |
|||
coachOptions: [], |
|||
venueOptions: [], |
|||
timeSlotOptions: [], |
|||
|
|||
// 选中的数据对象 |
|||
selectedCourse: null, |
|||
selectedClass: null, |
|||
selectedCoach: null, |
|||
selectedVenue: null, |
|||
|
|||
// 状态标记 |
|||
submitting: false, |
|||
|
|||
// 预填充数据 |
|||
prefillDate: '', |
|||
prefillTime: '', |
|||
prefillTimeSlot: '' |
|||
}; |
|||
}, |
|||
|
|||
onLoad(options) { |
|||
// 从路由参数获取预填充数据 |
|||
if (options.date) { |
|||
this.prefillDate = options.date; |
|||
this.formData.course_date = options.date; |
|||
} |
|||
|
|||
if (options.time) { |
|||
this.prefillTime = options.time; |
|||
} |
|||
|
|||
if (options.time_slot) { |
|||
this.prefillTimeSlot = options.time_slot; |
|||
this.formData.time_slot = options.time_slot; |
|||
} |
|||
|
|||
// 加载初始数据 |
|||
this.loadFilterOptions(); |
|||
}, |
|||
|
|||
methods: { |
|||
// 返回上一页 |
|||
goBack() { |
|||
uni.navigateBack(); |
|||
}, |
|||
|
|||
// 加载选项数据 |
|||
async loadFilterOptions() { |
|||
uni.showLoading({ |
|||
title: '加载数据中...' |
|||
}); |
|||
|
|||
try { |
|||
const res = await api.getCourseScheduleFilterOptions(); |
|||
|
|||
if (res.code === 1) { |
|||
// 设置课程选项 |
|||
this.courseOptions = res.data.courses || []; |
|||
|
|||
// 设置班级选项 |
|||
this.classOptions = res.data.classes || []; |
|||
|
|||
// 设置教练选项 |
|||
this.coachOptions = res.data.coaches || []; |
|||
|
|||
// 设置场地选项 |
|||
this.venueOptions = res.data.venues || []; |
|||
|
|||
// 生成时间段选项 |
|||
this.generateTimeSlotOptions(); |
|||
|
|||
// 如果有预填充时间段,设置选中的时间段 |
|||
if (this.prefillTimeSlot) { |
|||
this.formData.time_slot = this.prefillTimeSlot; |
|||
} |
|||
} else { |
|||
uni.showToast({ |
|||
title: res.msg || '加载筛选选项失败', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
} catch (error) { |
|||
console.error('加载筛选选项失败:', error); |
|||
uni.showToast({ |
|||
title: '加载筛选选项失败', |
|||
icon: 'none' |
|||
}); |
|||
} finally { |
|||
uni.hideLoading(); |
|||
} |
|||
}, |
|||
|
|||
// 生成时间段选项 |
|||
generateTimeSlotOptions() { |
|||
const timeSlots = []; |
|||
|
|||
// 早上时间段 |
|||
for (let hour = 8; hour < 12; hour++) { |
|||
const startHour = hour.toString().padStart(2, '0'); |
|||
const endHour = (hour + 1).toString().padStart(2, '0'); |
|||
timeSlots.push({ |
|||
value: `${startHour}:00-${endHour}:00`, |
|||
text: `${startHour}:00-${endHour}:00` |
|||
}); |
|||
} |
|||
|
|||
// 下午时间段 |
|||
for (let hour = 12; hour < 18; hour++) { |
|||
const startHour = hour.toString().padStart(2, '0'); |
|||
const endHour = (hour + 1).toString().padStart(2, '0'); |
|||
timeSlots.push({ |
|||
value: `${startHour}:00-${endHour}:00`, |
|||
text: `${startHour}:00-${endHour}:00` |
|||
}); |
|||
} |
|||
|
|||
// 晚上时间段 |
|||
for (let hour = 18; hour < 22; hour++) { |
|||
const startHour = hour.toString().padStart(2, '0'); |
|||
const endHour = (hour + 1).toString().padStart(2, '0'); |
|||
timeSlots.push({ |
|||
value: `${startHour}:00-${endHour}:00`, |
|||
text: `${startHour}:00-${endHour}:00` |
|||
}); |
|||
} |
|||
|
|||
this.timeSlotOptions = timeSlots; |
|||
}, |
|||
|
|||
// 选择器处理方法 |
|||
onCourseSelect(e) { |
|||
const index = e.index; |
|||
if (index >= 0 && index < this.courseOptions.length) { |
|||
this.selectedCourse = this.courseOptions[index]; |
|||
this.formData.course_id = this.selectedCourse.id; |
|||
} |
|||
this.showCoursePicker = false; |
|||
}, |
|||
|
|||
onClassSelect(e) { |
|||
const index = e.index; |
|||
if (index >= 0 && index < this.classOptions.length) { |
|||
this.selectedClass = this.classOptions[index]; |
|||
this.formData.class_id = this.selectedClass.id; |
|||
} else { |
|||
this.selectedClass = null; |
|||
this.formData.class_id = ''; |
|||
} |
|||
this.showClassPicker = false; |
|||
}, |
|||
|
|||
onCoachSelect(e) { |
|||
const index = e.index; |
|||
if (index >= 0 && index < this.coachOptions.length) { |
|||
this.selectedCoach = this.coachOptions[index]; |
|||
this.formData.coach_id = this.selectedCoach.id; |
|||
} |
|||
this.showCoachPicker = false; |
|||
}, |
|||
|
|||
onVenueSelect(e) { |
|||
const index = e.index; |
|||
if (index >= 0 && index < this.venueOptions.length) { |
|||
this.selectedVenue = this.venueOptions[index]; |
|||
this.formData.venue_id = this.selectedVenue.id; |
|||
|
|||
// 如果场地有默认容量,设置容量 |
|||
if (this.selectedVenue.capacity) { |
|||
this.formData.available_capacity = this.selectedVenue.capacity; |
|||
} |
|||
} |
|||
this.showVenuePicker = false; |
|||
}, |
|||
|
|||
onDateSelect(e) { |
|||
this.formData.course_date = e.result; |
|||
this.showDatePicker = false; |
|||
}, |
|||
|
|||
onTimeSelect(e) { |
|||
const index = e.index; |
|||
if (index >= 0 && index < this.timeSlotOptions.length) { |
|||
this.formData.time_slot = this.timeSlotOptions[index].value; |
|||
} |
|||
this.showTimePicker = false; |
|||
}, |
|||
|
|||
// 表单验证 |
|||
validateForm() { |
|||
if (!this.formData.course_id) { |
|||
uni.showToast({ |
|||
title: '请选择课程', |
|||
icon: 'none' |
|||
}); |
|||
return false; |
|||
} |
|||
|
|||
if (!this.formData.coach_id) { |
|||
uni.showToast({ |
|||
title: '请选择授课教练', |
|||
icon: 'none' |
|||
}); |
|||
return false; |
|||
} |
|||
|
|||
if (!this.formData.venue_id) { |
|||
uni.showToast({ |
|||
title: '请选择上课场地', |
|||
icon: 'none' |
|||
}); |
|||
return false; |
|||
} |
|||
|
|||
if (!this.formData.course_date) { |
|||
uni.showToast({ |
|||
title: '请选择上课日期', |
|||
icon: 'none' |
|||
}); |
|||
return false; |
|||
} |
|||
|
|||
if (!this.formData.time_slot) { |
|||
uni.showToast({ |
|||
title: '请选择上课时间', |
|||
icon: 'none' |
|||
}); |
|||
return false; |
|||
} |
|||
|
|||
if (!this.formData.available_capacity) { |
|||
uni.showToast({ |
|||
title: '请输入课程容量', |
|||
icon: 'none' |
|||
}); |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
}, |
|||
|
|||
// 提交表单 |
|||
async submitForm() { |
|||
if (!this.validateForm()) { |
|||
return; |
|||
} |
|||
|
|||
this.submitting = true; |
|||
|
|||
try { |
|||
const res = await api.createCourseSchedule(this.formData); |
|||
|
|||
if (res.code === 1) { |
|||
uni.showToast({ |
|||
title: '创建成功', |
|||
icon: 'success' |
|||
}); |
|||
|
|||
// 延迟返回,让用户看到成功提示 |
|||
setTimeout(() => { |
|||
uni.navigateBack(); |
|||
}, 1500); |
|||
} else { |
|||
uni.showToast({ |
|||
title: res.msg || '创建失败', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
} catch (error) { |
|||
console.error('创建课程安排失败:', error); |
|||
uni.showToast({ |
|||
title: '创建失败,请重试', |
|||
icon: 'none' |
|||
}); |
|||
} finally { |
|||
this.submitting = false; |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.add-schedule-container { |
|||
min-height: 100vh; |
|||
background-color: #18181c; |
|||
padding-top: 88rpx; |
|||
} |
|||
|
|||
.form-container { |
|||
padding: 30rpx; |
|||
} |
|||
|
|||
.selector-input { |
|||
height: 80rpx; |
|||
background-color: #23232a; |
|||
border-radius: 8rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
padding: 0 24rpx; |
|||
font-size: 28rpx; |
|||
color: #fff; |
|||
} |
|||
|
|||
.btn-container { |
|||
margin-top: 60rpx; |
|||
padding: 0 30rpx; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,497 @@ |
|||
<template> |
|||
<view class="adjust-course-container"> |
|||
<uni-nav-bar |
|||
title="调整课程安排" |
|||
left-icon="left" |
|||
fixed="true" |
|||
background-color="#292929" |
|||
color="#FFFFFF" |
|||
@clickLeft="goBack" |
|||
></uni-nav-bar> |
|||
|
|||
<view class="form-container"> |
|||
<view v-if="loading" class="loading-container"> |
|||
<fui-loading></fui-loading> |
|||
<text class="loading-text">加载中...</text> |
|||
</view> |
|||
|
|||
<fui-form v-else> |
|||
<!-- 课程信息 --> |
|||
<view class="section-title">当前课程信息</view> |
|||
<view class="course-info-card"> |
|||
<view class="info-row"> |
|||
<text class="info-label">课程名称:</text> |
|||
<text class="info-value">{{ scheduleInfo.course_name }}</text> |
|||
</view> |
|||
<view class="info-row"> |
|||
<text class="info-label">上课日期:</text> |
|||
<text class="info-value">{{ scheduleInfo.course_date }}</text> |
|||
</view> |
|||
<view class="info-row"> |
|||
<text class="info-label">上课时间:</text> |
|||
<text class="info-value">{{ scheduleInfo.time_slot }}</text> |
|||
</view> |
|||
<view class="info-row"> |
|||
<text class="info-label">授课教练:</text> |
|||
<text class="info-value">{{ scheduleInfo.coach_name }}</text> |
|||
</view> |
|||
<view class="info-row"> |
|||
<text class="info-label">上课场地:</text> |
|||
<text class="info-value">{{ scheduleInfo.venue_name }}</text> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="section-title">调整后信息</view> |
|||
|
|||
<!-- 教练选择 --> |
|||
<fui-form-item label="授课教练"> |
|||
<view class="selector-input" @click="showCoachPicker = true"> |
|||
<text>{{ selectedCoach ? selectedCoach.name : scheduleInfo.coach_name }}</text> |
|||
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon> |
|||
</view> |
|||
<fui-picker |
|||
:show="showCoachPicker" |
|||
:options="coachOptions" |
|||
valueKey="id" |
|||
textKey="name" |
|||
@confirm="onCoachSelect" |
|||
@cancel="showCoachPicker = false" |
|||
></fui-picker> |
|||
</fui-form-item> |
|||
|
|||
<!-- 场地选择 --> |
|||
<fui-form-item label="上课场地"> |
|||
<view class="selector-input" @click="showVenuePicker = true"> |
|||
<text>{{ selectedVenue ? selectedVenue.venue_name : scheduleInfo.venue_name }}</text> |
|||
<fui-icon name="arrowdown" :size="32" color="#CCCCCC"></fui-icon> |
|||
</view> |
|||
<fui-picker |
|||
:show="showVenuePicker" |
|||
:options="venueOptions" |
|||
valueKey="id" |
|||
textKey="venue_name" |
|||
@confirm="onVenueSelect" |
|||
@cancel="showVenuePicker = false" |
|||
></fui-picker> |
|||
</fui-form-item> |
|||
|
|||
<!-- 日期选择 --> |
|||
<fui-form-item label="上课日期"> |
|||
<view class="selector-input" @click="showDatePicker = true"> |
|||
<text>{{ formData.course_date || scheduleInfo.course_date }}</text> |
|||
<fui-icon name="calendar" :size="32" color="#CCCCCC"></fui-icon> |
|||
</view> |
|||
<fui-date-picker |
|||
:show="showDatePicker" |
|||
@confirm="onDateSelect" |
|||
@cancel="showDatePicker = false" |
|||
:value="formData.course_date || scheduleInfo.course_date" |
|||
></fui-date-picker> |
|||
</fui-form-item> |
|||
|
|||
<!-- 时间选择 --> |
|||
<fui-form-item label="上课时间"> |
|||
<view class="selector-input" @click="showTimePicker = true"> |
|||
<text>{{ formData.time_slot || scheduleInfo.time_slot }}</text> |
|||
<fui-icon name="time" :size="32" color="#CCCCCC"></fui-icon> |
|||
</view> |
|||
<fui-picker |
|||
:show="showTimePicker" |
|||
:options="timeSlotOptions" |
|||
valueKey="value" |
|||
textKey="text" |
|||
@confirm="onTimeSelect" |
|||
@cancel="showTimePicker = false" |
|||
></fui-picker> |
|||
</fui-form-item> |
|||
|
|||
<!-- 容量设置 --> |
|||
<fui-form-item label="课程容量"> |
|||
<fui-input |
|||
type="number" |
|||
:value="formData.available_capacity || scheduleInfo.available_capacity" |
|||
placeholder="请输入课程容量" |
|||
@input="formData.available_capacity = $event" |
|||
></fui-input> |
|||
</fui-form-item> |
|||
|
|||
<!-- 调整原因 --> |
|||
<fui-form-item label="调整原因" required> |
|||
<fui-textarea |
|||
:value="formData.adjust_reason" |
|||
placeholder="请输入调整原因" |
|||
@input="formData.adjust_reason = $event" |
|||
maxlength="200" |
|||
></fui-textarea> |
|||
</fui-form-item> |
|||
|
|||
<!-- 提交按钮 --> |
|||
<view class="btn-container"> |
|||
<fui-button type="primary" @click="submitForm" :loading="submitting">确认调整</fui-button> |
|||
</view> |
|||
</fui-form> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import api from '@/api/apiRoute.js'; |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
// 状态标记 |
|||
loading: true, |
|||
submitting: false, |
|||
|
|||
// 课程ID |
|||
scheduleId: null, |
|||
|
|||
// 课程信息 |
|||
scheduleInfo: {}, |
|||
|
|||
// 表单数据 |
|||
formData: { |
|||
schedule_id: '', |
|||
coach_id: '', |
|||
venue_id: '', |
|||
course_date: '', |
|||
time_slot: '', |
|||
available_capacity: '', |
|||
adjust_reason: '' |
|||
}, |
|||
|
|||
// 选择器数据 |
|||
showCoachPicker: false, |
|||
showVenuePicker: false, |
|||
showDatePicker: false, |
|||
showTimePicker: false, |
|||
|
|||
// 选项数据 |
|||
coachOptions: [], |
|||
venueOptions: [], |
|||
timeSlotOptions: [], |
|||
|
|||
// 选中的数据对象 |
|||
selectedCoach: null, |
|||
selectedVenue: null |
|||
}; |
|||
}, |
|||
|
|||
onLoad(options) { |
|||
if (options.id) { |
|||
this.scheduleId = options.id; |
|||
this.formData.schedule_id = options.id; |
|||
this.loadScheduleInfo(); |
|||
this.loadFilterOptions(); |
|||
} else { |
|||
uni.showToast({ |
|||
title: '参数错误', |
|||
icon: 'none' |
|||
}); |
|||
setTimeout(() => { |
|||
uni.navigateBack(); |
|||
}, 1500); |
|||
} |
|||
}, |
|||
|
|||
methods: { |
|||
// 返回上一页 |
|||
goBack() { |
|||
uni.navigateBack(); |
|||
}, |
|||
|
|||
// 加载课程安排信息 |
|||
async loadScheduleInfo() { |
|||
try { |
|||
const res = await api.getCourseScheduleInfo({ schedule_id: this.scheduleId }); |
|||
|
|||
if (res.code === 1) { |
|||
this.scheduleInfo = res.data; |
|||
|
|||
// 初始化表单数据 |
|||
this.formData.coach_id = this.scheduleInfo.coach_id; |
|||
this.formData.venue_id = this.scheduleInfo.venue_id; |
|||
this.formData.course_date = this.scheduleInfo.course_date; |
|||
this.formData.time_slot = this.scheduleInfo.time_slot; |
|||
this.formData.available_capacity = this.scheduleInfo.available_capacity; |
|||
} else { |
|||
uni.showToast({ |
|||
title: res.msg || '获取课程安排信息失败', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
} catch (error) { |
|||
console.error('获取课程安排信息失败:', error); |
|||
uni.showToast({ |
|||
title: '获取课程安排信息失败', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
}, |
|||
|
|||
// 加载选项数据 |
|||
async loadFilterOptions() { |
|||
try { |
|||
const res = await api.getCourseScheduleFilterOptions(); |
|||
|
|||
if (res.code === 1) { |
|||
// 设置教练选项 |
|||
this.coachOptions = res.data.coaches || []; |
|||
|
|||
// 设置场地选项 |
|||
this.venueOptions = res.data.venues || []; |
|||
|
|||
// 生成时间段选项 |
|||
this.generateTimeSlotOptions(); |
|||
|
|||
// 找到当前选中的教练和场地 |
|||
this.findSelectedOptions(); |
|||
} else { |
|||
uni.showToast({ |
|||
title: res.msg || '加载筛选选项失败', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
} catch (error) { |
|||
console.error('加载筛选选项失败:', error); |
|||
uni.showToast({ |
|||
title: '加载筛选选项失败', |
|||
icon: 'none' |
|||
}); |
|||
} finally { |
|||
this.loading = false; |
|||
} |
|||
}, |
|||
|
|||
// 查找当前选中的选项 |
|||
findSelectedOptions() { |
|||
// 查找当前教练 |
|||
if (this.scheduleInfo.coach_id) { |
|||
this.selectedCoach = this.coachOptions.find(coach => coach.id === this.scheduleInfo.coach_id); |
|||
} |
|||
|
|||
// 查找当前场地 |
|||
if (this.scheduleInfo.venue_id) { |
|||
this.selectedVenue = this.venueOptions.find(venue => venue.id === this.scheduleInfo.venue_id); |
|||
} |
|||
}, |
|||
|
|||
// 生成时间段选项 |
|||
generateTimeSlotOptions() { |
|||
const timeSlots = []; |
|||
|
|||
// 早上时间段 |
|||
for (let hour = 8; hour < 12; hour++) { |
|||
const startHour = hour.toString().padStart(2, '0'); |
|||
const endHour = (hour + 1).toString().padStart(2, '0'); |
|||
timeSlots.push({ |
|||
value: `${startHour}:00-${endHour}:00`, |
|||
text: `${startHour}:00-${endHour}:00` |
|||
}); |
|||
} |
|||
|
|||
// 下午时间段 |
|||
for (let hour = 12; hour < 18; hour++) { |
|||
const startHour = hour.toString().padStart(2, '0'); |
|||
const endHour = (hour + 1).toString().padStart(2, '0'); |
|||
timeSlots.push({ |
|||
value: `${startHour}:00-${endHour}:00`, |
|||
text: `${startHour}:00-${endHour}:00` |
|||
}); |
|||
} |
|||
|
|||
// 晚上时间段 |
|||
for (let hour = 18; hour < 22; hour++) { |
|||
const startHour = hour.toString().padStart(2, '0'); |
|||
const endHour = (hour + 1).toString().padStart(2, '0'); |
|||
timeSlots.push({ |
|||
value: `${startHour}:00-${endHour}:00`, |
|||
text: `${startHour}:00-${endHour}:00` |
|||
}); |
|||
} |
|||
|
|||
this.timeSlotOptions = timeSlots; |
|||
}, |
|||
|
|||
// 选择器处理方法 |
|||
onCoachSelect(e) { |
|||
const index = e.index; |
|||
if (index >= 0 && index < this.coachOptions.length) { |
|||
this.selectedCoach = this.coachOptions[index]; |
|||
this.formData.coach_id = this.selectedCoach.id; |
|||
} |
|||
this.showCoachPicker = false; |
|||
}, |
|||
|
|||
onVenueSelect(e) { |
|||
const index = e.index; |
|||
if (index >= 0 && index < this.venueOptions.length) { |
|||
this.selectedVenue = this.venueOptions[index]; |
|||
this.formData.venue_id = this.selectedVenue.id; |
|||
|
|||
// 如果场地有默认容量,设置容量 |
|||
if (this.selectedVenue.capacity && !this.formData.available_capacity) { |
|||
this.formData.available_capacity = this.selectedVenue.capacity; |
|||
} |
|||
} |
|||
this.showVenuePicker = false; |
|||
}, |
|||
|
|||
onDateSelect(e) { |
|||
this.formData.course_date = e.result; |
|||
this.showDatePicker = false; |
|||
}, |
|||
|
|||
onTimeSelect(e) { |
|||
const index = e.index; |
|||
if (index >= 0 && index < this.timeSlotOptions.length) { |
|||
this.formData.time_slot = this.timeSlotOptions[index].value; |
|||
} |
|||
this.showTimePicker = false; |
|||
}, |
|||
|
|||
// 表单验证 |
|||
validateForm() { |
|||
// 检查是否有任何修改 |
|||
const hasChanges = this.formData.coach_id !== this.scheduleInfo.coach_id || |
|||
this.formData.venue_id !== this.scheduleInfo.venue_id || |
|||
this.formData.course_date !== this.scheduleInfo.course_date || |
|||
this.formData.time_slot !== this.scheduleInfo.time_slot || |
|||
this.formData.available_capacity !== this.scheduleInfo.available_capacity; |
|||
|
|||
if (!hasChanges) { |
|||
uni.showToast({ |
|||
title: '未进行任何修改', |
|||
icon: 'none' |
|||
}); |
|||
return false; |
|||
} |
|||
|
|||
if (!this.formData.adjust_reason) { |
|||
uni.showToast({ |
|||
title: '请输入调整原因', |
|||
icon: 'none' |
|||
}); |
|||
return false; |
|||
} |
|||
|
|||
return true; |
|||
}, |
|||
|
|||
// 提交表单 |
|||
async submitForm() { |
|||
if (!this.validateForm()) { |
|||
return; |
|||
} |
|||
|
|||
this.submitting = true; |
|||
|
|||
try { |
|||
const res = await api.updateCourseSchedule(this.formData); |
|||
|
|||
if (res.code === 1) { |
|||
uni.showToast({ |
|||
title: '调整成功', |
|||
icon: 'success' |
|||
}); |
|||
|
|||
// 延迟返回,让用户看到成功提示 |
|||
setTimeout(() => { |
|||
uni.navigateBack(); |
|||
}, 1500); |
|||
} else { |
|||
uni.showToast({ |
|||
title: res.msg || '调整失败', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
} catch (error) { |
|||
console.error('调整课程安排失败:', error); |
|||
uni.showToast({ |
|||
title: '调整失败,请重试', |
|||
icon: 'none' |
|||
}); |
|||
} finally { |
|||
this.submitting = false; |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.adjust-course-container { |
|||
min-height: 100vh; |
|||
background-color: #18181c; |
|||
padding-top: 88rpx; |
|||
} |
|||
|
|||
.form-container { |
|||
padding: 30rpx; |
|||
} |
|||
|
|||
.loading-container { |
|||
height: 200rpx; |
|||
display: flex; |
|||
flex-direction: column; |
|||
justify-content: center; |
|||
align-items: center; |
|||
} |
|||
|
|||
.loading-text { |
|||
margin-top: 20rpx; |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
|
|||
.section-title { |
|||
font-size: 32rpx; |
|||
font-weight: bold; |
|||
color: #29d3b4; |
|||
margin: 30rpx 0 20rpx; |
|||
padding-bottom: 10rpx; |
|||
border-bottom: 1px solid #333; |
|||
} |
|||
|
|||
.course-info-card { |
|||
background-color: #23232a; |
|||
border-radius: 12rpx; |
|||
padding: 20rpx; |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.info-row { |
|||
display: flex; |
|||
margin-bottom: 16rpx; |
|||
font-size: 28rpx; |
|||
} |
|||
|
|||
.info-label { |
|||
color: #999; |
|||
width: 160rpx; |
|||
flex-shrink: 0; |
|||
} |
|||
|
|||
.info-value { |
|||
color: #fff; |
|||
flex: 1; |
|||
} |
|||
|
|||
.selector-input { |
|||
height: 80rpx; |
|||
background-color: #23232a; |
|||
border-radius: 8rpx; |
|||
display: flex; |
|||
align-items: center; |
|||
justify-content: space-between; |
|||
padding: 0 24rpx; |
|||
font-size: 28rpx; |
|||
color: #fff; |
|||
} |
|||
|
|||
.btn-container { |
|||
margin-top: 60rpx; |
|||
padding: 0 30rpx; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,511 @@ |
|||
<template> |
|||
<view class="sign-in-container"> |
|||
<uni-nav-bar |
|||
title="课程点名" |
|||
left-icon="left" |
|||
fixed="true" |
|||
background-color="#292929" |
|||
color="#FFFFFF" |
|||
@clickLeft="goBack" |
|||
></uni-nav-bar> |
|||
|
|||
<view class="content"> |
|||
<!-- 课程信息 --> |
|||
<view class="course-info-card" v-if="scheduleInfo"> |
|||
<view class="course-title">{{ scheduleInfo.course_name }}</view> |
|||
<view class="course-time">{{ scheduleInfo.course_date }} {{ scheduleInfo.time_slot }}</view> |
|||
<view class="course-detail"> |
|||
<view class="detail-item"> |
|||
<text class="detail-label">授课教练:</text> |
|||
<text class="detail-value">{{ scheduleInfo.coach_name }}</text> |
|||
</view> |
|||
<view class="detail-item"> |
|||
<text class="detail-label">上课场地:</text> |
|||
<text class="detail-value">{{ scheduleInfo.venue_name }}</text> |
|||
</view> |
|||
<view class="detail-item"> |
|||
<text class="detail-label">学员人数:</text> |
|||
<text class="detail-value">{{ studentList.length }}人</text> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 学员列表 --> |
|||
<view class="student-section"> |
|||
<view class="section-header"> |
|||
<view class="section-title">学员点名</view> |
|||
<view class="action-buttons"> |
|||
<fui-button type="primary" size="small" @click="checkAllStudents">全部签到</fui-button> |
|||
<fui-button type="danger" size="small" @click="uncheckAllStudents">全部取消</fui-button> |
|||
</view> |
|||
</view> |
|||
|
|||
<view class="empty-list" v-if="studentList.length === 0"> |
|||
<image src="/static/icon-img/empty.png" mode="aspectFit" class="empty-img"></image> |
|||
<text class="empty-text">暂无学员数据</text> |
|||
</view> |
|||
|
|||
<view class="student-list" v-else> |
|||
<view |
|||
class="student-item" |
|||
v-for="(student, index) in studentList" |
|||
:key="index" |
|||
@click="toggleStudentStatus(index)" |
|||
> |
|||
<view class="student-avatar"> |
|||
<image :src="student.avatar || '/static/icon-img/avatar.png'" mode="aspectFill"></image> |
|||
<view class="status-badge" :class="getStatusClass(student.status)"></view> |
|||
</view> |
|||
|
|||
<view class="student-info"> |
|||
<text class="student-name">{{ student.name }}</text> |
|||
<text class="student-phone">{{ student.phone_number || '无联系电话' }}</text> |
|||
</view> |
|||
|
|||
<view class="status-container"> |
|||
<view class="status-select"> |
|||
<view |
|||
class="status-option" |
|||
:class="{ active: student.status === 1 }" |
|||
@click.stop="setStudentStatus(index, 1)" |
|||
> |
|||
已到 |
|||
</view> |
|||
<view |
|||
class="status-option" |
|||
:class="{ active: student.status === 2 }" |
|||
@click.stop="setStudentStatus(index, 2)" |
|||
> |
|||
请假 |
|||
</view> |
|||
<view |
|||
class="status-option" |
|||
:class="{ active: student.status === 0 }" |
|||
@click.stop="setStudentStatus(index, 0)" |
|||
> |
|||
未到 |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
|
|||
<!-- 点名备注 --> |
|||
<view class="remark-section"> |
|||
<view class="section-title">点名备注</view> |
|||
<fui-textarea |
|||
v-model="signInRemark" |
|||
placeholder="请输入点名备注(可选)" |
|||
maxlength="200" |
|||
></fui-textarea> |
|||
</view> |
|||
|
|||
<!-- 提交按钮 --> |
|||
<view class="submit-btn"> |
|||
<fui-button type="primary" @click="submitSignIn" :loading="submitting">提交点名</fui-button> |
|||
</view> |
|||
</view> |
|||
</view> |
|||
</template> |
|||
|
|||
<script> |
|||
import api from '@/api/apiRoute.js'; |
|||
|
|||
export default { |
|||
data() { |
|||
return { |
|||
// 课程ID |
|||
scheduleId: null, |
|||
|
|||
// 课程信息 |
|||
scheduleInfo: null, |
|||
|
|||
// 学员列表 |
|||
studentList: [], |
|||
|
|||
// 点名备注 |
|||
signInRemark: '', |
|||
|
|||
// 提交状态 |
|||
submitting: false |
|||
}; |
|||
}, |
|||
|
|||
onLoad(options) { |
|||
if (options.id) { |
|||
this.scheduleId = options.id; |
|||
this.loadScheduleInfo(); |
|||
} else { |
|||
uni.showToast({ |
|||
title: '参数错误', |
|||
icon: 'none' |
|||
}); |
|||
setTimeout(() => { |
|||
uni.navigateBack(); |
|||
}, 1500); |
|||
} |
|||
}, |
|||
|
|||
methods: { |
|||
// 返回上一页 |
|||
goBack() { |
|||
uni.navigateBack(); |
|||
}, |
|||
|
|||
// 加载课程安排信息 |
|||
async loadScheduleInfo() { |
|||
uni.showLoading({ |
|||
title: '加载中...' |
|||
}); |
|||
|
|||
try { |
|||
const res = await api.getCourseScheduleInfo({ schedule_id: this.scheduleId }); |
|||
|
|||
if (res.code === 1) { |
|||
this.scheduleInfo = res.data; |
|||
|
|||
// 处理学员列表 |
|||
if (this.scheduleInfo.students && this.scheduleInfo.students.length > 0) { |
|||
this.studentList = [...this.scheduleInfo.students]; |
|||
} |
|||
} else { |
|||
uni.showToast({ |
|||
title: res.msg || '获取课程安排信息失败', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
} catch (error) { |
|||
console.error('获取课程安排信息失败:', error); |
|||
uni.showToast({ |
|||
title: '获取课程安排信息失败', |
|||
icon: 'none' |
|||
}); |
|||
} finally { |
|||
uni.hideLoading(); |
|||
} |
|||
}, |
|||
|
|||
// 获取学员状态样式 |
|||
getStatusClass(status) { |
|||
const statusMap = { |
|||
0: 'status-absent', |
|||
1: 'status-present', |
|||
2: 'status-leave' |
|||
}; |
|||
|
|||
return statusMap[status] || 'status-absent'; |
|||
}, |
|||
|
|||
// 切换学员状态 |
|||
toggleStudentStatus(index) { |
|||
const student = this.studentList[index]; |
|||
|
|||
// 状态循环:未到 -> 已到 -> 请假 -> 未到 |
|||
let newStatus = 0; |
|||
|
|||
if (student.status === 0) { |
|||
newStatus = 1; |
|||
} else if (student.status === 1) { |
|||
newStatus = 2; |
|||
} else { |
|||
newStatus = 0; |
|||
} |
|||
|
|||
this.setStudentStatus(index, newStatus); |
|||
}, |
|||
|
|||
// 设置学员状态 |
|||
setStudentStatus(index, status) { |
|||
if (index >= 0 && index < this.studentList.length) { |
|||
this.studentList[index].status = status; |
|||
|
|||
// 更新状态文本 |
|||
const statusTextMap = { |
|||
0: '待上课', |
|||
1: '已上课', |
|||
2: '请假' |
|||
}; |
|||
|
|||
this.studentList[index].status_text = statusTextMap[status]; |
|||
} |
|||
}, |
|||
|
|||
// 全部签到 |
|||
checkAllStudents() { |
|||
this.studentList.forEach((student, index) => { |
|||
this.setStudentStatus(index, 1); |
|||
}); |
|||
}, |
|||
|
|||
// 全部取消 |
|||
uncheckAllStudents() { |
|||
this.studentList.forEach((student, index) => { |
|||
this.setStudentStatus(index, 0); |
|||
}); |
|||
}, |
|||
|
|||
// 提交点名 |
|||
async submitSignIn() { |
|||
// 准备提交数据 |
|||
const studentData = this.studentList.map(student => ({ |
|||
student_id: student.student_id, |
|||
resource_id: student.resource_id, |
|||
status: student.status |
|||
})); |
|||
|
|||
const submitData = { |
|||
schedule_id: this.scheduleId, |
|||
students: studentData, |
|||
remark: this.signInRemark |
|||
}; |
|||
|
|||
this.submitting = true; |
|||
|
|||
try { |
|||
// 使用API进行点名 |
|||
const res = await api.submitScheduleSignIn(submitData); |
|||
|
|||
if (res.code === 1) { |
|||
uni.showToast({ |
|||
title: '点名成功', |
|||
icon: 'success' |
|||
}); |
|||
|
|||
// 延迟返回 |
|||
setTimeout(() => { |
|||
uni.navigateBack(); |
|||
}, 1500); |
|||
} else { |
|||
uni.showToast({ |
|||
title: res.msg || '点名失败', |
|||
icon: 'none' |
|||
}); |
|||
} |
|||
} catch (error) { |
|||
console.error('点名失败:', error); |
|||
uni.showToast({ |
|||
title: '点名失败,请重试', |
|||
icon: 'none' |
|||
}); |
|||
} finally { |
|||
this.submitting = false; |
|||
} |
|||
} |
|||
} |
|||
}; |
|||
</script> |
|||
|
|||
<style lang="scss" scoped> |
|||
.sign-in-container { |
|||
min-height: 100vh; |
|||
background-color: #18181c; |
|||
padding-top: 88rpx; |
|||
} |
|||
|
|||
.content { |
|||
padding: 30rpx; |
|||
} |
|||
|
|||
.course-info-card { |
|||
background-color: #23232a; |
|||
border-radius: 12rpx; |
|||
padding: 24rpx; |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.course-title { |
|||
font-size: 32rpx; |
|||
font-weight: bold; |
|||
color: #fff; |
|||
margin-bottom: 10rpx; |
|||
} |
|||
|
|||
.course-time { |
|||
font-size: 26rpx; |
|||
color: #29d3b4; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.course-detail { |
|||
background-color: #2a2a2a; |
|||
border-radius: 8rpx; |
|||
padding: 16rpx; |
|||
} |
|||
|
|||
.detail-item { |
|||
display: flex; |
|||
margin-bottom: 10rpx; |
|||
font-size: 26rpx; |
|||
|
|||
&:last-child { |
|||
margin-bottom: 0; |
|||
} |
|||
} |
|||
|
|||
.detail-label { |
|||
color: #999; |
|||
width: 140rpx; |
|||
} |
|||
|
|||
.detail-value { |
|||
color: #fff; |
|||
flex: 1; |
|||
} |
|||
|
|||
.student-section { |
|||
background-color: #23232a; |
|||
border-radius: 12rpx; |
|||
padding: 24rpx; |
|||
margin-bottom: 30rpx; |
|||
} |
|||
|
|||
.section-header { |
|||
display: flex; |
|||
justify-content: space-between; |
|||
align-items: center; |
|||
margin-bottom: 24rpx; |
|||
} |
|||
|
|||
.section-title { |
|||
font-size: 30rpx; |
|||
font-weight: bold; |
|||
color: #fff; |
|||
} |
|||
|
|||
.action-buttons { |
|||
display: flex; |
|||
gap: 16rpx; |
|||
} |
|||
|
|||
.student-list { |
|||
max-height: 600rpx; |
|||
overflow-y: auto; |
|||
} |
|||
|
|||
.student-item { |
|||
display: flex; |
|||
align-items: center; |
|||
background-color: #2a2a2a; |
|||
border-radius: 8rpx; |
|||
padding: 16rpx; |
|||
margin-bottom: 16rpx; |
|||
|
|||
&:last-child { |
|||
margin-bottom: 0; |
|||
} |
|||
} |
|||
|
|||
.student-avatar { |
|||
width: 80rpx; |
|||
height: 80rpx; |
|||
border-radius: 40rpx; |
|||
overflow: hidden; |
|||
position: relative; |
|||
margin-right: 20rpx; |
|||
|
|||
image { |
|||
width: 100%; |
|||
height: 100%; |
|||
} |
|||
} |
|||
|
|||
.status-badge { |
|||
position: absolute; |
|||
bottom: 0; |
|||
right: 0; |
|||
width: 24rpx; |
|||
height: 24rpx; |
|||
border-radius: 12rpx; |
|||
background-color: #999; |
|||
border: 2rpx solid #fff; |
|||
} |
|||
|
|||
.status-absent { |
|||
background-color: #ff3b30; |
|||
} |
|||
|
|||
.status-present { |
|||
background-color: #34c759; |
|||
} |
|||
|
|||
.status-leave { |
|||
background-color: #ff9500; |
|||
} |
|||
|
|||
.student-info { |
|||
flex: 1; |
|||
display: flex; |
|||
flex-direction: column; |
|||
} |
|||
|
|||
.student-name { |
|||
font-size: 28rpx; |
|||
color: #fff; |
|||
margin-bottom: 6rpx; |
|||
} |
|||
|
|||
.student-phone { |
|||
font-size: 24rpx; |
|||
color: #999; |
|||
} |
|||
|
|||
.status-container { |
|||
margin-left: 16rpx; |
|||
} |
|||
|
|||
.status-select { |
|||
display: flex; |
|||
gap: 10rpx; |
|||
} |
|||
|
|||
.status-option { |
|||
padding: 8rpx 16rpx; |
|||
font-size: 24rpx; |
|||
border-radius: 30rpx; |
|||
background-color: #3a3a3a; |
|||
color: #fff; |
|||
|
|||
&.active { |
|||
background-color: #29d3b4; |
|||
} |
|||
} |
|||
|
|||
.empty-list { |
|||
display: flex; |
|||
flex-direction: column; |
|||
align-items: center; |
|||
justify-content: center; |
|||
padding: 60rpx 0; |
|||
|
|||
.empty-img { |
|||
width: 200rpx; |
|||
height: 200rpx; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
|
|||
.empty-text { |
|||
font-size: 28rpx; |
|||
color: #999; |
|||
} |
|||
} |
|||
|
|||
.remark-section { |
|||
background-color: #23232a; |
|||
border-radius: 12rpx; |
|||
padding: 24rpx; |
|||
margin-bottom: 40rpx; |
|||
|
|||
.section-title { |
|||
font-size: 30rpx; |
|||
font-weight: bold; |
|||
color: #fff; |
|||
margin-bottom: 20rpx; |
|||
} |
|||
} |
|||
|
|||
.submit-btn { |
|||
margin-top: 40rpx; |
|||
padding-bottom: 40rpx; |
|||
} |
|||
</style> |
|||
Loading…
Reference in new issue