444 lines
11 KiB
Vue
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>
|