Files
zhxg_app/pages/dormitory/studentDormInfo/index.vue
2025-10-25 03:20:44 +08:00

609 lines
14 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="page-container">
<!-- 搜索区 -->
<view class="fixed-search-wrap">
<!-- 搜索区内容 -->
<view class="search-content">
<view class="search-card">
<!-- 搜索输入框 -->
<view class="search-row">
<uni-icons type="search" size="28rpx" color="#1890FF" class="search-icon"></uni-icons>
<input v-model="queryParams.stuName" placeholder="输入学生姓名搜索" @input="handleSearchInput"
class="search-input" />
</view>
<!-- 筛选行 -->
<view class="filter-row">
<!-- 年级筛选 -->
<view class="filter-item">
<text class="filter-label">年级</text>
<picker mode="selector" :range="gradeList" :range-key="'gradeName'" :value="gradeIndex"
@change="handleGradeChange" class="filter-picker">
<view class="picker-content">
<!-- 添加空值判断 -->
<text
class="picker-text">{{ gradeList[gradeIndex] ? gradeList[gradeIndex].gradeName : '全部' }}</text>
<uni-icons type="bottom" size="20rpx" color="#999"></uni-icons>
</view>
</picker>
</view>
<!-- 宿舍区筛选 -->
<view class="filter-item">
<text class="filter-label">宿舍区</text>
<picker mode="selector" :range="dormAreaList" :range-key="'name'" :value="dormAreaIndex"
@change="handleDormAreaChange" class="filter-picker">
<view class="picker-content">
<text
class="picker-text">{{ dormAreaList[dormAreaIndex] ? dormAreaList[dormAreaIndex].name : '全部' }}</text>
<uni-icons type="bottom" size="20rpx" color="#999"></uni-icons>
</view>
</picker>
</view>
<!-- 园区筛选 -->
<view class="filter-item">
<text class="filter-label">园区</text>
<picker mode="selector" :range="parkList" :range-key="'name'" :value="parkIndex" @change="handleParkChange"
class="filter-picker">
<view class="picker-content">
<text class="picker-text">{{ parkList[parkIndex] ? parkList[parkIndex].name : '全部' }}</text>
<uni-icons type="bottom" size="20rpx" color="#999"></uni-icons>
</view>
</picker>
</view>
<!-- 宿舍号筛选 -->
<!-- <view class="filter-item">
<text class="filter-label">宿舍号</text>
<input v-model="queryParams.roomNo" placeholder="输入" @input="handleSearchInput"
class="filter-input" />
</view> -->
</view>
<!-- 操作按钮 -->
<view class="action-row">
<button class="reset-btn" @click="resetFilter">
<uni-icons type="clear" size="24rpx" color="#1890FF" class="btn-icon"></uni-icons>
重置
</button>
<button class="search-btn" @click="handleSearch">
<uni-icons type="search" size="24rpx" color="#fff" class="btn-icon"></uni-icons>
搜索
</button>
</view>
</view>
</view>
</view>
<!-- 添加信息收集按钮 -->
<view class="add" @click="addDormInfo">+</view>
<!-- 滚动容器 -->
<scroll-view class="scroll-container" scroll-y @scrolltolower="loadNextPage" @refresherrefresh="handleRefresh">
<!-- 入住收集列表 -->
<view class="dorm-list">
<view class="dorm-item" v-for="(item, index) in dormInfo"
:key="`${item.id}_${Math.ceil((index + 1) / queryParams.pageSize)}`" @click="handleItemClick(item)">
<view class="item-header">
<view class="name">姓名: {{ item.stuName }}</view>
<view class="tag" :class="item.isDormitoryHead === 1 ? 'head-tag' : ''">
{{ item.isDormitoryHead === 1 ? '宿舍长' : '' }}
</view>
</view>
<view class="item-content">
<view>学号: {{ item.stuNo }}</view>
<view>班级: {{ item.className }}</view>
<view>院系: {{ item.deptName }}</view>
<view>宿舍: {{ item.parkName }} {{ item.buildingName }}
{{ item.floorName }}{{ item.roomNo }}
</view>
<view>入住时间: {{ item.checkinTime }}</view>
<view>状态: {{ item.inStatus === '1' ? '已入住' : '未入住' }}</view>
</view>
</view>
</view>
<!-- 加载状态 -->
<view v-if="isLoading" class="loading">加载中...</view>
<view v-if="!hasMore && dormInfo.length > 0" class="no-more">已加载全部数据</view>
<view v-if="dormInfo.length === 0 && !isLoading" class="no-data">暂无数据</view>
</scroll-view>
</view>
</template>
<script>
import {
listAllCampus,
listParkByCampus,
listStudent
} from "@/api/dms/studentDormInfo/index.js"
import {
getUserProfile
} from '@/api/system/user'
import {
listGrade
} from "@/api/dms/studentDormInfo/index.js"
export default {
data() {
return {
queryParams: {
pageNum: 1,
pageSize: 5, // 每页5条便于测试分页
dormitoryId: null,
bedId: null,
stuNo: null,
isDormitoryHead: null,
status: null,
checkinTime: null,
inStatus: null,
stuName: null,
gradeId: null,
campusId: null,
parkId:null,
dormArea: null, // 宿舍区筛选参数
parkName: null // 园区筛选参数
},
dormInfo: [],
isLoading: false,
hasMore: true,
// 筛选下拉选项数据
gradeList: [],
dormAreaList: [],
parkList: [],
// 筛选选中索引
gradeIndex: 0,
dormAreaIndex: 0,
parkIndex: 0,
searchTimer: null,
roleGroup: null
}
},
onShow() {
// 每次显示页面时重置数据并重新加载
this.resetData()
this.getUserProfile()
this.listGrade()
this.listAllCampus()
},
methods: {
// 重置分页数据(用于刷新或重新加载)
resetData() {
this.queryParams.pageNum = 1
this.dormInfo = []
this.hasMore = true
},
// 查询列表数据
async getList() {
if (this.isLoading || !this.hasMore) {
// 若无需加载,直接停止刷新
uni.stopPullDownRefresh()
return
}
this.isLoading = true
try {
const res = await listStudent(this.queryParams)
if (res.code === 200) {
const newData = res.rows || []
this.dormInfo = this.queryParams.pageNum === 1 ? newData : [...this.dormInfo, ...newData]
this.hasMore = newData.length === this.queryParams.pageSize
} else {
uni.showToast({
title: '加载失败',
icon: 'none'
})
}
} catch (err) {
console.error('加载数据出错:', err)
uni.showToast({
title: '网络错误',
icon: 'none'
})
} finally {
this.isLoading = false
// 最终兜底:无论如何都停止刷新
uni.stopPullDownRefresh()
}
},
// 加载下一页
loadNextPage() {
console.log('触发加载下一页')
if (this.hasMore) {
this.queryParams.pageNum++
this.getList()
}
},
// 下拉刷新
handleRefresh() {
console.log('触发下拉刷新')
this.resetData()
// 直接先停止一次(防止初始动画卡住)
uni.stopPullDownRefresh()
// 执行数据加载,加载完成后再次停止
this.getList().then(() => {
// 延迟300ms确保动画正常结束兼容快速加载场景
setTimeout(() => {
uni.stopPullDownRefresh()
}, 300)
}).catch(() => {
setTimeout(() => {
uni.stopPullDownRefresh()
}, 300)
})
},
// 获取用户信息
async getUserProfile() {
try {
const res = await getUserProfile()
console.log('当前用户信息:', res)
this.roleGroup = res.roleGroup
if (res.roleGroup === "学生") {
this.queryParams.stuName = res.data.nickName
}
this.getList()
} catch (err) {
console.error('获取用户信息失败:', err)
}
},
// 跳转添加页面
addDormInfo() {
if (this.roleGroup === "学生") {
if (this.dormInfo.length > 0) {
uni.showToast({
title: '请勿重复提交',
icon: 'none'
});
return
}
}
uni.navigateTo({
url: `/pages/dormitory/studentDormInfo/informationCollection`
})
},
// 点击列表项
handleItemClick(item) {
console.log('点击学生信息:', item)
// 可添加跳转详情页逻辑
// uni.navigateTo({ url: `/pages/detail?id=${item.id}` })
},
// 获取年级
async listGrade() {
let res = await listGrade()
this.gradeList = [{
gradeName: '全部'
}, ...res.rows]
},
// 获取校区数据
async listAllCampus() {
let res = await listAllCampus()
if (res.code == 200) {
this.dormAreaList = [{
name: '全部'
}, ...res.data]
}
},
// 搜索输入防抖
handleSearchInput() {
clearTimeout(this.searchTimer)
this.searchTimer = setTimeout(() => {
this.resetData()
this.getList()
}, 1000)
},
// 年级筛选
handleGradeChange(e) {
this.gradeIndex = e.detail.value
const selectedGrade = this.gradeList[this.gradeIndex]
// 修复“全部”选项无gradeId设为null
this.queryParams.gradeId = selectedGrade.gradeId || null
this.resetData()
this.getList()
},
// 宿舍区筛选
async handleDormAreaChange(e) {
this.dormAreaIndex = e.detail.value
const selectedArea = this.dormAreaList[this.dormAreaIndex]
this.queryParams.campusId = selectedArea.id || null
this.resetData()
this.getList()
if (this.queryParams.campusId) {
// 根据校区id获取园区
let res = await listParkByCampus(this.queryParams.campusId)
if (res.code == 200) {
this.parkList = [{
name: '全部'
}, ...res.data]
}
}
},
// 园区筛选
handleParkChange(e) {
this.parkIndex = e.detail.value
const selectedPark = this.parkList[this.parkIndex]
this.queryParams.parkId = selectedPark.id || null
this.resetData()
this.getList()
},
// 重置筛选
resetFilter() {
this.gradeIndex = 0
this.dormAreaIndex = 0
this.parkIndex = 0
// 重置筛选参数,保留分页基础参数
this.queryParams = {
...this.queryParams,
gradeId: null,
dormArea: null,
parkName: null,
roomNo: null,
stuName: null
}
this.resetData()
this.getList()
},
// 手动搜索
handleSearch() {
this.resetData()
this.getList()
},
},
destroyed() {
clearTimeout(this.searchTimer)
}
}
</script>
<style scoped lang="scss">
.page-container {
position: relative;
height: 100vh;
padding-top: 44px;
/* 预留原生title高度 */
background-color: #f5f7fa;
box-sizing: border-box;
}
/* 固定搜索区:收缩时无空白 */
.fixed-search-wrap {
position: fixed;
top: 44px;
left: 0;
right: 0;
z-index: 90;
background-color: #fff;
/* 背景改为白色,与页面融合 */
border-bottom: 1px solid #eee;
padding: 14px;
}
/* 触发器:样式简洁,点击区域大 */
.search-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20rpx;
cursor: pointer;
border-bottom: 1px solid #f5f5f5;
}
.search-header-title {
font-size: 28rpx;
font-weight: 500;
color: #333;
}
/* 搜索内容*/
.search-content {
transition: all 0.3s ease;
/* 动画过渡更自然 */
}
.search-card {
padding: 20rpx;
box-shadow: none;
/* 收缩时去掉阴影,更简洁 */
}
/* 滚动容器:调整顶部内边距,适应搜索区高度变化 */
.scroll-container {
height: 100%;
width: 100%;
padding-top: 290rpx;
padding-bottom: 120rpx;
box-sizing: border-box;
}
/* 以下为原有样式,无需修改 */
.add {
position: fixed;
bottom: 50rpx;
right: 50rpx;
width: 90rpx;
height: 90rpx;
border-radius: 50%;
background-color: #1890FF;
color: white;
display: flex;
justify-content: center;
align-items: center;
font-size: 60rpx;
box-shadow: 0 4rpx 12rpx rgba(24, 144, 255, 0.3);
z-index: 99;
}
.dorm-list {
padding: 20rpx;
}
.dorm-item {
background-color: #fff;
border-radius: 16rpx;
padding: 28rpx;
margin-bottom: 20rpx;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.05);
transition: transform 0.2s;
}
.dorm-item:active {
transform: scale(0.99);
}
.item-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20rpx;
padding-bottom: 16rpx;
border-bottom: 1px solid #f0f0f0;
}
.name {
font-size: 34rpx;
font-weight: 600;
color: #333;
}
.tag {
font-size: 24rpx;
padding: 4rpx 16rpx;
border-radius: 20rpx;
color: #fff;
}
.head-tag {
background-color: #28a745;
}
.item-content {
font-size: 28rpx;
color: #666;
line-height: 1.8;
}
.item-content view {
margin-bottom: 14rpx;
}
.item-content view:last-child {
margin-bottom: 0;
}
.loading,
.no-more,
.no-data {
text-align: center;
padding: 30rpx 0;
color: #999;
font-size: 28rpx;
}
.no-data {
padding: 100rpx 0;
}
.search-row {
display: flex;
align-items: center;
background-color: #f5f7fa;
border-radius: 60rpx;
padding: 14rpx 20rpx;
margin-bottom: 24rpx;
}
.search-icon {
margin-right: 12rpx;
}
.search-input {
flex: 1;
font-size: 28rpx;
color: #333;
}
.filter-row {
display: flex;
flex-wrap: wrap;
gap: 16rpx;
margin-bottom: 24rpx;
}
.filter-item {
flex: 1;
min-width: 120rpx;
display: flex;
flex-direction: column;
gap: 8rpx;
}
.filter-label {
font-size: 24rpx;
color: #666;
padding-left: 4rpx;
}
.picker-content,
.filter-input {
display: flex;
align-items: center;
font-size: 26rpx;
color: #333;
padding: 14rpx 16rpx;
background-color: #f5f7fa;
border-radius: 12rpx;
height: 60rpx;
box-sizing: border-box;
}
.filter-input::placeholder {
color: #999;
}
.action-row {
display: flex;
gap: 16rpx;
}
.reset-btn,
.search-btn {
flex: 1;
height: 72rpx;
line-height: 72rpx;
border-radius: 36rpx;
font-size: 28rpx;
display: flex;
justify-content: center;
align-items: center;
}
.reset-btn {
background-color: #fff;
color: #1890FF;
border: 1px solid #1890FF;
}
.search-btn {
background-color: #1890FF;
color: #fff;
border: none;
}
.btn-icon {
margin-right: 6rpx;
}
</style>