zhxy-jsd/src/pages/base/groupTeaching/dmXkkcRecord.vue
2025-08-11 21:11:19 +08:00

444 lines
11 KiB
Vue

<template>
<BasicLayout>
<!-- 课程信息卡片 -->
<view class="course-card mx-15 my-15 bg-white white-bg-color r-md p-15">
<view class="flex-row items-center mb-15">
<view class="course-icon flex-center mr-10">
<u-icon name="calendar" color="#4080ff" size="20"></u-icon>
</view>
<text class="font-16 font-bold">{{ xkkc.kcmc }}</text>
<text class="font-14 cor-999 ml-10">{{ todayInfo.date }} ({{ todayInfo.weekName }})</text>
</view>
</view>
<!-- 点名记录统计 -->
<view class="record-stats mx-15 mb-15">
<view class="stats-grid">
<view class="stat-item total" @click="showStudentList('total')">
<view class="stat-number">{{ totalStudents }}</view>
<view class="stat-label">总人数</view>
</view>
<view class="stat-item present" @click="showStudentList('present')">
<view class="stat-number">{{ presentStudents }}</view>
<view class="stat-label">实到</view>
</view>
<view class="stat-item leave" @click="showStudentList('leave')">
<view class="stat-number">{{ leaveStudents }}</view>
<view class="stat-label">请假</view>
</view>
<view class="stat-item absent" @click="showStudentList('absent')">
<view class="stat-number">{{ absentStudents }}</view>
<view class="stat-label">缺勤</view>
</view>
</view>
</view>
<!-- 学生列表弹窗 -->
<uni-popup ref="studentPopup" type="bottom" :mask-click="false">
<view class="student-popup">
<view class="popup-header">
<text class="popup-title">{{ currentStatTitle }}</text>
<view class="close-btn" @click="closeStudentList">
<u-icon name="close" color="#666" size="20"></u-icon>
</view>
</view>
<view class="student-list">
<view
v-for="(student, index) in currentStudentList"
:key="student.xsId"
class="student-item"
>
<view class="student-avatar">
<image
v-if="student.xstx"
:src="getImageUrl(student.xstx)"
mode="aspectFill"
class="avatar-img"
/>
<view v-else class="avatar-text">{{ student.xsxm?.charAt(0) || '学' }}</view>
</view>
<view class="student-info">
<view class="student-name">{{ student.xsxm }}</view>
<view class="student-class">{{ student.njmcName }} {{ student.bjmc }}</view>
</view>
<view class="student-status">
<text :class="getStatusClass(student.xszt)">{{ getStatusText(student.xszt) }}</text>
</view>
</view>
</view>
</view>
</uni-popup>
<!-- 返回按钮 -->
<view class="bottom-actions mx-15 mb-30">
<button class="back-btn" @click="goBack">返回</button>
</view>
</BasicLayout>
</template>
<script setup lang="ts">
import { ref, computed, onMounted } from "vue";
import { useUserStore } from "@/store/modules/user";
import { useDataStore } from "@/store/modules/data";
import BasicLayout from "@/components/BasicLayout/Layout.vue";
import { jsdXkXsListApi } from "@/api/base/server";
import { BASE_IMAGE_URL } from "@/config";
import dayjs from "dayjs";
const { getJs } = useUserStore();
const { getData } = useDataStore();
const js = computed(() => getJs);
const xkkc = computed(() => getData);
// 今日信息
const now = dayjs();
let wDay = now.day();
if (wDay === 0) {
wDay = 7;
}
const wdNameList = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"];
const todayInfo = ref({
date: now.format("YYYY-MM-DD"),
weekName: wdNameList[wDay - 1],
});
// 学生列表数据
const studentList = ref<any[]>([]);
const currentStatType = ref<string>('');
const currentStatTitle = ref<string>('');
const currentStudentList = ref<any[]>([]);
// 统计数量
const totalStudents = computed(() => studentList.value.length);
const presentStudents = computed(() => studentList.value.filter(s => s.xszt === '正常').length);
const leaveStudents = computed(() => studentList.value.filter(s => s.xszt === '请假').length);
const absentStudents = computed(() => studentList.value.filter(s => s.xszt === '缺勤').length);
// 弹窗引用
const studentPopup = ref<any>(null);
// 获取图片完整URL
const getImageUrl = (path: string) => {
if (!path) return '';
if (path.startsWith('http')) return path;
return BASE_IMAGE_URL + path;
};
// 获取状态样式类
const getStatusClass = (status: string) => {
switch (status) {
case '正常': return 'status-normal';
case '请假': return 'status-leave';
case '缺勤': return 'status-absent';
default: return 'status-normal';
}
};
// 获取状态文本
const getStatusText = (status: string) => {
switch (status) {
case '正常': return '正常';
case '请假': return '请假';
case '缺勤': return '缺勤';
default: return '正常';
}
};
// 显示学生列表
const showStudentList = (type: string) => {
currentStatType.value = type;
switch (type) {
case 'total':
currentStatTitle.value = '总人数学生列表';
currentStudentList.value = studentList.value;
break;
case 'present':
currentStatTitle.value = '实到学生列表';
currentStudentList.value = studentList.value.filter(s => s.xszt === '正常');
break;
case 'leave':
currentStatTitle.value = '请假学生列表';
currentStudentList.value = studentList.value.filter(s => s.xszt === '请假');
break;
case 'absent':
currentStatTitle.value = '缺勤学生列表';
currentStudentList.value = studentList.value.filter(s => s.xszt === '缺勤');
break;
}
studentPopup.value.open();
};
// 关闭学生列表
const closeStudentList = () => {
studentPopup.value.close();
};
// 返回上一页
const goBack = () => {
uni.navigateBack();
};
// 加载学生列表
const loadStudentList = async () => {
try {
uni.showLoading({ title: '加载中...' });
const res = await jsdXkXsListApi({
xkkcId: xkkc.value.id,
date: todayInfo.value.date
});
if (res && res.resultCode === 1) {
studentList.value = res.result || [];
} else {
studentList.value = [];
uni.showToast({
title: res?.message || '获取学生列表失败',
icon: 'none'
});
}
} catch (error) {
console.error('加载学生列表失败:', error);
studentList.value = [];
uni.showToast({
title: '加载学生列表失败',
icon: 'none'
});
} finally {
uni.hideLoading();
}
};
onMounted(() => {
loadStudentList();
});
</script>
<style scoped lang="scss">
.course-card {
border-radius: 8px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05);
}
.course-icon {
width: 30px;
height: 30px;
border-radius: 4px;
background-color: rgba(64, 128, 255, 0.1);
}
.record-stats {
.stats-title {
font-size: 16px;
font-weight: bold;
color: #333;
margin-bottom: 15px;
padding-left: 10px;
border-left: 4px solid #4080ff;
}
.stats-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 15px;
}
.stat-item {
background: white;
border-radius: 12px;
padding: 20px 15px;
text-align: center;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
transition: all 0.3s ease;
cursor: pointer;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
}
&.total {
border: 2px solid #666;
.stat-number { color: #333; }
}
&.present {
border: 2px solid #2879ff;
.stat-number { color: #2879ff; }
}
&.leave {
border: 2px solid #ff9900;
.stat-number { color: #ff9900; }
}
&.absent {
border: 2px solid #ff4d4f;
.stat-number { color: #ff4d4f; }
}
.stat-number {
font-size: 24px;
font-weight: bold;
margin-bottom: 8px;
}
.stat-label {
font-size: 14px;
color: #666;
}
}
}
.student-popup {
background: white;
border-radius: 20px 20px 0 0;
max-height: 70vh;
overflow: hidden;
.popup-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px;
border-bottom: 1px solid #eee;
.popup-title {
font-size: 18px;
font-weight: bold;
color: #333;
}
.close-btn {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #f5f5f5;
cursor: pointer;
}
}
.student-list {
padding: 0 20px 20px;
max-height: 60vh;
overflow-y: auto;
.student-item {
display: flex;
align-items: center;
padding: 15px 0;
border-bottom: 1px solid #f0f0f0;
&:last-child {
border-bottom: none;
}
.student-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
overflow: hidden;
margin-right: 15px;
.avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
}
.avatar-text {
width: 100%;
height: 100%;
background: #4080ff;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
font-weight: bold;
}
}
.student-info {
flex: 1;
.student-name {
font-size: 16px;
font-weight: 500;
color: #333;
margin-bottom: 4px;
}
.student-class {
font-size: 14px;
color: #666;
}
}
.student-status {
.status-normal {
color: #2879ff;
background: rgba(40, 121, 255, 0.1);
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
.status-leave {
color: #ff9900;
background: rgba(255, 153, 0, 0.1);
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
.status-absent {
color: #ff4d4f;
background: rgba(255, 77, 79, 0.1);
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
}
}
}
}
}
.bottom-actions {
.back-btn {
width: 100%;
height: 44px;
background: #4080ff;
color: white;
border: none;
border-radius: 22px;
font-size: 16px;
font-weight: 500;
}
}
// 工具类
.mx-15 { margin-left: 15px; margin-right: 15px; }
.my-15 { margin-top: 15px; margin-bottom: 15px; }
.mb-15 { margin-bottom: 15px; }
.mb-30 { margin-bottom: 30px; }
.mr-10 { margin-right: 10px; }
.ml-10 { margin-left: 10px; }
.bg-white { background-color: white; }
.white-bg-color { background-color: white; }
.r-md { border-radius: 8px; }
.p-15 { padding: 15px; }
.flex-row { display: flex; flex-direction: row; }
.items-center { align-items: center; }
.font-16 { font-size: 16px; }
.font-14 { font-size: 14px; }
.font-bold { font-weight: bold; }
.cor-999 { color: #999; }
</style>