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.
1568 lines
38 KiB
1568 lines
38 KiB
<template>
|
|
<view class="course-schedule">
|
|
<!-- Course Info -->
|
|
<view class="course-info">
|
|
<text class="course-title">课程安排详情</text>
|
|
<view class="course-detail">
|
|
<text class="course-item">日期:{{ schedule_info.course_date }}</text>
|
|
<!-- <text class="course-item">课程名称:{{ schedule_info.course_name }}</text>-->
|
|
<text class="course-item">班级名称:{{ schedule_info.class_name || '未设置' }}</text>
|
|
<text class="course-item">课程时间:{{ schedule_info.time_slot }}</text>
|
|
<text class="course-item">主教练:{{ schedule_info.coach_name || '待安排' }}</text>
|
|
<text class="course-item">场地信息:{{ schedule_info.venue_name }}</text>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Formal Students Section -->
|
|
<view class="section">
|
|
<text class="section-title">正式学员</text>
|
|
<view class="cards-grid">
|
|
<!-- Student Card with Data -->
|
|
<view
|
|
v-for="(stu, idx) in formalStudents"
|
|
:key="idx"
|
|
class="student-card filled"
|
|
@tap="viewStudent(stu)"
|
|
>
|
|
<view v-if="stu.renewal_status" class="renewal-badge">待续费</view>
|
|
<view class="avatar">{{ stu.name && stu.name.charAt(0) }}</view>
|
|
<view class="student-info">
|
|
<view class="student-name">{{ stu.name }}</view>
|
|
<view class="student-age">年龄:{{ stu.age || '未知' }}岁</view>
|
|
<view class="course-status">课程状态:{{ stu.courseStatus }}</view>
|
|
<view class="course-status">上课情况:{{ stu.course_progress.used }}/{{ stu.course_progress.total }}节</view>
|
|
<view class="expiry-date" v-if="stu.student_course_info">到期时间:{{ stu.student_course_info.end_date || '未设置' }}</view>
|
|
</view>
|
|
<view v-if="stu.status === 1" class="status-badge signed">已签到</view>
|
|
<view v-else-if="stu.status === 0" class="status-badge unsigned">未签到</view>
|
|
</view>
|
|
|
|
<!-- Empty Slots -->
|
|
<view
|
|
v-for="n in formalEmptySeats"
|
|
:key="n"
|
|
class="student-card empty"
|
|
:data-type="'formal'"
|
|
:data-index="n"
|
|
@tap="openStudentModal"
|
|
>
|
|
<view class="add-icon">
|
|
<text class="plus-icon">+</text>
|
|
</view>
|
|
<view class="add-text">
|
|
<view class="slot-title">空位</view>
|
|
<view class="slot-subtitle">点击添加学员</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Waiting List Section -->
|
|
<view class="section">
|
|
<text class="section-title waiting-title">等待位</text>
|
|
<view class="cards-grid">
|
|
<!-- Waiting Students -->
|
|
<view
|
|
v-for="(stu, idx) in waitingStudents"
|
|
:key="idx"
|
|
class="student-card waiting filled"
|
|
@tap="viewStudent(stu)"
|
|
>
|
|
<view class="avatar waiting-avatar">{{ stu.name && stu.name.charAt(0) }}</view>
|
|
<view class="student-info">
|
|
<view class="student-name">{{ stu.name }}</view>
|
|
<view class="student-age">年龄:{{ stu.age || '未知' }}岁</view>
|
|
<view class="course-status">课程状态:{{ stu.courseStatus }}</view>
|
|
<view class="course-status">上课情况:
|
|
{{ stu.course_progress.used }}/{{ stu.course_progress.total }}节 ({{ stu.course_progress.percentage }}%)
|
|
</view>
|
|
<view class="expiry-date" v-if="stu.expiryDate">到期时间:{{ stu.expiryDate || '未设置' }}</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Waiting Empty Slots -->
|
|
<view
|
|
v-for="n in waitingEmptySeats"
|
|
:key="n"
|
|
class="student-card waiting"
|
|
:data-type="'waiting'"
|
|
:data-index="n"
|
|
@tap="openStudentModal"
|
|
>
|
|
<view class="add-icon waiting-icon">
|
|
<text class="plus-icon">+</text>
|
|
</view>
|
|
<view class="add-text">
|
|
<view class="slot-title">等待位</view>
|
|
<view class="slot-subtitle">点击添加学员</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Bottom Popup Modal -->
|
|
<view v-if="showModal" class="modal-overlay" @tap="closeModal">
|
|
<view class="modal-content" @tap.stop>
|
|
<view class="modal-header">
|
|
<text>添加学员</text>
|
|
</view>
|
|
|
|
<view class="modal-body">
|
|
<!-- 预设学生信息显示 - 优先显示 -->
|
|
<view v-if="presetStudent && presetStudent.name && presetStudent.phone" class="form-section">
|
|
<text class="form-label">选中学员</text>
|
|
<view class="preset-student">
|
|
<view class="student-avatar">{{ presetStudent.name.charAt(0) }}</view>
|
|
<view class="student-details">
|
|
<view class="student-name">{{ presetStudent.name }}</view>
|
|
<view class="student-phone">{{ presetStudent.phone }}</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Customer Selection - 只在没有预设学生时显示 -->
|
|
<view v-if="!presetStudent || !presetStudent.name || !presetStudent.phone" class="form-section">
|
|
<text class="form-label">客户选择</text>
|
|
|
|
<input
|
|
v-model="searchQuery"
|
|
placeholder="请输入手机号或姓名"
|
|
class="search-input"
|
|
@input="searchStudents"
|
|
/>
|
|
|
|
<!-- Search Results -->
|
|
<view v-if="searchResults.length > 0" class="search-results">
|
|
<view
|
|
v-for="student in searchResults"
|
|
:key="student.id"
|
|
:class="['student-item', { selected: selectedStudent && selectedStudent.id === student.id }]"
|
|
@tap="selectStudent(student)"
|
|
>
|
|
<view class="student-avatar">{{ student.name ? student.name.charAt(0) : '?' }}</view>
|
|
<view class="student-details">
|
|
<view class="student-name">
|
|
{{ student.name }}
|
|
<text v-if="student.is_formal_student" class="formal-badge">正式学员</text>
|
|
</view>
|
|
<view class="student-phone">{{ student.phone }}</view>
|
|
</view>
|
|
<view v-if="selectedStudent && selectedStudent.id === student.id" class="check-icon">
|
|
<text>✓</text>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Course Arrangement -->
|
|
<view class="form-section">
|
|
<text class="form-label">课程安排</text>
|
|
<view class="radio-group">
|
|
<label class="radio-item">
|
|
<radio
|
|
value="1"
|
|
:checked="courseArrangement === '1'"
|
|
@tap="courseArrangement = '1'"
|
|
/>
|
|
<text class="radio-text">临时课</text>
|
|
</label>
|
|
<label class="radio-item" :class="{ disabled: !canSelectFixedCourse }">
|
|
<radio
|
|
value="2"
|
|
:checked="courseArrangement === '2'"
|
|
:disabled="!canSelectFixedCourse"
|
|
@tap="selectFixedCourse"
|
|
/>
|
|
<text class="radio-text" :class="{ disabled: !canSelectFixedCourse }">
|
|
固定课
|
|
<text v-if="!canSelectFixedCourse" class="disabled-tip">(需要付费课程)</text>
|
|
</text>
|
|
</label>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Remarks -->
|
|
<view class="form-section">
|
|
<text class="form-label">备注</text>
|
|
<textarea
|
|
v-model="remarks"
|
|
placeholder="请输入备注信息"
|
|
class="remarks-textarea"
|
|
></textarea>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- Modal Footer -->
|
|
<view class="modal-footer">
|
|
<view class="btn btn-cancel" @tap="closeModal">取消</view>
|
|
<view class="btn btn-confirm" @tap="confirmSelection">确定</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 请假原因弹窗 -->
|
|
<uni-popup ref="leaveReasonModal" type="dialog">
|
|
<view class="leave-modal">
|
|
<view class="leave-modal-header">
|
|
<text class="leave-modal-title">请假申请</text>
|
|
</view>
|
|
<view class="leave-form">
|
|
<view class="leave-label">请假原因</view>
|
|
<view class="leave-input">
|
|
<textarea
|
|
v-model="leaveReason"
|
|
placeholder="请输入请假原因"
|
|
maxlength="200"
|
|
class="leave-textarea"
|
|
></textarea>
|
|
</view>
|
|
<view class="leave-buttons">
|
|
<view class="leave-btn cancel-btn" @tap="closeLeaveModal">取消</view>
|
|
<view class="leave-btn confirm-btn" @tap="submitLeaveRequest">提交</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</uni-popup>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import apiRoute from '@/api/apiRoute.js';
|
|
|
|
export default {
|
|
name: 'ClassArrangementDetail',
|
|
components: {
|
|
// FirstUI 组件如果需要局部导入,在这里添加
|
|
},
|
|
data() {
|
|
return {
|
|
course_id: '',
|
|
schedule_info: {}, // 课程安排基本信息
|
|
date: '',
|
|
students: [], // 所有学员数据
|
|
formalStudents: [], // 正式学员
|
|
waitingStudents: [], // 等待位学员
|
|
resource_id: '',
|
|
student_id: '', // 预设学生ID
|
|
leaveReason: '', // 请假原因
|
|
currentStudent: null, // 当前操作的学生
|
|
formalEmptySeats: [], // 正式学员空位数组
|
|
waitingEmptySeats: [1], // 等待位始终显示1个空位供添加
|
|
|
|
// 新增弹窗相关数据
|
|
showModal: false,
|
|
searchQuery: '',
|
|
courseArrangement: '1', // 1=临时课, 2=固定课
|
|
remarks: '',
|
|
selectedStudent: null,
|
|
currentSlot: null,
|
|
searchResults: [],
|
|
presetStudent: null // 预设学生信息
|
|
};
|
|
},
|
|
onLoad(query) {
|
|
console.log('onLoad 参数:', query);
|
|
// 支持两种参数名:id 和 schedule_id
|
|
this.course_id = query.id || query.schedule_id || '';
|
|
this.resource_id = query.resource_id || '';
|
|
this.student_id = query.student_id || '';
|
|
|
|
console.log('初始化参数 - course_id:', this.course_id, 'resource_id:', this.resource_id, 'student_id:', this.student_id);
|
|
|
|
// 智能路由逻辑:如果有预设的resource_id和student_id,直接获取学生信息
|
|
if (this.resource_id && this.student_id) {
|
|
this.loadPresetStudent();
|
|
}
|
|
|
|
// 确保有课程ID才调用API
|
|
if (this.course_id) {
|
|
this.getScheduleDetail();
|
|
} else {
|
|
console.error('缺少课程ID参数');
|
|
uni.showToast({
|
|
title: '缺少课程ID参数',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
},
|
|
computed: {
|
|
// 判断当前选中的学员是否可以选择固定课
|
|
canSelectFixedCourse() {
|
|
if (!this.selectedStudent) {
|
|
return false;
|
|
}
|
|
// 预设学生 或 搜索结果中选中的学生,都需要有付费课程才能选固定课
|
|
return this.selectedStudent.is_formal_student || false;
|
|
}
|
|
},
|
|
methods: {
|
|
// 返回上一页
|
|
goBack() {
|
|
uni.navigateBack();
|
|
},
|
|
|
|
// 加载预设学生信息
|
|
async loadPresetStudent() {
|
|
try {
|
|
// 使用新的专用API获取预设学员信息(不受状态限制)
|
|
const res = await apiRoute.getPresetStudentInfo({
|
|
resource_id: this.resource_id,
|
|
student_id: this.student_id
|
|
});
|
|
console.log('预设学员信息完整响应:', res);
|
|
|
|
if (res.code === 1 && res.data) {
|
|
const studentData = res.data;
|
|
|
|
this.presetStudent = {
|
|
id: studentData.student_id || this.student_id,
|
|
name: studentData.name || `学员${this.student_id}`,
|
|
phone: studentData.phone_number || '待获取',
|
|
age: studentData.age || null,
|
|
member_id: studentData.user_id || this.student_id,
|
|
student_id: studentData.student_id || this.student_id, // 学员表ID
|
|
resource_id: studentData.resource_id || this.resource_id, // 客户资源表ID
|
|
is_formal_student: studentData.is_formal_student || false,
|
|
course_info: studentData.course_info || [],
|
|
student_course_id: studentData.student_course_id || null
|
|
};
|
|
|
|
this.selectedStudent = this.presetStudent;
|
|
console.log('加载预设学生信息成功:', this.presetStudent);
|
|
console.log('是否可选择固定课:', this.presetStudent.is_formal_student);
|
|
} else {
|
|
console.warn('预设学员API响应异常:', res);
|
|
// 创建基础的预设学生
|
|
this.createFallbackPresetStudent();
|
|
}
|
|
} catch (error) {
|
|
console.error('加载预设学生信息失败:', error);
|
|
// 创建备用预设学生信息
|
|
this.createFallbackPresetStudent();
|
|
}
|
|
},
|
|
|
|
// 创建备用预设学生信息
|
|
createFallbackPresetStudent() {
|
|
if (this.resource_id && this.student_id) {
|
|
this.presetStudent = {
|
|
id: this.student_id,
|
|
name: `学员${this.student_id}`,
|
|
phone: '待获取',
|
|
age: null,
|
|
member_id: this.student_id,
|
|
student_id: this.student_id, // 学员表ID
|
|
resource_id: this.resource_id, // 客户资源表ID
|
|
is_formal_student: false,
|
|
course_info: [],
|
|
student_course_id: null
|
|
};
|
|
this.selectedStudent = this.presetStudent;
|
|
console.log('使用备用预设学生信息:', this.presetStudent);
|
|
}
|
|
},
|
|
|
|
// 打开学生选择弹窗
|
|
openStudentModal(event) {
|
|
// 从 data 属性获取参数
|
|
const type = event.currentTarget.dataset.type;
|
|
const index = event.currentTarget.dataset.index;
|
|
|
|
this.showModal = true;
|
|
this.currentSlot = { type, index };
|
|
this.resetForm();
|
|
|
|
// 如果有预设学生,验证信息完整性后选中
|
|
if (this.presetStudent) {
|
|
// 检查预设学员信息是否完整
|
|
if (this.presetStudent.name && this.presetStudent.phone) {
|
|
this.selectedStudent = this.presetStudent;
|
|
} else {
|
|
console.warn('预设学员信息不完整:', this.presetStudent);
|
|
// 清空不完整的预设学员信息
|
|
this.presetStudent = null;
|
|
this.selectedStudent = null;
|
|
}
|
|
}
|
|
},
|
|
|
|
// 关闭弹窗
|
|
closeModal() {
|
|
this.showModal = false;
|
|
this.resetForm();
|
|
},
|
|
|
|
// 重置表单
|
|
resetForm() {
|
|
if (!this.presetStudent) {
|
|
this.searchQuery = '';
|
|
this.searchResults = [];
|
|
this.selectedStudent = null;
|
|
}
|
|
this.courseArrangement = '1';
|
|
this.remarks = '';
|
|
},
|
|
|
|
// 搜索学生 - 智能检索
|
|
async searchStudents() {
|
|
if (!this.searchQuery.trim()) {
|
|
this.searchResults = [];
|
|
return;
|
|
}
|
|
|
|
// 智能判断输入类型:如果全是数字则判断为手机号,否则为姓名
|
|
const isPhoneNumber = /^\d+$/.test(this.searchQuery.trim());
|
|
|
|
try {
|
|
uni.showLoading({
|
|
title: '搜索中...'
|
|
});
|
|
|
|
// 调用后端接口进行检索
|
|
const params = {};
|
|
if (isPhoneNumber) {
|
|
params.phone_number = this.searchQuery.trim();
|
|
} else {
|
|
params.name = this.searchQuery.trim();
|
|
}
|
|
|
|
const res = await apiRoute.searchStudents(params);
|
|
|
|
uni.hideLoading();
|
|
|
|
if (res.code === 1 && Array.isArray(res.data)) {
|
|
// 后端已经返回了正式学员状态,直接使用
|
|
this.searchResults = res.data.map(student => ({
|
|
id: student.id,
|
|
name: student.name,
|
|
phone: student.phone_number,
|
|
age: student.age,
|
|
member_id: student.member_id,
|
|
student_id: student.student_id || student.id, // 学员表ID
|
|
resource_id: student.resource_id || 0, // 客户资源表ID,如果没有则为0
|
|
is_formal_student: student.is_formal_student || false,
|
|
course_info: student.course_info || []
|
|
}));
|
|
} else {
|
|
this.searchResults = [];
|
|
}
|
|
} catch (error) {
|
|
uni.hideLoading();
|
|
console.error('搜索学员失败:', error);
|
|
this.searchResults = [];
|
|
}
|
|
},
|
|
|
|
|
|
// 选择学生
|
|
selectStudent(student) {
|
|
this.selectedStudent = student;
|
|
// 选择学员后,如果是体验课学员,自动切换到临时课
|
|
if (!this.canSelectFixedCourse) {
|
|
this.courseArrangement = '1';
|
|
}
|
|
},
|
|
|
|
// 选择固定课
|
|
selectFixedCourse() {
|
|
if (this.canSelectFixedCourse) {
|
|
this.courseArrangement = '2';
|
|
} else {
|
|
uni.showToast({
|
|
title: '该学员只能选择临时课',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
},
|
|
|
|
// 确认选择
|
|
async confirmSelection() {
|
|
// 1. 检查是否选择了学员
|
|
if (!this.selectedStudent) {
|
|
uni.showToast({
|
|
title: '请选择学员',
|
|
icon: 'none'
|
|
});
|
|
return;
|
|
}
|
|
|
|
console.log(this.selectedStudent);
|
|
// 2. 检查学员信息是否完整
|
|
if (!this.selectedStudent.name || !this.selectedStudent.phone) {
|
|
uni.showToast({
|
|
title: '学员信息不完整,请重新选择',
|
|
icon: 'none'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 3. 检查关键字段是否存在
|
|
if (!this.selectedStudent.resource_id && !this.resource_id) {
|
|
uni.showToast({
|
|
title: '缺少学员资源ID,无法添加课程',
|
|
icon: 'none'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 4. 如果选择的是固定课,需要验证是否为正式学员
|
|
if (this.courseArrangement === '2') {
|
|
if (!this.selectedStudent.is_formal_student) {
|
|
uni.showToast({
|
|
title: '只有正式学员才能选择固定课',
|
|
icon: 'none'
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
// 构建添加学员的数据
|
|
const data = {
|
|
'resources_id': this.selectedStudent.resource_id || this.resource_id || 0,
|
|
'person_type': 'customer_resource',
|
|
'schedule_id': this.course_id,
|
|
'course_date': this.schedule_info.course_date,
|
|
'time_slot': this.schedule_info.time_slot,
|
|
'position': this.currentSlot.index,
|
|
'schedule_type': this.currentSlot.type === 'waiting' ? 2 : 1,
|
|
'course_type': this.currentSlot.type === 'waiting' ? 3 : parseInt(this.courseArrangement),
|
|
'remark': this.remarks,
|
|
'student_id': this.selectedStudent.student_id || this.selectedStudent.id,
|
|
};
|
|
|
|
try {
|
|
uni.showLoading({
|
|
title: '添加中...'
|
|
});
|
|
|
|
let res = await apiRoute.addSchedule(data);
|
|
|
|
uni.hideLoading();
|
|
|
|
if (res.code == 1) {
|
|
uni.showToast({
|
|
title: '添加成功',
|
|
icon: 'success'
|
|
});
|
|
|
|
this.closeModal();
|
|
// 刷新数据
|
|
await this.getScheduleDetail();
|
|
} else {
|
|
uni.showToast({
|
|
title: res.msg || '添加失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
} catch (error) {
|
|
uni.hideLoading();
|
|
console.error('添加学员失败:', error);
|
|
|
|
// 尝试获取具体的错误信息
|
|
let errorMsg = '添加失败';
|
|
if (error && error.data && error.data.msg) {
|
|
errorMsg = error.data.msg;
|
|
} else if (error && error.message) {
|
|
errorMsg = error.message;
|
|
} else if (typeof error === 'string') {
|
|
errorMsg = error;
|
|
}
|
|
|
|
uni.showToast({
|
|
title: errorMsg,
|
|
icon: 'none'
|
|
});
|
|
}
|
|
},
|
|
|
|
// 获取课程安排类型文本
|
|
getScheduleTypeText(type) {
|
|
const typeMap = {
|
|
1: '固定课',
|
|
2: '临时课'
|
|
};
|
|
return typeMap[type] || '未知';
|
|
},
|
|
viewStudent(stu) {
|
|
console.log(stu, this.schedule_info);
|
|
|
|
// 根据学员状态和类型构建操作选项
|
|
let itemList = [];
|
|
if (stu.person_type === 'customer_resource') {
|
|
itemList.push('取消课程');
|
|
// 如果是等待位学员,且正式位有空位,增加升级选项
|
|
if (stu.schedule_type === 2 && this.formalEmptySeats.length > 0) {
|
|
itemList.push('升级为正式学员');
|
|
}
|
|
} else {
|
|
itemList.push('申请请假');
|
|
}
|
|
|
|
// 显示操作选项弹窗
|
|
uni.showActionSheet({
|
|
title: `学员: ${stu.name}`,
|
|
itemList: itemList,
|
|
success: (res) => {
|
|
const selectedOption = itemList[res.tapIndex];
|
|
|
|
if (selectedOption === '取消课程') {
|
|
// 弹出确认取消课程的弹窗
|
|
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.schedule_info.id,
|
|
student_id: stu.student_id || stu.member_id || '',
|
|
remark: '取消课程'
|
|
};
|
|
|
|
const result = await apiRoute.schedule_del(params);
|
|
|
|
uni.hideLoading();
|
|
|
|
if (result.code === 1) {
|
|
uni.showToast({
|
|
title: '取消课程成功',
|
|
icon: 'success'
|
|
});
|
|
|
|
// 刷新数据
|
|
await this.getScheduleDetail();
|
|
} else {
|
|
uni.showToast({
|
|
title: result.msg || '取消课程失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
} catch (error) {
|
|
uni.hideLoading();
|
|
uni.showToast({
|
|
title: '操作失败,请重试',
|
|
icon: 'none'
|
|
});
|
|
console.error('取消课程失败:', error);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
} else if (selectedOption === '升级为正式学员') {
|
|
// 确认升级操作
|
|
this.confirmUpgradeStudent(stu);
|
|
} else if (selectedOption === '申请请假') {
|
|
// 如果是学生,弹出请假原因输入框
|
|
this.$refs.leaveReasonModal.open();
|
|
this.currentStudent = stu; // 保存当前操作的学生信息
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// 确认升级学员
|
|
confirmUpgradeStudent(stu) {
|
|
// 检查正式位是否有空位
|
|
if (this.formalEmptySeats.length === 0) {
|
|
uni.showToast({
|
|
title: '正式位已满,无法升级',
|
|
icon: 'none'
|
|
});
|
|
return;
|
|
}
|
|
|
|
uni.showModal({
|
|
title: '升级确认',
|
|
content: `是否将等待位学员 ${stu.name} 升级为正式学员?升级后将占用一个正式位。`,
|
|
success: async (res) => {
|
|
if (res.confirm) {
|
|
await this.upgradeStudent(stu);
|
|
}
|
|
}
|
|
});
|
|
},
|
|
|
|
// 执行学员升级
|
|
async upgradeStudent(stu) {
|
|
try {
|
|
uni.showLoading({
|
|
title: '升级中...'
|
|
});
|
|
|
|
// 构建升级参数
|
|
const upgradeData = {
|
|
resources_id: stu.resources_id,
|
|
schedule_id: this.course_id,
|
|
student_id: stu.student_id,
|
|
from_schedule_type: 2, // 从等待位
|
|
to_schedule_type: 1, // 升级到正式位
|
|
position: this.formalStudents.length + 1, // 新的正式位位置
|
|
course_type: stu.course_type === 3 ? 1 : stu.course_type // 等待位课程类型改为正式课
|
|
};
|
|
|
|
// 调用升级接口(需要在后端实现)
|
|
const result = await apiRoute.upgradeStudentSchedule(upgradeData);
|
|
|
|
uni.hideLoading();
|
|
|
|
if (result.code === 1) {
|
|
uni.showToast({
|
|
title: '升级成功',
|
|
icon: 'success'
|
|
});
|
|
|
|
// 刷新数据
|
|
await this.getScheduleDetail();
|
|
} else {
|
|
uni.showToast({
|
|
title: result.msg || '升级失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
} catch (error) {
|
|
uni.hideLoading();
|
|
console.error('升级学员失败:', error);
|
|
|
|
// 尝试获取具体的错误信息
|
|
let errorMsg = '升级失败';
|
|
if (error && error.data && error.data.msg) {
|
|
errorMsg = error.data.msg;
|
|
} else if (error && error.message) {
|
|
errorMsg = error.message;
|
|
} else if (typeof error === 'string') {
|
|
errorMsg = error;
|
|
}
|
|
|
|
uni.showToast({
|
|
title: errorMsg,
|
|
icon: 'none'
|
|
});
|
|
}
|
|
},
|
|
|
|
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 getScheduleDetail() {
|
|
try {
|
|
console.log('开始获取课程安排详情, schedule_id:', this.course_id);
|
|
let res = await apiRoute.getScheduleDetail({
|
|
'schedule_id': this.course_id
|
|
})
|
|
console.log('课程安排详情响应:', res);
|
|
|
|
if (res.code === 1 && res.data) {
|
|
this.schedule_info = res.data.schedule_info;
|
|
this.formalStudents = res.data.formal_students || [];
|
|
this.waitingStudents = res.data.waiting_students || [];
|
|
|
|
// 计算可用容量
|
|
this.updateAvailableCapacity();
|
|
} else {
|
|
uni.showToast({
|
|
title: res.msg || '获取课程安排详情失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('获取课程安排详情失败:', error);
|
|
uni.showToast({
|
|
title: '获取课程安排详情失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
},
|
|
// 更新可用容量
|
|
updateAvailableCapacity() {
|
|
// 确保schedule_info已加载
|
|
if (!this.schedule_info) return;
|
|
|
|
console.log('更新可用容量 - 课程最大容量:', this.schedule_info.max_students, '正式学员数量:', this.formalStudents.length);
|
|
|
|
// 计算正式位已占用的位置数
|
|
const occupiedFormalSeats = this.formalStudents.length;
|
|
|
|
// 获取课程最大容量(正式位)
|
|
const maxStudents = parseInt(this.schedule_info.max_students) || 0;
|
|
|
|
let remainingFormalSeats = 0;
|
|
if (maxStudents === 0) {
|
|
// 如果max_students为0,则没有学员限制,可以一直新增学员
|
|
// 始终显示1个空位供添加,类似等待位的逻辑
|
|
remainingFormalSeats = 1;
|
|
} else {
|
|
// 有限制的情况下,计算剩余空位
|
|
remainingFormalSeats = Math.max(0, maxStudents - occupiedFormalSeats);
|
|
}
|
|
|
|
console.log('计算后的正式位剩余容量:', remainingFormalSeats);
|
|
|
|
// 初始化正式位空位数组
|
|
this.formalEmptySeats = [];
|
|
for (let i = 1; i <= remainingFormalSeats; i++) {
|
|
this.formalEmptySeats.push(i);
|
|
}
|
|
|
|
// 等待位永远都能添加学员,始终显示1个空位供添加
|
|
this.waitingEmptySeats = [1];
|
|
|
|
console.log('等待位空位:', this.waitingEmptySeats.length);
|
|
},
|
|
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.schedule_info.course_date,
|
|
'time_slot': this.schedule_info.time_slot,
|
|
'position': index, // 位置信息
|
|
'schedule_type': type === 'waiting' ? 2 : 1, // 1=正式位, 2=等待位
|
|
'course_type': type === 'waiting' ? 3 : 1 // 3=等待位, 1=正式课
|
|
};
|
|
|
|
try {
|
|
uni.showLoading({
|
|
title: '添加中...'
|
|
});
|
|
|
|
let res = await apiRoute.addSchedule(data)
|
|
|
|
uni.hideLoading();
|
|
|
|
if (res.code == 1) {
|
|
uni.showToast({
|
|
title: '添加成功',
|
|
icon: 'success'
|
|
});
|
|
|
|
// 刷新数据并更新可用容量
|
|
await this.getScheduleDetail();
|
|
} else {
|
|
uni.showToast({
|
|
title: res.msg || '添加失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
|
|
} catch (error) {
|
|
uni.hideLoading();
|
|
console.error('添加学员失败:', error);
|
|
|
|
// 尝试获取具体的错误信息
|
|
let errorMsg = '添加失败';
|
|
if (error && error.data && error.data.msg) {
|
|
errorMsg = error.data.msg;
|
|
} else if (error && error.message) {
|
|
errorMsg = error.message;
|
|
} else if (typeof error === 'string') {
|
|
errorMsg = error;
|
|
}
|
|
|
|
uni.showToast({
|
|
title: errorMsg,
|
|
icon: 'none'
|
|
});
|
|
}
|
|
},
|
|
// 关闭请假弹窗
|
|
closeLeaveModal() {
|
|
this.$refs.leaveReasonModal.close();
|
|
this.leaveReason = '';
|
|
},
|
|
|
|
// 提交请假申请
|
|
submitLeaveRequest() {
|
|
if (!this.leaveReason.trim()) {
|
|
uni.showToast({
|
|
title: '请填写请假原因',
|
|
icon: 'none'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 调用请假接口
|
|
uni.showLoading({
|
|
title: '提交中...'
|
|
});
|
|
|
|
// 构建请假参数
|
|
const params = {
|
|
resources_id: this.currentStudent.resources_id,
|
|
id: this.schedule_info.id,
|
|
student_id: this.currentStudent.student_id,
|
|
remark: this.leaveReason
|
|
};
|
|
|
|
// 调用请假接口
|
|
apiRoute.schedule_del(params)
|
|
.then(res => {
|
|
uni.hideLoading();
|
|
if (res.code === 1) {
|
|
uni.showModal({
|
|
content: '请假申请提交成功',
|
|
showCancel: false,
|
|
})
|
|
this.closeLeaveModal();
|
|
|
|
// 刷新数据并更新可用容量
|
|
this.getScheduleDetail().then(() => {
|
|
// 确保数据刷新后更新可用容量
|
|
this.updateAvailableCapacity();
|
|
});
|
|
} else {
|
|
uni.showToast({
|
|
title: res.msg || '请假申请提交失败',
|
|
icon: 'none'
|
|
});
|
|
}
|
|
})
|
|
.catch(err => {
|
|
uni.hideLoading();
|
|
uni.showToast({
|
|
title: '请假申请提交失败,请重试',
|
|
icon: 'none'
|
|
});
|
|
});
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.course-schedule {
|
|
background: #232323;
|
|
min-height: 100vh;
|
|
padding-bottom: 30rpx;
|
|
}
|
|
|
|
.header {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 40rpx 30rpx 20rpx 30rpx;
|
|
position: relative;
|
|
|
|
.back-btn {
|
|
position: absolute;
|
|
left: 30rpx;
|
|
width: 60rpx;
|
|
height: 60rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
|
|
.back-icon {
|
|
color: #fff;
|
|
font-size: 48rpx;
|
|
font-weight: bold;
|
|
}
|
|
}
|
|
|
|
.title {
|
|
color: #fff;
|
|
font-size: 36rpx;
|
|
font-weight: bold;
|
|
text-align: center;
|
|
flex: 1;
|
|
}
|
|
}
|
|
|
|
.course-info {
|
|
font-size: 28rpx;
|
|
background: #434544;
|
|
border-radius: 16rpx;
|
|
padding: 30rpx 20rpx;
|
|
|
|
.course-title {
|
|
color: #fff;
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
margin-bottom: 16rpx;
|
|
display: block;
|
|
}
|
|
|
|
.course-detail {
|
|
.course-item {
|
|
color: #29d3b4;
|
|
font-size: 26rpx;
|
|
margin-bottom: 8rpx;
|
|
display: block;
|
|
}
|
|
}
|
|
}
|
|
|
|
.section {
|
|
margin: 30rpx;
|
|
|
|
.section-title {
|
|
color: #ffd86b;
|
|
font-size: 28rpx;
|
|
margin-bottom: 30rpx;
|
|
display: block;
|
|
|
|
&.waiting-title {
|
|
color: #8a7fff !important;
|
|
}
|
|
}
|
|
}
|
|
|
|
.cards-grid {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
margin-right: -20rpx; /* 抵消最后一列的右边距 */
|
|
}
|
|
|
|
.student-card {
|
|
background: #333;
|
|
border-radius: 12rpx;
|
|
padding: 24rpx;
|
|
width: calc(50% - 20rpx);
|
|
margin-right: 20rpx;
|
|
margin-bottom: 20rpx;
|
|
box-sizing: border-box;
|
|
display: flex;
|
|
align-items: flex-start;
|
|
position: relative;
|
|
min-height: 200rpx;
|
|
|
|
.renewal-badge {
|
|
position: absolute;
|
|
top: 8rpx;
|
|
right: 8rpx;
|
|
background: #ff6b6b;
|
|
color: #fff;
|
|
font-size: 20rpx;
|
|
padding: 4rpx 8rpx;
|
|
border-radius: 8rpx;
|
|
}
|
|
|
|
.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;
|
|
flex-shrink: 0;
|
|
|
|
&.waiting-avatar {
|
|
background: #8a7fff;
|
|
}
|
|
}
|
|
|
|
.student-info {
|
|
flex: 1;
|
|
overflow: hidden;
|
|
|
|
.student-name {
|
|
color: #fff;
|
|
font-size: 28rpx;
|
|
font-weight: bold;
|
|
margin-bottom: 8rpx;
|
|
}
|
|
|
|
.student-age,
|
|
.course-status,
|
|
.course-arrangement,
|
|
.remaining-hours,
|
|
.expiry-date {
|
|
color: #bdbdbd;
|
|
font-size: 22rpx;
|
|
margin-bottom: 4rpx;
|
|
line-height: 1.3;
|
|
}
|
|
}
|
|
|
|
.add-icon {
|
|
width: 60rpx;
|
|
height: 60rpx;
|
|
border-radius: 50%;
|
|
background: #ffd86b;
|
|
color: #232323;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
margin-right: 18rpx;
|
|
flex-shrink: 0;
|
|
|
|
.plus-icon {
|
|
font-size: 36rpx;
|
|
font-weight: bold;
|
|
}
|
|
|
|
&.waiting-icon {
|
|
background: #8a7fff;
|
|
color: #1a1a2a;
|
|
}
|
|
}
|
|
|
|
.add-text {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: center;
|
|
|
|
.slot-title {
|
|
color: #ffd86b;
|
|
font-size: 28rpx;
|
|
font-weight: bold;
|
|
margin-bottom: 4rpx;
|
|
}
|
|
|
|
.slot-subtitle {
|
|
color: #ffd86b;
|
|
font-size: 22rpx;
|
|
}
|
|
}
|
|
|
|
&.empty {
|
|
border: 2rpx dashed #ffd86b;
|
|
background: #232323;
|
|
}
|
|
|
|
&.waiting {
|
|
background: #2a2a3a;
|
|
border: 1rpx solid #8a7fff;
|
|
|
|
&.empty {
|
|
border: 2rpx dashed #8a7fff;
|
|
background: #1a1a2a;
|
|
|
|
.add-text {
|
|
.slot-title,
|
|
.slot-subtitle {
|
|
color: #8a7fff;
|
|
}
|
|
}
|
|
}
|
|
|
|
&.filled {
|
|
.student-info {
|
|
.student-name {
|
|
color: #8a7fff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// 添加签到状态样式
|
|
.status-badge {
|
|
position: absolute;
|
|
top: 8rpx;
|
|
right: 8rpx;
|
|
padding: 4rpx 8rpx;
|
|
border-radius: 8rpx;
|
|
font-size: 20rpx;
|
|
font-weight: bold;
|
|
|
|
&.signed {
|
|
background-color: #29d3b4;
|
|
color: #fff;
|
|
}
|
|
|
|
&.unsigned {
|
|
background-color: #ffd86b;
|
|
color: #232323;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 弹窗样式 */
|
|
.modal-overlay {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
width: 100vw;
|
|
height: 100vh;
|
|
background: rgba(0, 0, 0, 0.5);
|
|
z-index: 1000;
|
|
display: flex;
|
|
align-items: flex-end;
|
|
}
|
|
|
|
.modal-content {
|
|
background: #fff;
|
|
border-radius: 24rpx 24rpx 0 0;
|
|
width: 100%;
|
|
overflow: hidden;
|
|
}
|
|
|
|
.modal-header {
|
|
padding: 40rpx;
|
|
border-bottom: 1rpx solid #eee;
|
|
text-align: center;
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
color: #333;
|
|
}
|
|
|
|
.modal-body {
|
|
padding: 40rpx;
|
|
max-height: 60vh;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.form-section {
|
|
margin-bottom: 40rpx;
|
|
|
|
.form-label {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
margin-bottom: 20rpx;
|
|
display: block;
|
|
}
|
|
}
|
|
|
|
.search-tabs {
|
|
display: flex;
|
|
gap: 20rpx;
|
|
margin-bottom: 20rpx;
|
|
|
|
.tab-btn {
|
|
flex: 1;
|
|
padding: 20rpx;
|
|
text-align: center;
|
|
background: #f5f5f5;
|
|
border-radius: 8rpx;
|
|
font-size: 26rpx;
|
|
color: #666;
|
|
|
|
&.active {
|
|
background: #29d3b4;
|
|
color: #fff;
|
|
}
|
|
}
|
|
}
|
|
|
|
.search-input {
|
|
width: 100%;
|
|
padding: 24rpx;
|
|
border: 1rpx solid #ddd;
|
|
border-radius: 8rpx;
|
|
font-size: 28rpx;
|
|
box-sizing: border-box;
|
|
height: 90rpx;
|
|
}
|
|
|
|
.search-results {
|
|
margin-top: 20rpx;
|
|
max-height: 400rpx;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.student-item {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 20rpx;
|
|
border: 1rpx solid #eee;
|
|
border-radius: 8rpx;
|
|
margin-bottom: 16rpx;
|
|
|
|
&.selected {
|
|
border-color: #29d3b4;
|
|
background: rgba(41, 211, 180, 0.1);
|
|
}
|
|
|
|
.student-avatar {
|
|
width: 60rpx;
|
|
height: 60rpx;
|
|
border-radius: 50%;
|
|
background: #29d3b4;
|
|
color: #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 24rpx;
|
|
margin-right: 20rpx;
|
|
}
|
|
|
|
.student-details {
|
|
flex: 1;
|
|
|
|
.student-name {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
margin-bottom: 8rpx;
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16rpx;
|
|
|
|
.formal-badge {
|
|
background: #29d3b4;
|
|
color: #fff;
|
|
font-size: 20rpx;
|
|
padding: 4rpx 8rpx;
|
|
border-radius: 8rpx;
|
|
white-space: nowrap;
|
|
}
|
|
}
|
|
|
|
.student-phone {
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
}
|
|
}
|
|
|
|
.check-icon {
|
|
color: #29d3b4;
|
|
font-size: 32rpx;
|
|
}
|
|
}
|
|
|
|
.preset-student {
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 20rpx;
|
|
background: #f5f5f5;
|
|
border-radius: 8rpx;
|
|
|
|
.student-avatar {
|
|
width: 60rpx;
|
|
height: 60rpx;
|
|
border-radius: 50%;
|
|
background: #29d3b4;
|
|
color: #fff;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 24rpx;
|
|
margin-right: 20rpx;
|
|
}
|
|
|
|
.student-details {
|
|
flex: 1;
|
|
|
|
.student-name {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
margin-bottom: 8rpx;
|
|
}
|
|
|
|
.student-phone {
|
|
font-size: 24rpx;
|
|
color: #666;
|
|
}
|
|
}
|
|
}
|
|
|
|
.radio-group {
|
|
display: flex;
|
|
gap: 40rpx;
|
|
|
|
.radio-item {
|
|
display: flex;
|
|
align-items: center;
|
|
|
|
.radio-text {
|
|
margin-left: 16rpx;
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
}
|
|
|
|
&.disabled {
|
|
opacity: 0.5;
|
|
|
|
.radio-text {
|
|
color: #999;
|
|
|
|
.disabled-tip {
|
|
font-size: 24rpx;
|
|
color: #999;
|
|
margin-left: 8rpx;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
.remarks-textarea {
|
|
width: 100%;
|
|
min-height: 120rpx;
|
|
padding: 20rpx;
|
|
border: 1rpx solid #ddd;
|
|
border-radius: 8rpx;
|
|
font-size: 28rpx;
|
|
box-sizing: border-box;
|
|
resize: none;
|
|
}
|
|
|
|
.modal-footer {
|
|
padding: 40rpx;
|
|
border-top: 1rpx solid #eee;
|
|
display: flex;
|
|
gap: 20rpx;
|
|
|
|
.btn {
|
|
flex: 1;
|
|
padding: 24rpx;
|
|
text-align: center;
|
|
border-radius: 8rpx;
|
|
font-size: 28rpx;
|
|
|
|
&.btn-cancel {
|
|
background: #f5f5f5;
|
|
color: #666;
|
|
}
|
|
|
|
&.btn-confirm {
|
|
background: #29d3b4;
|
|
color: #fff;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 请假弹窗样式 */
|
|
.leave-modal {
|
|
background: #fff;
|
|
border-radius: 16rpx;
|
|
width: 600rpx;
|
|
max-width: 80vw;
|
|
}
|
|
|
|
.leave-modal-header {
|
|
padding: 40rpx 40rpx 20rpx 40rpx;
|
|
text-align: center;
|
|
border-bottom: 1rpx solid #eee;
|
|
|
|
.leave-modal-title {
|
|
font-size: 32rpx;
|
|
font-weight: bold;
|
|
color: #333;
|
|
}
|
|
}
|
|
|
|
.leave-form {
|
|
padding: 40rpx;
|
|
|
|
.leave-label {
|
|
font-size: 28rpx;
|
|
color: #333;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.leave-input {
|
|
margin-bottom: 40rpx;
|
|
|
|
.leave-textarea {
|
|
width: 100%;
|
|
min-height: 200rpx;
|
|
padding: 20rpx;
|
|
border: 1rpx solid #ddd;
|
|
border-radius: 8rpx;
|
|
font-size: 28rpx;
|
|
box-sizing: border-box;
|
|
resize: none;
|
|
background: #f9f9f9;
|
|
}
|
|
}
|
|
|
|
.leave-buttons {
|
|
display: flex;
|
|
gap: 20rpx;
|
|
|
|
.leave-btn {
|
|
flex: 1;
|
|
padding: 24rpx;
|
|
text-align: center;
|
|
border-radius: 8rpx;
|
|
font-size: 28rpx;
|
|
|
|
&.cancel-btn {
|
|
background: #f5f5f5;
|
|
color: #666;
|
|
}
|
|
|
|
&.confirm-btn {
|
|
background: #29d3b4;
|
|
color: #fff;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
</style>
|
|
|