376 lines
9.1 KiB
Vue
376 lines
9.1 KiB
Vue
<template>
|
||
<view class="schedule-page">
|
||
<PageHeader
|
||
title="我的排班"
|
||
:is-back="true"
|
||
:border-bottom="false"
|
||
:background="headerBackground"
|
||
/>
|
||
|
||
<view class="content-wrapper">
|
||
<view class="form-container">
|
||
<view class="form-card calendar-card">
|
||
<view class="calendar-header">
|
||
<text class="month-action" @click="handlePrevMonth">‹</text>
|
||
<picker
|
||
mode="date"
|
||
fields="month"
|
||
:value="monthValue"
|
||
@change="handleMonthPickerChange"
|
||
>
|
||
<view class="card-title month-title">{{ monthTitle }}</view>
|
||
</picker>
|
||
<text class="month-action" @click="handleNextMonth">›</text>
|
||
</view>
|
||
<view class="week-row">
|
||
<text
|
||
v-for="(label, index) in weekLabels"
|
||
:key="index"
|
||
class="week-cell"
|
||
>
|
||
{{ label }}
|
||
</text>
|
||
</view>
|
||
<view class="date-grid">
|
||
<view
|
||
v-for="(item, index) in calendarDays"
|
||
:key="index"
|
||
class="date-cell"
|
||
:class="{
|
||
empty: !item.day,
|
||
active: item.isToday
|
||
}"
|
||
@click="handleSelectDay(item)"
|
||
>
|
||
<view class="day-number">{{ item.day }}</view>
|
||
<view v-if="item.hasDuty" class="duty-tag">值班</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-card duty-card">
|
||
<view class="card-title">{{ selectedDateTitle }}</view>
|
||
<view class="duty-row">
|
||
<text class="duty-label">今日排班:</text>
|
||
<text class="duty-value">{{ isSelectedDuty ? "值班" : "无" }}</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import PageHeader from "@/components/PageHeader.vue";
|
||
|
||
export default {
|
||
name: "MySchedule",
|
||
components: {
|
||
PageHeader,
|
||
},
|
||
data() {
|
||
return {
|
||
headerBackground: {
|
||
background: "transparent",
|
||
},
|
||
weekLabels: ["日", "一", "二", "三", "四", "五", "六"],
|
||
currentYear: 0,
|
||
currentMonth: 0,
|
||
selectedDay: 0,
|
||
scheduleList: [],
|
||
calendarDays: [],
|
||
};
|
||
},
|
||
onLoad() {
|
||
const today = new Date();
|
||
this.currentYear = today.getFullYear();
|
||
this.currentMonth = today.getMonth() + 1;
|
||
this.selectedDay = today.getDate();
|
||
this.calendarDays = this.buildCalendarDays(this.currentYear, this.currentMonth, []);
|
||
this.fetchSchedule();
|
||
},
|
||
computed: {
|
||
monthTitle() {
|
||
return `${this.currentYear}年${String(this.currentMonth).padStart(2, "0")}月`;
|
||
},
|
||
monthValue() {
|
||
return `${this.currentYear}-${String(this.currentMonth).padStart(2, "0")}`;
|
||
},
|
||
selectedDateTitle() {
|
||
const dayText = String(this.selectedDay).padStart(2, "0");
|
||
return `${this.currentYear}年${String(this.currentMonth).padStart(2, "0")}月${dayText}日`;
|
||
},
|
||
isSelectedDuty() {
|
||
return this.scheduleList.some((item) => {
|
||
return (
|
||
item.year === this.currentYear &&
|
||
item.month === this.currentMonth &&
|
||
item.day === this.selectedDay
|
||
);
|
||
});
|
||
},
|
||
},
|
||
methods: {
|
||
fetchSchedule(
|
||
year = this.currentYear,
|
||
month = this.currentMonth,
|
||
day = this.selectedDay
|
||
) {
|
||
const userInfo = this.vuex_user || {};
|
||
const teacherId = userInfo.id || userInfo.Id;
|
||
if (!teacherId) {
|
||
this.$u.toast("缺少教师ID");
|
||
return;
|
||
}
|
||
const scheduleDate = new Date(year, month - 1, day, 12, 0, 0);
|
||
this.$u.api
|
||
.getShiftSchedulingData({
|
||
SchedulingTime: scheduleDate.toISOString(),
|
||
TeacherManagementId: teacherId,
|
||
})
|
||
.then((res) => {
|
||
if (res && res.succeed) {
|
||
const list = Array.isArray(res.data) ? res.data : [];
|
||
this.scheduleList = list
|
||
.map((item) => this.parseScheduleItem(item))
|
||
.filter(Boolean);
|
||
this.calendarDays = this.buildCalendarDays(
|
||
this.currentYear,
|
||
this.currentMonth,
|
||
this.scheduleList
|
||
);
|
||
} else {
|
||
this.$u.toast(res?.error || "获取排班失败");
|
||
}
|
||
})
|
||
.catch(() => {
|
||
this.$u.toast("获取排班失败");
|
||
});
|
||
},
|
||
parseScheduleItem(item) {
|
||
if (!item || !item.schedulingTime) return null;
|
||
const date = new Date(item.schedulingTime);
|
||
if (Number.isNaN(date.getTime())) return null;
|
||
return {
|
||
id: item.id,
|
||
year: date.getFullYear(),
|
||
month: date.getMonth() + 1,
|
||
day: date.getDate(),
|
||
};
|
||
},
|
||
buildCalendarDays(year, month, scheduleList = []) {
|
||
const firstDay = new Date(year, month - 1, 1).getDay();
|
||
const daysInMonth = new Date(year, month, 0).getDate();
|
||
const totalCells = 42;
|
||
const result = [];
|
||
const dutySet = new Set(
|
||
scheduleList
|
||
.filter((item) => item.year === year && item.month === month)
|
||
.map((item) => item.day)
|
||
);
|
||
|
||
for (let i = 0; i < totalCells; i += 1) {
|
||
const dayNumber = i - firstDay + 1;
|
||
if (dayNumber <= 0 || dayNumber > daysInMonth) {
|
||
result.push({ day: "", isToday: false, hasDuty: false });
|
||
} else {
|
||
result.push({
|
||
day: dayNumber,
|
||
isToday:
|
||
dayNumber === this.selectedDay &&
|
||
year === this.currentYear &&
|
||
month === this.currentMonth,
|
||
hasDuty: dutySet.has(dayNumber),
|
||
});
|
||
}
|
||
}
|
||
|
||
return result;
|
||
},
|
||
handleSelectDay(item) {
|
||
if (!item || !item.day) return;
|
||
this.selectedDay = item.day;
|
||
this.calendarDays = this.buildCalendarDays(
|
||
this.currentYear,
|
||
this.currentMonth,
|
||
this.scheduleList
|
||
);
|
||
},
|
||
handlePrevMonth() {
|
||
this.changeMonth(-1);
|
||
},
|
||
handleNextMonth() {
|
||
this.changeMonth(1);
|
||
},
|
||
handleMonthPickerChange(event) {
|
||
const value = event?.detail?.value || "";
|
||
const [yearText, monthText] = value.split("-");
|
||
const year = Number(yearText);
|
||
const month = Number(monthText);
|
||
if (!year || !month) return;
|
||
this.updateMonth(year, month);
|
||
},
|
||
changeMonth(offset) {
|
||
const date = new Date(this.currentYear, this.currentMonth - 1 + offset, 1);
|
||
this.updateMonth(date.getFullYear(), date.getMonth() + 1);
|
||
},
|
||
updateMonth(year, month) {
|
||
this.currentYear = year;
|
||
this.currentMonth = month;
|
||
const today = new Date();
|
||
if (today.getFullYear() === year && today.getMonth() + 1 === month) {
|
||
this.selectedDay = today.getDate();
|
||
} else {
|
||
this.selectedDay = 1;
|
||
}
|
||
this.calendarDays = this.buildCalendarDays(
|
||
this.currentYear,
|
||
this.currentMonth,
|
||
this.scheduleList
|
||
);
|
||
this.fetchSchedule(year, month, this.selectedDay);
|
||
},
|
||
},
|
||
};
|
||
</script>
|
||
|
||
<style scoped>
|
||
.schedule-page {
|
||
height: 100vh;
|
||
background-image: url("@/static/notes/bg.png");
|
||
background-position: center top;
|
||
background-repeat: no-repeat;
|
||
background-size: 100% auto;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.content-wrapper {
|
||
flex: 1;
|
||
display: flex;
|
||
flex-direction: column;
|
||
padding: 20rpx 30rpx 60px;
|
||
overflow: hidden;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.form-container {
|
||
border-radius: 8px;
|
||
margin-top: 46rpx;
|
||
}
|
||
|
||
.form-card {
|
||
background-color: #ffffff;
|
||
border-radius: 20rpx;
|
||
padding: 30rpx;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.calendar-header {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 30rpx;
|
||
font-weight: 600;
|
||
color: #333333;
|
||
}
|
||
|
||
.month-title {
|
||
padding: 6rpx 18rpx;
|
||
border-radius: 999rpx;
|
||
background-color: #f5f6ff;
|
||
text-align: center;
|
||
min-width: 200rpx;
|
||
}
|
||
|
||
.month-action {
|
||
font-size: 40rpx;
|
||
color: #4f6aff;
|
||
width: 40rpx;
|
||
text-align: center;
|
||
line-height: 40rpx;
|
||
}
|
||
|
||
.week-row {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
margin-bottom: 12rpx;
|
||
color: #999999;
|
||
font-size: 22rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.week-cell {
|
||
line-height: 40rpx;
|
||
}
|
||
|
||
.date-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(7, 1fr);
|
||
gap: 12rpx 0;
|
||
}
|
||
|
||
.date-cell {
|
||
height: 82rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: flex-start;
|
||
position: relative;
|
||
padding-top: 8rpx;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
.date-cell.empty {
|
||
color: transparent;
|
||
}
|
||
|
||
.day-number {
|
||
width: 48rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 26rpx;
|
||
color: #333333;
|
||
line-height: 32rpx;
|
||
height: 32rpx;
|
||
}
|
||
|
||
.date-cell.active .day-number {
|
||
height: 32rpx;
|
||
line-height: 32rpx;
|
||
background-color: #4f6aff;
|
||
color: #ffffff;
|
||
border-radius: 8rpx;
|
||
text-align: center;
|
||
}
|
||
|
||
.duty-tag {
|
||
margin-top: 6rpx;
|
||
font-size: 18rpx;
|
||
color: #4f6aff;
|
||
line-height: 22rpx;
|
||
height: 22rpx;
|
||
}
|
||
|
||
.duty-row {
|
||
display: flex;
|
||
align-items: center;
|
||
font-size: 26rpx;
|
||
color: #333333;
|
||
}
|
||
|
||
.duty-label {
|
||
color: #666666;
|
||
}
|
||
|
||
.duty-value {
|
||
color: #4f6aff;
|
||
font-weight: 600;
|
||
}
|
||
</style>
|