690 lines
17 KiB
Vue
690 lines
17 KiB
Vue
<template>
|
||
<view class="interest-course">
|
||
<!-- 选课信息头部 - 固定部分 -->
|
||
<view class="selection-header">
|
||
<view class="header-content">
|
||
<view class="title-section">
|
||
<view class="title">
|
||
<text v-if="xkkcList && xkkcList.length > 0">
|
||
{{ getCurrentSemesterName() }} - 我的课程
|
||
</text>
|
||
<text v-else>暂无课程</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 可滚动的内容区域 -->
|
||
<view class="scrollable-content">
|
||
<!-- 课程网格列表 -->
|
||
<view class="course-list" v-if="xkkcList && xkkcList.length > 0">
|
||
<view v-for="(xkkc, index) in xkkcList" :key="xkkc.id || index" class="course-item">
|
||
<view class="course-name">{{ xkkc.kcmc }}</view>
|
||
<view class="course-info-item">
|
||
<view class="info-label">上课周期:</view>
|
||
<view class="info-data">{{ xkkc.skzqmc }}</view>
|
||
</view>
|
||
<view class="course-info-item">
|
||
<view class="info-label">上课时间:</view>
|
||
<view class="info-data">{{ formatClassTime(xkkc.skkstime, xkkc.skjstime) }}</view>
|
||
</view>
|
||
<view class="course-info-item">
|
||
<view class="info-label">开课地点:</view>
|
||
<view class="info-data">{{ xkkc.kcdd }}</view>
|
||
</view>
|
||
<view class="course-info-item">
|
||
<view class="info-label">上课人数:</view>
|
||
<view class="info-data">{{ xkkc.hasNum || 0 }} | {{ xkkc.maxNum || 0 }}</view>
|
||
</view>
|
||
<view class="separator-line"></view>
|
||
<view class="course-btn-group">
|
||
<view class="dm-btn" @click.stop="goDm(xkkc)">点名</view>
|
||
<view class="record-btn" @click.stop="goRecord(xkkc)">点名记录</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 暂无数据提示 -->
|
||
<view v-else class="empty-course-list">
|
||
<view class="empty-icon">
|
||
<u-icon name="list" size="50" color="#C8C9CC"></u-icon>
|
||
</view>
|
||
<view class="empty-text">暂无课程数据</view>
|
||
</view>
|
||
</view>
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script setup lang="ts">
|
||
import {
|
||
ref,
|
||
onBeforeUnmount,
|
||
onMounted,
|
||
} from "vue";
|
||
import { useUserStore } from "@/store/modules/user";
|
||
import { useDataStore } from "@/store/modules/data";
|
||
import { xkkcListByJsIdApi } from "@/api/base/xkApi";
|
||
import { dmBeforeMinuteApi } from "@/api/system/config/index";
|
||
import dayjs from "dayjs";
|
||
|
||
const { getJs } = useUserStore();
|
||
const { getData, setData } = useDataStore();
|
||
|
||
const wdNameList = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
|
||
|
||
// 课程列表数据
|
||
const xkkcList = ref<any[]>([]);
|
||
|
||
const dmBeforeMinute = ref<number>(0);
|
||
|
||
onMounted(async () => {
|
||
uni.showLoading({
|
||
title: "加载中...",
|
||
});
|
||
try {
|
||
await loadCourseList();
|
||
await loadDmBeforeMinute();
|
||
} catch (error) {
|
||
console.error('页面初始化失败:', error);
|
||
uni.showToast({
|
||
title: '页面加载失败',
|
||
icon: 'none'
|
||
});
|
||
} finally {
|
||
uni.hideLoading();
|
||
}
|
||
});
|
||
|
||
// 加载课程列表
|
||
const loadCourseList = async () => {
|
||
try {
|
||
const res:any = await xkkcListByJsIdApi({ jsId: getJs.id });
|
||
if (res.resultCode == 1) {
|
||
if (res.result && res.result.length) {
|
||
xkkcList.value = res.result;
|
||
// 处理课程周期显示
|
||
processCoursePeriods();
|
||
} else {
|
||
xkkcList.value = [];
|
||
}
|
||
} else {
|
||
xkkcList.value = [];
|
||
uni.showToast({
|
||
title: res.message || '获取课程列表失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
} catch (error) {
|
||
console.error('加载课程列表失败:', error);
|
||
xkkcList.value = [];
|
||
uni.showToast({
|
||
title: '加载课程列表失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
};
|
||
|
||
// 处理课程周期显示
|
||
const processCoursePeriods = () => {
|
||
for (let i = 0; i < xkkcList.value.length; i++) {
|
||
let xkkc = xkkcList.value[i];
|
||
// 判断周期
|
||
switch (xkkc.skzqlx) {
|
||
case '每天':
|
||
xkkc.skzqmc = "每天";
|
||
break;
|
||
case '每周':
|
||
const daysOfWeek = xkkc.skzq.split(',').map(Number);
|
||
// 从wdNameList读取daysOfWeek对应的周几
|
||
xkkc.skzqmc = daysOfWeek.map((day: number) => wdNameList[day - 1]).join(',');
|
||
break;
|
||
case '每月':
|
||
const daysOfMonth = xkkc.skzq.split(',').map(Number);
|
||
// 从根据编号加
|
||
xkkc.skzqmc = daysOfMonth.map((day: number) => day + "号").join(',');
|
||
break;
|
||
}
|
||
}
|
||
};
|
||
|
||
// 获取当前学期名称
|
||
const getCurrentSemesterName = () => {
|
||
if (xkkcList.value && xkkcList.value.length > 0) {
|
||
// 从第一个课程获取学期名称
|
||
return xkkcList.value[0].xqmc || '当前学期';
|
||
}
|
||
return '当前学期';
|
||
};
|
||
|
||
// 格式化上课时间
|
||
const formatClassTime = (startTime: string, endTime: string) => {
|
||
if (!startTime || !endTime) {
|
||
return '';
|
||
}
|
||
|
||
try {
|
||
// 尝试解析时间,支持多种格式
|
||
let start, end;
|
||
|
||
// 如果是时间格式(HH:mm:ss 或 HH:mm)
|
||
if (startTime.includes(':') && !startTime.includes('-') && !startTime.includes('/')) {
|
||
start = startTime;
|
||
end = endTime;
|
||
} else {
|
||
// 尝试用 dayjs 解析
|
||
const startDate = dayjs(startTime);
|
||
const endDate = dayjs(endTime);
|
||
|
||
if (startDate.isValid() && endDate.isValid()) {
|
||
start = startDate.format('HH:mm:ss');
|
||
end = endDate.format('HH:mm:ss');
|
||
} else {
|
||
// 如果解析失败,直接返回原始值
|
||
return `${startTime}~${endTime}`;
|
||
}
|
||
}
|
||
|
||
return `${start}~${end}`;
|
||
} catch (error) {
|
||
console.error('时间格式化错误:', error);
|
||
// 如果出错,返回原始值
|
||
return `${startTime}~${endTime}`;
|
||
}
|
||
};
|
||
|
||
// 获取点名时间提前分钟数
|
||
const loadDmBeforeMinute = async () => {
|
||
const res = await dmBeforeMinuteApi();
|
||
if (res.resultCode == 1) {
|
||
// 将res.result从字符串转换成int的number赋给dmBeforeMinute
|
||
dmBeforeMinute.value = parseInt(res.result);
|
||
}
|
||
}
|
||
|
||
// 跳转到点名
|
||
const goDm = (xkkc: any) => {
|
||
const now = dayjs();
|
||
let wDay = now.day();
|
||
if (wDay === 0) {
|
||
wDay = 7;
|
||
}
|
||
let mDay = now.date();
|
||
const strDate = now.format('YYYY-MM-DD') + ' ';
|
||
let dmFlag = false;
|
||
let msg = "";
|
||
// 判断周期
|
||
switch (xkkc.skzqlx) {
|
||
case '每天':
|
||
dmFlag = true;
|
||
break;
|
||
case '每周':
|
||
const daysOfWeek = xkkc.skzq.split(',').map(Number);
|
||
dmFlag = daysOfWeek.includes(wDay);
|
||
// 从wdNameList读取daysOfWeek对应的周几
|
||
xkkc.skzqmc = daysOfWeek.map((day: number) => wdNameList[day - 1]).join(',');
|
||
break;
|
||
case '每月':
|
||
const daysOfMonth = xkkc.skzq.split(',').map(Number);
|
||
dmFlag = daysOfMonth.includes(mDay);
|
||
// 从根据编号加
|
||
xkkc.skzqmc = daysOfMonth.map((day: number) => day + "号").join(',');
|
||
break;
|
||
}
|
||
// 判断日期是否合格
|
||
if (dmFlag) {
|
||
// xkkc.skkstime开始时间向前dmBeforeMinute分钟
|
||
const startTime = dayjs(strDate + xkkc.skkstime).subtract(dmBeforeMinute.value, 'minute').format('YYYY-MM-DD HH:mm:ss');
|
||
const endTime = dayjs(strDate + xkkc.skjstime, 'YYYY-MM-DD HH:mm:ss');
|
||
dmFlag = now.isBefore(endTime) && now.isAfter(startTime)
|
||
} else {
|
||
msg = "上课时间未到,无法点名";
|
||
}
|
||
if (dmFlag) {
|
||
setData(xkkc);
|
||
uni.navigateTo({
|
||
url: `/pages/view/routine/xk/dm`,
|
||
});
|
||
} else {
|
||
if (msg === "") {
|
||
msg = "上课时间未到,无法点名";
|
||
}
|
||
uni.showToast({
|
||
title: msg,
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
}
|
||
};
|
||
|
||
// 跳转到点名记录
|
||
const goRecord = (xkkc: any) => {
|
||
setData(xkkc);
|
||
uni.navigateTo({
|
||
url: `/pages/view/routine/xk/dmList`,
|
||
});
|
||
};
|
||
|
||
// 页面卸载前清除定时器
|
||
onBeforeUnmount(() => {
|
||
});
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
.interest-course {
|
||
min-height: 100%;
|
||
background: linear-gradient(180deg, #f8fafc 0%, #f1f5f9 100%);
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.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 {
|
||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||
padding: 25px 20px;
|
||
color: #fff;
|
||
border-radius: 0 0 20px 20px;
|
||
box-shadow: 0 8px 32px rgba(102, 126, 234, 0.3);
|
||
position: sticky;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
z-index: 10;
|
||
position: relative;
|
||
overflow: hidden;
|
||
|
||
&::before {
|
||
content: '';
|
||
position: absolute;
|
||
top: 0;
|
||
left: 0;
|
||
right: 0;
|
||
bottom: 0;
|
||
background: linear-gradient(45deg, rgba(255, 255, 255, 0.1) 0%, transparent 50%, rgba(255, 255, 255, 0.05) 100%);
|
||
pointer-events: none;
|
||
}
|
||
|
||
.header-content {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 15px;
|
||
position: relative;
|
||
z-index: 1;
|
||
|
||
.title-section {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.title {
|
||
font-size: 20px;
|
||
font-weight: 700;
|
||
text-align: center;
|
||
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
||
letter-spacing: 0.5px;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 可滚动内容区域样式
|
||
.scrollable-content {
|
||
flex: 1;
|
||
overflow-y: auto;
|
||
-webkit-overflow-scrolling: touch; // 增强iOS滚动体验
|
||
}
|
||
|
||
.course-list {
|
||
padding: 15px 15px 0 15px;
|
||
|
||
.course-item {
|
||
position: relative;
|
||
width: 100%;
|
||
margin-bottom: 20px;
|
||
background-color: #fff;
|
||
border-radius: 12px;
|
||
padding: 20px;
|
||
box-sizing: border-box;
|
||
border: 1px solid #f0f0f0;
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
|
||
animation: fadeInUp 0.6s ease-out;
|
||
|
||
&:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 8px 25px rgba(0, 0, 0, 0.12);
|
||
border-color: #e8e8e8;
|
||
}
|
||
|
||
&.selected {
|
||
border: 2px solid #3fbf72;
|
||
background-color: rgba(63, 191, 114, 0.02);
|
||
box-shadow: 0 4px 20px rgba(63, 191, 114, 0.15);
|
||
}
|
||
|
||
.course-name {
|
||
font-size: 17px;
|
||
font-weight: 600;
|
||
color: #1a1a1a;
|
||
margin-bottom: 15px;
|
||
line-height: 1.4;
|
||
animation: fadeInLeft 0.5s ease-out 0.1s both;
|
||
}
|
||
|
||
.course-btn-group {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 10px; // 按钮之间的间距
|
||
|
||
.dm-btn {
|
||
display: inline-block;
|
||
color: #2879ff;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
padding: 8px 18px;
|
||
border-radius: 8px;
|
||
background: linear-gradient(135deg, rgba(40, 121, 255, 0.1), rgba(40, 121, 255, 0.05));
|
||
border: 1px solid #2879ff;
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
box-shadow: 0 2px 8px rgba(40, 121, 255, 0.15);
|
||
animation: fadeInUp 0.5s ease-out 0.3s both;
|
||
|
||
&:hover {
|
||
background: linear-gradient(135deg, rgba(40, 121, 255, 0.15), rgba(40, 121, 255, 0.1));
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 15px rgba(40, 121, 255, 0.25);
|
||
}
|
||
|
||
&:active {
|
||
background: linear-gradient(135deg, rgba(40, 121, 255, 0.2), rgba(40, 121, 255, 0.15));
|
||
transform: translateY(0);
|
||
box-shadow: 0 2px 8px rgba(40, 121, 255, 0.2);
|
||
}
|
||
}
|
||
|
||
.record-btn {
|
||
display: inline-block;
|
||
color: #4CAF50; /* A green color for record */
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
padding: 8px 18px;
|
||
border-radius: 8px;
|
||
background: linear-gradient(135deg, rgba(76, 175, 80, 0.1), rgba(76, 175, 80, 0.05));
|
||
border: 1px solid #4CAF50;
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.15);
|
||
animation: fadeInUp 0.5s ease-out 0.4s both;
|
||
|
||
&:hover {
|
||
background: linear-gradient(135deg, rgba(76, 175, 80, 0.15), rgba(76, 175, 80, 0.1));
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 15px rgba(76, 175, 80, 0.25);
|
||
}
|
||
|
||
&:active {
|
||
background: linear-gradient(135deg, rgba(76, 175, 80, 0.2), rgba(76, 175, 80, 0.15));
|
||
transform: translateY(0);
|
||
box-shadow: 0 2px 8px rgba(76, 175, 80, 0.2);
|
||
}
|
||
}
|
||
|
||
.photo-btn {
|
||
display: inline-block;
|
||
color: #ff6b35;
|
||
font-size: 14px;
|
||
font-weight: 600;
|
||
padding: 8px 18px;
|
||
border-radius: 8px;
|
||
background: linear-gradient(135deg, rgba(255, 107, 53, 0.1), rgba(255, 107, 53, 0.05));
|
||
border: 1px solid #ff6b35;
|
||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.15);
|
||
animation: fadeInUp 0.5s ease-out 0.4s both;
|
||
|
||
&:hover {
|
||
background: linear-gradient(135deg, rgba(255, 107, 53, 0.15), rgba(255, 107, 53, 0.1));
|
||
transform: translateY(-1px);
|
||
box-shadow: 0 4px 15px rgba(255, 107, 53, 0.25);
|
||
}
|
||
|
||
&:active {
|
||
background: linear-gradient(135deg, rgba(255, 107, 53, 0.2), rgba(255, 107, 53, 0.15));
|
||
transform: translateY(0);
|
||
box-shadow: 0 2px 8px rgba(255, 107, 53, 0.2);
|
||
}
|
||
}
|
||
}
|
||
|
||
.course-info-item {
|
||
display: flex;
|
||
margin-bottom: 14px;
|
||
font-size: 13px;
|
||
align-items: center;
|
||
animation: fadeInUp 0.5s ease-out 0.15s both;
|
||
|
||
.info-label {
|
||
color: #666;
|
||
flex: 0 0 100px;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.info-data {
|
||
flex: 1 0 1px;
|
||
color: #333;
|
||
font-weight: 400;
|
||
}
|
||
}
|
||
|
||
.separator-line {
|
||
height: 1px;
|
||
background: linear-gradient(90deg, transparent, #e8e8e8, transparent);
|
||
margin: 18px 0;
|
||
opacity: 0.8;
|
||
animation: fadeIn 0.5s ease-out 0.25s both;
|
||
}
|
||
}
|
||
}
|
||
|
||
// 添加动画关键帧
|
||
@keyframes fadeInUp {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateY(20px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateY(0);
|
||
}
|
||
}
|
||
|
||
@keyframes fadeInLeft {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateX(-20px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateX(0);
|
||
}
|
||
}
|
||
|
||
@keyframes fadeInRight {
|
||
from {
|
||
opacity: 0;
|
||
transform: translateX(20px);
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
transform: translateX(0);
|
||
}
|
||
}
|
||
|
||
@keyframes fadeIn {
|
||
from {
|
||
opacity: 0;
|
||
}
|
||
to {
|
||
opacity: 1;
|
||
}
|
||
}
|
||
|
||
/* 全局图片样式 */
|
||
.w-full {
|
||
width: 100%;
|
||
}
|
||
|
||
.h-full {
|
||
height: 100%;
|
||
}
|
||
|
||
// 暂无数据样式
|
||
.empty-course-list {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 80px 20px;
|
||
text-align: center;
|
||
|
||
.empty-icon {
|
||
margin-bottom: 25px;
|
||
background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
|
||
width: 90px;
|
||
height: 90px;
|
||
border-radius: 50%;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||
border: 2px solid rgba(255, 255, 255, 0.8);
|
||
}
|
||
|
||
.empty-text {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
color: #475569;
|
||
margin-bottom: 8px;
|
||
letter-spacing: 0.3px;
|
||
}
|
||
|
||
.empty-desc {
|
||
font-size: 14px;
|
||
color: #64748b;
|
||
max-width: 80%;
|
||
line-height: 1.6;
|
||
}
|
||
}
|
||
|
||
/* 选课已结束样式 */
|
||
.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;
|
||
}
|
||
|
||
/* 俱乐部选择弹窗样式 */
|
||
.xk-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;
|
||
}
|
||
}
|
||
|
||
.xk-list {
|
||
padding: 0 15px;
|
||
|
||
.xk-item {
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 15px 0;
|
||
border-bottom: 1px solid #f2f2f2;
|
||
transition: all 0.3s ease;
|
||
|
||
&:last-child {
|
||
border-bottom: none;
|
||
}
|
||
|
||
&-active {
|
||
background-color: rgba(64, 158, 255, 0.05);
|
||
}
|
||
|
||
&:hover {
|
||
background-color: rgba(64, 158, 255, 0.02);
|
||
}
|
||
|
||
.xk-info {
|
||
flex: 1;
|
||
margin-left: 12px;
|
||
|
||
.xk-name {
|
||
font-size: 15px;
|
||
font-weight: 500;
|
||
color: #303133;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.xk-type {
|
||
font-size: 13px;
|
||
color: #303133;
|
||
margin-bottom: 4px;
|
||
}
|
||
|
||
.xk-class {
|
||
font-size: 13px;
|
||
color: #606266;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style>
|