2025-09-19 09:47:19 +08:00

905 lines
23 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<view class="interest-course">
<!-- 选课信息头部 - 固定部分 -->
<view class="selection-header">
<view class="header-content">
<view class="title-section" @click="clickShowXkSelector">
<view class="title">
<text v-if="xkData && xkData.xkmc">{{ xkData.xkmc }}</text>
<text v-else>巡查选课</text>
</view>
<view class="switch-btn" v-if="xkList.length > 1">切换</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-status" :class="xkkc.sfxc === '是' ? 'status-done' : 'status-pending'">
{{ xkkc.sfxc === '是' ? '已巡查' : '待巡查' }}
</view>
<view class="course-name">{{ xkkc.kcmc }}</view>
<!-- 第一行上课周期开课年级 -->
<view class="course-info-row">
<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">{{ xkkc.njname || '暂无' }}</view>
</view>
</view>
<!-- 第二行授课教师开课地点 -->
<view class="course-info-row">
<view class="course-info-item">
<view class="info-label">授课教师</view>
<view class="info-data">{{ xkkc.jsName || '暂无' }}</view>
</view>
<view class="course-info-item">
<view class="info-label">开课地点</view>
<view class="info-data">{{ xkkc.kcdd }}</view>
</view>
</view>
<view class="separator-line"></view>
<view class="course-btn-group">
<view class="xc-btn" @click.stop="goXc(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>
<!-- 选课选择弹窗 -->
<u-popup
:show="showXkFlag"
@close="showXkFlag = false"
mode="bottom"
round="10"
>
<view class="xk-selector">
<view class="selector-header">
<text class="selector-title">选择俱乐部</text>
<u-icon name="close" size="20" @click="showXkFlag = false"></u-icon>
</view>
<view class="xk-list">
<view
v-for="(xk, index) in xkList"
:key="index"
class="xk-item"
:class="{
'xk-item-active': xkData.id === xk.id,
}"
@click="switchXk(xk)"
>
<view class="xk-info">
<text class="xk-name">{{ xk.xkmc }}</text>
<text class="xk-type">{{ xk.xkmc }}</text>
</view>
<u-icon
v-if="xkData.id === xk.id"
name="checkmark"
color="#409EFF"
size="20"
></u-icon>
</view>
</view>
</view>
</u-popup>
</view>
</view>
</template>
<script setup lang="ts">
import { jsdXkListApi } from "@/api/base/server";
import { getXcCourseListApi } from "@/api/base/pbApi";
import { useDataStore } from "@/store/modules/data";
import { useUserStore } from "@/store/modules/user";
import { onBeforeUnmount, onMounted, ref } from "vue";
import dayjs from "dayjs";
const { getJs } = useUserStore();
const dataStore = useDataStore();
const wdNameList = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
// 检查当前时间是否符合课程时间安排
const isCurrentTimeMatch = (xkkc: any) => {
const now = dayjs();
let wDay = now.day();
if (wDay === 0) {
wDay = 7; // 将周日从0改为7
}
let mDay = now.date(); // 当前日期1-31
// 判断周期类型
switch (xkkc.skzqlx) {
case '每天':
return true; // 每天都可以巡查
case '每周':
if (!xkkc.skzq) return false;
const daysOfWeek = xkkc.skzq.split(',').map(Number);
return daysOfWeek.includes(wDay);
case '每月':
if (!xkkc.skzq) return false;
const daysOfMonth = xkkc.skzq.split(',').map(Number);
return daysOfMonth.includes(mDay);
default:
return false;
}
};
// 控制选择器显示状态
const showXkFlag = ref(false);
const xkList = ref<any>([]);
const xkData = ref();
// 课程列表数据
const xkkcList = ref<any[]>([]);
const xcBeforeMinute = ref<number>(0);
onMounted(async () => {
uni.showLoading({
title: "加载中...",
});
// 检查是否从排班页面跳转过来
const pbData = dataStore.getData;
if (pbData && pbData.pbId) {
// 检查是否是课程数据包含kcmc字段
if (pbData.kcmc) {
// 这是课程数据,需要重建排班数据
const reconstructedPbData = {
pbId: pbData.pbId, // 使用pbId作为排班ID
status: 'A',
njIds: pbData.njIds || '',
bjIds: pbData.bjIds || '',
xcbt: pbData.xcbt,
xclx: pbData.xclx,
xqId: pbData.xqId,
xqmc: pbData.xqmc,
xcstime: pbData.xcstime,
xcjstime: pbData.xcjstime,
pk: pbData.pk
};
dataStore.setGlobal(reconstructedPbData);
await loadXcCourseList(reconstructedPbData);
} else {
// 这是排班数据确保有pbId字段
const pbDataWithPbId = {
...pbData,
pbId: pbData.pbId || pbData.id
};
delete pbDataWithPbId.id; // 移除原始的id字段
dataStore.setGlobal(pbDataWithPbId);
await loadXcCourseList(pbDataWithPbId);
}
} else {
// 正常加载课程列表
await loadCourseList();
}
// 监听刷新事件
uni.$on('refreshCourseList', async () => {
console.log('收到刷新事件,重新加载课程列表');
await refreshCourseList();
});
uni.hideLoading();
});
// 加载课程列表
const loadCourseList = async () => {
const res = await jsdXkListApi({
jsId: getJs.id,
});
if (res.resultCode == 1) {
if (res.result && res.result.length) {
xkList.value = res.result;
switchXk(res.result[0]);
} else {
xkList.value = [];
xkData.value = {};
xkkcList.value = [];
}
}
};
// 刷新课程列表
const refreshCourseList = async () => {
try {
uni.showLoading({
title: "刷新中...",
});
// 检查是否有排班数据
const pbData = dataStore.getGlobal;
if (pbData && pbData.pbId) {
// 重新加载巡查课程列表
await loadXcCourseList(pbData);
} else {
// 重新加载普通课程列表
await loadCourseList();
}
uni.hideLoading();
uni.showToast({
title: "刷新成功",
icon: "success",
duration: 1000
});
} catch (error) {
console.error('刷新课程列表失败:', error);
uni.hideLoading();
uni.showToast({
title: "刷新失败",
icon: "none"
});
}
};
// 加载巡查课程列表
const loadXcCourseList = async (pbData: any) => {
try {
const res = await getXcCourseListApi({
jsId: getJs.id,
pbId: pbData.pbId,
xclx: pbData.xclx
});
if (res && res.resultCode == 1) {
const list = res.result || [];
// 先映射数据,然后进行时间过滤
let mappedList = [];
if (pbData.xclx === 'A') {
// 类型A直接使用返回的课程数据
mappedList = list.map((item: any) => ({
id: item.id,
kcmc: item.kcmc,
skzqmc: item.skzqmc || '每周',
skkstime: item.skkstime,
skjstime: item.skjstime,
kcdd: item.kcdd || '暂无',
njname: item.njname || '暂无',
hasNum: item.hasNum || 0,
maxNum: item.maxNum || 0,
skzqlx: item.skzqlx,
skzq: item.skzq,
jsName: item.jsName,
kcjsId: item.kcjsId, // 添加开课教师ID字段
jxjh: item.jxjh, // 添加教学计划字段
jxll: item.jxll, // 添加教学理论字段
pbLxId: item.pbLxId, // 添加排班类型ID字段
sfxc: item.sfxc || '否' // 添加是否巡查字段
}));
} else if (pbData.xclx === 'B') {
// 类型B直接使用返回的课程数据
mappedList = list.map((item: any) => ({
id: item.id,
kcmc: item.kcmc,
skzqmc: item.skzqmc || '每周',
skkstime: item.skkstime,
skjstime: item.skjstime,
kcdd: item.kcdd || '暂无',
njname: item.njname || '暂无',
hasNum: item.hasNum || 0,
maxNum: item.maxNum || 0,
skzqlx: item.skzqlx,
skzq: item.skzq,
jsName: item.jsName,
kcjsId: item.kcjsId || item.jsId, // 添加开课教师ID字段
jxjh: item.jxjh, // 添加教学计划字段
jxll: item.jxll, // 添加教学理论字段
pbLxId: item.pbLxId, // 添加排班类型ID字段
sfxc: item.sfxc || '否' // 添加是否巡查字段
}));
}
// 过滤出当前时间符合的课程
xkkcList.value = mappedList.filter((xkkc: any) => isCurrentTimeMatch(xkkc));
// 处理课程周期显示
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;
}
}
} else {
xkkcList.value = [];
uni.showToast({
title: (res as any).resultMessage || '获取巡查课程失败',
icon: 'none'
});
}
} catch (error) {
console.error('加载巡查课程失败:', error);
xkkcList.value = [];
uni.showToast({
title: '加载巡查课程失败',
icon: 'none'
});
}
};
// 显示选课选择器
function clickShowXkSelector() {
if (xkList.value.length > 1) {
showXkFlag.value = true;
}
}
// 切换选课
function switchXk(xk: any) {
xkData.value = xk;
xkkcList.value = xk.xkkcs;
showXkFlag.value = false;
for (let i = 0; i < xk.xkkcs.length; i++) {
let xkkc = xk.xkkcs[i];
// 判断周期
switch (xkkc.skzqlx) {
case "每天":
xkkc.skzqmc = "每天";
break;
case "每周":
const daysOfWeek = xkkc.skzq.split(",").map(Number);
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;
}
}
// 显示切换成功提示
uni.showToast({
title: `已切换到${xk.xkmc}`,
icon: "none",
});
}
// 跳转到巡查
const goXc = (xkkc: any) => {
// 直接从global获取排班数据
const pbData = dataStore.getGlobal;
// 检查排班数据是否有效
if (!pbData || !pbData.pbId || !pbData.xcbt) {
console.log('排班数据检查失败:', pbData);
uni.showToast({
title: '数据异常,请重新选择排班',
icon: 'none'
});
return;
}
// 合并排班数据和课程数据
const combinedData = {
...xkkc,
id: xkkc.id, // 课程记录ID
pbId: pbData.pbId, // 排班ID - 确保使用正确的排班ID
pbLxId: xkkc.pbLxId || pbData.pbLxId, // 传递 pbLxId优先使用课程中的 pbLxId
kcjsId: xkkc.kcjsId , // 传递开课教师ID
xclx: pbData.xclx,
xcbt: pbData.xcbt,
xqmc: pbData.xqmc
};
dataStore.setData(combinedData);
uni.navigateTo({
url: `/pages/view/routine/kefuxuncha/xcXkkcDetail`,
});
};
// 跳转到巡查记录
const goRecord = (xkkc: any) => {
// 直接从global获取排班数据
const pbData = dataStore.getGlobal;
// 检查排班数据是否有效
if (!pbData || !pbData.pbId || !pbData.xcbt) {
console.log('排班数据检查失败:', pbData);
uni.showToast({
title: '数据异常,请重新选择排班',
icon: 'none'
});
return;
}
// 合并排班数据和课程数据
const combinedData = {
...xkkc,
id: xkkc.id, // 课程记录ID
pbId: pbData.pbId, // 排班ID - 确保使用正确的排班ID
xclx: pbData.xclx,
xcbt: pbData.xcbt,
xqmc: pbData.xqmc
};
dataStore.setData(combinedData);
uni.navigateTo({
url: `/pages/view/routine/kefuxuncha/xcRecord`,
});
};
// 格式化课程时间
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}`;
}
};
// 页面卸载前清除定时器和事件监听器
onBeforeUnmount(() => {
// 移除事件监听器
uni.$off('refreshCourseList');
});
</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;
}
.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: space-between;
cursor: pointer;
.title {
font-size: 20px;
font-weight: 700;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
letter-spacing: 0.5px;
}
.switch-btn {
padding: 8px 16px;
background-color: rgba(255, 255, 255, 0.2);
color: #fff;
border-radius: 20px;
font-size: 14px;
font-weight: 500;
transition: all 0.3s ease;
&:hover {
background-color: rgba(255, 255, 255, 0.3);
transform: translateY(-1px);
}
}
}
}
}
// 可滚动内容区域样式
.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-status {
position: absolute;
top: 15px;
right: 15px;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 600;
z-index: 2;
animation: fadeInRight 0.5s ease-out 0.2s both;
&.status-done {
background: linear-gradient(135deg, #67c23a, #85ce61);
color: #fff;
box-shadow: 0 2px 8px rgba(103, 194, 58, 0.3);
}
&.status-pending {
background: linear-gradient(135deg, #e6a23c, #f0c78a);
color: #fff;
box-shadow: 0 2px 8px rgba(230, 162, 60, 0.3);
}
}
.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;
padding-right: 80px; // 为状态标识留出空间
}
.course-btn-group {
display: flex;
justify-content: flex-end;
gap: 10px; // Added gap for spacing between buttons
.xc-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.3s 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);
}
}
.record-btn {
display: inline-block;
color: #409EFF;
font-size: 14px;
font-weight: 600;
padding: 8px 18px;
border-radius: 8px;
background: linear-gradient(135deg, rgba(64, 158, 255, 0.1), rgba(64, 158, 255, 0.05));
border: 1px solid #409EFF;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.15);
animation: fadeInUp 0.5s ease-out 0.3s both;
&:hover {
background: linear-gradient(135deg, rgba(64, 158, 255, 0.15), rgba(64, 158, 255, 0.1));
transform: translateY(-1px);
box-shadow: 0 4px 15px rgba(64, 158, 255, 0.25);
}
&:active {
background: linear-gradient(135deg, rgba(64, 158, 255, 0.2), rgba(64, 158, 255, 0.15));
transform: translateY(0);
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
}
}
}
.course-info-row {
display: flex;
margin-bottom: 14px;
gap: 20px;
animation: fadeInUp 0.5s ease-out 0.15s both;
}
.course-info-item {
display: flex;
flex: 1;
font-size: 13px;
align-items: center;
.info-label {
color: #666;
flex: 0 0 70px;
font-weight: 500;
margin-right: 5px;
}
.info-data {
flex: 1;
color: #333;
font-weight: 400;
word-break: break-all;
}
}
.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;
}
}
/* 选课选择弹窗样式 */
.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>