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.
2078 lines
64 KiB
2078 lines
64 KiB
<!-- 课程安排表 -->
|
|
<template>
|
|
<view class="schedule-container">
|
|
<!-- 顶部筛选区域 -->
|
|
<view class="filter-header">
|
|
<view class="filter-tabs">
|
|
<view
|
|
:class="['filter-tab',activeFilter === 'time' ? 'active' : '']"
|
|
@click="openFilter('time')"
|
|
>
|
|
时间
|
|
</view>
|
|
<view
|
|
:class="['filter-tab',activeFilter === 'teacher' ? 'active' : '']"
|
|
@click="openFilter('teacher')"
|
|
>
|
|
老师
|
|
</view>
|
|
<view
|
|
:class="['filter-tab',activeFilter === 'classroom' ? 'active' : '']"
|
|
@click="openFilter('classroom')"
|
|
>
|
|
教室
|
|
</view>
|
|
<view
|
|
:class="['filter-tab',activeFilter === 'class' ? 'active' : '']"
|
|
@click="openFilter('class')"
|
|
>
|
|
班级
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 筛选按钮 -->
|
|
<view class="filter-btn" @click="showFilterModal = true">
|
|
<fui-icon name="filter" size="20" color="#29d3b4"></fui-icon>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 日期导航 -->
|
|
<view class="date-nav">
|
|
<view class="nav-btn" @click="prevWeek">
|
|
<fui-icon name="arrowleft" size="16" color="#29d3b4"></fui-icon>
|
|
</view>
|
|
<view class="date-range">
|
|
{{ formatDateRange() }}
|
|
</view>
|
|
<view class="nav-btn" @click="nextWeek">
|
|
<fui-icon name="arrowright" size="16" color="#29d3b4"></fui-icon>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 课程统计 -->
|
|
<view class="schedule-stats">
|
|
共 {{ totalCourses }} 节课,未点名 {{ unnamedCourses }} 节课
|
|
</view>
|
|
|
|
<!-- 课程表主体 -->
|
|
<view class="schedule-main">
|
|
<!-- 统一滚动区域 -->
|
|
<scroll-view
|
|
class="schedule-scroll"
|
|
scroll-x
|
|
scroll-y
|
|
:style="{ height: scrollViewHeight + 'px' }"
|
|
:enable-flex="true"
|
|
:enhanced="isH5"
|
|
:scroll-anchoring="false"
|
|
:bounces="false"
|
|
:scroll-with-animation="false"
|
|
:show-scrollbar="false"
|
|
@scroll="onScroll"
|
|
>
|
|
<view class="schedule-container-inner" :style="{ width: (tableWidth + 120) + 'rpx', minWidth: '1380rpx' }">
|
|
<!-- 表头行 -->
|
|
<view class="schedule-header-row">
|
|
<!-- 左上角标题 -->
|
|
<view class="time-header-cell">
|
|
<template v-if="activeFilter === 'time' || activeFilter === ''">时间</template>
|
|
<template v-else-if="activeFilter === 'teacher'">教练</template>
|
|
<template v-else-if="activeFilter === 'classroom'">教室</template>
|
|
<template v-else-if="activeFilter === 'class'">班级</template>
|
|
</view>
|
|
|
|
<!-- 日期列 -->
|
|
<view
|
|
class="date-header-cell"
|
|
v-for="(date, index) in weekDates"
|
|
:key="index"
|
|
>
|
|
<view class="date-week">{{ date.weekName }}</view>
|
|
<view class="date-day">{{ date.dateStr }}</view>
|
|
<view class="date-courses">共{{ date.courseCount }}节课</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 内容网格 -->
|
|
<view class="schedule-grid">
|
|
<!-- 时间模式内容 -->
|
|
<template v-if="activeFilter === 'time' || activeFilter === ''">
|
|
<view
|
|
class="schedule-row"
|
|
v-for="(timeSlot, timeIndex) in timeSlots"
|
|
:key="timeIndex"
|
|
:ref="`scheduleRow_${timeIndex}`"
|
|
>
|
|
<!-- 左侧时间列 -->
|
|
<view :class="['left-column-cell', !timeSlot.available ? 'time-unavailable' : '']">
|
|
{{ timeSlot.time }}
|
|
</view>
|
|
|
|
<!-- 课程单元格 -->
|
|
<view
|
|
:class="['course-cell',!timeSlot.available ? 'cell-unavailable' : '']"
|
|
v-for="(date, dateIndex) in weekDates"
|
|
:key="dateIndex"
|
|
@click="timeSlot.available ? handleCellClick(timeSlot, date) : null"
|
|
>
|
|
<!-- 课程项目 -->
|
|
<view
|
|
class="course-item"
|
|
v-for="course in getCoursesByTimeAndDate(timeSlot.time, date.date)"
|
|
:key="course.id"
|
|
:class="[
|
|
course.type === 'normal' ? 'course-normal' : '',
|
|
course.type === 'private' ? 'course-private' : '',
|
|
course.type === 'activity' ? 'course-activity' : ''
|
|
]"
|
|
:style="{
|
|
minHeight: getCourseMinHeight(course),
|
|
zIndex: 5
|
|
}"
|
|
@click.stop="viewScheduleDetail(course.id)"
|
|
>
|
|
<!-- 班级名称和课程名称 -->
|
|
<view class="course-name">{{ course.class_name || course.courseName }}</view>
|
|
|
|
<!-- 校区信息 -->
|
|
<view class="course-campus" v-if="course.campus_name">📍 {{ course.campus_name }}</view>
|
|
|
|
<!-- 上课时间 -->
|
|
<view class="course-time-info" v-if="course.raw && course.raw.time_info">
|
|
⏰ {{ course.raw.time_info.start_time }}-{{ course.raw.time_info.end_time }} ({{ course.raw.time_info.duration }}分钟)
|
|
</view>
|
|
|
|
<!-- 教练和场地 -->
|
|
<view class="course-basic-info">
|
|
<view class="course-teacher">👨🏫 {{ course.teacher }}</view>
|
|
<view class="course-venue">🏠 {{ course.venue }}</view>
|
|
</view>
|
|
|
|
<!-- 课程状态 -->
|
|
<view class="course-status">{{ course.status }}</view>
|
|
|
|
<!-- 学员信息 -->
|
|
<view class="course-students" v-if="course.raw && course.raw.students && course.raw.students.length > 0">
|
|
<view class="students-title">学员列表:</view>
|
|
<view class="student-item" v-for="student in course.raw.students" :key="student.id">
|
|
{{ student.name }} ({{ student.person_type_text }}{{ student.age ? ', ' + student.age + '岁' : '' }})
|
|
</view>
|
|
</view>
|
|
<view class="course-students" v-else>
|
|
{{ course.students }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<!-- 教练模式内容 -->
|
|
<template v-else-if="activeFilter === 'teacher'">
|
|
<view
|
|
class="schedule-row"
|
|
v-for="(teacher, teacherIndex) in teacherOptions"
|
|
:key="teacher.id"
|
|
:ref="`scheduleRow_${teacherIndex}`"
|
|
>
|
|
<!-- 左侧教练列 -->
|
|
<view class="left-column-cell">
|
|
{{ teacher.name }}
|
|
</view>
|
|
|
|
<!-- 课程单元格 -->
|
|
<view
|
|
class="course-cell"
|
|
v-for="(date, dateIndex) in weekDates"
|
|
:key="dateIndex"
|
|
@click="handleCellClick({time: ''}, date, teacher.id)"
|
|
>
|
|
<!-- 课程项目 -->
|
|
<view
|
|
class="course-item"
|
|
v-for="course in getCoursesByTeacherAndDate(teacher.id, date.date)"
|
|
:key="course.id"
|
|
:class="[
|
|
course.type === 'normal' ? 'course-normal' : '',
|
|
course.type === 'private' ? 'course-private' : '',
|
|
course.type === 'activity' ? 'course-activity' : ''
|
|
]"
|
|
:style="{
|
|
minHeight: getCourseMinHeight(course),
|
|
zIndex: 5
|
|
}"
|
|
@click.stop="viewScheduleDetail(course.id)"
|
|
>
|
|
<!-- 班级名称和课程名称 -->
|
|
<view class="course-name">{{ course.class_name || course.courseName }}</view>
|
|
|
|
<!-- 校区信息 -->
|
|
<view class="course-campus" v-if="course.campus_name">📍 {{ course.campus_name }}</view>
|
|
|
|
<!-- 上课时间 -->
|
|
<view class="course-time-info" v-if="course.raw && course.raw.time_info">
|
|
⏰ {{ course.raw.time_info.start_time }}-{{ course.raw.time_info.end_time }} ({{ course.raw.time_info.duration }}分钟)
|
|
</view>
|
|
|
|
<!-- 教练和场地 -->
|
|
<view class="course-basic-info">
|
|
<view class="course-teacher">👨🏫 {{ course.teacher }}</view>
|
|
<view class="course-venue">🏠 {{ course.venue }}</view>
|
|
</view>
|
|
|
|
<!-- 课程状态 -->
|
|
<view class="course-status">{{ course.status }}</view>
|
|
|
|
<!-- 学员信息 -->
|
|
<view class="course-students" v-if="course.raw && course.raw.students && course.raw.students.length > 0">
|
|
<view class="students-title">学员列表:</view>
|
|
<view class="student-item" v-for="student in course.raw.students" :key="student.id">
|
|
{{ student.name }} ({{ student.person_type_text }}{{ student.age ? ', ' + student.age + '岁' : '' }})
|
|
</view>
|
|
</view>
|
|
<view class="course-students" v-else>
|
|
{{ course.students }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<!-- 教室模式内容 -->
|
|
<template v-else-if="activeFilter === 'classroom'">
|
|
<view
|
|
class="schedule-row"
|
|
v-for="(venue, venueIndex) in venues"
|
|
:key="venue.id"
|
|
:ref="`scheduleRow_${venueIndex}`"
|
|
>
|
|
<!-- 左侧教室列 -->
|
|
<view class="left-column-cell">
|
|
{{ venue.name }}
|
|
</view>
|
|
|
|
<!-- 课程单元格 -->
|
|
<view
|
|
class="course-cell"
|
|
v-for="(date, dateIndex) in weekDates"
|
|
:key="dateIndex"
|
|
@click="handleCellClick({time: ''}, date, null, venue.id)"
|
|
>
|
|
<!-- 课程项目 -->
|
|
<view
|
|
class="course-item"
|
|
v-for="course in getCoursesByVenueAndDate(venue.id, date.date)"
|
|
:key="course.id"
|
|
:class="[
|
|
course.type === 'normal' ? 'course-normal' : '',
|
|
course.type === 'private' ? 'course-private' : '',
|
|
course.type === 'activity' ? 'course-activity' : ''
|
|
]"
|
|
:style="{
|
|
minHeight: getCourseMinHeight(course),
|
|
zIndex: 5
|
|
}"
|
|
@click.stop="viewScheduleDetail(course.id)"
|
|
>
|
|
<!-- 班级名称和课程名称 -->
|
|
<view class="course-name">{{ course.class_name || course.courseName }}</view>
|
|
|
|
<!-- 校区信息 -->
|
|
<view class="course-campus" v-if="course.campus_name">📍 {{ course.campus_name }}</view>
|
|
|
|
<!-- 上课时间 -->
|
|
<view class="course-time-info" v-if="course.raw && course.raw.time_info">
|
|
⏰ {{ course.raw.time_info.start_time }}-{{ course.raw.time_info.end_time }} ({{ course.raw.time_info.duration }}分钟)
|
|
</view>
|
|
|
|
<!-- 教练和场地 -->
|
|
<view class="course-basic-info">
|
|
<view class="course-teacher">👨🏫 {{ course.teacher }}</view>
|
|
<view class="course-venue">🏠 {{ course.venue }}</view>
|
|
</view>
|
|
|
|
<!-- 课程状态 -->
|
|
<view class="course-status">{{ course.status }}</view>
|
|
|
|
<!-- 学员信息 -->
|
|
<view class="course-students" v-if="course.raw && course.raw.students && course.raw.students.length > 0">
|
|
<view class="students-title">学员列表:</view>
|
|
<view class="student-item" v-for="student in course.raw.students" :key="student.id">
|
|
{{ student.name }} ({{ student.person_type_text }}{{ student.age ? ', ' + student.age + '岁' : '' }})
|
|
</view>
|
|
</view>
|
|
<view class="course-students" v-else>
|
|
{{ course.students }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
|
|
<!-- 班级模式内容 -->
|
|
<template v-else-if="activeFilter === 'class'">
|
|
<view
|
|
class="schedule-row"
|
|
v-for="(cls, clsIndex) in classOptions"
|
|
:key="cls.id"
|
|
:ref="`scheduleRow_${clsIndex}`"
|
|
>
|
|
<!-- 左侧班级列 -->
|
|
<view class="left-column-cell">
|
|
{{ cls.name }}
|
|
</view>
|
|
|
|
<!-- 课程单元格 -->
|
|
<view
|
|
class="course-cell"
|
|
v-for="(date, dateIndex) in weekDates"
|
|
:key="dateIndex"
|
|
@click="handleCellClick({time: ''}, date, null, null, cls.id)"
|
|
>
|
|
<!-- 课程项目 -->
|
|
<view
|
|
class="course-item"
|
|
v-for="course in getCoursesByClassAndDate(cls.id, date.date)"
|
|
:key="course.id"
|
|
:class="[
|
|
course.type === 'normal' ? 'course-normal' : '',
|
|
course.type === 'private' ? 'course-private' : '',
|
|
course.type === 'activity' ? 'course-activity' : ''
|
|
]"
|
|
:style="{
|
|
minHeight: getCourseMinHeight(course),
|
|
zIndex: 5
|
|
}"
|
|
@click.stop="viewScheduleDetail(course.id)"
|
|
>
|
|
<!-- 班级名称和课程名称 -->
|
|
<view class="course-name">{{ course.class_name || course.courseName }}</view>
|
|
|
|
<!-- 校区信息 -->
|
|
<view class="course-campus" v-if="course.campus_name">📍 {{ course.campus_name }}</view>
|
|
|
|
<!-- 上课时间 -->
|
|
<view class="course-time-info" v-if="course.raw && course.raw.time_info">
|
|
⏰ {{ course.raw.time_info.start_time }}-{{ course.raw.time_info.end_time }} ({{ course.raw.time_info.duration }}分钟)
|
|
</view>
|
|
|
|
<!-- 教练和场地 -->
|
|
<view class="course-basic-info">
|
|
<view class="course-teacher">👨🏫 {{ course.teacher }}</view>
|
|
<view class="course-venue">🏠 {{ course.venue }}</view>
|
|
</view>
|
|
|
|
<!-- 课程状态 -->
|
|
<view class="course-status">{{ course.status }}</view>
|
|
|
|
<!-- 学员信息 -->
|
|
<view class="course-students" v-if="course.raw && course.raw.students && course.raw.students.length > 0">
|
|
<view class="students-title">学员列表:</view>
|
|
<view class="student-item" v-for="student in course.raw.students" :key="student.id">
|
|
{{ student.name }} ({{ student.person_type_text }}{{ student.age ? ', ' + student.age + '岁' : '' }})
|
|
</view>
|
|
</view>
|
|
<view class="course-students" v-else>
|
|
{{ course.students }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</template>
|
|
</view>
|
|
</view>
|
|
</scroll-view>
|
|
</view>
|
|
|
|
<!-- 筛选弹窗 -->
|
|
<fui-modal
|
|
:show="showFilterModal"
|
|
title="筛选条件"
|
|
@cancel="closeFilterModal"
|
|
:buttons="[
|
|
{ text: '重置', type: 'secondary' },
|
|
{ text: '确定', type: 'primary' }
|
|
]"
|
|
@click="handleFilterConfirm"
|
|
>
|
|
<view class="filter-content">
|
|
<!-- 时间筛选 -->
|
|
<view class="filter-section">
|
|
<view class="filter-title">时间范围</view>
|
|
<view class="filter-options">
|
|
<view
|
|
class="filter-option"
|
|
:class="[selectedTimeRange === option.value ? 'selected' : '']"
|
|
v-for="option in timeRangeOptions"
|
|
:key="option.value"
|
|
@click="selectedTimeRange = option.value"
|
|
>
|
|
{{ option.label }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 老师筛选 -->
|
|
<view class="filter-section">
|
|
<view class="filter-title">授课老师</view>
|
|
<view class="filter-options">
|
|
<view
|
|
class="filter-option"
|
|
:class="[selectedTeachers.includes(teacher.id) ? 'selected' : '']"
|
|
v-for="teacher in teacherOptions"
|
|
:key="teacher.id"
|
|
@click="toggleTeacher(teacher.id)"
|
|
>
|
|
{{ teacher.name }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 场地筛选 -->
|
|
<view class="filter-section">
|
|
<view class="filter-title">选择场地</view>
|
|
<view class="filter-options">
|
|
<view
|
|
class="filter-option"
|
|
:class="[selectedVenueId === venue.id ? 'selected' : '']"
|
|
v-for="venue in venues"
|
|
:key="venue.id"
|
|
@click="selectVenue(venue.id)"
|
|
>
|
|
{{ venue.name }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 场地筛选 -->
|
|
<view class="filter-section">
|
|
<view class="filter-title">上课场地</view>
|
|
<view class="filter-options">
|
|
<view
|
|
class="filter-option"
|
|
:class="[selectedVenues.includes(venue.id) ? 'selected' : '']"
|
|
v-for="venue in venues"
|
|
:key="venue.id"
|
|
@click="toggleVenue(venue.id)"
|
|
>
|
|
{{ venue.name }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
|
|
<!-- 班级筛选 -->
|
|
<view class="filter-section">
|
|
<view class="filter-title">班级</view>
|
|
<view class="filter-options">
|
|
<view
|
|
class="filter-option"
|
|
:class="[selectedClasses.includes(cls.id) ? 'selected' : '']"
|
|
v-for="cls in classOptions"
|
|
:key="cls.id"
|
|
@click="toggleClass(cls.id)"
|
|
>
|
|
{{ cls.name }}
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</view>
|
|
</fui-modal>
|
|
|
|
<!-- 课程详情弹窗 -->
|
|
<schedule-detail
|
|
:visible="showScheduleDetail"
|
|
:scheduleId="selectedScheduleId"
|
|
@update:visible="showScheduleDetail = $event"
|
|
@edit-course="handleEditCourse"
|
|
@add-new-course="handleAddNewCourse"
|
|
@student-attendance="handleStudentAttendance"
|
|
></schedule-detail>
|
|
</view>
|
|
</template>
|
|
|
|
<script>
|
|
import api from '@/api/apiRoute.js'
|
|
import ScheduleDetail from '@/components/schedule/ScheduleDetail.vue'
|
|
|
|
export default {
|
|
components: {
|
|
ScheduleDetail
|
|
},
|
|
data() {
|
|
return {
|
|
// 当前显示的周
|
|
currentWeekStart: null,
|
|
|
|
// 筛选相关
|
|
activeFilter: 'time',
|
|
showFilterModal: false,
|
|
selectedTimeRange: '',
|
|
selectedTeachers: [],
|
|
selectedVenues: [],
|
|
selectedClasses: [],
|
|
|
|
// 滚动相关
|
|
scrollTimer: null, // 滚动防抖定时器
|
|
|
|
// 表格配置 - 统一滚动后不包含左侧列宽度
|
|
tableWidth: 1260, // 7天内容宽度 (7*180=1260rpx),左侧120rpx在容器内部
|
|
|
|
// 时间段配置(动态生成,支持场地时间限制)
|
|
timeSlots: [],
|
|
|
|
// 场地信息
|
|
venues: [],
|
|
|
|
// 当前选中的场地
|
|
selectedVenueId: null,
|
|
|
|
// 筛选选项
|
|
timeRangeOptions: [
|
|
{ label: '全部时间', value: '' },
|
|
{ label: '上午', value: 'morning' },
|
|
{ label: '下午', value: 'afternoon' },
|
|
{ label: '晚上', value: 'evening' },
|
|
],
|
|
|
|
// 筛选选项数据
|
|
teacherOptions: [],
|
|
classOptions: [],
|
|
|
|
// 课程数据
|
|
courses: [],
|
|
|
|
// 加载状态
|
|
loading: false,
|
|
|
|
// 课程详情弹窗
|
|
selectedScheduleId: null,
|
|
showScheduleDetail: false,
|
|
|
|
// 平台识别
|
|
isH5: false,
|
|
|
|
// 滚动视图高度
|
|
scrollViewHeight: 0,
|
|
|
|
// 筛选参数
|
|
filterParams: {
|
|
start_date: '',
|
|
end_date: '',
|
|
coach_id: '',
|
|
venue_id: '',
|
|
class_id: '',
|
|
time_range: '',
|
|
view_type: 'time', // 视图类型:time(时间), teacher(教练), classroom(教室), class(班级)
|
|
},
|
|
}
|
|
},
|
|
|
|
computed: {
|
|
// 当前周的日期
|
|
weekDates() {
|
|
const dates = []
|
|
const startDate = new Date(this.currentWeekStart)
|
|
|
|
for (let i = 0; i < 7; i++) {
|
|
const date = new Date(startDate)
|
|
date.setDate(startDate.getDate() + i)
|
|
|
|
const weekNames = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']
|
|
const dateStr = String(date.getMonth() + 1).padStart(2, '0') + '-' + String(date.getDate()).padStart(2, '0')
|
|
|
|
dates.push({
|
|
date: date.toISOString().split('T')[0],
|
|
weekName: weekNames[date.getDay()],
|
|
dateStr: dateStr,
|
|
courseCount: this.getCourseCountByDate(date.toISOString().split('T')[0]),
|
|
})
|
|
}
|
|
|
|
return dates
|
|
},
|
|
|
|
// 总课程数
|
|
totalCourses() {
|
|
return this.courses.length
|
|
},
|
|
|
|
// 未点名课程数
|
|
unnamedCourses() {
|
|
return this.courses.filter(course =>
|
|
course.status === '未点名' ||
|
|
course.status === '即将开始' ||
|
|
course.raw?.status === 'upcoming'
|
|
).length
|
|
},
|
|
},
|
|
|
|
mounted() {
|
|
// 检测平台
|
|
// #ifdef H5
|
|
this.isH5 = true
|
|
// #endif
|
|
|
|
this.initCurrentWeek()
|
|
this.initTimeSlots()
|
|
|
|
// 初始化响应式布局
|
|
this.handleResize()
|
|
|
|
// 计算scroll-view高度
|
|
this.calculateScrollViewHeight()
|
|
|
|
// 先加载筛选选项,然后加载课程安排列表
|
|
this.loadFilterOptions().then(() => {
|
|
this.loadScheduleList()
|
|
})
|
|
|
|
// 监听窗口大小变化,调整布局(仅在H5环境下)
|
|
// #ifndef MP-WEIXIN
|
|
if (typeof window !== 'undefined') {
|
|
window.addEventListener('resize', this.handleResize)
|
|
}
|
|
// #endif
|
|
},
|
|
|
|
beforeDestroy() {
|
|
// 清理滚动定时器
|
|
if (this.scrollTimer) {
|
|
clearTimeout(this.scrollTimer)
|
|
this.scrollTimer = null
|
|
}
|
|
|
|
// 移除事件监听(仅在H5环境下)
|
|
// #ifndef MP-WEIXIN
|
|
if (typeof window !== 'undefined') {
|
|
window.removeEventListener('resize', this.handleResize)
|
|
}
|
|
// #endif
|
|
},
|
|
|
|
methods: {
|
|
// 时间标准化函数 - 统一时间格式为 HH:mm
|
|
normalizeTime(timeStr) {
|
|
if (!timeStr) return '';
|
|
|
|
// 移除空格并转换为字符串
|
|
const cleanTime = String(timeStr).trim();
|
|
|
|
// 如果已经是 HH:mm 格式,直接返回
|
|
if (/^\d{2}:\d{2}$/.test(cleanTime)) {
|
|
return cleanTime;
|
|
}
|
|
|
|
// 如果是 H:mm 格式,补零
|
|
if (/^\d{1}:\d{2}$/.test(cleanTime)) {
|
|
return '0' + cleanTime;
|
|
}
|
|
|
|
// 如果是 HH:m 格式,补零
|
|
if (/^\d{2}:\d{1}$/.test(cleanTime)) {
|
|
return cleanTime.slice(0, 3) + '0' + cleanTime.slice(3);
|
|
}
|
|
|
|
// 如果是 H:m 格式,都补零
|
|
if (/^\d{1}:\d{1}$/.test(cleanTime)) {
|
|
const [hour, minute] = cleanTime.split(':');
|
|
return `0${hour}:0${minute}`;
|
|
}
|
|
|
|
// 其他格式尝试解析
|
|
try {
|
|
const [hour, minute] = cleanTime.split(':');
|
|
const h = parseInt(hour).toString().padStart(2, '0');
|
|
const m = parseInt(minute || 0).toString().padStart(2, '0');
|
|
return `${h}:${m}`;
|
|
} catch (e) {
|
|
console.warn('时间格式解析失败:', timeStr);
|
|
return cleanTime;
|
|
}
|
|
},
|
|
|
|
// 时间匹配函数 - 支持时间段内匹配
|
|
isTimeInSlot(courseTime, slotTime) {
|
|
const normalizedCourseTime = this.normalizeTime(courseTime);
|
|
const normalizedSlotTime = this.normalizeTime(slotTime);
|
|
|
|
if (!normalizedCourseTime || !normalizedSlotTime) return false;
|
|
|
|
// 精确匹配
|
|
if (normalizedCourseTime === normalizedSlotTime) return true;
|
|
|
|
// 查找时间段匹配 - 课程开始时间在这个时间段内
|
|
const slotIndex = this.timeSlots.findIndex(slot =>
|
|
this.normalizeTime(slot.time) === normalizedSlotTime
|
|
);
|
|
|
|
if (slotIndex >= 0 && slotIndex < this.timeSlots.length - 1) {
|
|
const currentSlotTime = this.normalizeTime(this.timeSlots[slotIndex].time);
|
|
const nextSlotTime = this.normalizeTime(this.timeSlots[slotIndex + 1].time);
|
|
|
|
// 检查课程时间是否在当前时间段内(包含开始时间,不包含结束时间)
|
|
return normalizedCourseTime >= currentSlotTime && normalizedCourseTime < nextSlotTime;
|
|
}
|
|
|
|
return false;
|
|
},
|
|
|
|
// 初始化当前周
|
|
initCurrentWeek() {
|
|
const today = new Date()
|
|
const currentDay = today.getDay() // 0是周日,1是周一
|
|
|
|
// 计算本周周一的日期
|
|
let diff = 1 - currentDay // 距离周一的天数
|
|
if (currentDay === 0) { // 如果今天是周日,则向前调7天到上周一
|
|
diff = -6
|
|
}
|
|
|
|
const monday = new Date(today)
|
|
monday.setDate(today.getDate() + diff)
|
|
this.currentWeekStart = monday.toISOString().split('T')[0]
|
|
|
|
// 设置筛选参数的日期范围
|
|
const endDate = new Date(this.currentWeekStart)
|
|
endDate.setDate(endDate.getDate() + 6)
|
|
|
|
this.filterParams.start_date = this.currentWeekStart
|
|
this.filterParams.end_date = endDate.toISOString().split('T')[0]
|
|
},
|
|
|
|
// 初始化时间段(基于场地可用时间)
|
|
initTimeSlots() {
|
|
// 如果没有选中场地,使用默认时间段
|
|
if (!this.selectedVenueId) {
|
|
this.generateDefaultTimeSlots()
|
|
return
|
|
}
|
|
|
|
const venue = this.venues.find(v => v.id === this.selectedVenueId)
|
|
if (!venue) {
|
|
this.generateDefaultTimeSlots()
|
|
return
|
|
}
|
|
|
|
this.timeSlots = this.generateVenueTimeSlots(venue)
|
|
},
|
|
|
|
// 生成默认时间段
|
|
generateDefaultTimeSlots() {
|
|
this.timeSlots = []
|
|
// 生成常用时间段,包含半小时间隔,从 08:00 开始
|
|
const timeSlots = [
|
|
'08:00', '08:30', '09:00', '09:30', '10:00', '10:30', '11:00', '11:30', '12:00',
|
|
'12:30', '13:00', '13:30', '14:00', '14:30', '15:00', '15:30', '16:00',
|
|
'16:30', '17:00', '17:30', '18:00', '18:30', '19:00', '19:30', '20:00',
|
|
'20:30', '21:00'
|
|
]
|
|
|
|
timeSlots.forEach(time => {
|
|
const [hour, minute] = time.split(':')
|
|
const nextHour = minute === '30' ? parseInt(hour) + 1 : parseInt(hour)
|
|
const nextMinute = minute === '30' ? '00' : '30'
|
|
const endTime = `${nextHour.toString().padStart(2, '0')}:${nextMinute}`
|
|
|
|
this.timeSlots.push({
|
|
time: time,
|
|
value: parseInt(hour) + (minute === '30' ? 0.5 : 0),
|
|
available: true,
|
|
timeSlot: `${time}-${endTime}`,
|
|
})
|
|
})
|
|
},
|
|
|
|
// 根据场地生成时间段
|
|
generateVenueTimeSlots(venue) {
|
|
const slots = []
|
|
|
|
if (venue.time_range_type === 'all') {
|
|
// 全天可用
|
|
for (let hour = 0; hour < 24; hour++) {
|
|
slots.push({
|
|
time: `${hour.toString().padStart(2, '0')}:00`,
|
|
value: hour,
|
|
available: true,
|
|
timeSlot: `${hour.toString().padStart(2, '0')}:00-${(hour + 1).toString().padStart(2, '0')}:00`,
|
|
})
|
|
}
|
|
} else if (venue.time_range_type === 'range') {
|
|
// 范围类型
|
|
const startHour = parseInt(venue.time_range_start.split(':')[0])
|
|
const endHour = parseInt(venue.time_range_end.split(':')[0])
|
|
|
|
// 生成全天时间段,标记可用状态
|
|
for (let hour = 8; hour <= 22; hour++) {
|
|
slots.push({
|
|
time: `${hour.toString().padStart(2, '0')}:00`,
|
|
value: hour,
|
|
available: hour >= startHour && hour < endHour,
|
|
timeSlot: `${hour.toString().padStart(2, '0')}:00-${(hour + 1).toString().padStart(2, '0')}:00`,
|
|
})
|
|
}
|
|
} else if (venue.time_range_type === 'fixed') {
|
|
// 固定时间段类型
|
|
const availableHours = new Set()
|
|
|
|
venue.fixed_time_ranges.forEach(range => {
|
|
const [start, end] = range.split('-')
|
|
const startHour = parseInt(start.split(':')[0])
|
|
const endHour = parseInt(end.split(':')[0])
|
|
|
|
for (let hour = startHour; hour < endHour; hour++) {
|
|
availableHours.add(hour)
|
|
}
|
|
})
|
|
|
|
// 生成全天时间段,标记可用状态
|
|
for (let hour = 8; hour <= 22; hour++) {
|
|
slots.push({
|
|
time: `${hour.toString().padStart(2, '0')}:00`,
|
|
value: hour,
|
|
available: availableHours.has(hour),
|
|
timeSlot: `${hour.toString().padStart(2, '0')}:00-${(hour + 1).toString().padStart(2, '0')}:00`,
|
|
})
|
|
}
|
|
}
|
|
|
|
return slots
|
|
},
|
|
|
|
// 格式化日期范围
|
|
formatDateRange() {
|
|
const start = new Date(this.currentWeekStart)
|
|
const end = new Date(start)
|
|
end.setDate(start.getDate() + 6)
|
|
|
|
const formatDate = (date) => {
|
|
return `${date.getFullYear()}年${String(date.getMonth() + 1).padStart(2, '0')}月${String(date.getDate()).padStart(2, '0')}日`
|
|
}
|
|
|
|
return `${formatDate(start)} - ${formatDate(end)}`
|
|
},
|
|
|
|
// 上一周
|
|
prevWeek() {
|
|
const currentStart = new Date(this.currentWeekStart)
|
|
currentStart.setDate(currentStart.getDate() - 7)
|
|
this.currentWeekStart = currentStart.toISOString().split('T')[0]
|
|
|
|
// 更新筛选参数的日期范围
|
|
const endDate = new Date(this.currentWeekStart)
|
|
endDate.setDate(endDate.getDate() + 6)
|
|
|
|
this.filterParams.start_date = this.currentWeekStart
|
|
this.filterParams.end_date = endDate.toISOString().split('T')[0]
|
|
|
|
// 重新加载数据
|
|
this.loadScheduleList()
|
|
},
|
|
|
|
// 下一周
|
|
nextWeek() {
|
|
const currentStart = new Date(this.currentWeekStart)
|
|
currentStart.setDate(currentStart.getDate() + 7)
|
|
this.currentWeekStart = currentStart.toISOString().split('T')[0]
|
|
|
|
// 更新筛选参数的日期范围
|
|
const endDate = new Date(this.currentWeekStart)
|
|
endDate.setDate(endDate.getDate() + 6)
|
|
|
|
this.filterParams.start_date = this.currentWeekStart
|
|
this.filterParams.end_date = endDate.toISOString().split('T')[0]
|
|
|
|
// 重新加载数据
|
|
this.loadScheduleList()
|
|
},
|
|
|
|
// 获取指定日期的课程数量
|
|
getCourseCountByDate(date) {
|
|
return this.courses.filter(course => course.date === date).length
|
|
},
|
|
|
|
// 获取指定时间和日期的课程
|
|
getCoursesByTimeAndDate(time, date) {
|
|
const matchedCourses = this.courses.filter(course => {
|
|
if (course.date !== date) return false
|
|
// 使用改进的时间匹配算法
|
|
return this.isTimeInSlot(course.time, time)
|
|
})
|
|
return matchedCourses
|
|
},
|
|
|
|
// 获取课程跨越的时间段数量
|
|
getCourseSpanSlots(course) {
|
|
if (!course.duration) return 1
|
|
return Math.ceil(course.duration / 30)
|
|
},
|
|
|
|
// 获取课程显示的行高度 - 已废弃,使用getCourseMinHeight
|
|
getCourseRowHeight(course) {
|
|
const spanSlots = this.getCourseSpanSlots(course)
|
|
return 'auto'
|
|
},
|
|
|
|
// 获取课程的最小高度 - 用于单元格合并视觉效果
|
|
getCourseMinHeight(course) {
|
|
const spanSlots = this.getCourseSpanSlots(course)
|
|
// 设置最小高度保持单元格合并效果,但允许内容超出
|
|
return spanSlots * 120 + 'rpx'
|
|
},
|
|
|
|
// 获取指定教练和日期的课程
|
|
getCoursesByTeacherAndDate(teacherId, date) {
|
|
return this.courses.filter(course => {
|
|
// 匹配教练ID
|
|
const teacherMatch = course.teacher_id === teacherId ||
|
|
(this.teacherOptions.find(t => t.name === course.teacher)?.id === teacherId);
|
|
// 匹配日期
|
|
return teacherMatch && course.date === date;
|
|
});
|
|
},
|
|
|
|
// 获取指定教室和日期的课程
|
|
getCoursesByVenueAndDate(venueId, date) {
|
|
return this.courses.filter(course => {
|
|
// 匹配教室ID
|
|
const venueMatch = course.venue_id === venueId ||
|
|
(this.venues.find(v => v.name === course.venue)?.id === venueId);
|
|
// 匹配日期
|
|
return venueMatch && course.date === date;
|
|
});
|
|
},
|
|
|
|
// 获取指定班级和日期的课程
|
|
getCoursesByClassAndDate(classId, date) {
|
|
return this.courses.filter(course => {
|
|
// 匹配班级ID
|
|
const classMatch = course.class_id === classId ||
|
|
(this.classOptions.find(c => course.courseName.includes(c.name))?.id === classId);
|
|
// 匹配日期
|
|
return classMatch && course.date === date;
|
|
});
|
|
},
|
|
|
|
// 打开筛选
|
|
openFilter(type) {
|
|
// 如果已经是当前类型,则取消选中,否则切换为新类型
|
|
const newFilter = this.activeFilter === type ? '' : type;
|
|
|
|
// 只有当筛选类型发生变化时才执行操作
|
|
if (this.activeFilter !== newFilter) {
|
|
this.activeFilter = newFilter;
|
|
|
|
// 更新视图类型参数
|
|
this.filterParams.view_type = newFilter || 'time';
|
|
|
|
// 根据视图类型清空特定的筛选参数,让后端知道这是全量数据请求
|
|
// 清空所有特定的筛选参数,只通过 view_type 来区分不同的视图模式
|
|
this.filterParams.coach_id = '';
|
|
this.filterParams.venue_id = '';
|
|
this.filterParams.class_id = '';
|
|
|
|
// 模式切换完成
|
|
|
|
// 切换模式后重新加载课程安排
|
|
this.loadScheduleList();
|
|
}
|
|
},
|
|
|
|
// 关闭筛选弹窗
|
|
closeFilterModal() {
|
|
this.showFilterModal = false
|
|
},
|
|
|
|
// 筛选确认
|
|
async handleFilterConfirm(e) {
|
|
if (e.index === 0) {
|
|
// 重置
|
|
this.resetFilters()
|
|
} else if (e.index === 1) {
|
|
// 确定
|
|
this.applyFilters()
|
|
this.closeFilterModal()
|
|
|
|
// 重新加载数据
|
|
await this.loadScheduleList()
|
|
|
|
// 如果筛选改变了时间段,需要重新生成时间列
|
|
if (this.selectedTimeRange !== '' || this.selectedVenueId !== null) {
|
|
this.initTimeSlots()
|
|
}
|
|
}
|
|
},
|
|
|
|
// 重置筛选
|
|
resetFilters() {
|
|
this.selectedTimeRange = ''
|
|
this.selectedTeachers = []
|
|
this.selectedClassrooms = []
|
|
this.selectedClasses = []
|
|
},
|
|
|
|
// 应用筛选
|
|
applyFilters() {
|
|
// 更新筛选参数
|
|
this.filterParams.time_range = this.selectedTimeRange
|
|
|
|
// 处理教练筛选
|
|
if (this.selectedTeachers.length === 1) {
|
|
this.filterParams.coach_id = this.selectedTeachers[0]
|
|
} else if (this.selectedTeachers.length > 1) {
|
|
this.filterParams.coach_id = this.selectedTeachers
|
|
} else {
|
|
this.filterParams.coach_id = ''
|
|
}
|
|
|
|
// 处理场地筛选
|
|
if (this.selectedVenues.length === 1) {
|
|
this.filterParams.venue_id = this.selectedVenues[0]
|
|
} else if (this.selectedVenues.length > 1) {
|
|
this.filterParams.venue_id = this.selectedVenues
|
|
} else {
|
|
this.filterParams.venue_id = ''
|
|
}
|
|
|
|
// 处理班级筛选
|
|
if (this.selectedClasses.length === 1) {
|
|
this.filterParams.class_id = this.selectedClasses[0]
|
|
} else if (this.selectedClasses.length > 1) {
|
|
this.filterParams.class_id = this.selectedClasses
|
|
} else {
|
|
this.filterParams.class_id = ''
|
|
}
|
|
|
|
// 重新加载课程安排数据
|
|
this.loadScheduleList()
|
|
},
|
|
|
|
// 切换老师选择
|
|
toggleTeacher(teacherId) {
|
|
const index = this.selectedTeachers.indexOf(teacherId)
|
|
if (index > -1) {
|
|
this.selectedTeachers.splice(index, 1)
|
|
} else {
|
|
this.selectedTeachers.push(teacherId)
|
|
}
|
|
|
|
// 实时更新筛选器数据
|
|
this.applyFilters();
|
|
},
|
|
|
|
// 切换场地选择
|
|
toggleVenue(venueId) {
|
|
const index = this.selectedVenues.indexOf(venueId)
|
|
if (index > -1) {
|
|
this.selectedVenues.splice(index, 1)
|
|
} else {
|
|
this.selectedVenues.push(venueId)
|
|
}
|
|
|
|
// 实时更新筛选器数据
|
|
this.applyFilters();
|
|
},
|
|
|
|
// 切换班级选择
|
|
toggleClass(classId) {
|
|
const index = this.selectedClasses.indexOf(classId)
|
|
if (index > -1) {
|
|
this.selectedClasses.splice(index, 1)
|
|
} else {
|
|
this.selectedClasses.push(classId)
|
|
}
|
|
|
|
// 实时更新筛选器数据
|
|
this.applyFilters();
|
|
},
|
|
|
|
// 选择场地
|
|
selectVenue(venueId) {
|
|
this.selectedVenueId = venueId
|
|
this.filterParams.venue_id = venueId
|
|
this.initTimeSlots() // 重新初始化时间段
|
|
|
|
// 实时更新筛选器数据
|
|
this.applyFilters();
|
|
},
|
|
|
|
// 加载筛选选项
|
|
async loadFilterOptions() {
|
|
try {
|
|
uni.showLoading({
|
|
title: '加载数据中...'
|
|
})
|
|
|
|
// 调用接口获取筛选选项数据
|
|
const res = await api.getCourseScheduleFilterOptions()
|
|
|
|
// 如果服务器返回错误,则使用默认数据
|
|
if (!res || res.code !== 1) {
|
|
this.initMockData()
|
|
return
|
|
}
|
|
if (res.code === 1) {
|
|
const data = res.data
|
|
|
|
// 设置教练选项
|
|
this.teacherOptions = data.coaches.map(coach => ({
|
|
id: coach.id,
|
|
name: coach.name,
|
|
avatar: coach.avatar,
|
|
}))
|
|
|
|
// 设置场地选项
|
|
this.venues = data.venues.map(venue => ({
|
|
id: venue.id,
|
|
name: venue.venue_name,
|
|
capacity: venue.capacity,
|
|
description: venue.description,
|
|
time_range_type: venue.time_range_type || 'all',
|
|
time_range_start: venue.time_range_start,
|
|
time_range_end: venue.time_range_end,
|
|
fixed_time_ranges: venue.fixed_time_ranges || []
|
|
}))
|
|
|
|
// 设置班级选项
|
|
this.classOptions = data.classes.map(cls => ({
|
|
id: cls.id,
|
|
name: cls.class_name,
|
|
level: cls.class_level,
|
|
students: cls.total_students,
|
|
}))
|
|
|
|
// 生成默认时间段
|
|
this.initTimeSlots();
|
|
} else {
|
|
uni.showToast({
|
|
title: res.msg || '加载筛选选项失败',
|
|
icon: 'none',
|
|
})
|
|
// 如果服务器返回错误,则使用默认数据
|
|
this.initMockData();
|
|
}
|
|
} catch (error) {
|
|
console.error('加载筛选选项失败:', error)
|
|
uni.showToast({
|
|
title: '加载筛选选项失败',
|
|
icon: 'none',
|
|
})
|
|
// 发生错误时使用默认数据
|
|
this.initMockData();
|
|
} finally {
|
|
uni.hideLoading();
|
|
}
|
|
},
|
|
|
|
// 加载课程安排列表
|
|
async loadScheduleList() {
|
|
try {
|
|
this.loading = true
|
|
uni.showLoading({
|
|
title: '加载课程安排中...'
|
|
})
|
|
|
|
// 调用真实的API,禁用分页获取全量数据
|
|
const params = {
|
|
...this.filterParams,
|
|
page: 1,
|
|
limit: 9999 // 设置一个很大的值来获取所有数据,避免分页
|
|
}
|
|
const res = await api.getCourseScheduleList(params)
|
|
|
|
if (res.code === 1) {
|
|
// 转换数据格式
|
|
this.courses = res.data.list.map(item => {
|
|
// 提取并标准化时间
|
|
const rawTime = item.time_info?.start_time || item.time_slot?.split('-')[0] || '';
|
|
const normalizedTime = this.normalizeTime(rawTime);
|
|
|
|
return {
|
|
id: item.id,
|
|
date: item.course_date,
|
|
time: normalizedTime, // 使用标准化后的时间
|
|
courseName: item.course_name || '未命名课程',
|
|
students: `已报名${item.enrolled_count || 0}人`,
|
|
teacher: item.coach_name || '待分配',
|
|
teacher_id: item.coach_id, // 保存教练ID
|
|
status: item.status_text || '待定',
|
|
type: this.getCourseType(item),
|
|
venue: item.venue_name || '待分配',
|
|
venue_id: item.venue_id, // 保存场地ID
|
|
campus_name: item.campus_name || '', // 添加校区名称
|
|
class_id: item.class_id, // 保存班级ID
|
|
class_name: item.class_name || '', // 添加班级名称
|
|
duration: item.time_info?.duration || 60,
|
|
time_slot: item.time_slot,
|
|
raw: item, // 保存原始数据
|
|
}
|
|
})
|
|
|
|
// 根据当前视图模式动态更新左侧列数据
|
|
this.updateLeftColumnData();
|
|
|
|
} else {
|
|
uni.showToast({
|
|
title: res.msg || '加载课程安排列表失败',
|
|
icon: 'none',
|
|
})
|
|
}
|
|
} catch (error) {
|
|
console.error('加载课程安排列表失败:', error)
|
|
uni.showToast({
|
|
title: '加载课程安排列表失败',
|
|
icon: 'none',
|
|
})
|
|
} finally {
|
|
this.loading = false
|
|
uni.hideLoading()
|
|
}
|
|
},
|
|
|
|
// 获取课程类型
|
|
getCourseType(course) {
|
|
// 在这里判断课程类型,例如根据课程名称或其他属性
|
|
if (course.course_type === 'private') {
|
|
return 'private'
|
|
} else if (course.course_type === 'activity') {
|
|
return 'activity'
|
|
} else {
|
|
return 'normal'
|
|
}
|
|
},
|
|
|
|
// 根据当前视图模式动态更新左侧列数据
|
|
updateLeftColumnData() {
|
|
if (this.activeFilter === 'teacher') {
|
|
// 教练模式:从课程数据中提取所有唯一的教练
|
|
const teacherMap = new Map();
|
|
this.courses.forEach(course => {
|
|
if (course.teacher_id && course.teacher) {
|
|
teacherMap.set(course.teacher_id, {
|
|
id: course.teacher_id,
|
|
name: course.teacher
|
|
});
|
|
}
|
|
});
|
|
// 如果从接口数据中提取的教练不足,补充已有的教练选项
|
|
this.teacherOptions.forEach(teacher => {
|
|
if (!teacherMap.has(teacher.id)) {
|
|
teacherMap.set(teacher.id, teacher);
|
|
}
|
|
});
|
|
this.teacherOptions = Array.from(teacherMap.values());
|
|
|
|
} else if (this.activeFilter === 'classroom') {
|
|
// 教室模式:从课程数据中提取所有唯一的教室
|
|
const venueMap = new Map();
|
|
this.courses.forEach(course => {
|
|
if (course.venue_id && course.venue) {
|
|
venueMap.set(course.venue_id, {
|
|
id: course.venue_id,
|
|
name: course.venue
|
|
});
|
|
}
|
|
});
|
|
// 如果从接口数据中提取的教室不足,补充已有的教室选项
|
|
this.venues.forEach(venue => {
|
|
if (!venueMap.has(venue.id)) {
|
|
venueMap.set(venue.id, venue);
|
|
}
|
|
});
|
|
this.venues = Array.from(venueMap.values());
|
|
|
|
} else if (this.activeFilter === 'class') {
|
|
// 班级模式:从课程数据中提取所有唯一的班级
|
|
const classMap = new Map();
|
|
this.courses.forEach(course => {
|
|
if (course.class_id && course.courseName) {
|
|
classMap.set(course.class_id, {
|
|
id: course.class_id,
|
|
name: course.courseName
|
|
});
|
|
}
|
|
});
|
|
// 如果从接口数据中提取的班级不足,补充已有的班级选项
|
|
this.classOptions.forEach(cls => {
|
|
if (!classMap.has(cls.id)) {
|
|
classMap.set(cls.id, cls);
|
|
}
|
|
});
|
|
this.classOptions = Array.from(classMap.values());
|
|
}
|
|
|
|
},
|
|
|
|
// 滚动事件处理函数 - 简化版本
|
|
onScroll(e) {
|
|
// 统一滚动区域后不需要同步滚动位置
|
|
// 只保留防抖逻辑以优化性能
|
|
if (this.scrollTimer) {
|
|
clearTimeout(this.scrollTimer)
|
|
}
|
|
|
|
this.scrollTimer = setTimeout(() => {
|
|
// 可以在这里添加其他滚动相关的处理逻辑
|
|
// 例如懒加载、滚动到顶部/底部的处理等
|
|
}, 16)
|
|
},
|
|
|
|
// 单元格点击
|
|
handleCellClick(timeSlot, date, teacherId = null, venueId = null, classId = null) {
|
|
// 检查单元格是否已有课程
|
|
let existingCourses = [];
|
|
|
|
// 根据不同的视图模式获取该单元格的课程
|
|
if (this.activeFilter === 'time' || this.activeFilter === '') {
|
|
existingCourses = this.getCoursesByTimeAndDate(timeSlot.time, date.date);
|
|
} else if (this.activeFilter === 'teacher' && teacherId) {
|
|
existingCourses = this.getCoursesByTeacherAndDate(teacherId, date.date);
|
|
} else if (this.activeFilter === 'classroom' && venueId) {
|
|
existingCourses = this.getCoursesByVenueAndDate(venueId, date.date);
|
|
} else if (this.activeFilter === 'class' && classId) {
|
|
existingCourses = this.getCoursesByClassAndDate(classId, date.date);
|
|
}
|
|
|
|
// 如果已有课程,显示操作选择对话框
|
|
if (existingCourses.length > 0) {
|
|
// 检查是否还能添加更多课程(相同时间段,不同班级和教室可以继续添加)
|
|
const canAddMore = this.checkCanAddMoreCourses(timeSlot, date, teacherId, venueId, classId);
|
|
|
|
// 构建操作选项
|
|
let actionItems = [];
|
|
|
|
// 添加查看现有课程的选项
|
|
existingCourses.forEach(course => {
|
|
actionItems.push(`查看:${course.courseName} (${course.teacher})`);
|
|
});
|
|
|
|
// 如果还能添加更多课程,添加"添加新课程"选项
|
|
if (canAddMore) {
|
|
actionItems.push('➕ 添加新课程');
|
|
}
|
|
|
|
uni.showActionSheet({
|
|
itemList: actionItems,
|
|
success: (res) => {
|
|
const index = res.tapIndex;
|
|
|
|
// 如果选择的是添加新课程(最后一个选项)
|
|
if (canAddMore && index === actionItems.length - 1) {
|
|
this.openAddCourseForm(timeSlot, date, teacherId, venueId, classId);
|
|
} else {
|
|
// 否则查看对应的课程详情
|
|
if (index >= 0 && index < existingCourses.length) {
|
|
this.viewScheduleDetail(existingCourses[index].id);
|
|
}
|
|
}
|
|
}
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 如果没有课程,直接打开添加课程页面
|
|
this.openAddCourseForm(timeSlot, date, teacherId, venueId, classId);
|
|
},
|
|
|
|
// 检查是否还能添加更多课程
|
|
checkCanAddMoreCourses(timeSlot, date, teacherId = null, venueId = null, classId = null) {
|
|
// 在时间模式下,相同时间段可以有多个不同班级/教室的课程
|
|
if (this.activeFilter === 'time' || this.activeFilter === '') {
|
|
return true; // 时间模式下总是可以添加(不同班级/教室)
|
|
}
|
|
|
|
// 在教练模式下,同一教练同一时间只能有一个课程
|
|
if (this.activeFilter === 'teacher' && teacherId) {
|
|
return false; // 教练模式下不能重复添加
|
|
}
|
|
|
|
// 在教室模式下,同一教室同一时间只能有一个课程
|
|
if (this.activeFilter === 'classroom' && venueId) {
|
|
return false; // 教室模式下不能重复添加
|
|
}
|
|
|
|
// 在班级模式下,同一班级同一时间只能有一个课程
|
|
if (this.activeFilter === 'class' && classId) {
|
|
return false; // 班级模式下不能重复添加
|
|
}
|
|
|
|
return true;
|
|
},
|
|
|
|
// 打开添加课程表单
|
|
openAddCourseForm(timeSlot, date, teacherId = null, venueId = null, classId = null) {
|
|
let url = `/pages-coach/coach/schedule/add_schedule?date=${date.date}`;
|
|
|
|
// 根据不同模式添加参数
|
|
if (timeSlot && timeSlot.time) {
|
|
url += `&time=${timeSlot.time}&time_slot=${timeSlot.timeSlot || ''}`;
|
|
}
|
|
|
|
if (teacherId) {
|
|
url += `&coach_id=${teacherId}`;
|
|
}
|
|
|
|
if (venueId) {
|
|
url += `&venue_id=${venueId}`;
|
|
}
|
|
|
|
if (classId) {
|
|
url += `&class_id=${classId}`;
|
|
}
|
|
|
|
// 打开添加课程安排页面
|
|
uni.navigateTo({ url });
|
|
},
|
|
|
|
// 添加课程
|
|
addCourse() {
|
|
// 跳转到添加课程页面
|
|
uni.navigateTo({
|
|
url: '/pages-coach/coach/schedule/add_schedule',
|
|
})
|
|
},
|
|
|
|
// 查看课程安排详情(使用统一API)
|
|
async viewScheduleDetail(scheduleId) {
|
|
try {
|
|
if (!scheduleId) {
|
|
uni.showToast({
|
|
title: '课程ID不能为空',
|
|
icon: 'none'
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 显示课程详情弹窗
|
|
this.selectedScheduleId = scheduleId;
|
|
this.showScheduleDetail = true;
|
|
|
|
// 使用新的统一API获取课程安排详情,对接admin端功能
|
|
console.log('调用统一API获取课程安排详情:', scheduleId);
|
|
|
|
// 注意:具体的API调用逻辑在ScheduleDetail组件的fetchScheduleDetail方法中实现
|
|
// ScheduleDetail组件会调用api.getCourseArrangementDetail()方法,与admin端保持一致
|
|
// 这确保了移动端和管理端使用相同的数据结构和业务逻辑
|
|
|
|
// 预先验证API是否可用(可选的预检查)
|
|
try {
|
|
const testResponse = await api.getCourseArrangementDetail({
|
|
schedule_id: scheduleId
|
|
});
|
|
if (testResponse.code !== 1) {
|
|
console.warn('API预检查警告:', testResponse.msg);
|
|
}
|
|
} catch (preCheckError) {
|
|
console.warn('API预检查失败,将在ScheduleDetail组件中处理:', preCheckError);
|
|
}
|
|
|
|
} catch (error) {
|
|
console.error('获取课程安排详情失败:', error);
|
|
|
|
let errorMessage = '获取课程详情失败';
|
|
if (error.message) {
|
|
if (error.message.includes('timeout')) {
|
|
errorMessage = '请求超时,请重试';
|
|
} else if (error.message.includes('Network')) {
|
|
errorMessage = '网络连接失败';
|
|
}
|
|
}
|
|
|
|
uni.showToast({
|
|
title: errorMessage,
|
|
icon: 'none',
|
|
duration: 3000
|
|
});
|
|
|
|
// 即使出错也要显示弹窗,让ScheduleDetail组件处理错误
|
|
this.selectedScheduleId = scheduleId;
|
|
this.showScheduleDetail = true;
|
|
}
|
|
},
|
|
|
|
// 处理编辑课程事件
|
|
handleEditCourse(data) {
|
|
uni.navigateTo({
|
|
url: `/pages-coach/coach/schedule/adjust_course?id=${data.scheduleId}`,
|
|
})
|
|
},
|
|
|
|
// 处理新增课程事件
|
|
handleAddNewCourse(data) {
|
|
const { date, timeSlot } = data;
|
|
let url = `/pages-coach/coach/schedule/add_schedule?date=${date}`;
|
|
|
|
if (timeSlot) {
|
|
const [startTime] = timeSlot.split('-');
|
|
url += `&time=${startTime}&time_slot=${timeSlot}`;
|
|
}
|
|
|
|
uni.navigateTo({ url });
|
|
},
|
|
|
|
// 处理学员点名事件
|
|
handleStudentAttendance(data) {
|
|
console.log('学员点名:', data);
|
|
// 这里可以调用API更新学员出勤状态
|
|
// 暂时只显示提示信息,具体API调用可以后续实现
|
|
},
|
|
|
|
// 计算scroll-view高度
|
|
calculateScrollViewHeight() {
|
|
uni.getSystemInfo({
|
|
success: (res) => {
|
|
// 获取屏幕高度
|
|
let screenHeight = res.screenHeight || res.windowHeight
|
|
|
|
// 计算其他元素占用的高度
|
|
// 筛选区域 + 日期导航 + 统计信息 + 状态栏等
|
|
let otherHeight = 0
|
|
|
|
// #ifdef H5
|
|
otherHeight = 200 // H5端大约200px
|
|
// #endif
|
|
|
|
// #ifdef MP-WEIXIN
|
|
otherHeight = 160 // 小程序端大约160px (rpx转换)
|
|
// #endif
|
|
|
|
// 设置scroll-view高度
|
|
this.scrollViewHeight = screenHeight - otherHeight
|
|
|
|
// 最小高度限制
|
|
if (this.scrollViewHeight < 300) {
|
|
this.scrollViewHeight = 300
|
|
}
|
|
}
|
|
})
|
|
},
|
|
|
|
// 响应式调整
|
|
handleResize() {
|
|
// 根据窗口宽度调整表格尺寸
|
|
let width = 375 // 默认宽度
|
|
|
|
// #ifdef MP-WEIXIN
|
|
try {
|
|
const systemInfo = uni.getSystemInfoSync()
|
|
width = systemInfo.screenWidth || systemInfo.windowWidth || 375
|
|
} catch (e) {
|
|
console.warn('获取系统信息失败,使用默认宽度:', e)
|
|
width = 375
|
|
}
|
|
// #endif
|
|
|
|
// #ifndef MP-WEIXIN
|
|
if (typeof window !== 'undefined') {
|
|
width = window.innerWidth
|
|
}
|
|
// #endif
|
|
|
|
if (width <= 375) {
|
|
this.tableWidth = 1120 // 7*160=1120rpx (小屏幕每列160rpx)
|
|
} else if (width <= 768) {
|
|
this.tableWidth = 1260 // 7*180=1260rpx
|
|
} else {
|
|
this.tableWidth = 1260 // 7*180=1260rpx
|
|
}
|
|
|
|
// 重新计算scroll-view高度
|
|
this.calculateScrollViewHeight()
|
|
},
|
|
|
|
// 初始化模拟数据
|
|
initMockData() {
|
|
// 模拟教练选项
|
|
this.teacherOptions = [
|
|
{ id: 1, name: '张教练', avatar: '/static/avatar/teacher1.png' },
|
|
{ id: 2, name: '李教练', avatar: '/static/avatar/teacher2.png' },
|
|
{ id: 3, name: '王教练', avatar: '/static/avatar/teacher3.png' },
|
|
{ id: 4, name: '刘教练', avatar: '/static/avatar/teacher4.png' },
|
|
{ id: 5, name: '赵教练', avatar: '/static/avatar/teacher5.png' }
|
|
];
|
|
|
|
// 模拟场地选项
|
|
this.venues = [
|
|
{ id: 1, name: '舞蹈室A', capacity: 20, time_range_type: 'range', time_range_start: '09:00', time_range_end: '21:00' },
|
|
{ id: 2, name: '瑜伽B', capacity: 15, time_range_type: 'range', time_range_start: '08:00', time_range_end: '20:00' },
|
|
{ id: 3, name: '健身C', capacity: 10, time_range_type: 'all' },
|
|
{ id: 4, name: '泳池D', capacity: 8, time_range_type: 'fixed', fixed_time_ranges: ['09:00-11:00', '14:00-18:00'] },
|
|
{ id: 5, name: '综合场馆E', capacity: 30, time_range_type: 'range', time_range_start: '08:00', time_range_end: '22:00' }
|
|
];
|
|
|
|
// 模拟班级选项
|
|
this.classOptions = [
|
|
{ id: 1, name: '少儿形体班', level: '初级', students: 15 },
|
|
{ id: 2, name: '成人瑜伽班', level: '中级', students: 20 },
|
|
{ id: 3, name: '私教VIP班', level: '高级', students: 5 },
|
|
{ id: 4, name: '儿童游泳班', level: '初级', students: 12 },
|
|
{ id: 5, name: '暑期特训班', level: '混合', students: 25 }
|
|
];
|
|
},
|
|
},
|
|
}
|
|
</script>
|
|
|
|
<style lang="less" scoped>
|
|
.schedule-container {
|
|
width: 100%;
|
|
height: 100vh;
|
|
background: #292929;
|
|
display: flex;
|
|
flex-direction: column;
|
|
}
|
|
|
|
// 顶部筛选区域
|
|
.filter-header {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 20rpx 30rpx;
|
|
background: #434544;
|
|
border-bottom: 1px solid #555;
|
|
}
|
|
|
|
.filter-tabs {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 40rpx;
|
|
}
|
|
|
|
.filter-tab {
|
|
padding: 16rpx 32rpx;
|
|
border-radius: 8rpx;
|
|
background: transparent;
|
|
color: #999;
|
|
font-size: 28rpx;
|
|
transition: all 0.3s ease;
|
|
|
|
&.active {
|
|
background: #29d3b4;
|
|
color: #fff;
|
|
}
|
|
}
|
|
|
|
.filter-btn {
|
|
padding: 16rpx;
|
|
border-radius: 8rpx;
|
|
background: rgba(41, 211, 180, 0.1);
|
|
}
|
|
|
|
// 日期导航
|
|
.date-nav {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 20rpx 30rpx;
|
|
background: #292929;
|
|
border-bottom: 1px solid #434544;
|
|
}
|
|
|
|
.nav-btn {
|
|
padding: 16rpx;
|
|
border-radius: 8rpx;
|
|
background: rgba(41, 211, 180, 0.1);
|
|
}
|
|
|
|
.date-range {
|
|
color: #fff;
|
|
font-size: 32rpx;
|
|
font-weight: 500;
|
|
}
|
|
|
|
// 课程统计
|
|
.schedule-stats {
|
|
padding: 20rpx 30rpx;
|
|
background: #292929;
|
|
color: #ff9500;
|
|
font-size: 28rpx;
|
|
border-bottom: 1px solid #434544;
|
|
}
|
|
|
|
// 课程表主体
|
|
.schedule-main {
|
|
flex: 1;
|
|
position: relative;
|
|
overflow: hidden;
|
|
height: 0; /* 配合flex: 1 确保高度计算正确 */
|
|
}
|
|
|
|
// 统一滚动区域内部容器
|
|
.schedule-container-inner {
|
|
display: flex;
|
|
flex-direction: column;
|
|
min-width: 1380rpx; /* 左列120rpx + 7天 * 180rpx = 1380rpx */
|
|
}
|
|
|
|
// 表头行
|
|
.schedule-header-row {
|
|
display: flex;
|
|
background: #434544;
|
|
border-bottom: 2px solid #29d3b4;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.3);
|
|
}
|
|
|
|
// 左上角标题单元格
|
|
.time-header-cell {
|
|
width: 120rpx;
|
|
min-width: 120rpx;
|
|
min-height: 120rpx;
|
|
padding: 20rpx 10rpx;
|
|
color: #29d3b4;
|
|
font-size: 28rpx;
|
|
font-weight: 500;
|
|
text-align: center;
|
|
border-right: 1px solid #555;
|
|
background-color: #434544;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
word-wrap: break-word;
|
|
overflow-wrap: break-word;
|
|
flex-shrink: 0;
|
|
}
|
|
|
|
.date-header-cell {
|
|
width: 180rpx;
|
|
min-width: 180rpx;
|
|
padding: 15rpx 10rpx;
|
|
color: #fff;
|
|
font-size: 24rpx;
|
|
text-align: center;
|
|
border-right: 1px solid #555;
|
|
background: #434544;
|
|
flex-shrink: 0;
|
|
position: relative;
|
|
z-index: 11;
|
|
|
|
.date-week {
|
|
font-size: 26rpx;
|
|
font-weight: 500;
|
|
margin-bottom: 5rpx;
|
|
}
|
|
|
|
.date-day {
|
|
font-size: 24rpx;
|
|
color: #ccc;
|
|
margin-bottom: 5rpx;
|
|
}
|
|
|
|
.date-courses {
|
|
font-size: 20rpx;
|
|
color: #29d3b4;
|
|
}
|
|
}
|
|
|
|
// 内容滚动区域
|
|
.schedule-scroll {
|
|
flex: 1;
|
|
height: 100%;
|
|
width: 100%;
|
|
min-height: 0; /* 关键:允许flex子项收缩 */
|
|
/* 优化滚动性能 */
|
|
-webkit-overflow-scrolling: touch;
|
|
scroll-behavior: auto;
|
|
overscroll-behavior: none;
|
|
|
|
/* 小程序端专用优化 */
|
|
// #ifdef MP-WEIXIN
|
|
scroll-behavior: smooth;
|
|
// #endif
|
|
}
|
|
|
|
.schedule-grid {
|
|
width: 100%;
|
|
min-width: 1380rpx; /* 左列120rpx + 7天 * 180rpx = 1380rpx */
|
|
}
|
|
|
|
// 行布局
|
|
.schedule-row {
|
|
display: flex;
|
|
min-height: 120rpx;
|
|
border-bottom: 1px solid #434544;
|
|
}
|
|
|
|
// 左侧列单元格 (时间/教练/教室/班级)
|
|
.left-column-cell {
|
|
width: 120rpx;
|
|
min-width: 120rpx;
|
|
min-height: 120rpx;
|
|
padding: 20rpx 10rpx;
|
|
color: #999;
|
|
font-size: 24rpx;
|
|
text-align: center;
|
|
border-right: 1px solid #434544;
|
|
border-bottom: 1px solid #434544;
|
|
background: #3a3a3a;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
word-wrap: break-word;
|
|
overflow-wrap: break-word;
|
|
flex-shrink: 0;
|
|
|
|
&.time-unavailable {
|
|
background: #2a2a2a;
|
|
color: #555;
|
|
opacity: 0.5;
|
|
}
|
|
}
|
|
|
|
.course-cell {
|
|
width: 180rpx;
|
|
min-width: 180rpx;
|
|
min-height: 120rpx; /* 设置最小高度 */
|
|
padding: 10rpx;
|
|
border-right: 1px solid #434544;
|
|
border-bottom: 1px solid #434544;
|
|
position: relative;
|
|
background: #292929;
|
|
flex-shrink: 0;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: flex-start; /* 内容从顶部开始排列 */
|
|
|
|
&.cell-unavailable {
|
|
background: #1e1e1e;
|
|
opacity: 0.3;
|
|
pointer-events: none;
|
|
|
|
&::before {
|
|
content: '';
|
|
position: absolute;
|
|
top: 50%;
|
|
left: 0;
|
|
right: 0;
|
|
height: 1px;
|
|
background: #555;
|
|
transform: translateY(-50%);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 响应式适配
|
|
@media screen and (max-width: 375px) {
|
|
.left-column-cell {
|
|
width: 100rpx;
|
|
min-width: 100rpx;
|
|
font-size: 22rpx;
|
|
}
|
|
|
|
.time-header-cell {
|
|
width: 100rpx;
|
|
min-width: 100rpx;
|
|
font-size: 22rpx;
|
|
}
|
|
|
|
.course-cell {
|
|
width: 160rpx;
|
|
min-width: 160rpx;
|
|
}
|
|
|
|
.date-header-cell {
|
|
width: 160rpx;
|
|
min-width: 160rpx;
|
|
}
|
|
}
|
|
|
|
// 课程项目
|
|
.course-item {
|
|
width: 100%;
|
|
padding: 8rpx;
|
|
border-radius: 6rpx;
|
|
font-size: 20rpx;
|
|
line-height: 1.3;
|
|
margin-bottom: 5rpx;
|
|
word-wrap: break-word; /* 允许长文本换行 */
|
|
overflow-wrap: break-word;
|
|
min-height: auto; /* 允许根据内容调整高度 */
|
|
height: auto !important; /* 覆盖内联样式,允许内容自适应高度 */
|
|
flex-shrink: 0; /* 防止被压缩 */
|
|
position: relative;
|
|
overflow: visible; /* 允许内容溢出显示 */
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: flex-start;
|
|
|
|
&.course-normal {
|
|
background: rgba(41, 211, 180, 0.2);
|
|
border: 1px solid #29d3b4;
|
|
}
|
|
|
|
&.course-private {
|
|
background: rgba(255, 149, 0, 0.2);
|
|
border: 1px solid #ff9500;
|
|
}
|
|
|
|
&.course-activity {
|
|
background: rgba(255, 59, 48, 0.2);
|
|
border: 1px solid #ff3b30;
|
|
}
|
|
}
|
|
|
|
.course-name {
|
|
color: #fff;
|
|
font-weight: 500;
|
|
margin-bottom: 3rpx;
|
|
word-wrap: break-word;
|
|
overflow-wrap: break-word;
|
|
}
|
|
|
|
.course-time {
|
|
color: #29d3b4;
|
|
font-size: 18rpx;
|
|
margin-bottom: 2rpx;
|
|
}
|
|
|
|
.course-students {
|
|
color: #ccc;
|
|
font-size: 18rpx;
|
|
margin-bottom: 2rpx;
|
|
}
|
|
|
|
.course-teacher {
|
|
color: #ccc;
|
|
font-size: 18rpx;
|
|
margin-bottom: 2rpx;
|
|
word-wrap: break-word;
|
|
overflow-wrap: break-word;
|
|
}
|
|
|
|
.course-venue {
|
|
color: #ffa500;
|
|
font-size: 18rpx;
|
|
margin-bottom: 2rpx;
|
|
word-wrap: break-word;
|
|
overflow-wrap: break-word;
|
|
}
|
|
|
|
.course-status {
|
|
color: #29d3b4;
|
|
font-size: 18rpx;
|
|
}
|
|
|
|
// 课程信息合并显示
|
|
.course-info {
|
|
font-size: 18rpx;
|
|
color: #ccc;
|
|
line-height: 1.2;
|
|
margin-top: 4rpx;
|
|
word-wrap: break-word;
|
|
overflow-wrap: break-word;
|
|
}
|
|
|
|
// 时间信息显示
|
|
.course-time-info {
|
|
font-size: 16rpx;
|
|
color: #29d3b4;
|
|
line-height: 1.2;
|
|
margin-top: 2rpx;
|
|
font-weight: 500;
|
|
}
|
|
|
|
// 校区信息显示
|
|
.course-campus {
|
|
font-size: 16rpx;
|
|
color: #ff9500;
|
|
line-height: 1.2;
|
|
margin-top: 2rpx;
|
|
margin-bottom: 2rpx;
|
|
}
|
|
|
|
// 基础信息容器
|
|
.course-basic-info {
|
|
margin-top: 4rpx;
|
|
margin-bottom: 4rpx;
|
|
}
|
|
|
|
.course-basic-info .course-teacher,
|
|
.course-basic-info .course-venue {
|
|
font-size: 16rpx;
|
|
line-height: 1.2;
|
|
margin-bottom: 2rpx;
|
|
word-wrap: break-word;
|
|
overflow-wrap: break-word;
|
|
}
|
|
|
|
.course-basic-info .course-teacher {
|
|
color: #ccc;
|
|
}
|
|
|
|
.course-basic-info .course-venue {
|
|
color: #ffa500;
|
|
}
|
|
|
|
// 学员信息显示
|
|
.course-students {
|
|
margin-top: 4rpx;
|
|
font-size: 16rpx;
|
|
color: #999;
|
|
}
|
|
|
|
.students-title {
|
|
color: #29d3b4;
|
|
font-weight: 500;
|
|
margin-bottom: 2rpx;
|
|
}
|
|
|
|
.student-item {
|
|
color: #ccc;
|
|
line-height: 1.2;
|
|
margin-bottom: 2rpx;
|
|
word-wrap: break-word;
|
|
overflow-wrap: break-word;
|
|
}
|
|
|
|
// 添加按钮
|
|
.add-btn {
|
|
position: fixed;
|
|
bottom: 120rpx;
|
|
right: 30rpx;
|
|
width: 100rpx;
|
|
height: 100rpx;
|
|
border-radius: 50%;
|
|
background: #29d3b4;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
box-shadow: 0 4rpx 20rpx rgba(41, 211, 180, 0.3);
|
|
}
|
|
|
|
// 筛选弹窗内容
|
|
.filter-content {
|
|
padding: 0 20rpx;
|
|
}
|
|
|
|
.filter-section {
|
|
margin-bottom: 40rpx;
|
|
}
|
|
|
|
.filter-title {
|
|
color: #333;
|
|
font-size: 30rpx;
|
|
font-weight: 500;
|
|
margin-bottom: 20rpx;
|
|
}
|
|
|
|
.filter-options {
|
|
display: flex;
|
|
flex-wrap: wrap;
|
|
gap: 20rpx;
|
|
}
|
|
|
|
.filter-option {
|
|
padding: 16rpx 24rpx;
|
|
border: 1px solid #ddd;
|
|
border-radius: 6rpx;
|
|
background: #f8f8f8;
|
|
color: #333;
|
|
font-size: 26rpx;
|
|
|
|
&.selected {
|
|
border-color: #29d3b4;
|
|
background: #29d3b4;
|
|
color: #fff;
|
|
}
|
|
}
|
|
</style>
|