| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486 |
- <template>
- <view class="room-rollcall-container">
- <u-loading-page v-if="loading" loading-text="加载床位信息..."></u-loading-page>
- <view v-if="!loading" class="rollcall-content">
- <!-- 床位网格 -->
- <view v-if="bedDetails.length > 0" class="beds-grid">
- <view
- v-for="bed in bedDetails"
- :key="bed.bedId"
- class="bed-card"
- :class="getBedCardClass(bed)"
- @click="handleBedClick(bed)"
- >
- <view class="bed-name">{{ bed.bedName }}</view>
- <view v-if="bed.studentName" class="student-info">
- <view class="student-name">{{ bed.studentName }}</view>
- <view class="student-no">{{ bed.studentNo }}</view>
- </view>
- <view v-else class="empty-bed">空床位</view>
- <view v-if="bed.isLeaveAuto" class="leave-tag">
- <u-tag text="请假" type="warning" size="mini"></u-tag>
- </view>
- <view class="status-label" :class="getStatusColorClass(bed.rollCallStatus)">
- {{ getStatusLabel(bed.rollCallStatus) }}
- </view>
- </view>
- </view>
- <view v-else style="padding: 60rpx 0">
- <u-empty text="暂无床位信息" mode="data"></u-empty>
- </view>
- <!-- 汇总统计 -->
- <view v-if="bedDetails.length > 0" class="summary-bar">
- <view class="summary-item">
- <text class="summary-label">在寝:</text>
- <text class="summary-value present-count">{{ summary.present }}</text>
- </view>
- <view class="summary-item">
- <text class="summary-label">请假:</text>
- <text class="summary-value leave-count">{{ summary.leave }}</text>
- </view>
- <view class="summary-item">
- <text class="summary-label">不在:</text>
- <text class="summary-value absent-count">{{ summary.absent }}</text>
- </view>
- <view class="summary-item">
- <text class="summary-label">空床位:</text>
- <text class="summary-value empty-count">{{ summary.empty }}</text>
- </view>
- </view>
- <!-- 提示文字 -->
- <view v-if="bedDetails.length > 0" class="hint-bar">
- <u-icon name="info-circle" color="#909399" size="14"></u-icon>
- <text class="hint-text">点击床位切换"在寝"和"不在"状态,请假不可修改</text>
- </view>
- <!-- 提交按钮 -->
- <view v-if="bedDetails.length > 0" class="submit-bar">
- <u-button type="primary" @click="handleSubmit" :loading="submitting" :disabled="submitting">
- 提交点名
- </u-button>
- </view>
- </view>
- </view>
- </template>
- <script>
- import { getRoomStudents, getDormBedList, submitRollCall } from '@/api/dormRollCall.js';
- export default {
- name: 'RoomRollCallPopup',
- props: {
- visible: {
- type: Boolean,
- default: false,
- },
- roomId: {
- type: [String, Number],
- default: '',
- },
- roomName: {
- type: String,
- default: '',
- },
- buildingId: {
- type: [String, Number],
- default: '',
- },
- batchNo: {
- type: String,
- default: '',
- },
- },
- data() {
- return {
- loading: false,
- submitting: false,
- bedDetails: [],
- };
- },
- computed: {
- summary() {
- let present = 0, leave = 0, absent = 0, empty = 0;
- this.bedDetails.forEach((bed) => {
- const status = bed.rollCallStatus;
- if (status === '0') present++;
- else if (status === '1') leave++;
- else if (status === '2') absent++;
- else if (status === '3') empty++;
- });
- return { present, leave, absent, empty };
- },
- },
- watch: {
- visible: {
- immediate: true,
- handler(val) {
- if (val) {
- this.loadBedDetails();
- }
- },
- },
- roomId(val) {
- if (val && this.visible) {
- this.loadBedDetails();
- }
- },
- },
- mounted() {
- // v-if 现挂载时 visible 已为 true,watch 不会触发初始值,这里兜底
- if (this.visible && this.roomId) {
- this.loadBedDetails();
- }
- },
- methods: {
- async loadBedDetails() {
- if (!this.roomId) return;
- this.loading = true;
- try {
- // 1. 获取有学生的床位(与web端一致,使用已验证可用的接口)
- const studentRes = await getRoomStudents(this.roomId);
- const studentBeds = studentRes.data || studentRes || [];
- const studentBedList = Array.isArray(studentBeds) ? studentBeds : [];
- // 2. 收集已占用的床位ID
- const occupiedBedIds = new Set();
- studentBedList.forEach((s) => {
- if (s.bedId != null) {
- occupiedBedIds.add(Number(s.bedId));
- }
- });
- // 3. 查询该房间所有床位,补充空床位
- const allBedsRes = await getDormBedList(this.roomId);
- const allBeds = allBedsRes.rows || allBedsRes.data || [];
- // 4. 合并:有学生的床位 + 空床位
- const mergedList = [...studentBedList];
- (Array.isArray(allBeds) ? allBeds : []).forEach((bed) => {
- if (!occupiedBedIds.has(Number(bed.id))) {
- mergedList.push({
- bedId: bed.id,
- bedCode: bed.bedCode,
- bedName: bed.bedName,
- studentId: null,
- studentName: null,
- studentNo: null,
- classId: null,
- className: null,
- majorId: null,
- majorName: null,
- collegeId: null,
- collegeName: null,
- });
- }
- });
- // 5. 映射为统一格式,并处理点名状态与请假信息
- this.bedDetails = mergedList.map((item) => {
- const bed = {
- bedId: item.bedId || item.id,
- bedCode: item.bedCode,
- bedName: item.bedName,
- studentId: item.studentId,
- studentName: item.studentName,
- studentNo: item.studentNo,
- classId: item.classId,
- className: item.className,
- majorId: item.majorId,
- majorName: item.majorName,
- collegeId: item.collegeId,
- collegeName: item.collegeName,
- rollCallStatus: '0',
- isLeaveAuto: false,
- leaveId: null,
- leaveTypeName: null,
- leaveStartTime: null,
- leaveEndTime: null,
- };
- if (!bed.studentId) {
- bed.rollCallStatus = '3';
- } else if (item.leaveInfo) {
- bed.rollCallStatus = '1';
- bed.isLeaveAuto = true;
- bed.leaveId = item.leaveInfo.leaveId;
- bed.leaveTypeName = item.leaveInfo.leaveTypeName;
- bed.leaveStartTime = item.leaveInfo.leaveStartTime;
- bed.leaveEndTime = item.leaveInfo.leaveEndTime;
- }
- return bed;
- });
- // 6. 按床位名称排序
- this.bedDetails.sort((a, b) => {
- const aName = a.bedName || '';
- const bName = b.bedName || '';
- return aName.localeCompare(bName, undefined, { numeric: true });
- });
- } catch (error) {
- console.error('加载床位信息失败:', error);
- uni.showToast({ title: '加载床位信息失败', icon: 'none' });
- } finally {
- this.loading = false;
- }
- },
- getBedCardClass(bed) {
- if (bed.rollCallStatus === '0') return 'bed-present';
- if (bed.rollCallStatus === '1') return 'bed-leave';
- if (bed.rollCallStatus === '2') return 'bed-absent';
- if (bed.rollCallStatus === '3') return 'bed-empty';
- return '';
- },
- getStatusLabel(status) {
- if (status === '0') return '在寝';
- if (status === '1') return '请假';
- if (status === '2') return '不在';
- if (status === '3') return '空床位';
- return '';
- },
- getStatusColorClass(status) {
- if (status === '0') return 'color-present';
- if (status === '1') return 'color-leave';
- if (status === '2') return 'color-absent';
- if (status === '3') return 'color-empty';
- return '';
- },
- handleBedClick(bed) {
- if (bed.isLeaveAuto) {
- uni.showToast({ title: '该学生处于请假状态,不可修改', icon: 'none' });
- return;
- }
- if (!bed.studentId) {
- return;
- }
- if (bed.rollCallStatus === '0') {
- bed.rollCallStatus = '2';
- } else if (bed.rollCallStatus === '2') {
- bed.rollCallStatus = '0';
- }
- },
- formatLocalDateTime(date) {
- const pad = (n) => String(n).padStart(2, '0');
- return `${date.getFullYear()}-${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}:${pad(date.getSeconds())}`;
- },
- async handleSubmit() {
- if (!this.buildingId || !this.roomId) {
- uni.showToast({ title: '缺少必要参数', icon: 'none' });
- return;
- }
- this.submitting = true;
- try {
- const details = this.bedDetails.map((bed) => ({
- bedId: bed.bedId,
- bedCode: bed.bedCode,
- bedName: bed.bedName,
- studentId: bed.studentId,
- studentName: bed.studentName,
- studentNo: bed.studentNo,
- classId: bed.classId,
- className: bed.className,
- majorId: bed.majorId,
- majorName: bed.majorName,
- collegeId: bed.collegeId,
- collegeName: bed.collegeName,
- rollCallStatus: bed.rollCallStatus,
- leaveId: bed.leaveId,
- leaveTypeName: bed.leaveTypeName,
- leaveStartTime: bed.leaveStartTime,
- leaveEndTime: bed.leaveEndTime,
- }));
- const submitData = {
- batchNo: this.batchNo,
- checkDate: this.formatLocalDateTime(new Date()),
- buildingId: this.buildingId,
- roomId: this.roomId,
- roomName: this.roomName,
- totalBeds: this.bedDetails.length,
- presentCount: this.summary.present,
- leaveCount: this.summary.leave,
- absentCount: this.summary.absent,
- emptyCount: this.summary.empty,
- details,
- };
- const res = await submitRollCall(submitData);
- if (res.code === 200) {
- uni.showToast({ title: '点名提交成功', icon: 'success' });
- this.$emit('submit', submitData);
- } else {
- uni.showToast({ title: res.msg || '提交失败', icon: 'none' });
- }
- } catch (error) {
- console.error('提交点名失败:', error);
- uni.showToast({ title: '提交点名失败', icon: 'none' });
- } finally {
- this.submitting = false;
- }
- },
- },
- };
- </script>
- <style lang="scss" scoped>
- .room-rollcall-container {
- padding: 20rpx;
- }
- .rollcall-content {
- width: 100%;
- }
- .beds-grid {
- display: grid;
- grid-template-columns: repeat(3, 1fr);
- gap: 16rpx;
- margin-bottom: 24rpx;
- }
- .bed-card {
- border: 2rpx solid #e4e7ed;
- border-radius: 12rpx;
- padding: 16rpx;
- text-align: center;
- position: relative;
- transition: all 0.2s;
- }
- .bed-present {
- background-color: #e8f5e9;
- border-color: #67c23a;
- }
- .bed-leave {
- background-color: #fff8e1;
- border-color: #e6a23c;
- }
- .bed-absent {
- background-color: #ffebee;
- border-color: #f56c6c;
- }
- .bed-empty {
- background-color: #f5f5f5;
- border-color: #dcdfe6;
- }
- .bed-name {
- font-weight: 600;
- font-size: 24rpx;
- color: #303133;
- margin-bottom: 8rpx;
- }
- .student-info {
- margin-bottom: 8rpx;
- }
- .student-name {
- font-size: 24rpx;
- font-weight: 500;
- color: #303133;
- overflow: hidden;
- text-overflow: ellipsis;
- white-space: nowrap;
- }
- .student-no {
- font-size: 20rpx;
- color: #909399;
- }
- .empty-bed {
- color: #c0c4cc;
- font-size: 24rpx;
- }
- .leave-tag {
- margin: 8rpx 0;
- }
- .status-label {
- font-size: 22rpx;
- margin-top: 4rpx;
- font-weight: 500;
- }
- .color-present {
- color: #19be6b;
- }
- .color-leave {
- color: #e6a23c;
- }
- .color-absent {
- color: #f56c6c;
- }
- .color-empty {
- color: #909399;
- }
- .summary-bar {
- display: flex;
- justify-content: space-around;
- padding: 24rpx 16rpx;
- background-color: #f5f7fa;
- border-radius: 12rpx;
- margin-bottom: 20rpx;
- }
- .summary-item {
- display: flex;
- flex-direction: column;
- align-items: center;
- }
- .summary-label {
- font-size: 22rpx;
- color: #909399;
- margin-bottom: 4rpx;
- }
- .summary-value {
- font-size: 36rpx;
- font-weight: 700;
- }
- .present-count {
- color: #19be6b;
- }
- .leave-count {
- color: #e6a23c;
- }
- .absent-count {
- color: #f56c6c;
- }
- .empty-count {
- color: #909399;
- }
- .hint-bar {
- display: flex;
- align-items: center;
- padding: 12rpx 16rpx;
- background-color: #f4f4f5;
- border-radius: 8rpx;
- margin-bottom: 24rpx;
- }
- .hint-text {
- font-size: 22rpx;
- color: #909399;
- margin-left: 8rpx;
- }
- .submit-bar {
- padding: 16rpx 0;
- }
- </style>
|