|
|
@@ -1,25 +1,28 @@
|
|
|
<template>
|
|
|
<div class="p-2">
|
|
|
- <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
|
|
- <div v-show="showSearch" class="mb-[10px]">
|
|
|
- <el-card shadow="hover">
|
|
|
- <el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
|
|
- <!-- <el-form-item label="删除标志" prop="deleted">
|
|
|
+ <!-- Tab页组件 -->
|
|
|
+ <el-tabs v-model="activeTab" @tab-click="handleTabClick" class="mb-4">
|
|
|
+ <el-tab-pane label="全部留宿数据" name="all">
|
|
|
+ <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
|
|
|
+ <div v-show="showSearch" class="mb-[10px]">
|
|
|
+ <el-card shadow="hover">
|
|
|
+ <el-form ref="queryFormRef" :model="queryParams" :inline="true">
|
|
|
+ <!-- <el-form-item label="删除标志" prop="deleted">
|
|
|
<el-input v-model="queryParams.deleted" placeholder="请输入删除标志" clearable @keyup.enter="handleQuery" />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="学生ID,关联xg_student_msg.id" prop="studentId">
|
|
|
<el-input v-model="queryParams.studentId" placeholder="请输入学生ID,关联xg_student_msg.id" clearable @keyup.enter="handleQuery" />
|
|
|
</el-form-item> -->
|
|
|
- <el-form-item label="学生姓名" prop="studentName">
|
|
|
- <el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
|
|
- </el-form-item>
|
|
|
- <el-form-item label="状态" prop="status">
|
|
|
- <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
|
|
- <el-option label="有效" value="valid" />
|
|
|
- <el-option label="失效" value="invalid" />
|
|
|
- </el-select>
|
|
|
- </el-form-item>
|
|
|
- <!-- <el-form-item label="学号" prop="studentNo">
|
|
|
+ <el-form-item label="学生姓名" prop="studentName">
|
|
|
+ <el-input v-model="queryParams.studentName" placeholder="请输入学生姓名" clearable @keyup.enter="handleQuery" />
|
|
|
+ </el-form-item>
|
|
|
+ <el-form-item label="状态" prop="status">
|
|
|
+ <el-select v-model="queryParams.status" placeholder="请选择状态" clearable>
|
|
|
+ <el-option label="有效" value="valid" />
|
|
|
+ <el-option label="失效" value="invalid" />
|
|
|
+ </el-select>
|
|
|
+ </el-form-item>
|
|
|
+ <!-- <el-form-item label="学号" prop="studentNo">
|
|
|
<el-input v-model="queryParams.studentNo" placeholder="请输入学号" clearable @keyup.enter="handleQuery" />
|
|
|
</el-form-item>
|
|
|
<el-form-item label="班级ID" prop="classId">
|
|
|
@@ -132,122 +135,327 @@
|
|
|
<el-form-item label="扩展字段3" prop="extField3">
|
|
|
<el-input v-model="queryParams.extField3" placeholder="请输入扩展字段3" clearable @keyup.enter="handleQuery" />
|
|
|
</el-form-item> -->
|
|
|
- <el-form-item>
|
|
|
- <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
|
|
- <el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
|
|
- </el-form-item>
|
|
|
- </el-form>
|
|
|
- </el-card>
|
|
|
- </div>
|
|
|
- </transition>
|
|
|
-
|
|
|
- <el-card shadow="never">
|
|
|
- <template #header>
|
|
|
- <el-row :gutter="10" class="mb8">
|
|
|
- <el-col :span="1.5">
|
|
|
- <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['holidayStay:holidayStay:add']">新增</el-button>
|
|
|
- </el-col>
|
|
|
- <el-col :span="1.5">
|
|
|
- <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['holidayStay:holidayStay:edit']"
|
|
|
- >修改</el-button
|
|
|
- >
|
|
|
- </el-col>
|
|
|
- <!-- <el-col :span="1.5">
|
|
|
+ <el-form-item>
|
|
|
+ <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
|
|
|
+ <el-button icon="Refresh" @click="resetQuery">重置</el-button>
|
|
|
+ </el-form-item>
|
|
|
+ </el-form>
|
|
|
+ </el-card>
|
|
|
+ </div>
|
|
|
+ </transition>
|
|
|
+
|
|
|
+ <el-card shadow="never">
|
|
|
+ <template #header>
|
|
|
+ <el-row :gutter="10" class="mb8">
|
|
|
+ <el-col :span="1.5">
|
|
|
+ <el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['holidayStay:holidayStay:add']">新增</el-button>
|
|
|
+ </el-col>
|
|
|
+ <el-col :span="1.5">
|
|
|
+ <el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['holidayStay:holidayStay:edit']"
|
|
|
+ >修改</el-button
|
|
|
+ >
|
|
|
+ </el-col>
|
|
|
+ <!-- <el-col :span="1.5">
|
|
|
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['holidayStay:holidayStay:remove']"
|
|
|
>删除</el-button
|
|
|
>
|
|
|
</el-col> -->
|
|
|
- <!-- <el-col :span="1.5">
|
|
|
+ <!-- <el-col :span="1.5">
|
|
|
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['holidayStay:holidayStay:export']">导出</el-button>
|
|
|
</el-col> -->
|
|
|
- <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
|
|
- </el-row>
|
|
|
- </template>
|
|
|
-
|
|
|
- <el-table v-loading="loading" border :data="holidayStayList" @selection-change="handleSelectionChange">
|
|
|
- <el-table-column type="selection" width="55" align="center" />
|
|
|
- <el-table-column label="主键ID" align="center" prop="id" v-if="false" />
|
|
|
- <el-table-column label="删除标志" align="center" prop="deleted" v-if="false" />
|
|
|
- <el-table-column label="学生ID,关联xg_student_msg.id" align="center" prop="studentId" v-if="false" />
|
|
|
- <el-table-column label="学生姓名" align="center" prop="studentName" />
|
|
|
- <el-table-column label="学号" align="center" prop="studentNo" />
|
|
|
- <el-table-column label="班级ID" align="center" prop="classId" v-if="false" />
|
|
|
- <el-table-column label="班级名称" align="center" prop="className" />
|
|
|
- <el-table-column label="专业ID" align="center" prop="majorId" v-if="false" />
|
|
|
- <el-table-column label="专业名称" align="center" prop="majorName" />
|
|
|
- <el-table-column label="学院ID" align="center" prop="collegeId" v-if="false" />
|
|
|
- <el-table-column label="学院名称" align="center" prop="collegeName" />
|
|
|
- <el-table-column label="校区ID,关联xg_dorm_campus.id" align="center" prop="campusId" v-if="false" />
|
|
|
- <el-table-column label="校区" align="center" prop="campusName" />
|
|
|
- <el-table-column label="宿舍区ID,关联xg_dorm_area.id" align="center" prop="areaId" v-if="false" />
|
|
|
- <el-table-column label="宿舍区" align="center" prop="areaName" />
|
|
|
- <el-table-column label="宿舍楼ID,关联xg_dorm_building.id" align="center" prop="buildingId" v-if="false" />
|
|
|
- <el-table-column label="宿舍楼" align="center" prop="buildingName" />
|
|
|
- <el-table-column label="房间ID,关联xg_dorm_room.id" align="center" prop="roomId" v-if="false" />
|
|
|
- <el-table-column label="房间名称" align="center" prop="roomName" />
|
|
|
- <el-table-column label="床位ID,关联xg_dorm_bed.id" align="center" prop="bedId" v-if="false" />
|
|
|
- <el-table-column label="床位名称" align="center" prop="bedName" />
|
|
|
- <el-table-column label="假期类型" align="center" prop="holidayType">
|
|
|
- <template #default="scope">
|
|
|
- <dict-tag :options="xg_holiday_type" :value="scope.row.holidayType" />
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="留宿开始日期" align="center" prop="applyStartDate" width="180">
|
|
|
- <template #default="scope">
|
|
|
- <span>{{ parseTime(scope.row.applyStartDate, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="留宿结束日期" align="center" prop="applyEndDate" width="180">
|
|
|
- <template #default="scope">
|
|
|
- <span>{{ parseTime(scope.row.applyEndDate, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
|
|
+ <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
|
|
|
+ </el-row>
|
|
|
</template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="留宿原因" align="center" prop="stayReason" v-if="false" />
|
|
|
- <el-table-column label="留宿原因类型" align="center" prop="stayReasonType" v-if="false">
|
|
|
- <template #default="scope">
|
|
|
- <dict-tag :options="xg_stay_reason_type" :value="scope.row.stayReasonType" />
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="学生联系电话" align="center" prop="studentPhone" />
|
|
|
- <el-table-column label="紧急联系人姓名" align="center" prop="emergencyContact" v-if="false" />
|
|
|
- <el-table-column label="紧急联系人电话" align="center" prop="emergencyPhone" />
|
|
|
- <el-table-column label="家长同意" align="center" prop="parentConsent" v-if="false">
|
|
|
- <template #default="scope">
|
|
|
- <dict-tag :options="sys_yesno" :value="scope.row.parentConsent" />
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="附件OSS文件ID,多个用逗号分隔" align="center" prop="attachmentOssIds" v-if="false" />
|
|
|
- <el-table-column label="扩展字段1" align="center" prop="extField1" v-if="false" />
|
|
|
- <el-table-column label="扩展字段2" align="center" prop="extField2" v-if="false" />
|
|
|
- <el-table-column label="扩展字段3" align="center" prop="extField3" v-if="false" />
|
|
|
- <el-table-column label="备注信息" align="center" prop="remark" />
|
|
|
- <el-table-column label="状态" align="center" prop="status" width="90">
|
|
|
- <template #default="scope">
|
|
|
- <el-tag :type="getStatusType(scope.row)" size="small">
|
|
|
- {{ getStatusText(scope.row) }}
|
|
|
- </el-tag>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="100">
|
|
|
- <template #default="scope">
|
|
|
- <el-tooltip content="修改" placement="top">
|
|
|
- <el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['holidayStay:holidayStay:edit']"></el-button>
|
|
|
- </el-tooltip>
|
|
|
- <el-tooltip content="删除" placement="top">
|
|
|
- <el-button
|
|
|
- link
|
|
|
- type="danger"
|
|
|
- icon="Delete"
|
|
|
- @click="handleDelete(scope.row)"
|
|
|
- v-hasPermi="['holidayStay:holidayStay:remove']"
|
|
|
- ></el-button>
|
|
|
- </el-tooltip>
|
|
|
+
|
|
|
+ <el-table v-loading="loading" border :data="holidayStayList" @selection-change="handleSelectionChange">
|
|
|
+ <el-table-column type="selection" width="55" align="center" />
|
|
|
+ <el-table-column label="主键ID" align="center" prop="id" v-if="false" />
|
|
|
+ <el-table-column label="删除标志" align="center" prop="deleted" v-if="false" />
|
|
|
+ <el-table-column label="学生ID,关联xg_student_msg.id" align="center" prop="studentId" v-if="false" />
|
|
|
+ <el-table-column label="学生姓名" align="center" prop="studentName" />
|
|
|
+ <el-table-column label="学号" align="center" prop="studentNo" />
|
|
|
+ <el-table-column label="班级ID" align="center" prop="classId" v-if="false" />
|
|
|
+ <el-table-column label="班级名称" align="center" prop="className" />
|
|
|
+ <el-table-column label="专业ID" align="center" prop="majorId" v-if="false" />
|
|
|
+ <el-table-column label="专业名称" align="center" prop="majorName" />
|
|
|
+ <el-table-column label="学院ID" align="center" prop="collegeId" v-if="false" />
|
|
|
+ <el-table-column label="学院名称" align="center" prop="collegeName" />
|
|
|
+ <el-table-column label="校区ID,关联xg_dorm_campus.id" align="center" prop="campusId" v-if="false" />
|
|
|
+ <el-table-column label="校区" align="center" prop="campusName" />
|
|
|
+ <el-table-column label="宿舍区ID,关联xg_dorm_area.id" align="center" prop="areaId" v-if="false" />
|
|
|
+ <el-table-column label="宿舍区" align="center" prop="areaName" />
|
|
|
+ <el-table-column label="宿舍楼ID,关联xg_dorm_building.id" align="center" prop="buildingId" v-if="false" />
|
|
|
+ <el-table-column label="宿舍楼" align="center" prop="buildingName" />
|
|
|
+ <el-table-column label="房间ID,关联xg_dorm_room.id" align="center" prop="roomId" v-if="false" />
|
|
|
+ <el-table-column label="房间名称" align="center" prop="roomName" />
|
|
|
+ <el-table-column label="床位ID,关联xg_dorm_bed.id" align="center" prop="bedId" v-if="false" />
|
|
|
+ <el-table-column label="床位名称" align="center" prop="bedName" />
|
|
|
+ <el-table-column label="假期类型" align="center" prop="holidayType">
|
|
|
+ <template #default="scope">
|
|
|
+ <dict-tag :options="xg_holiday_type" :value="scope.row.holidayType" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="留宿开始日期" align="center" prop="applyStartDate" width="180">
|
|
|
+ <template #default="scope">
|
|
|
+ <span>{{ parseTime(scope.row.applyStartDate, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="留宿结束日期" align="center" prop="applyEndDate" width="180">
|
|
|
+ <template #default="scope">
|
|
|
+ <span>{{ parseTime(scope.row.applyEndDate, '{y}-{m}-{d} {h}:{i}:{s}') }}</span>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="留宿原因" align="center" prop="stayReason" v-if="false" />
|
|
|
+ <el-table-column label="留宿原因类型" align="center" prop="stayReasonType" v-if="false">
|
|
|
+ <template #default="scope">
|
|
|
+ <dict-tag :options="xg_stay_reason_type" :value="scope.row.stayReasonType" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="学生联系电话" align="center" prop="studentPhone" />
|
|
|
+ <el-table-column label="紧急联系人姓名" align="center" prop="emergencyContact" v-if="false" />
|
|
|
+ <el-table-column label="紧急联系人电话" align="center" prop="emergencyPhone" />
|
|
|
+ <el-table-column label="家长同意" align="center" prop="parentConsent" v-if="false">
|
|
|
+ <template #default="scope">
|
|
|
+ <dict-tag :options="sys_yesno" :value="scope.row.parentConsent" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="附件OSS文件ID,多个用逗号分隔" align="center" prop="attachmentOssIds" v-if="false" />
|
|
|
+ <el-table-column label="扩展字段1" align="center" prop="extField1" v-if="false" />
|
|
|
+ <el-table-column label="扩展字段2" align="center" prop="extField2" v-if="false" />
|
|
|
+ <el-table-column label="扩展字段3" align="center" prop="extField3" v-if="false" />
|
|
|
+ <el-table-column label="备注信息" align="center" prop="remark" />
|
|
|
+ <el-table-column label="状态" align="center" prop="status" width="90">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tag :type="getStatusType(scope.row)" size="small">
|
|
|
+ {{ getStatusText(scope.row) }}
|
|
|
+ </el-tag>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tooltip content="修改" placement="top">
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="primary"
|
|
|
+ icon="Edit"
|
|
|
+ @click="handleUpdate(scope.row)"
|
|
|
+ v-hasPermi="['holidayStay:holidayStay:edit']"
|
|
|
+ ></el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ <el-tooltip content="删除" placement="top">
|
|
|
+ <el-button
|
|
|
+ link
|
|
|
+ type="danger"
|
|
|
+ icon="Delete"
|
|
|
+ @click="handleDelete(scope.row)"
|
|
|
+ v-hasPermi="['holidayStay:holidayStay:remove']"
|
|
|
+ ></el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <pagination
|
|
|
+ v-show="total > 0"
|
|
|
+ :total="total"
|
|
|
+ v-model:page="queryParams.pageNum"
|
|
|
+ v-model:limit="queryParams.pageSize"
|
|
|
+ @pagination="getList"
|
|
|
+ />
|
|
|
+ </el-card>
|
|
|
+ </el-tab-pane>
|
|
|
+
|
|
|
+ <!-- 单人留宿tab页 -->
|
|
|
+ <el-tab-pane label="单人留宿数据" name="singlePerson">
|
|
|
+ <el-card shadow="never">
|
|
|
+ <template #header>
|
|
|
+ <el-row :gutter="10" class="mb8">
|
|
|
+ <el-col :span="24">
|
|
|
+ <div class="flex items-center justify-between">
|
|
|
+ <div class="flex items-center gap-4">
|
|
|
+ <div class="text-lg font-medium">单人留宿有效数据</div>
|
|
|
+ <el-radio-group v-model="singlePersonDisplayMode" @change="handleDisplayModeChange" size="small">
|
|
|
+ <el-radio-button label="list">列表视图</el-radio-button>
|
|
|
+ <el-radio-button label="group">学生分组</el-radio-button>
|
|
|
+ </el-radio-group>
|
|
|
+ </div>
|
|
|
+ <div class="text-sm text-gray-500">
|
|
|
+ 共 {{ singlePersonStayList.length }} 条记录 ({{ Object.keys(groupedSinglePersonStayList).length }} 名学生)
|
|
|
+ <el-button type="primary" icon="Refresh" @click="getSinglePersonValidStayData" class="ml-2">刷新</el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </el-col>
|
|
|
+ </el-row>
|
|
|
</template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
|
|
|
- <pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
|
|
|
- </el-card>
|
|
|
+ <!-- 列表视图:单人留宿数据表格 -->
|
|
|
+ <el-table
|
|
|
+ v-if="singlePersonDisplayMode === 'list'"
|
|
|
+ v-loading="singlePersonLoading"
|
|
|
+ border
|
|
|
+ :data="singlePersonStayList"
|
|
|
+ stripe
|
|
|
+ style="width: 100%"
|
|
|
+ table-layout="fixed"
|
|
|
+ >
|
|
|
+ <el-table-column label="序号" type="index" width="60" align="center" />
|
|
|
+ <el-table-column label="学生信息" align="center" min-width="160">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="student-info-cell">
|
|
|
+ <div class="student-name">{{ scope.row.studentName }}</div>
|
|
|
+ <div class="student-details">
|
|
|
+ <span class="student-no">{{ scope.row.studentNo }}</span>
|
|
|
+ <span class="divider">|</span>
|
|
|
+ <span class="class-name">{{ scope.row.className }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="宿舍信息" align="center" min-width="140">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="dorm-info-cell">
|
|
|
+ <div class="room-info">
|
|
|
+ <el-tag type="success" size="small">{{ scope.row.roomName }}</el-tag>
|
|
|
+ <span class="bed-name">{{ scope.row.bedName }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="building-info">
|
|
|
+ <span>{{ scope.row.buildingName }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="单人留宿时间段" align="center" min-width="250">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="time-period-cell">
|
|
|
+ <div class="time-period-main">
|
|
|
+ <el-icon class="time-icon"><Clock /></el-icon>
|
|
|
+ <div class="time-range">
|
|
|
+ <div class="start-time">{{ parseTime(scope.row.applyStartDate, '{y}-{m}-{d} {h}:{i}') }}</div>
|
|
|
+ <div class="time-arrow">→</div>
|
|
|
+ <div class="end-time">{{ parseTime(scope.row.applyEndDate, '{y}-{m}-{d} {h}:{i}') }}</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <!-- <div class="time-duration" v-if="scope.row.remark">
|
|
|
+ <el-tag type="warning" size="small" effect="plain">
|
|
|
+ {{ extractSinglePersonInfo(scope.row.remark) }}
|
|
|
+ </el-tag>
|
|
|
+ </div> -->
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="假期类型" align="center" prop="holidayType" width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ <dict-tag :options="xg_holiday_type" :value="scope.row.holidayType" />
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="留宿原因" align="center" min-width="120">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="reason-cell">
|
|
|
+ <dict-tag :options="xg_stay_reason_type" :value="scope.row.stayReasonType" />
|
|
|
+ <div class="reason-text" v-if="scope.row.stayReason" :title="scope.row.stayReason">
|
|
|
+ {{ scope.row.stayReason.length > 20 ? scope.row.stayReason.substring(0, 20) + '...' : scope.row.stayReason }}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="联系方式" align="center" min-width="160">
|
|
|
+ <template #default="scope">
|
|
|
+ <div class="contact-cell">
|
|
|
+ <div class="phone-info">
|
|
|
+ <el-icon class="phone-icon"><Phone /></el-icon>
|
|
|
+ <span>{{ scope.row.studentPhone }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="emergency-phone" v-if="scope.row.emergencyPhone">
|
|
|
+ <span class="emergency-label">紧急:</span>
|
|
|
+ <span>{{ scope.row.emergencyPhone }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ <el-table-column label="操作" align="center" class-name="small-padding fixed-width" fixed="right" width="100">
|
|
|
+ <template #default="scope">
|
|
|
+ <el-tooltip content="查看详情" placement="top">
|
|
|
+ <el-button link type="primary" icon="View" @click="handleView(scope.row)"></el-button>
|
|
|
+ </el-tooltip>
|
|
|
+ </template>
|
|
|
+ </el-table-column>
|
|
|
+ </el-table>
|
|
|
+
|
|
|
+ <!-- 分组视图:按学生分组显示 -->
|
|
|
+ <div v-else-if="singlePersonDisplayMode === 'group'" v-loading="singlePersonLoading">
|
|
|
+ <div v-for="(studentStays, studentId) in groupedSinglePersonStayList" :key="studentId" class="student-group-card">
|
|
|
+ <!-- 学生信息头部 -->
|
|
|
+ <div class="student-group-header">
|
|
|
+ <div class="student-basic-info">
|
|
|
+ <div class="student-avatar">
|
|
|
+ <el-icon class="avatar-icon"><User /></el-icon>
|
|
|
+ </div>
|
|
|
+ <div class="student-info">
|
|
|
+ <div class="student-name-title">{{ studentStays[0].studentName }}</div>
|
|
|
+ <div class="student-meta">
|
|
|
+ <span class="student-no-badge">{{ studentStays[0].studentNo }}</span>
|
|
|
+ <span class="class-info">{{ studentStays[0].className }}</span>
|
|
|
+ <span class="college-info">{{ studentStays[0].collegeName }}</span>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="student-stats">
|
|
|
+ <el-tag type="success" size="small">{{ studentStays.length }} 个时间段</el-tag>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 单人留宿时间段列表 -->
|
|
|
+ <div class="time-periods-list">
|
|
|
+ <div v-for="(stay, index) in studentStays" :key="stay.id || index" class="time-period-item">
|
|
|
+ <div class="period-index">{{ index + 1 }}</div>
|
|
|
+ <div class="period-content">
|
|
|
+ <div class="period-time">
|
|
|
+ <div class="time-range-display">
|
|
|
+ <el-icon class="time-icon"><Clock /></el-icon>
|
|
|
+ <span class="start-time">{{ parseTime(stay.applyStartDate, '{y}-{m}-{d} {h}:{i}') }}</span>
|
|
|
+ <span class="time-arrow">→</span>
|
|
|
+ <span class="end-time">{{ parseTime(stay.applyEndDate, '{y}-{m}-{d} {h}:{i}') }}</span>
|
|
|
+ </div>
|
|
|
+ <!-- <div class="duration-info" v-if="stay.remark">
|
|
|
+ <el-tag type="warning" size="small" effect="plain">
|
|
|
+ {{ extractSinglePersonInfo(stay.remark) }}
|
|
|
+ </el-tag>
|
|
|
+ </div> -->
|
|
|
+ </div>
|
|
|
+ <div class="period-details">
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">宿舍:</span>
|
|
|
+ <el-tag type="success" size="small">{{ stay.roomName }}</el-tag>
|
|
|
+ <span class="bed-info">{{ stay.bedName }}</span>
|
|
|
+ </div>
|
|
|
+ <div class="detail-item">
|
|
|
+ <span class="detail-label">假期:</span>
|
|
|
+ <dict-tag :options="xg_holiday_type" :value="stay.holidayType" />
|
|
|
+ </div>
|
|
|
+ <div class="detail-item" v-if="stay.stayReasonType">
|
|
|
+ <span class="detail-label">原因:</span>
|
|
|
+ <dict-tag :options="xg_stay_reason_type" :value="stay.stayReasonType" />
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class="period-actions">
|
|
|
+ <el-button type="primary" link size="small" @click="handleView(stay)">
|
|
|
+ <el-icon><View /></el-icon>
|
|
|
+ </el-button>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <!-- 空数据提示 -->
|
|
|
+ <div v-if="!singlePersonLoading && singlePersonStayList.length === 0" class="text-center py-8">
|
|
|
+ <el-empty description="暂无单人留宿数据" />
|
|
|
+ </div>
|
|
|
+ </el-card>
|
|
|
+ </el-tab-pane>
|
|
|
+ </el-tabs>
|
|
|
+
|
|
|
<!-- 添加或修改假期留宿管理对话框 -->
|
|
|
<el-dialog :title="dialog.title" v-model="dialog.visible" width="1000px" append-to-body>
|
|
|
<el-form ref="holidayStayFormRef" :model="form" :rules="rules" label-width="120px">
|
|
|
@@ -578,7 +786,7 @@ import { ref, reactive, toRefs, onMounted, getCurrentInstance, ComponentInternal
|
|
|
import ElMessage from 'element-plus/es/components/message/index';
|
|
|
import ElMessageBox from 'element-plus/es/components/message-box/index';
|
|
|
import ElLoading from 'element-plus/es/components/loading/index';
|
|
|
-import { UploadFilled, Document, Loading, Download, Delete } from '@element-plus/icons-vue';
|
|
|
+import { UploadFilled, Document, Loading, Download, Delete, Clock, Phone, User, View } from '@element-plus/icons-vue';
|
|
|
import { getToken } from '@/utils/auth';
|
|
|
import ImagePreview from '@/components/ImagePreview/index.vue';
|
|
|
import request from '@/utils/request';
|
|
|
@@ -594,7 +802,8 @@ import {
|
|
|
getAreaOptions,
|
|
|
getBuildingOptions,
|
|
|
getRoomOptions,
|
|
|
- getBedOptions
|
|
|
+ getBedOptions,
|
|
|
+ getSinglePersonValidStay
|
|
|
} from '@/api/xg/xgss/holidayStay/holidayStay/index-sg';
|
|
|
import {
|
|
|
HolidayStayVO,
|
|
|
@@ -615,6 +824,9 @@ const { xg_holiday_type, xg_stay_reason_type, sys_yesno } = toRefs<any>(proxy?.u
|
|
|
// 用户store - 用于获取当前登录用户信息,实现权限控制
|
|
|
const userStore = useUserStore();
|
|
|
|
|
|
+// Tab页相关状态
|
|
|
+const activeTab = ref('all');
|
|
|
+
|
|
|
const holidayStayList = ref<HolidayStayVO[]>([]);
|
|
|
const buttonLoading = ref(false);
|
|
|
const loading = ref(true);
|
|
|
@@ -624,6 +836,12 @@ const single = ref(true);
|
|
|
const multiple = ref(true);
|
|
|
const total = ref(0);
|
|
|
|
|
|
+// 单人留宿数据相关状态
|
|
|
+const singlePersonStayList = ref<HolidayStayVO[]>([]);
|
|
|
+const singlePersonLoading = ref(false);
|
|
|
+const singlePersonDisplayMode = ref('list'); // 'list' | 'group'
|
|
|
+const groupedSinglePersonStayList = ref<Record<string, HolidayStayVO[]>>({});
|
|
|
+
|
|
|
// 学生选择器相关状态
|
|
|
const studentOptions = ref<StudentOptionVO[]>([]);
|
|
|
const studentLoading = ref(false);
|
|
|
@@ -847,16 +1065,16 @@ const getList = async () => {
|
|
|
const currentTime = new Date().toISOString().slice(0, 19).replace('T', ' ');
|
|
|
|
|
|
if (requestParams.status === 'valid') {
|
|
|
- // 查询有效状态:当前时间在留宿开始时间和结束时间之间
|
|
|
- // 条件:currentTime >= applyStartDate AND currentTime <= applyEndDate
|
|
|
+ // 查询有效状态:当前时间还没有超过留宿结束时间(包括预约状态)
|
|
|
+ // 条件:currentTime <= applyEndDate
|
|
|
requestParams.params = {
|
|
|
...requestParams.params,
|
|
|
statusType: 'valid',
|
|
|
currentTime: currentTime
|
|
|
};
|
|
|
} else if (requestParams.status === 'invalid') {
|
|
|
- // 查询失效状态:当前时间不在留宿时间范围内
|
|
|
- // 条件:currentTime < applyStartDate OR currentTime > applyEndDate
|
|
|
+ // 查询失效状态:当前时间已超过留宿结束时间
|
|
|
+ // 条件:currentTime > applyEndDate
|
|
|
requestParams.params = {
|
|
|
...requestParams.params,
|
|
|
statusType: 'invalid',
|
|
|
@@ -935,6 +1153,44 @@ const handleUpdate = async (row?: HolidayStayVO) => {
|
|
|
const res = await getHolidayStay(_id);
|
|
|
Object.assign(form.value, res.data);
|
|
|
|
|
|
+ // 确保当前学生信息在选项中(修复学生选择器显示问题)
|
|
|
+ if (form.value.studentId && form.value.studentName && form.value.studentNo) {
|
|
|
+ const currentStudent: StudentOptionVO = {
|
|
|
+ id: form.value.studentId,
|
|
|
+ name: form.value.studentName,
|
|
|
+ studentNo: form.value.studentNo,
|
|
|
+ gender: '0', // 默认值,实际不影响显示
|
|
|
+ phone: form.value.studentPhone,
|
|
|
+ collegeId: form.value.collegeId,
|
|
|
+ collegeName: form.value.collegeName,
|
|
|
+ majorId: form.value.majorId,
|
|
|
+ majorName: form.value.majorName,
|
|
|
+ classId: form.value.classId,
|
|
|
+ className: form.value.className,
|
|
|
+ dormInfo: {
|
|
|
+ campusId: form.value.campusId,
|
|
|
+ campusName: form.value.campusName,
|
|
|
+ areaId: form.value.areaId,
|
|
|
+ areaName: form.value.areaName,
|
|
|
+ buildingId: form.value.buildingId,
|
|
|
+ buildingName: form.value.buildingName,
|
|
|
+ roomId: form.value.roomId,
|
|
|
+ roomName: form.value.roomName,
|
|
|
+ bedId: form.value.bedId,
|
|
|
+ bedName: form.value.bedName
|
|
|
+ }
|
|
|
+ };
|
|
|
+
|
|
|
+ // 检查是否已经存在,避免重复添加
|
|
|
+ const existingStudent = studentOptions.value.find((s) => s.id === form.value.studentId);
|
|
|
+ if (!existingStudent) {
|
|
|
+ studentOptions.value = [currentStudent];
|
|
|
+ } else {
|
|
|
+ // 更新现有学生信息
|
|
|
+ Object.assign(existingStudent, currentStudent);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// 根据已有数据级联加载宿舍选项
|
|
|
await loadCascadeOptionsForUpdate(form.value);
|
|
|
|
|
|
@@ -1001,7 +1257,7 @@ const remoteSearchStudents = async (query: string) => {
|
|
|
const res = await getStudentOptions(params);
|
|
|
// 根据实际接口返回格式调整数据获取方式
|
|
|
console.log('学生搜索响应:', res);
|
|
|
- studentOptions.value = res.rows || [];
|
|
|
+ studentOptions.value = (res as any).rows || [];
|
|
|
|
|
|
// 调试:检查第一个学生的数据
|
|
|
if (studentOptions.value.length > 0) {
|
|
|
@@ -1044,7 +1300,7 @@ const loadInitialStudents = async () => {
|
|
|
const res = await getStudentOptions(params);
|
|
|
// 根据实际接口返回格式调整数据获取方式
|
|
|
console.log('初始学生数据响应:', res);
|
|
|
- studentOptions.value = res.rows || [];
|
|
|
+ studentOptions.value = (res as any).rows || [];
|
|
|
|
|
|
// 调试:检查第一个学生的数据
|
|
|
if (studentOptions.value.length > 0) {
|
|
|
@@ -1395,38 +1651,28 @@ const loadCascadeOptionsForUpdate = async (formData: HolidayStayForm) => {
|
|
|
|
|
|
/** 获取状态文本 */
|
|
|
const getStatusText = (row: HolidayStayVO) => {
|
|
|
- if (!row.applyStartDate || !row.applyEndDate) {
|
|
|
+ if (!row.applyEndDate) {
|
|
|
return '未知';
|
|
|
}
|
|
|
|
|
|
const currentTime = new Date();
|
|
|
- const startTime = new Date(row.applyStartDate);
|
|
|
const endTime = new Date(row.applyEndDate);
|
|
|
|
|
|
- // 有效条件:当前时间在留宿开始时间和结束时间之间(包含边界)
|
|
|
- if (currentTime >= startTime && currentTime <= endTime) {
|
|
|
- return '有效';
|
|
|
- } else {
|
|
|
- return '失效';
|
|
|
- }
|
|
|
+ // 有效条件:当前时间还没有超过留宿结束时间(包括预约状态)
|
|
|
+ return currentTime > endTime ? '失效' : '有效';
|
|
|
};
|
|
|
|
|
|
/** 获取状态标签类型 */
|
|
|
const getStatusType = (row: HolidayStayVO) => {
|
|
|
- if (!row.applyStartDate || !row.applyEndDate) {
|
|
|
+ if (!row.applyEndDate) {
|
|
|
return 'info';
|
|
|
}
|
|
|
|
|
|
const currentTime = new Date();
|
|
|
- const startTime = new Date(row.applyStartDate);
|
|
|
const endTime = new Date(row.applyEndDate);
|
|
|
|
|
|
- // 有效条件:当前时间在留宿开始时间和结束时间之间(包含边界)
|
|
|
- if (currentTime >= startTime && currentTime <= endTime) {
|
|
|
- return 'success'; // 有效 - 绿色
|
|
|
- } else {
|
|
|
- return 'danger'; // 失效 - 红色
|
|
|
- }
|
|
|
+ // 有效条件:当前时间还没有超过留宿结束时间(包括预约状态)
|
|
|
+ return currentTime > endTime ? 'danger' : 'success';
|
|
|
};
|
|
|
|
|
|
// 文件类型判断
|
|
|
@@ -1634,6 +1880,7 @@ const handleAttachmentData = async () => {
|
|
|
|
|
|
// 将逗号分隔的字符串转换为数组
|
|
|
const ossIdsArray = form.value.attachmentOssIds
|
|
|
+ .toString()
|
|
|
.split(',')
|
|
|
.map((id) => id.trim())
|
|
|
.filter((id) => id);
|
|
|
@@ -1681,10 +1928,127 @@ const handleAttachmentData = async () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+/** Tab页切换处理 */
|
|
|
+const handleTabClick = (tab: any) => {
|
|
|
+ console.log('切换到tab:', tab.name);
|
|
|
+ if (tab.name === 'singlePerson') {
|
|
|
+ // 切换到单人留宿tab时,强制刷新数据
|
|
|
+ console.log('切换到单人留宿tab,开始加载数据...');
|
|
|
+ getSinglePersonValidStayData();
|
|
|
+ } else if (tab.name === 'all') {
|
|
|
+ // 切换到全部数据tab时,刷新列表
|
|
|
+ getList();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/** 获取单人留宿有效数据 */
|
|
|
+const getSinglePersonValidStayData = async () => {
|
|
|
+ singlePersonLoading.value = true;
|
|
|
+ try {
|
|
|
+ console.log('开始获取单人留宿有效数据...');
|
|
|
+ const res = await getSinglePersonValidStay();
|
|
|
+ console.log('单人留宿数据响应:', res);
|
|
|
+
|
|
|
+ if (res.code === 200) {
|
|
|
+ singlePersonStayList.value = res.data || [];
|
|
|
+ console.log('获取到单人留宿数据:', singlePersonStayList.value.length, '条');
|
|
|
+
|
|
|
+ // 按学生分组数据
|
|
|
+ groupStudentStayData();
|
|
|
+ } else {
|
|
|
+ console.error('获取单人留宿数据失败:', res.msg);
|
|
|
+ ElMessage.error(res.msg || '获取单人留宿数据失败');
|
|
|
+ singlePersonStayList.value = [];
|
|
|
+ groupedSinglePersonStayList.value = {};
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取单人留宿数据出错:', error);
|
|
|
+ ElMessage.error('获取单人留宿数据失败,请稍后重试');
|
|
|
+ singlePersonStayList.value = [];
|
|
|
+ } finally {
|
|
|
+ singlePersonLoading.value = false;
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/** 查看详情 */
|
|
|
+const handleView = (row: HolidayStayVO) => {
|
|
|
+ // 复用编辑对话框进行查看,但设置为只读模式
|
|
|
+ handleUpdate(row);
|
|
|
+ // 可以在这里添加只读逻辑
|
|
|
+ console.log('查看留宿详情:', row);
|
|
|
+};
|
|
|
+
|
|
|
+/** 提取单人留宿信息 */
|
|
|
+const extractSinglePersonInfo = (remark: string) => {
|
|
|
+ if (!remark) return '';
|
|
|
+
|
|
|
+ // 查找单人留宿相关信息
|
|
|
+ const singlePersonRegex = /单人留宿(\d+天)/;
|
|
|
+ const match = remark.match(singlePersonRegex);
|
|
|
+
|
|
|
+ if (match) {
|
|
|
+ return match[1]; // 返回天数信息,如 "3天"
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有找到标准格式,尝试查找其他格式
|
|
|
+ if (remark.includes('单人留宿')) {
|
|
|
+ const parts = remark.split('单人留宿');
|
|
|
+ if (parts.length > 1) {
|
|
|
+ const info = parts[1].trim();
|
|
|
+ // 提取括号内的信息
|
|
|
+ const bracketMatch = info.match(/\(([^)]+)\)/);
|
|
|
+ if (bracketMatch) {
|
|
|
+ return bracketMatch[1];
|
|
|
+ }
|
|
|
+ // 提取前20个字符
|
|
|
+ return info.length > 20 ? info.substring(0, 20) + '...' : info;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return '';
|
|
|
+};
|
|
|
+
|
|
|
+/** 按学生分组数据 */
|
|
|
+const groupStudentStayData = () => {
|
|
|
+ const grouped: Record<string, HolidayStayVO[]> = {};
|
|
|
+
|
|
|
+ singlePersonStayList.value.forEach((stay) => {
|
|
|
+ const studentId = stay.studentId?.toString() || 'unknown';
|
|
|
+ if (!grouped[studentId]) {
|
|
|
+ grouped[studentId] = [];
|
|
|
+ }
|
|
|
+ grouped[studentId].push(stay);
|
|
|
+ });
|
|
|
+
|
|
|
+ // 按时间排序每个学生的留宿记录
|
|
|
+ Object.keys(grouped).forEach((studentId) => {
|
|
|
+ grouped[studentId].sort((a, b) => {
|
|
|
+ if (!a.applyStartDate || !b.applyStartDate) return 0;
|
|
|
+ return new Date(a.applyStartDate).getTime() - new Date(b.applyStartDate).getTime();
|
|
|
+ });
|
|
|
+ });
|
|
|
+
|
|
|
+ groupedSinglePersonStayList.value = grouped;
|
|
|
+ console.log('按学生分组完成:', Object.keys(grouped).length, '名学生');
|
|
|
+};
|
|
|
+
|
|
|
+/** 显示模式切换处理 */
|
|
|
+const handleDisplayModeChange = (mode: string) => {
|
|
|
+ console.log('切换显示模式:', mode);
|
|
|
+ singlePersonDisplayMode.value = mode;
|
|
|
+
|
|
|
+ // 如果是分组模式且还没有分组数据,重新分组
|
|
|
+ if (mode === 'group' && Object.keys(groupedSinglePersonStayList.value).length === 0) {
|
|
|
+ groupStudentStayData();
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
onMounted(() => {
|
|
|
getList();
|
|
|
// 加载校区选项
|
|
|
loadCampusOptions();
|
|
|
+ // 预加载单人留宿数据,确保第一次点击tab时有数据
|
|
|
+ getSinglePersonValidStayData();
|
|
|
});
|
|
|
</script>
|
|
|
|
|
|
@@ -1955,4 +2319,399 @@ onMounted(() => {
|
|
|
cursor: not-allowed;
|
|
|
background: #f5f5f5;
|
|
|
}
|
|
|
+
|
|
|
+/* 新增tab页相关样式 */
|
|
|
+.el-tabs {
|
|
|
+ background: #fff;
|
|
|
+ border-radius: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 确保单人留宿数据表格占满宽度 */
|
|
|
+.el-tab-pane .el-card {
|
|
|
+ width: 100%;
|
|
|
+}
|
|
|
+
|
|
|
+.el-tab-pane .el-card .el-table {
|
|
|
+ width: 100% !important;
|
|
|
+ table-layout: fixed !important;
|
|
|
+}
|
|
|
+
|
|
|
+/* 单人留宿数据tab页的表格容器 */
|
|
|
+.el-tab-pane[aria-labelledby='tab-singlePerson'] .el-card__body {
|
|
|
+ padding: 20px;
|
|
|
+ overflow-x: auto;
|
|
|
+}
|
|
|
+
|
|
|
+.el-tab-pane[aria-labelledby='tab-singlePerson'] .el-table {
|
|
|
+ min-width: 1000px;
|
|
|
+ width: 100% !important;
|
|
|
+}
|
|
|
+
|
|
|
+.el-tabs__header {
|
|
|
+ margin: 0 0 20px 0;
|
|
|
+ padding: 0 20px;
|
|
|
+ background: #f8f9fa;
|
|
|
+ border-radius: 6px 6px 0 0;
|
|
|
+}
|
|
|
+
|
|
|
+.el-tabs__nav-wrap {
|
|
|
+ padding: 10px 0;
|
|
|
+}
|
|
|
+
|
|
|
+:deep(.el-tabs__nav-scroll) {
|
|
|
+ margin-left: 50px !important;
|
|
|
+}
|
|
|
+
|
|
|
+.el-tab-pane {
|
|
|
+ min-height: 400px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 单人留宿数据表格样式优化 */
|
|
|
+.student-info-cell {
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.student-name {
|
|
|
+ font-weight: 600;
|
|
|
+ color: #303133;
|
|
|
+ font-size: 14px;
|
|
|
+ margin-bottom: 2px;
|
|
|
+}
|
|
|
+
|
|
|
+.student-details {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ font-size: 12px;
|
|
|
+ color: #606266;
|
|
|
+}
|
|
|
+
|
|
|
+.student-no {
|
|
|
+ background: #f0f9ff;
|
|
|
+ color: #0369a1;
|
|
|
+ padding: 1px 6px;
|
|
|
+ border-radius: 3px;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.divider {
|
|
|
+ color: #d1d5db;
|
|
|
+}
|
|
|
+
|
|
|
+.class-name {
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.dorm-info-cell {
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.room-info {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.bed-name {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6b7280;
|
|
|
+ background: #f9fafb;
|
|
|
+ padding: 2px 6px;
|
|
|
+ border-radius: 3px;
|
|
|
+}
|
|
|
+
|
|
|
+.building-info {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #9ca3af;
|
|
|
+}
|
|
|
+
|
|
|
+.time-period-cell {
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.time-period-main {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.time-icon {
|
|
|
+ color: #059669;
|
|
|
+ font-size: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.time-range {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+.start-time,
|
|
|
+.end-time {
|
|
|
+ color: #374151;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.time-arrow {
|
|
|
+ color: #9ca3af;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.time-duration {
|
|
|
+ margin-left: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.reason-cell {
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.reason-text {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6b7280;
|
|
|
+ margin-top: 4px;
|
|
|
+ line-height: 1.3;
|
|
|
+}
|
|
|
+
|
|
|
+.contact-cell {
|
|
|
+ text-align: left;
|
|
|
+}
|
|
|
+
|
|
|
+.phone-info {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 4px;
|
|
|
+ margin-bottom: 4px;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+.phone-icon {
|
|
|
+ color: #3b82f6;
|
|
|
+ font-size: 14px;
|
|
|
+}
|
|
|
+
|
|
|
+.emergency-phone {
|
|
|
+ font-size: 12px;
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.emergency-label {
|
|
|
+ color: #dc2626;
|
|
|
+ font-weight: 500;
|
|
|
+ margin-right: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 学生分组视图样式 */
|
|
|
+.student-group-card {
|
|
|
+ background: #fff;
|
|
|
+ border: 1px solid #e5e7eb;
|
|
|
+ border-radius: 8px;
|
|
|
+ margin-bottom: 16px;
|
|
|
+ overflow: hidden;
|
|
|
+ transition: all 0.3s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.student-group-card:hover {
|
|
|
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
|
|
+ border-color: #d1d5db;
|
|
|
+}
|
|
|
+
|
|
|
+.student-group-header {
|
|
|
+ background: linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%);
|
|
|
+ padding: 16px 20px;
|
|
|
+ border-bottom: 1px solid #e5e7eb;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: space-between;
|
|
|
+}
|
|
|
+
|
|
|
+.student-basic-info {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.student-avatar {
|
|
|
+ width: 40px;
|
|
|
+ height: 40px;
|
|
|
+ background: linear-gradient(135deg, #3b82f6, #1d4ed8);
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ color: white;
|
|
|
+}
|
|
|
+
|
|
|
+.avatar-icon {
|
|
|
+ font-size: 20px;
|
|
|
+}
|
|
|
+
|
|
|
+.student-info {
|
|
|
+ flex: 1;
|
|
|
+}
|
|
|
+
|
|
|
+.student-name-title {
|
|
|
+ font-size: 16px;
|
|
|
+ font-weight: 600;
|
|
|
+ color: #111827;
|
|
|
+ margin-bottom: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.student-meta {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ font-size: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.student-no-badge {
|
|
|
+ background: #dbeafe;
|
|
|
+ color: #1e40af;
|
|
|
+ padding: 2px 8px;
|
|
|
+ border-radius: 12px;
|
|
|
+ font-weight: 500;
|
|
|
+}
|
|
|
+
|
|
|
+.class-info {
|
|
|
+ color: #6b7280;
|
|
|
+}
|
|
|
+
|
|
|
+.college-info {
|
|
|
+ color: #9ca3af;
|
|
|
+}
|
|
|
+
|
|
|
+.student-stats {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+}
|
|
|
+
|
|
|
+.time-periods-list {
|
|
|
+ padding: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.time-period-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 16px;
|
|
|
+ padding: 16px 20px;
|
|
|
+ border-bottom: 1px solid #f3f4f6;
|
|
|
+ transition: all 0.2s ease;
|
|
|
+}
|
|
|
+
|
|
|
+.time-period-item:last-child {
|
|
|
+ border-bottom: none;
|
|
|
+}
|
|
|
+
|
|
|
+.time-period-item:hover {
|
|
|
+ background: #f9fafb;
|
|
|
+}
|
|
|
+
|
|
|
+.period-index {
|
|
|
+ width: 24px;
|
|
|
+ height: 24px;
|
|
|
+ background: #e5e7eb;
|
|
|
+ color: #6b7280;
|
|
|
+ border-radius: 50%;
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ justify-content: center;
|
|
|
+ font-size: 12px;
|
|
|
+ font-weight: 600;
|
|
|
+ flex-shrink: 0;
|
|
|
+ margin-top: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.period-content {
|
|
|
+ flex: 1;
|
|
|
+ min-width: 0;
|
|
|
+}
|
|
|
+
|
|
|
+.period-time {
|
|
|
+ margin-bottom: 12px;
|
|
|
+}
|
|
|
+
|
|
|
+.time-range-display {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 8px;
|
|
|
+ margin-bottom: 6px;
|
|
|
+}
|
|
|
+
|
|
|
+.time-range-display .time-icon {
|
|
|
+ color: #059669;
|
|
|
+ font-size: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.time-range-display .start-time,
|
|
|
+.time-range-display .end-time {
|
|
|
+ font-size: 14px;
|
|
|
+ font-weight: 500;
|
|
|
+ color: #374151;
|
|
|
+}
|
|
|
+
|
|
|
+.time-range-display .time-arrow {
|
|
|
+ color: #9ca3af;
|
|
|
+ font-weight: bold;
|
|
|
+}
|
|
|
+
|
|
|
+.duration-info {
|
|
|
+ margin-left: 24px;
|
|
|
+}
|
|
|
+
|
|
|
+.period-details {
|
|
|
+ display: flex;
|
|
|
+ flex-wrap: wrap;
|
|
|
+ gap: 16px;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-item {
|
|
|
+ display: flex;
|
|
|
+ align-items: center;
|
|
|
+ gap: 6px;
|
|
|
+ font-size: 13px;
|
|
|
+}
|
|
|
+
|
|
|
+.detail-label {
|
|
|
+ color: #6b7280;
|
|
|
+ font-weight: 500;
|
|
|
+ min-width: 40px;
|
|
|
+}
|
|
|
+
|
|
|
+.bed-info {
|
|
|
+ color: #9ca3af;
|
|
|
+ font-size: 12px;
|
|
|
+ margin-left: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+.period-actions {
|
|
|
+ display: flex;
|
|
|
+ align-items: flex-start;
|
|
|
+ padding-top: 4px;
|
|
|
+}
|
|
|
+
|
|
|
+/* 响应式设计 */
|
|
|
+@media (max-width: 768px) {
|
|
|
+ .student-group-header {
|
|
|
+ flex-direction: column;
|
|
|
+ align-items: flex-start;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .time-period-item {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 12px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .period-details {
|
|
|
+ flex-direction: column;
|
|
|
+ gap: 8px;
|
|
|
+ }
|
|
|
+
|
|
|
+ .period-actions {
|
|
|
+ align-self: flex-end;
|
|
|
+ }
|
|
|
+}
|
|
|
</style>
|