470 lines
11 KiB
Vue
470 lines
11 KiB
Vue
<template>
|
||
<view class="schedule-page">
|
||
<!-- 日历视图 -->
|
||
<view class="calendar-section">
|
||
<view class="calendar-header">
|
||
<button class="nav-btn" @click="prevMonth">‹</button>
|
||
<text class="current-month">{{ currentMonth }}</text>
|
||
<button class="nav-btn" @click="nextMonth">›</button>
|
||
</view>
|
||
|
||
<view class="calendar-weekdays">
|
||
<text v-for="day in weekdays" :key="day" class="weekday">{{ day }}</text>
|
||
</view>
|
||
|
||
<view class="calendar-days">
|
||
<view
|
||
v-for="(day, index) in calendarDays"
|
||
:key="index"
|
||
class="day-cell"
|
||
:class="{
|
||
'other-month': day.isOtherMonth,
|
||
'today': day.isToday,
|
||
'selected': day.isSelected,
|
||
'has-course': day.hasCourse
|
||
}"
|
||
@click="selectDate(day)"
|
||
>
|
||
<text class="day-number">{{ day.day }}</text>
|
||
<view v-if="day.courseCount > 0" class="course-dot">
|
||
<text>{{ day.courseCount }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 当日课程列表 -->
|
||
<view class="course-list-section">
|
||
<view class="section-header">
|
||
<text class="section-title">{{ selectedDateText }}的课程</text>
|
||
<text class="course-count">{{ dayCourses.length }}节</text>
|
||
</view>
|
||
|
||
<view v-if="dayCourses.length > 0" class="course-list">
|
||
<view
|
||
v-for="course in dayCourses"
|
||
:key="course.id"
|
||
class="course-item"
|
||
@click="goCourseDetail(course.id)"
|
||
>
|
||
<view class="course-time">
|
||
<text class="time">{{ course.time }}</text>
|
||
</view>
|
||
|
||
<view class="course-info">
|
||
<text class="course-name">{{ course.courseName }}</text>
|
||
<text class="student-name">👨🎓 {{ course.studentName }}</text>
|
||
<text class="location">📍 {{ course.location }}</text>
|
||
</view>
|
||
|
||
<view class="course-status" :class="'status-' + course.status">
|
||
{{ course.statusText }}
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view v-else class="empty-state">
|
||
<text class="empty-icon">📅</text>
|
||
<text class="empty-text">这天没有课程安排</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
export default {
|
||
data() {
|
||
return {
|
||
currentYear: 0,
|
||
currentMonth: '',
|
||
selectedDate: null,
|
||
selectedDateText: '',
|
||
weekdays: ['日', '一', '二', '三', '四', '五', '六'],
|
||
calendarDays: [],
|
||
dayCourses: [],
|
||
courseData: {} // 存储每天的课程数据
|
||
}
|
||
},
|
||
|
||
onLoad() {
|
||
this.initCalendar()
|
||
this.loadCourseData()
|
||
},
|
||
|
||
methods: {
|
||
initCalendar() {
|
||
const now = new Date()
|
||
this.currentYear = now.getFullYear()
|
||
const month = now.getMonth() + 1
|
||
this.currentMonth = `${this.currentYear}年${month}月`
|
||
|
||
this.selectedDate = now
|
||
this.selectedDateText = `${month}月${now.getDate()}日`
|
||
|
||
this.generateCalendar(this.currentYear, month)
|
||
},
|
||
|
||
generateCalendar(year, month) {
|
||
const firstDay = new Date(year, month - 1, 1)
|
||
const lastDay = new Date(year, month, 0)
|
||
const daysInMonth = lastDay.getDate()
|
||
const startWeekday = firstDay.getDay()
|
||
|
||
const days = []
|
||
const today = new Date()
|
||
|
||
// 上个月的日期
|
||
const prevMonthLastDay = new Date(year, month - 1, 0).getDate()
|
||
for (let i = startWeekday - 1; i >= 0; i--) {
|
||
days.push({
|
||
day: prevMonthLastDay - i,
|
||
isOtherMonth: true,
|
||
hasCourse: false,
|
||
courseCount: 0
|
||
})
|
||
}
|
||
|
||
// 当月日期
|
||
for (let i = 1; i <= daysInMonth; i++) {
|
||
const date = new Date(year, month - 1, i)
|
||
const dateStr = `${year}-${String(month).padStart(2, '0')}-${String(i).padStart(2, '0')}`
|
||
const courseCount = this.courseData[dateStr] || 0
|
||
|
||
days.push({
|
||
day: i,
|
||
date: date,
|
||
dateStr: dateStr,
|
||
isOtherMonth: false,
|
||
isToday: date.toDateString() === today.toDateString(),
|
||
isSelected: this.selectedDate && date.toDateString() === this.selectedDate.toDateString(),
|
||
hasCourse: courseCount > 0,
|
||
courseCount: courseCount
|
||
})
|
||
}
|
||
|
||
// 下个月的日期
|
||
const remainingDays = 42 - days.length
|
||
for (let i = 1; i <= remainingDays; i++) {
|
||
days.push({
|
||
day: i,
|
||
isOtherMonth: true,
|
||
hasCourse: false,
|
||
courseCount: 0
|
||
})
|
||
}
|
||
|
||
this.calendarDays = days
|
||
},
|
||
|
||
async loadCourseData() {
|
||
try {
|
||
const res = await this.$api.providerApi.getSchedule({
|
||
startDate: this.currentMonth,
|
||
type: 'month'
|
||
})
|
||
if (res.code === 200 && res.data) {
|
||
this.courseData = res.data.courseData || {}
|
||
}
|
||
} catch (error) {
|
||
console.error('加载课程数据失败:', error)
|
||
this.courseData = {}
|
||
}
|
||
|
||
this.generateCalendar(this.currentYear, parseInt(this.currentMonth.split('年')[1]))
|
||
this.loadDayCourses()
|
||
},
|
||
|
||
async loadDayCourses() {
|
||
if (!this.selectedDate) return
|
||
|
||
try {
|
||
const res = await this.$api.providerApi.getSchedule({
|
||
date: this.selectedDate,
|
||
type: 'day'
|
||
})
|
||
if (res.code === 200 && res.data) {
|
||
this.dayCourses = res.data.courses || []
|
||
}
|
||
} catch (error) {
|
||
console.error('加载当天课程失败:', error)
|
||
this.dayCourses = []
|
||
}
|
||
},
|
||
|
||
selectDate(day) {
|
||
if (day.isOtherMonth) return
|
||
|
||
this.selectedDate = day.date
|
||
const month = day.date.getMonth() + 1
|
||
const date = day.date.getDate()
|
||
this.selectedDateText = `${month}月${date}日`
|
||
|
||
this.generateCalendar(this.currentYear, parseInt(this.currentMonth.split('年')[1]))
|
||
this.loadDayCourses()
|
||
},
|
||
|
||
prevMonth() {
|
||
let [year, month] = this.currentMonth.split('年')
|
||
year = parseInt(year)
|
||
month = parseInt(month) - 1
|
||
|
||
if (month < 1) {
|
||
month = 12
|
||
year--
|
||
}
|
||
|
||
this.currentYear = year
|
||
this.currentMonth = `${year}年${month}月`
|
||
this.generateCalendar(year, month)
|
||
},
|
||
|
||
nextMonth() {
|
||
let [year, month] = this.currentMonth.split('年')
|
||
year = parseInt(year)
|
||
month = parseInt(month) + 1
|
||
|
||
if (month > 12) {
|
||
month = 1
|
||
year++
|
||
}
|
||
|
||
this.currentYear = year
|
||
this.currentMonth = `${year}年${month}月`
|
||
this.generateCalendar(year, month)
|
||
},
|
||
|
||
goCourseDetail(id) {
|
||
uni.navigateTo({ url: `/pages/provider/course-detail?id=${id}` })
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@import '@/static/css/common.scss';
|
||
|
||
.schedule-page {
|
||
min-height: 100vh;
|
||
background: $bg-color;
|
||
}
|
||
|
||
.calendar-section {
|
||
background: #fff;
|
||
padding: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
|
||
.calendar-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 30rpx;
|
||
|
||
.nav-btn {
|
||
width: 60rpx;
|
||
height: 60rpx;
|
||
background: $bg-color;
|
||
border-radius: 30rpx;
|
||
font-size: 32rpx;
|
||
color: $text-color;
|
||
border: none;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
|
||
.current-month {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: $text-color;
|
||
}
|
||
}
|
||
|
||
.calendar-weekdays {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
margin-bottom: 20rpx;
|
||
|
||
.weekday {
|
||
text-align: center;
|
||
font-size: 24rpx;
|
||
color: $text-secondary;
|
||
padding: 10rpx 0;
|
||
}
|
||
}
|
||
|
||
.calendar-days {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
gap: 10rpx;
|
||
}
|
||
|
||
.day-cell {
|
||
position: relative;
|
||
aspect-ratio: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
border-radius: 8rpx;
|
||
|
||
&.other-month {
|
||
.day-number {
|
||
color: $text-placeholder;
|
||
}
|
||
}
|
||
|
||
&.today {
|
||
background: rgba($primary-color, 0.1);
|
||
|
||
.day-number {
|
||
color: $primary-color;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
|
||
&.selected {
|
||
background: $primary-color;
|
||
|
||
.day-number {
|
||
color: #fff;
|
||
}
|
||
|
||
.course-dot {
|
||
background: #fff;
|
||
color: $primary-color;
|
||
}
|
||
}
|
||
|
||
.day-number {
|
||
font-size: 26rpx;
|
||
color: $text-color;
|
||
}
|
||
|
||
.course-dot {
|
||
position: absolute;
|
||
bottom: 4rpx;
|
||
width: 32rpx;
|
||
height: 32rpx;
|
||
background: $secondary-color;
|
||
color: #fff;
|
||
border-radius: 50%;
|
||
font-size: 20rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
}
|
||
}
|
||
|
||
.course-list-section {
|
||
padding: 30rpx;
|
||
|
||
.section-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 20rpx;
|
||
|
||
.section-title {
|
||
font-size: 32rpx;
|
||
font-weight: bold;
|
||
color: $text-color;
|
||
}
|
||
|
||
.course-count {
|
||
font-size: 26rpx;
|
||
color: $primary-color;
|
||
font-weight: bold;
|
||
}
|
||
}
|
||
}
|
||
|
||
.course-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.course-item {
|
||
display: flex;
|
||
background: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 24rpx;
|
||
gap: 20rpx;
|
||
|
||
.course-time {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-width: 100rpx;
|
||
|
||
.time {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: $primary-color;
|
||
}
|
||
}
|
||
|
||
.course-info {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 8rpx;
|
||
|
||
.course-name {
|
||
font-size: 28rpx;
|
||
font-weight: bold;
|
||
color: $text-color;
|
||
}
|
||
|
||
.student-name,
|
||
.location {
|
||
font-size: 24rpx;
|
||
color: $text-secondary;
|
||
}
|
||
}
|
||
|
||
.course-status {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 8rpx 16rpx;
|
||
border-radius: 20rpx;
|
||
font-size: 22rpx;
|
||
height: fit-content;
|
||
|
||
&.status-pending {
|
||
background: rgba($warning-color, 0.1);
|
||
color: $warning-color;
|
||
}
|
||
|
||
&.status-active {
|
||
background: rgba($success-color, 0.1);
|
||
color: $success-color;
|
||
}
|
||
|
||
&.status-completed {
|
||
background: rgba(0, 0, 0, 0.05);
|
||
color: $text-secondary;
|
||
}
|
||
}
|
||
}
|
||
|
||
.empty-state {
|
||
padding: 120rpx 0;
|
||
text-align: center;
|
||
|
||
.empty-icon {
|
||
display: block;
|
||
font-size: 100rpx;
|
||
margin-bottom: 20rpx;
|
||
opacity: 0.3;
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 28rpx;
|
||
color: $text-secondary;
|
||
}
|
||
}
|
||
</style>
|