2025-05-07 09:44:23 +08:00
|
|
|
|
<template>
|
|
|
|
|
|
<view class="interest-course">
|
2025-05-16 16:16:41 +08:00
|
|
|
|
<!-- 选课信息头部 - 固定部分 -->
|
2025-05-07 09:44:23 +08:00
|
|
|
|
<view class="selection-header">
|
|
|
|
|
|
<view class="header-content">
|
2025-05-16 16:16:41 +08:00
|
|
|
|
<view class="title-section">
|
|
|
|
|
|
<view class="title">
|
|
|
|
|
|
<text v-if="kcData && kcData.xkmc">{{ kcData.xkmc }}</text>
|
|
|
|
|
|
<text v-else>选课信息</text>
|
2025-05-07 09:44:23 +08:00
|
|
|
|
</view>
|
2025-05-16 16:16:41 +08:00
|
|
|
|
<!-- <view class="subtitle">互动兴趣课程</view> -->
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<!-- 学生选择部分 -->
|
|
|
|
|
|
<view class="student-selector-bar" @click="showStudentSelector">
|
|
|
|
|
|
<view class="user-avatar">
|
|
|
|
|
|
<image
|
|
|
|
|
|
:src="currentStudent.avatar || '/static/base/home/11222.png'"
|
|
|
|
|
|
class="w-full h-full"
|
|
|
|
|
|
></image>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="student-info">
|
|
|
|
|
|
<text class="student-name">{{ currentStudent.xm }}</text>
|
|
|
|
|
|
<text class="student-class"
|
|
|
|
|
|
>{{ currentStudent.njmc }} {{ currentStudent.bjmc }}</text
|
|
|
|
|
|
>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="switch-btn" v-if="studentList.length > 1">切换</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="countdown-section">
|
|
|
|
|
|
<template v-if="!isEnrollmentEnded">
|
|
|
|
|
|
<view class="countdown-title">{{ countdownTitle }}</view>
|
|
|
|
|
|
<view class="countdown-timer">
|
|
|
|
|
|
<view class="time-block">
|
|
|
|
|
|
<text class="time-value">{{ countdownTime.hours }}</text>
|
|
|
|
|
|
<text class="time-unit">时</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<text class="time-separator">:</text>
|
|
|
|
|
|
<view class="time-block">
|
|
|
|
|
|
<text class="time-value">{{ countdownTime.minutes }}</text>
|
|
|
|
|
|
<text class="time-unit">分</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<text class="time-separator">:</text>
|
|
|
|
|
|
<view class="time-block">
|
|
|
|
|
|
<text class="time-value">{{ countdownTime.seconds }}</text>
|
|
|
|
|
|
<text class="time-unit">秒</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
<template v-else>
|
|
|
|
|
|
<view class="enrollment-ended">
|
|
|
|
|
|
<u-icon name="info-circle" color="#FF9900" size="20"></u-icon>
|
|
|
|
|
|
<text>选课已经结束</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
2025-05-07 09:44:23 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
<!-- 可滚动的内容区域 -->
|
|
|
|
|
|
<view class="scrollable-content">
|
|
|
|
|
|
<!-- 课程网格列表 -->
|
|
|
|
|
|
<view class="course-grid" v-if="courseListData.length > 0">
|
|
|
|
|
|
<view
|
|
|
|
|
|
v-for="(course, index) in courseListData"
|
|
|
|
|
|
:key="course.id || index"
|
|
|
|
|
|
class="course-item"
|
|
|
|
|
|
:class="{ selected: course.isSelected }"
|
|
|
|
|
|
@click="toggleSelection(course)"
|
2025-05-07 09:44:23 +08:00
|
|
|
|
>
|
2025-05-16 16:16:41 +08:00
|
|
|
|
<view class="course-name">{{ course.kcmc }}</view>
|
|
|
|
|
|
<view class="register-info">
|
|
|
|
|
|
<text>报名情况:</text>
|
|
|
|
|
|
<text class="register-count">{{ course.ybmr }}</text>
|
|
|
|
|
|
<text> | {{ course.maxNum }}</text>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="detail-btn" @click.stop="viewCourseDetail(course)"
|
|
|
|
|
|
>详情</view
|
|
|
|
|
|
>
|
|
|
|
|
|
<view v-if="course.isSelected" class="selected-mark">
|
|
|
|
|
|
<uni-icons
|
|
|
|
|
|
type="checkbox-filled"
|
|
|
|
|
|
color="#3FBF72"
|
|
|
|
|
|
size="22"
|
|
|
|
|
|
></uni-icons>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
|
|
|
|
|
<!-- 暂无数据提示 -->
|
|
|
|
|
|
<view v-else class="empty-course-list">
|
|
|
|
|
|
<view class="empty-icon">
|
|
|
|
|
|
<u-icon name="list" size="50" color="#C8C9CC"></u-icon>
|
2025-05-07 09:44:23 +08:00
|
|
|
|
</view>
|
2025-05-16 16:16:41 +08:00
|
|
|
|
<view class="empty-text">暂无课程数据</view>
|
2025-05-07 09:44:23 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
<!-- 底部报名按钮 - 固定部分 -->
|
2025-05-07 09:44:23 +08:00
|
|
|
|
<view class="register-btn-container">
|
2025-05-30 17:22:30 +08:00
|
|
|
|
<view class="register-btn" @click="submitRegistration">
|
|
|
|
|
|
<text v-if="selectedCoursesCount > 0">
|
|
|
|
|
|
点击报名 (已选{{ selectedCoursesCount }}门)
|
|
|
|
|
|
</text>
|
|
|
|
|
|
<text v-else>点击报名</text>
|
2025-05-16 16:16:41 +08:00
|
|
|
|
</view>
|
2025-05-07 09:44:23 +08:00
|
|
|
|
</view>
|
2025-05-16 16:16:41 +08:00
|
|
|
|
|
|
|
|
|
|
<view>
|
|
|
|
|
|
<!-- 学生选择弹窗 -->
|
|
|
|
|
|
<u-popup
|
|
|
|
|
|
:show="showSelector"
|
|
|
|
|
|
@close="showSelector = false"
|
|
|
|
|
|
mode="bottom"
|
|
|
|
|
|
round="10"
|
|
|
|
|
|
>
|
|
|
|
|
|
<view class="student-selector">
|
|
|
|
|
|
<view class="selector-header">
|
|
|
|
|
|
<text class="selector-title">选择学生</text>
|
|
|
|
|
|
<u-icon
|
|
|
|
|
|
name="close"
|
|
|
|
|
|
size="20"
|
|
|
|
|
|
@click="showSelector = false"
|
|
|
|
|
|
></u-icon>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="student-list">
|
|
|
|
|
|
<view
|
|
|
|
|
|
v-for="(student, index) in studentList"
|
|
|
|
|
|
:key="index"
|
|
|
|
|
|
class="student-item"
|
|
|
|
|
|
:class="{
|
|
|
|
|
|
'student-item-active': currentStudent.id === student.id,
|
|
|
|
|
|
}"
|
|
|
|
|
|
@click="switchStudent(student)"
|
|
|
|
|
|
>
|
|
|
|
|
|
<view class="student-avatar">
|
|
|
|
|
|
<image
|
|
|
|
|
|
:src="student.avatar || '/static/base/home/11222.png'"
|
|
|
|
|
|
class="w-full h-full"
|
|
|
|
|
|
></image>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<view class="student-info">
|
|
|
|
|
|
<text class="student-name">{{ student.xm }}</text>
|
|
|
|
|
|
<text class="student-class"
|
|
|
|
|
|
>{{ student.njmc }} {{ student.bjmc }}</text
|
|
|
|
|
|
>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
<u-icon
|
|
|
|
|
|
v-if="currentStudent.id === student.id"
|
|
|
|
|
|
name="checkmark"
|
|
|
|
|
|
color="#409EFF"
|
|
|
|
|
|
size="20"
|
|
|
|
|
|
></u-icon>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</view>
|
|
|
|
|
|
</u-popup>
|
|
|
|
|
|
</view>
|
2025-05-07 09:44:23 +08:00
|
|
|
|
</view>
|
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
|
|
<script setup lang="ts">
|
2025-05-30 17:22:30 +08:00
|
|
|
|
import {
|
|
|
|
|
|
ref,
|
|
|
|
|
|
computed,
|
|
|
|
|
|
reactive,
|
|
|
|
|
|
onBeforeUnmount,
|
|
|
|
|
|
watch,
|
|
|
|
|
|
onMounted,
|
|
|
|
|
|
} from "vue";
|
2025-05-16 16:16:41 +08:00
|
|
|
|
import { useUserStore } from "@/store/modules/user";
|
|
|
|
|
|
import { useDataStore } from "@/store/modules/data";
|
2025-05-30 17:22:30 +08:00
|
|
|
|
import {
|
|
|
|
|
|
xkAddXkqdApi,
|
|
|
|
|
|
xkListApi,
|
|
|
|
|
|
xkxkbmInfoApi,
|
|
|
|
|
|
xkXkqdApi,
|
|
|
|
|
|
} from "@/api/base/server";
|
2025-05-16 16:16:41 +08:00
|
|
|
|
import dayjs from "dayjs";
|
2025-05-07 09:44:23 +08:00
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
const { getUser } = useUserStore();
|
2025-05-30 17:22:30 +08:00
|
|
|
|
const { getData, setKcData, setData } = useDataStore();
|
2025-05-16 16:16:41 +08:00
|
|
|
|
const { sign_file } = getData;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 倒计时相关
|
|
|
|
|
|
const countdownTime = reactive({
|
|
|
|
|
|
hours: "00",
|
|
|
|
|
|
minutes: "00",
|
|
|
|
|
|
seconds: "00",
|
|
|
|
|
|
});
|
2025-05-07 09:44:23 +08:00
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 倒计时标题文本
|
|
|
|
|
|
const countdownTitle = computed(() => {
|
|
|
|
|
|
return kcStatus.value ? "距离选课结束还剩" : "距离选课开始还剩";
|
2025-05-07 09:44:23 +08:00
|
|
|
|
});
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 当前选中的学生
|
|
|
|
|
|
let countdownTimer: number | null = null;
|
|
|
|
|
|
const remainTime = ref("00:00:00");
|
|
|
|
|
|
|
|
|
|
|
|
// 学生列表数据
|
|
|
|
|
|
const studentList = computed(() => {
|
|
|
|
|
|
return getUser.xsList;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
const currentStudent = ref();
|
|
|
|
|
|
|
|
|
|
|
|
// 控制选择器显示状态
|
|
|
|
|
|
const showSelector = ref(false);
|
|
|
|
|
|
|
|
|
|
|
|
if (studentList.value.length > 1) {
|
|
|
|
|
|
showSelector.value = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const kcData = ref();
|
|
|
|
|
|
const kcStatus = ref(false);
|
|
|
|
|
|
|
|
|
|
|
|
// 添加选课是否已结束的标记
|
|
|
|
|
|
const isEnrollmentEnded = ref(false);
|
|
|
|
|
|
|
2025-05-30 17:22:30 +08:00
|
|
|
|
// 添加轮询定时器变量
|
|
|
|
|
|
let pollTimer: number | null = null;
|
|
|
|
|
|
|
|
|
|
|
|
const courseInfo = ref({});
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 封装检查学生报名状态的通用方法
|
|
|
|
|
|
function checkStudentEnrollmentApi(student: any): Promise<boolean> {
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
|
|
if (!student || !student.id || !student.njId) {
|
|
|
|
|
|
console.error("学生信息不完整:", student);
|
|
|
|
|
|
resolve(false);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-30 17:22:30 +08:00
|
|
|
|
// 检查kcData是否已加载
|
|
|
|
|
|
if (!kcData.value || !kcData.value.id) {
|
|
|
|
|
|
console.error("课程数据未加载:", kcData.value);
|
|
|
|
|
|
resolve(false);
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 调用选课签到接口检查学生是否已报名
|
|
|
|
|
|
xkXkqdApi({
|
|
|
|
|
|
njId: student.njId,
|
|
|
|
|
|
xsId: student.id,
|
2025-05-30 17:22:30 +08:00
|
|
|
|
xklxId: "816059832", // 课程类型ID
|
|
|
|
|
|
xkId: kcData.value.id,
|
2025-05-16 16:16:41 +08:00
|
|
|
|
})
|
|
|
|
|
|
.then((res) => {
|
2025-05-30 17:22:30 +08:00
|
|
|
|
console.log(1122, res);
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 根据接口返回的result判断是否已报名
|
|
|
|
|
|
if (res && res.resultCode === 1) {
|
2025-05-30 17:22:30 +08:00
|
|
|
|
setData({
|
|
|
|
|
|
...getData,
|
|
|
|
|
|
kcData,
|
|
|
|
|
|
studentInfo: student,
|
|
|
|
|
|
enrolledCourse: res.result,
|
|
|
|
|
|
});
|
|
|
|
|
|
resolve(res.result.length > 0); // 转换为布尔值返回
|
2025-05-16 16:16:41 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// 接口调用成功但返回错误
|
|
|
|
|
|
console.warn("检查报名状态接口返回错误:", res);
|
|
|
|
|
|
resolve(false);
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
|
// 接口调用失败
|
|
|
|
|
|
console.error("调用检查报名状态接口失败:", error);
|
|
|
|
|
|
reject(error);
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-30 17:22:30 +08:00
|
|
|
|
// 轮询获取课程报名人数
|
|
|
|
|
|
const pollEnrollmentCount = () => {
|
|
|
|
|
|
xkxkbmInfoApi({ xkId: kcData.value.id })
|
|
|
|
|
|
.then((res) => {
|
|
|
|
|
|
if (res && res.resultCode === 1 && Array.isArray(res.result)) {
|
|
|
|
|
|
updateEnrollmentCount(res.result);
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch((error) => {
|
|
|
|
|
|
console.error("获取报名人数失败:", error);
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 更新课程报名人数
|
|
|
|
|
|
const updateEnrollmentCount = (
|
|
|
|
|
|
data: Array<{ xkkcId: string; bmrs: number }>
|
|
|
|
|
|
) => {
|
|
|
|
|
|
if (!Array.isArray(courseListData.value) || courseListData.value.length === 0)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
let hasUpdates = false;
|
|
|
|
|
|
|
|
|
|
|
|
courseListData.value.forEach((course) => {
|
|
|
|
|
|
const newCount = data.find((item) => item.xkkcId === course.id);
|
|
|
|
|
|
if (newCount && course.ybmr !== newCount.bmrs) {
|
|
|
|
|
|
course.ybmr = newCount.bmrs;
|
|
|
|
|
|
hasUpdates = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 如果有更新,强制重新渲染列表
|
|
|
|
|
|
if (hasUpdates) {
|
|
|
|
|
|
courseListData.value = [...courseListData.value];
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 启动轮询
|
|
|
|
|
|
const startPolling = (interval = 5000) => {
|
|
|
|
|
|
// 清除可能存在的旧定时器
|
|
|
|
|
|
stopPolling();
|
|
|
|
|
|
|
|
|
|
|
|
// 立即执行一次
|
|
|
|
|
|
pollEnrollmentCount();
|
|
|
|
|
|
|
|
|
|
|
|
// 设置定时器定期执行
|
|
|
|
|
|
pollTimer = setInterval(pollEnrollmentCount, interval) as unknown as number;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 停止轮询
|
|
|
|
|
|
const stopPolling = () => {
|
|
|
|
|
|
if (pollTimer) {
|
|
|
|
|
|
clearInterval(pollTimer);
|
|
|
|
|
|
pollTimer = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 在组件挂载时开始轮询
|
|
|
|
|
|
onMounted(() => {
|
|
|
|
|
|
// 当课程列表加载完成后启动轮询
|
|
|
|
|
|
watch(courseListData, (newVal) => {
|
|
|
|
|
|
if (newVal.length > 0 && !pollTimer) {
|
|
|
|
|
|
startPolling();
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 页面初始化时检查当前学生是否已报名
|
|
|
|
|
|
const checkInitialEnrollment = (currentStudent: any) => {
|
|
|
|
|
|
if (!currentStudent) return;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 使用封装的API函数检查学生报名状态
|
|
|
|
|
|
checkStudentEnrollmentApi(currentStudent)
|
|
|
|
|
|
.then((isEnrolled) => {
|
|
|
|
|
|
if (isEnrolled) {
|
|
|
|
|
|
uni.hideLoading();
|
|
|
|
|
|
uni.reLaunch({
|
|
|
|
|
|
url: `/pages/base/course-selection/enrolled`,
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => {
|
2025-05-30 17:22:30 +08:00
|
|
|
|
// 检查失败时不做特殊处理,继续正常流程
|
|
|
|
|
|
console.log("检查学生报名状态失败,继续正常流程");
|
2025-05-16 16:16:41 +08:00
|
|
|
|
});
|
2025-05-07 09:44:23 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 加载课程列表
|
|
|
|
|
|
const loadCourseList = (currentStudent: any) => {
|
|
|
|
|
|
if (!currentStudent) {
|
|
|
|
|
|
uni.hideLoading();
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
xkListApi({
|
|
|
|
|
|
njId: currentStudent.njId,
|
2025-05-30 17:22:30 +08:00
|
|
|
|
xklxId: "816059832",
|
2025-05-16 16:16:41 +08:00
|
|
|
|
})
|
|
|
|
|
|
.then((res) => {
|
|
|
|
|
|
if (res.resultCode == 1) {
|
|
|
|
|
|
if (res.result) {
|
|
|
|
|
|
kcData.value = res.result;
|
|
|
|
|
|
//获取当前时间
|
|
|
|
|
|
const now = dayjs().valueOf();
|
|
|
|
|
|
//获取选课开始时间
|
|
|
|
|
|
const xkkstime = dayjs(res.result.xkkstime).valueOf();
|
|
|
|
|
|
//获取选课结束时间
|
|
|
|
|
|
const xkjstime = dayjs(res.result.xkjstime).valueOf();
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否已经超过选课结束时间
|
|
|
|
|
|
if (now > xkjstime) {
|
|
|
|
|
|
kcStatus.value = true;
|
|
|
|
|
|
isEnrollmentEnded.value = true;
|
|
|
|
|
|
countdownTime.hours = "00";
|
|
|
|
|
|
countdownTime.minutes = "00";
|
|
|
|
|
|
countdownTime.seconds = "00";
|
|
|
|
|
|
}
|
|
|
|
|
|
//判断选课是否开始
|
|
|
|
|
|
else if (now < xkkstime) {
|
|
|
|
|
|
kcStatus.value = false;
|
|
|
|
|
|
startCountdown(xkkstime);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
kcStatus.value = true;
|
|
|
|
|
|
startCountdown(xkjstime);
|
|
|
|
|
|
}
|
2025-05-30 17:22:30 +08:00
|
|
|
|
|
|
|
|
|
|
// 课程数据加载完成后,检查学生报名状态
|
|
|
|
|
|
checkInitialEnrollment(currentStudent);
|
2025-05-16 16:16:41 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
uni.reLaunch({
|
|
|
|
|
|
url: "/pages/base/course-selection/notopen",
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
2025-05-30 17:22:30 +08:00
|
|
|
|
uni.hideLoading();
|
2025-05-16 16:16:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
.catch(() => {
|
|
|
|
|
|
uni.hideLoading();
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-05-30 17:22:30 +08:00
|
|
|
|
if (studentList.value.length > 0 && studentList.value.length === 1) {
|
|
|
|
|
|
currentStudent.value = studentList.value[0];
|
|
|
|
|
|
// 先加载课程列表,不立即检查报名状态
|
|
|
|
|
|
uni.showLoading({
|
|
|
|
|
|
title: "加载中...",
|
|
|
|
|
|
});
|
|
|
|
|
|
loadCourseList(currentStudent.value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 显示学生选择器
|
|
|
|
|
|
function showStudentSelector() {
|
|
|
|
|
|
if (studentList.value.length > 1) {
|
|
|
|
|
|
showSelector.value = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 切换学生
|
|
|
|
|
|
function switchStudent(student: any) {
|
|
|
|
|
|
currentStudent.value = student;
|
|
|
|
|
|
showSelector.value = false;
|
|
|
|
|
|
|
|
|
|
|
|
// 显示加载中
|
|
|
|
|
|
uni.showLoading({
|
|
|
|
|
|
title: "加载中...",
|
2025-05-07 09:44:23 +08:00
|
|
|
|
});
|
2025-05-16 16:16:41 +08:00
|
|
|
|
|
2025-05-30 17:22:30 +08:00
|
|
|
|
// 显示切换成功提示
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: `已切换到${student.xm}`,
|
|
|
|
|
|
icon: "none",
|
|
|
|
|
|
});
|
2025-05-16 16:16:41 +08:00
|
|
|
|
|
2025-05-30 17:22:30 +08:00
|
|
|
|
// 先加载当前学生的课程列表,课程数据加载完成后会自动检查报名状态
|
|
|
|
|
|
loadCourseList(student);
|
2025-05-16 16:16:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 启动倒计时
|
|
|
|
|
|
const startCountdown = (endTimeStamp: number) => {
|
|
|
|
|
|
if (countdownTimer) {
|
|
|
|
|
|
clearInterval(countdownTimer);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const updateCountdown = () => {
|
|
|
|
|
|
const now = new Date().getTime();
|
|
|
|
|
|
const timeLeft = endTimeStamp - now;
|
|
|
|
|
|
|
|
|
|
|
|
if (timeLeft <= 0) {
|
|
|
|
|
|
// 倒计时结束
|
|
|
|
|
|
countdownTime.hours = "00";
|
|
|
|
|
|
countdownTime.minutes = "00";
|
|
|
|
|
|
countdownTime.seconds = "00";
|
|
|
|
|
|
remainTime.value = "00:00:00";
|
|
|
|
|
|
|
|
|
|
|
|
if (countdownTimer) {
|
|
|
|
|
|
clearInterval(countdownTimer);
|
|
|
|
|
|
countdownTimer = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断当前倒计时是选课开始还是选课结束
|
|
|
|
|
|
if (!kcStatus.value && kcData.value) {
|
|
|
|
|
|
// 如果是选课开始倒计时结束,则切换到选课结束倒计时
|
|
|
|
|
|
kcStatus.value = true; // 更新状态为选课已开始
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: "选课已开始",
|
|
|
|
|
|
icon: "none",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 开始选课结束倒计时
|
|
|
|
|
|
const xkjstime = dayjs(kcData.value.xkjstime).valueOf();
|
|
|
|
|
|
startCountdown(xkjstime);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 如果是选课结束倒计时结束
|
|
|
|
|
|
isEnrollmentEnded.value = true; // 标记选课已结束
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: "选课已结束",
|
|
|
|
|
|
icon: "none",
|
|
|
|
|
|
});
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 计算小时、分钟和秒
|
|
|
|
|
|
const hours = Math.floor(timeLeft / (1000 * 60 * 60));
|
|
|
|
|
|
const minutes = Math.floor((timeLeft % (1000 * 60 * 60)) / (1000 * 60));
|
|
|
|
|
|
const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000);
|
|
|
|
|
|
|
|
|
|
|
|
// 格式化显示
|
|
|
|
|
|
countdownTime.hours = hours < 10 ? `0${hours}` : `${hours}`;
|
|
|
|
|
|
countdownTime.minutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
|
|
|
|
|
|
countdownTime.seconds = seconds < 10 ? `0${seconds}` : `${seconds}`;
|
|
|
|
|
|
|
|
|
|
|
|
// 更新remainTime(兼容原有逻辑)
|
|
|
|
|
|
remainTime.value = `${countdownTime.hours}:${countdownTime.minutes}:${countdownTime.seconds}`;
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 立即执行一次
|
|
|
|
|
|
updateCountdown();
|
|
|
|
|
|
|
|
|
|
|
|
// 每秒更新一次
|
|
|
|
|
|
countdownTimer = setInterval(updateCountdown, 1000) as unknown as number;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
};
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 从课程数据中提取课程列表
|
|
|
|
|
|
const displayCourseList = computed(() => {
|
|
|
|
|
|
if (
|
|
|
|
|
|
!kcData.value ||
|
|
|
|
|
|
!kcData.value.xkkcs ||
|
|
|
|
|
|
!Array.isArray(kcData.value.xkkcs)
|
|
|
|
|
|
) {
|
|
|
|
|
|
return [];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 获取本地存储的已选课程ID数组
|
|
|
|
|
|
const selectedCourseIds = uni.getStorageSync("selectedCourseIds") || [];
|
|
|
|
|
|
|
|
|
|
|
|
// 为课程添加选中状态属性
|
|
|
|
|
|
return kcData.value.xkkcs.map((course: any) => ({
|
|
|
|
|
|
...course,
|
|
|
|
|
|
isSelected: selectedCourseIds.includes(course.id),
|
|
|
|
|
|
}));
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 可修改的课程列表数据
|
|
|
|
|
|
const courseListData = ref<any[]>([]);
|
|
|
|
|
|
|
2025-05-30 17:22:30 +08:00
|
|
|
|
// 计算已选择的课程数量
|
|
|
|
|
|
const selectedCoursesCount = computed(() => {
|
|
|
|
|
|
return courseListData.value.filter((course: any) => course.isSelected).length;
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 监听计算属性变化,更新可修改的数据
|
|
|
|
|
|
watch(
|
|
|
|
|
|
displayCourseList,
|
|
|
|
|
|
(newVal) => {
|
|
|
|
|
|
courseListData.value = JSON.parse(JSON.stringify(newVal));
|
2025-05-30 17:22:30 +08:00
|
|
|
|
|
|
|
|
|
|
// 初始化时,将已选课程的全部信息存入courseInfo
|
|
|
|
|
|
const selectedCourseIds = uni.getStorageSync("selectedCourseIds") || [];
|
|
|
|
|
|
if (selectedCourseIds.length > 0) {
|
|
|
|
|
|
const selectedCourses = newVal.filter((course: any) =>
|
|
|
|
|
|
selectedCourseIds.includes(course.id)
|
|
|
|
|
|
);
|
|
|
|
|
|
courseInfo.value = selectedCourses;
|
|
|
|
|
|
}
|
2025-05-16 16:16:41 +08:00
|
|
|
|
},
|
|
|
|
|
|
{ immediate: true }
|
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
// 选择或取消选择课程
|
2025-05-07 09:44:23 +08:00
|
|
|
|
const toggleSelection = (course: any) => {
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 只检查课程是否已满,不判断选课是否开始
|
|
|
|
|
|
if (course.ybmr >= course.maxNum) {
|
2025-05-07 09:44:23 +08:00
|
|
|
|
uni.showToast({
|
2025-05-16 16:16:41 +08:00
|
|
|
|
title: "该课程名额已满",
|
2025-05-07 09:44:23 +08:00
|
|
|
|
icon: "none",
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
const courseIndex = courseListData.value.findIndex(
|
|
|
|
|
|
(item) => item.id === course.id
|
|
|
|
|
|
);
|
|
|
|
|
|
if (courseIndex === -1) return;
|
|
|
|
|
|
|
|
|
|
|
|
// 切换当前课程的选中状态
|
|
|
|
|
|
courseListData.value[courseIndex].isSelected =
|
|
|
|
|
|
!courseListData.value[courseIndex].isSelected;
|
|
|
|
|
|
|
2025-05-30 17:22:30 +08:00
|
|
|
|
// 获取所有选中的课程ID
|
|
|
|
|
|
const selectedCourseIds = courseListData.value
|
|
|
|
|
|
.filter((item: any) => item.isSelected)
|
|
|
|
|
|
.map((item: any) => item.id);
|
|
|
|
|
|
|
|
|
|
|
|
// 存储选中的课程ID数组
|
|
|
|
|
|
uni.setStorageSync("selectedCourseIds", selectedCourseIds);
|
|
|
|
|
|
|
|
|
|
|
|
// 更新courseInfo为所有选中的课程
|
|
|
|
|
|
courseInfo.value = courseListData.value.filter(
|
|
|
|
|
|
(item: any) => item.isSelected
|
|
|
|
|
|
);
|
2025-05-07 09:44:23 +08:00
|
|
|
|
|
2025-05-30 17:22:30 +08:00
|
|
|
|
// 显示提示
|
2025-05-16 16:16:41 +08:00
|
|
|
|
if (courseListData.value[courseIndex].isSelected) {
|
2025-05-30 17:22:30 +08:00
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: `已选择 ${course.kcmc}`,
|
|
|
|
|
|
icon: "none",
|
|
|
|
|
|
duration: 1500,
|
|
|
|
|
|
});
|
2025-05-16 16:16:41 +08:00
|
|
|
|
} else {
|
2025-05-30 17:22:30 +08:00
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: `已取消选择 ${course.kcmc}`,
|
|
|
|
|
|
icon: "none",
|
|
|
|
|
|
duration: 1500,
|
|
|
|
|
|
});
|
2025-05-16 16:16:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 查看课程详情
|
|
|
|
|
|
const viewCourseDetail = (course: any) => {
|
|
|
|
|
|
setKcData(course);
|
|
|
|
|
|
uni.navigateTo({
|
|
|
|
|
|
url: `/pages/base/course-selection/detail`,
|
2025-05-07 09:44:23 +08:00
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
|
|
// 提交报名
|
|
|
|
|
|
const submitRegistration = () => {
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 直接根据当前时间和选课时间判断是否可以报名
|
|
|
|
|
|
if (!kcData.value || !kcData.value.xkkstime || !kcData.value.xkjstime) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: "选课信息不完整,请联系校方!",
|
|
|
|
|
|
icon: "none",
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const now = dayjs().valueOf();
|
|
|
|
|
|
const xkkstime = dayjs(kcData.value.xkkstime).valueOf();
|
|
|
|
|
|
const xkjstime = dayjs(kcData.value.xkjstime).valueOf();
|
|
|
|
|
|
|
|
|
|
|
|
// 检查是否在选课时间范围内
|
|
|
|
|
|
if (now < xkkstime) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: "选课时间未开始,请等待!",
|
|
|
|
|
|
icon: "none",
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (now > xkjstime) {
|
|
|
|
|
|
uni.showToast({
|
|
|
|
|
|
title: "选课已结束,请等待下一次选课!",
|
|
|
|
|
|
icon: "none",
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
const selectedCourses = courseListData.value.filter(
|
|
|
|
|
|
(course: any) => course.isSelected
|
2025-05-07 09:44:23 +08:00
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
if (selectedCourses.length === 0) {
|
|
|
|
|
|
uni.showToast({
|
2025-05-16 16:16:41 +08:00
|
|
|
|
title: "请至少选择一门课程",
|
|
|
|
|
|
icon: "none",
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-30 17:22:30 +08:00
|
|
|
|
// 检查选中的课程是否有已满的
|
2025-05-16 16:16:41 +08:00
|
|
|
|
const fullCourses = selectedCourses.filter(
|
|
|
|
|
|
(course) => course.ybmr >= course.maxNum
|
|
|
|
|
|
);
|
|
|
|
|
|
if (fullCourses.length > 0) {
|
|
|
|
|
|
uni.showToast({
|
2025-05-30 17:22:30 +08:00
|
|
|
|
title: `课程"${fullCourses[0].kcmc}"名额已满,请重新选择!`,
|
2025-05-07 09:44:23 +08:00
|
|
|
|
icon: "none",
|
|
|
|
|
|
});
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-30 17:22:30 +08:00
|
|
|
|
// 构建课程名称列表用于确认弹窗
|
|
|
|
|
|
const courseNames = selectedCourses.map((course) => course.kcmc).join("、");
|
2025-05-16 16:16:41 +08:00
|
|
|
|
|
2025-05-07 09:44:23 +08:00
|
|
|
|
uni.showModal({
|
|
|
|
|
|
title: "确认报名",
|
2025-05-30 17:22:30 +08:00
|
|
|
|
content: `您确定要为${currentStudent.value.xm}报名以下课程吗?\n\n${courseNames}`,
|
2025-05-16 16:16:41 +08:00
|
|
|
|
success: async (res) => {
|
2025-05-07 09:44:23 +08:00
|
|
|
|
if (res.confirm) {
|
|
|
|
|
|
uni.showLoading({
|
|
|
|
|
|
title: "报名中...",
|
|
|
|
|
|
});
|
|
|
|
|
|
|
2025-05-30 17:22:30 +08:00
|
|
|
|
// 将选中的课程ID用逗号隔开
|
|
|
|
|
|
const selectedCourseIds = selectedCourses
|
|
|
|
|
|
.map((course) => course.id)
|
|
|
|
|
|
.join(",");
|
2025-05-16 16:16:41 +08:00
|
|
|
|
|
2025-05-30 17:22:30 +08:00
|
|
|
|
const res = await xkAddXkqdApi({
|
|
|
|
|
|
xsId: currentStudent.value.id,
|
|
|
|
|
|
xkkcId: selectedCourseIds,
|
|
|
|
|
|
qmFile: sign_file,
|
|
|
|
|
|
xklxId: "816059832",
|
|
|
|
|
|
});
|
|
|
|
|
|
uni.hideLoading();
|
|
|
|
|
|
if (res.resultCode == 1) {
|
|
|
|
|
|
setData({
|
|
|
|
|
|
...getData,
|
|
|
|
|
|
kcData,
|
|
|
|
|
|
studentInfo: currentStudent.value,
|
|
|
|
|
|
enrolledCourse: res.result,
|
|
|
|
|
|
});
|
2025-05-16 16:16:41 +08:00
|
|
|
|
uni.showToast({
|
2025-05-30 17:22:30 +08:00
|
|
|
|
title: "报名成功",
|
|
|
|
|
|
icon: "none",
|
2025-05-16 16:16:41 +08:00
|
|
|
|
});
|
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
|
uni.reLaunch({
|
|
|
|
|
|
url: `/pages/base/course-selection/enrolled`,
|
|
|
|
|
|
});
|
2025-05-30 17:22:30 +08:00
|
|
|
|
}, 1500);
|
2025-05-16 16:16:41 +08:00
|
|
|
|
}
|
2025-05-07 09:44:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
},
|
|
|
|
|
|
});
|
|
|
|
|
|
};
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 页面卸载前清除定时器
|
|
|
|
|
|
onBeforeUnmount(() => {
|
|
|
|
|
|
if (countdownTimer) {
|
|
|
|
|
|
clearInterval(countdownTimer);
|
|
|
|
|
|
countdownTimer = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-30 17:22:30 +08:00
|
|
|
|
stopPolling();
|
2025-05-07 09:44:23 +08:00
|
|
|
|
});
|
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
|
|
<style lang="scss" scoped>
|
|
|
|
|
|
.interest-course {
|
2025-05-16 16:16:41 +08:00
|
|
|
|
min-height: 100%;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
background-color: #f5f7fa;
|
2025-05-16 16:16:41 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
overflow: hidden;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.nav-bar {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
height: 44px;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
|
|
|
|
|
|
.nav-left {
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.nav-title {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.nav-right {
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: flex-end;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.selection-header {
|
2025-05-16 16:16:41 +08:00
|
|
|
|
background: linear-gradient(135deg, #4a90e2, #2879ff);
|
2025-05-07 09:44:23 +08:00
|
|
|
|
padding: 20px 15px;
|
2025-05-16 16:16:41 +08:00
|
|
|
|
color: #fff;
|
|
|
|
|
|
border-radius: 0 0 15px 15px;
|
|
|
|
|
|
box-shadow: 0 4px 12px rgba(40, 121, 255, 0.2);
|
|
|
|
|
|
position: sticky;
|
|
|
|
|
|
top: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
z-index: 10;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
|
|
|
|
|
|
.header-content {
|
|
|
|
|
|
display: flex;
|
2025-05-16 16:16:41 +08:00
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
gap: 15px;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
.title-section {
|
|
|
|
|
|
.title {
|
|
|
|
|
|
font-size: 24px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.subtitle {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
|
}
|
2025-05-07 09:44:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
// 学生选择栏样式
|
|
|
|
|
|
.student-selector-bar {
|
2025-05-07 09:44:23 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
2025-05-16 16:16:41 +08:00
|
|
|
|
background-color: rgba(255, 255, 255, 0.1);
|
|
|
|
|
|
border-radius: 10px;
|
|
|
|
|
|
padding: 10px;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
.user-avatar {
|
|
|
|
|
|
width: 40px;
|
|
|
|
|
|
height: 40px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
overflow: hidden;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
margin-right: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
.student-info {
|
|
|
|
|
|
flex: 1;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
.student-name {
|
2025-05-07 09:44:23 +08:00
|
|
|
|
font-size: 16px;
|
2025-05-16 16:16:41 +08:00
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
margin-bottom: 2px;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
2025-05-16 16:16:41 +08:00
|
|
|
|
.student-class {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
opacity: 0.8;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.switch-btn {
|
|
|
|
|
|
padding: 4px 12px;
|
|
|
|
|
|
background-color: rgba(255, 255, 255, 0.2);
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
border-radius: 20px;
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.countdown-section {
|
|
|
|
|
|
.countdown-title {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.countdown-timer {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
|
|
|
|
|
|
.time-block {
|
|
|
|
|
|
background-color: rgba(255, 255, 255, 0.2);
|
|
|
|
|
|
border-radius: 6px;
|
|
|
|
|
|
min-width: 40px;
|
|
|
|
|
|
padding: 5px 8px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
|
|
|
|
|
|
.time-value {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
display: block;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-unit {
|
|
|
|
|
|
font-size: 12px;
|
|
|
|
|
|
opacity: 0.9;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.time-separator {
|
|
|
|
|
|
font-size: 20px;
|
|
|
|
|
|
font-weight: bold;
|
|
|
|
|
|
margin: 0 5px;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-05-16 16:16:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 可滚动内容区域样式
|
|
|
|
|
|
.scrollable-content {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
overflow-y: auto;
|
|
|
|
|
|
-webkit-overflow-scrolling: touch; // 增强iOS滚动体验
|
2025-05-07 09:44:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.course-grid {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-wrap: wrap;
|
2025-05-16 16:16:41 +08:00
|
|
|
|
padding: 15px 15px 0 15px;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
|
|
|
|
|
|
.course-item {
|
|
|
|
|
|
position: relative;
|
|
|
|
|
|
width: calc(50% - 10px);
|
|
|
|
|
|
margin-bottom: 15px;
|
|
|
|
|
|
background-color: #fff;
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
box-sizing: border-box;
|
2025-05-16 16:16:41 +08:00
|
|
|
|
border: 1px solid transparent;
|
|
|
|
|
|
transition: all 0.3s ease;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
|
|
|
|
|
|
&:nth-child(odd) {
|
|
|
|
|
|
margin-right: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&:nth-child(even) {
|
|
|
|
|
|
margin-left: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&.selected {
|
|
|
|
|
|
border: 1px solid #3fbf72;
|
2025-05-16 16:16:41 +08:00
|
|
|
|
background-color: rgba(63, 191, 114, 0.05);
|
|
|
|
|
|
box-shadow: 0 2px 8px rgba(63, 191, 114, 0.15);
|
2025-05-07 09:44:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.course-name {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #333;
|
|
|
|
|
|
margin-bottom: 10px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.register-info {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #666;
|
|
|
|
|
|
margin-bottom: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
.register-count {
|
|
|
|
|
|
color: #2879ff;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.detail-btn {
|
|
|
|
|
|
display: inline-block;
|
|
|
|
|
|
color: #2879ff;
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.selected-mark {
|
|
|
|
|
|
position: absolute;
|
|
|
|
|
|
top: -6px;
|
|
|
|
|
|
right: -6px;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.register-btn-container {
|
2025-05-16 16:16:41 +08:00
|
|
|
|
position: sticky;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
bottom: 0;
|
|
|
|
|
|
left: 0;
|
|
|
|
|
|
right: 0;
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
background-color: #fff;
|
2025-05-16 16:16:41 +08:00
|
|
|
|
z-index: 10;
|
|
|
|
|
|
box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05);
|
|
|
|
|
|
|
2025-05-07 09:44:23 +08:00
|
|
|
|
.register-btn {
|
|
|
|
|
|
height: 50px;
|
|
|
|
|
|
line-height: 50px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
background-color: #2879ff;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
border-radius: 25px;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
}
|
2025-05-16 16:16:41 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 学生选择器弹窗样式 */
|
|
|
|
|
|
.student-selector {
|
|
|
|
|
|
background-color: #ffffff;
|
|
|
|
|
|
border-top-left-radius: 12px;
|
|
|
|
|
|
border-top-right-radius: 12px;
|
|
|
|
|
|
padding-bottom: 20px;
|
|
|
|
|
|
|
|
|
|
|
|
.selector-header {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
justify-content: space-between;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 15px;
|
|
|
|
|
|
border-bottom: 1px solid #f2f2f2;
|
|
|
|
|
|
|
|
|
|
|
|
.selector-title {
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.student-list {
|
|
|
|
|
|
padding: 0 15px;
|
|
|
|
|
|
|
|
|
|
|
|
.student-item {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
padding: 15px 0;
|
|
|
|
|
|
border-bottom: 1px solid #f2f2f2;
|
|
|
|
|
|
|
|
|
|
|
|
&:last-child {
|
|
|
|
|
|
border-bottom: none;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
&-active {
|
|
|
|
|
|
background-color: rgba(64, 158, 255, 0.05);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.student-avatar {
|
|
|
|
|
|
width: 45px;
|
|
|
|
|
|
height: 45px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
background-color: #f0f0f0;
|
|
|
|
|
|
overflow: hidden;
|
|
|
|
|
|
flex-shrink: 0;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.student-info {
|
|
|
|
|
|
flex: 1;
|
|
|
|
|
|
margin-left: 12px;
|
|
|
|
|
|
|
|
|
|
|
|
.student-name {
|
|
|
|
|
|
font-size: 15px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
margin-bottom: 4px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.student-class {
|
|
|
|
|
|
font-size: 13px;
|
|
|
|
|
|
color: #606266;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 全局图片样式 */
|
|
|
|
|
|
.w-full {
|
|
|
|
|
|
width: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.h-full {
|
|
|
|
|
|
height: 100%;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 暂无数据样式
|
|
|
|
|
|
.empty-course-list {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
flex-direction: column;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
padding: 60px 20px;
|
|
|
|
|
|
text-align: center;
|
|
|
|
|
|
|
|
|
|
|
|
.empty-icon {
|
|
|
|
|
|
margin-bottom: 20px;
|
|
|
|
|
|
background-color: #f5f6f7;
|
|
|
|
|
|
width: 80px;
|
|
|
|
|
|
height: 80px;
|
|
|
|
|
|
border-radius: 50%;
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-text {
|
|
|
|
|
|
font-size: 18px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #303133;
|
|
|
|
|
|
margin-bottom: 8px;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
.empty-desc {
|
|
|
|
|
|
font-size: 14px;
|
|
|
|
|
|
color: #909399;
|
|
|
|
|
|
max-width: 80%;
|
|
|
|
|
|
line-height: 1.5;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/* 选课已结束样式 */
|
|
|
|
|
|
.enrollment-ended {
|
|
|
|
|
|
display: flex;
|
|
|
|
|
|
align-items: center;
|
|
|
|
|
|
justify-content: center;
|
|
|
|
|
|
background-color: rgba(255, 255, 255, 0.2);
|
|
|
|
|
|
border-radius: 8px;
|
|
|
|
|
|
padding: 12px 15px;
|
|
|
|
|
|
font-size: 16px;
|
|
|
|
|
|
font-weight: 500;
|
|
|
|
|
|
color: #fff;
|
|
|
|
|
|
gap: 8px;
|
2025-05-07 09:44:23 +08:00
|
|
|
|
}
|
|
|
|
|
|
</style>
|