Files
zhxg_app/pages/dormitory/outsideAccommodation/index.vue
2025-12-16 11:53:21 +08:00

920 lines
22 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="app-container">
<!-- 搜索区仅非学生角色显示 -->
<view class="fixed-search-wrap" v-if="roleGroup != '学生'">
<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.studentName" placeholder="输入学生姓名搜索" @input="handleQuery"
class="search-input" />
</view>
<!-- 筛选行学号/学院/班级 -->
<view class="filter-row">
<view class="filter-item">
<text class="filter-label">学号</text>
<input class="picker-content" v-model="queryParams.studentNo" type="text"
placeholder="请输入" />
</view>
<view class="filter-item">
<text class="filter-label">学院</text>
<input class="picker-content" v-model="queryParams.deptName" type="text"
placeholder="请输入" />
</view>
<view class="filter-item">
<text class="filter-label">班级</text>
<input class="picker-content" v-model="queryParams.className" type="text"
placeholder="请输入" />
</view>
</view>
<!-- 操作按钮 -->
<view class="action-row">
<button class="reset-btn" @click="resetQuery">
<uni-icons type="clear" size="24rpx" color="#1890FF" class="btn-icon"></uni-icons>
重置
</button>
<button class="search-btn" @click="handleQuery">
<uni-icons type="search" size="24rpx" color="#fff" class="btn-icon"></uni-icons>
搜索
</button>
</view>
</view>
</view>
</view>
<!-- 数据列表滚动容器 -->
<scroll-view class="list-container" scroll-y @scrolltolower="handleLoadMore" lower-threshold="50" enhanced
:show-scrollbar="false">
<!-- 初始加载状态 -->
<uni-load-more :status="loading ? 'loading' : 'done'" v-if="loading" class="loading-wrap"></uni-load-more>
<!-- 空数据状态 -->
<view class="empty-state" v-if="!loading && !hasData">
<uni-icons type="empty" size="80" color="#c0c4cc"></uni-icons>
<text class="empty-text">暂无外宿申请数据</text>
<text class="empty-tip" v-if="roleGroup.includes('学生')">点击右下角"+"按钮提交新申请</text>
</view>
<!-- 数据列表 -->
<view class="card-list" v-if="!loading && hasData">
<view class="apply-card" v-for="(item, index) in validDataList" :key="index" hover-class="card-hover"
:hover-stop-propagation="true">
<!-- 卡片头部 -->
<view class="card-header">
<view class="apply-no">
<text class="label-text">申请编号</text>
<text class="no-text">{{ getSafeValue(item, 'applyNo', '无') }}</text>
</view>
<view class="status-wrap">
<view class="status-label" :class="getStatusClass(item)">
{{ getStatusText(item) }}
</view>
<view class="valid-label"
:class="getSafeValue(item, 'isValid') == 1 ? 'valid-success' : 'valid-info'">
{{ getSafeValue(item, 'isValid') == 1 ? '有效' : '到期' }}
</view>
</view>
</view>
<!-- 卡片主体折叠面板 -->
<uni-collapse accordion v-model="item.activeCollapse">
<uni-collapse-item title="基本信息" name="basic" class="collapse-item">
<view class="info-grid">
<view class="info-item"><text class="label">学号</text><text
class="value">{{ getSafeValue(item, 'studentNo', '-') }}</text></view>
<view class="info-item"><text class="label">姓名</text><text
class="value">{{ getSafeValue(item, 'studentName', '-') }}</text></view>
<view class="info-item"><text class="label">性别</text><text
class="value">{{ getSafeValue(item, 'gender') == 1 ? '男' : getSafeValue(item, 'gender') == 0 ? '女' : '-' }}</text>
</view>
<view class="info-item"><text class="label">出生年月</text><text
class="value">{{ parseTime(getSafeValue(item, 'birthDate')) || '-' }}</text>
</view>
<view class="info-item"><text class="label">学院</text><text
class="value">{{ getSafeValue(item, 'deptName', '-') }}</text></view>
<view class="info-item"><text class="label">专业</text><text
class="value">{{ getSafeValue(item, 'majorName', '-') }}</text></view>
<view class="info-item"><text class="label">班级</text><text
class="value">{{ getSafeValue(item, 'className', '-') }}</text></view>
<view class="info-item"><text class="label">辅导员</text><text
class="value">{{ getSafeValue(item, 'teacherName', '-') }}</text></view>
<view class="info-item"><text class="label">原宿舍号</text><text
class="value">{{ getSafeValue(item, 'originalDormitory', '-') }}</text></view>
<view class="info-item">
<text class="label">住宿费状态</text>
<text class="value">
<view class="fee-label"
:class="getSafeValue(item, 'accommodationFeeStatus') === 1 ? 'fee-success' : 'fee-info'">
{{ getSafeValue(item, 'accommodationFeeStatus') === 1 ? '已交' : '未交' }}
</view>
</text>
</view>
<view class="info-item"><text class="label">宿费交纳</text><text
class="value">{{ getSafeValue(item, 'accommodationFee', '-') }}</text></view>
</view>
</uni-collapse-item>
<uni-collapse-item title="外宿信息" name="outside" class="collapse-item">
<view class="info-form">
<view class="form-item"><text class="label">外宿原因</text><text
class="value">{{ getSafeValue(item, 'applyReason', '-') }}</text></view>
<view class="form-item"><text class="label">外宿地址</text><text
class="value">{{ (getSafeValue(item, 'address', '') + ' ' + getSafeValue(item, 'outsideAddress', '')) || '-' }}</text>
</view>
<view class="form-item">
<text class="label">外宿时间</text>
<text class="value">{{ parseTime(getSafeValue(item, 'startDate')) || '-' }}
<text class="split-text"></text>
{{ parseTime(getSafeValue(item, 'endDate')) || '-' }}
</text>
</view>
<view class="form-item"><text class="label">紧急联系人</text><text
class="value">{{ getSafeValue(item, 'emergencyContact', '-') }}
({{ getSafeValue(item, 'emergencyPhone', '-') }})</text></view>
<view class="form-item"><text class="label">家长意见</text><text
class="value">{{ getSafeValue(item, 'parentOpinion') == 1 ? '同意' : getSafeValue(item, 'parentOpinion') == 0 ? '不同意' : '-' }}</text>
</view>
<view class="form-item"><text class="label">家长联系方式</text><text
class="value">{{ getSafeValue(item, 'parentPhone', '-') }}</text></view>
</view>
</uni-collapse-item>
<uni-collapse-item title="审批记录" name="approval" class="collapse-item">
<view class="approval-list"
v-if="getSafeValue(item, 'outsideAccommodationApprovals', []).length > 0">
<view class="approval-item"
v-for="(approval, idx) in getSafeValue(item, 'outsideAccommodationApprovals', [])"
:key="idx">
<text
class="approval-node">{{ getSafeValue(approval, 'approvalNode', '未知节点') }}</text>
<text class="approval-result"
:class="getSafeValue(approval, 'approvalResult') == 1 ? 'result-pass' : 'result-reject'">
{{ getSafeValue(approval, 'approvalResult') == 1 ? '通过' : '驳回' }}
</text>
<text class="approval-time"
v-if="getSafeValue(approval, 'approvalTime')">{{ parseTime(getSafeValue(approval, 'approvalTime')) }}</text>
<text class="approval-remark"
v-if="getSafeValue(approval, 'approvalRemark')">备注{{ getSafeValue(approval, 'approvalRemark') }}</text>
</view>
</view>
<view class="empty-approval" v-else>暂无审批记录</view>
</uni-collapse-item>
</uni-collapse>
<!-- 卡片操作区 -->
<view class="card-actions">
<uni-button type="text" size="mini" @click="detail(item)" class="detail-btn"
:disabled="!getSafeValue(item, 'id')">
<uni-icons type="info" size="14" class="mr-5"></uni-icons>查看详情
</uni-button>
</view>
</view>
</view>
<!-- 加载更多状态 -->
<view class="load-more-wrap" v-if="hasData">
<uni-load-more :status="loadingMore ? 'loading' : (hasMore ? 'more' : 'nomore')"
:content-text="loadMoreText"></uni-load-more>
</view>
</scroll-view>
<!-- 添加外宿申请按钮 -->
<view class="add" @click="addOutsideAccommodation">+</view>
</view>
</template>
<script>
import {
listOutsideAccommodationApply
} from "@/api/dms/outsideAccommodation/outsideAccommodationApply";
import {
getUserProfile
} from '@/api/system/user';
export default {
name: "OutsideAccommodationApplyList",
data() {
return {
// 加载状态
loading: true,
loadingMore: false,
hasMore: true,
// 总数据量
totalCount: 0,
// 原始数据
outsideAccommodationApplyList: [],
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 5,
studentNo: null,
studentName: null,
deptName: null,
className: null,
teacherName: null
},
// 用户角色
roleGroup: '',
user: {},
// 加载更多文本
loadMoreText: {
contentdown: '上拉加载更多',
contentrefresh: '正在加载...',
contentnomore: '没有更多数据了'
}
};
},
computed: {
// 过滤后的有效数据列表
validDataList() {
return this.outsideAccommodationApplyList.filter(item => {
return item !== null && item !== undefined && typeof item === 'object';
}).map(item => {
if (!item.hasOwnProperty('activeCollapse')) {
item.activeCollapse = 'basic';
}
return item;
});
},
// 是否有有效数据
hasData() {
return this.validDataList.length > 0;
}
},
onLoad() {
this.getUser();
},
methods: {
/** 安全取值方法 */
getSafeValue(obj, key, defaultValue = '') {
if (!obj || typeof obj !== 'object') return defaultValue;
return obj[key] !== undefined && obj[key] !== null ? obj[key] : defaultValue;
},
/** 获取状态样式类 */
getStatusClass(item) {
const rejectInfo = this.getRejectInfo(item);
if (rejectInfo.isReject) {
return 'status-error';
}
const status = this.getSafeValue(item, 'status');
const typeMap = {
'0': 'status-info',
'1': 'status-warning',
'2': 'status-warning',
'3': 'status-warning',
'4': 'status-warning',
'5': 'status-success'
};
return typeMap[status] || 'status-default';
},
/** 获取驳回信息 */
getRejectInfo(item) {
const approvalList = this.getSafeValue(item, 'outsideAccommodationApprovals', []);
if (!Array.isArray(approvalList) || approvalList.length === 0) {
return {
isReject: false,
rejectText: ''
};
}
const rejectItem = approvalList.find(approval => {
return this.getSafeValue(approval, 'approvalResult') === 0;
});
if (rejectItem) {
const nodeName = this.getSafeValue(rejectItem, 'approvalNode', '未知').replace('审批', '');
return {
isReject: true,
rejectText: `${nodeName}驳回`
};
} else {
return {
isReject: false,
rejectText: ''
};
}
},
/** 获取状态文本 */
getStatusText(item) {
const rejectInfo = this.getRejectInfo(item);
if (rejectInfo.isReject) {
return rejectInfo.rejectText;
}
const status = this.getSafeValue(item, 'status');
const statusMap = {
'0': '待提交',
'1': '待辅导员审批',
'2': '待学院书记审批',
'3': '待学工处审批',
'4': '待学校领导审批',
'5': '审核通过'
};
return statusMap[status] || '未知状态';
},
/** 时间格式化 */
parseTime(time, format = '{y}-{m}-{d}') {
if (!time) return '';
try {
const date = new Date(time);
if (isNaN(date.getTime())) return '';
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
return format.replace('{y}', year).replace('{m}', month).replace('{d}', day);
} catch (error) {
return '';
}
},
/** 查询数据 */
getList(isRefresh = false) {
if (isRefresh) {
this.loading = true;
this.hasMore = true;
this.queryParams.pageNum = 1;
this.outsideAccommodationApplyList = [];
} else {
this.loadingMore = true;
}
listOutsideAccommodationApply(this.queryParams).then(response => {
const res = response || {};
const newData = Array.isArray(res.rows) ? res.rows : [];
this.totalCount = Number(res.total) || 0;
if (isRefresh) {
this.outsideAccommodationApplyList = newData;
} else {
this.outsideAccommodationApplyList = [...this.outsideAccommodationApplyList, ...newData];
}
// 更新状态
this.loading = false;
this.loadingMore = false;
this.hasMore = this.outsideAccommodationApplyList.length < this.totalCount;
}).catch((error) => {
console.error('数据加载失败:', error);
this.loading = false;
this.loadingMore = false;
uni.showToast({
title: '数据加载失败',
icon: 'none'
});
});
},
/** 获取用户信息 */
getUser() {
getUserProfile().then(response => {
const res = response || {};
this.user = res.data || {};
this.roleGroup = res.roleGroup || '';
// 填充查询条件
if (this.roleGroup.includes("学生")) {
this.queryParams.studentName = this.user.nickName || '';
} else if (this.roleGroup.includes("辅导员")) {
this.queryParams.teacherName = this.user.nickName || '';
} else if (this.roleGroup.includes("二级学院")) {
this.queryParams.deptName = this.user.dept?.deptName || '';
}
this.getList(true);
}).catch((error) => {
console.error('用户信息获取失败:', error);
this.loading = false;
uni.showToast({
title: '用户信息加载失败',
icon: 'none'
});
});
},
/** 搜索 */
handleQuery() {
this.getList(true);
},
/** 重置 */
resetQuery() {
this.queryParams = {
pageNum: 1,
pageSize: 5,
studentNo: null,
studentName: null,
deptName: null,
className: null,
teacherName: null
};
// 重新填充角色相关的默认查询条件
if (this.roleGroup.includes("学生")) {
this.queryParams.studentName = this.user.nickName || '';
} else if (this.roleGroup.includes("辅导员")) {
this.queryParams.teacherName = this.user.nickName || '';
} else if (this.roleGroup.includes("二级学院")) {
this.queryParams.deptName = this.user.dept?.deptName || '';
}
this.getList(true);
},
/** 加载更多 */
handleLoadMore() {
if (this.loadingMore || !this.hasMore) return;
this.queryParams.pageNum += 1;
this.getList(false);
},
/** 查看详情 */
detail(item) {
const id = this.getSafeValue(item, 'id');
if (!id) {
uni.showToast({
title: '数据异常,无法查看详情',
icon: 'none'
});
return;
}
uni.navigateTo({
url: `/pages/dormitory/outsideAccommodation/applicationForm?id=${id}`
});
},
// 跳转添加页面
addOutsideAccommodation() {
if (this.roleGroup === "学生") {
if (this.outsideAccommodationApplyList.length > 0) {
uni.showToast({
title: '请勿重复提交',
icon: 'none'
});
return
}
}
uni.navigateTo({
url: `/pages/dormitory/outsideAccommodation/applicationForm`
})
},
}
};
</script>
<style scoped>
.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;
}
/* 全局样式 */
.app-container {
padding: 20rpx;
background-color: #f5f7fa;
min-height: 100vh;
}
.mr-5 {
margin-right: 5px;
}
/* 搜索区样式 */
.fixed-search-wrap {
z-index: 90;
background-color: #fff;
border-bottom: 1px solid #eee;
margin-bottom: 15rpx;
}
.search-content {
transition: all 0.3s ease;
}
.search-card {
padding: 20rpx;
}
.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 {
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;
}
.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;
}
/* 列表滚动容器 */
.list-container {
width: 100%;
height: calc(100vh - 380rpx);
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.loading-wrap {
padding: 60rpx 0;
}
/* 加载更多区域 */
.load-more-wrap {
padding: 30rpx 0;
text-align: center;
height: 80rpx;
display: flex;
align-items: center;
justify-content: center;
}
/* 空数据状态 */
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
padding: 120rpx 0;
color: #909399;
}
.empty-text {
margin-top: 30rpx;
font-size: 32rpx;
}
.empty-tip {
margin-top: 16rpx;
font-size: 26rpx;
color: #c0c4cc;
}
/* 卡片列表 */
.card-list {
display: flex;
flex-direction: column;
gap: 24rpx;
}
/* 申请卡片 */
.apply-card {
background-color: #fff;
border-radius: 16rpx;
overflow: hidden;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.05);
transition: all 0.3s ease;
}
.card-hover {
transform: translateY(-4rpx);
box-shadow: 0 8rpx 30rpx rgba(0, 0, 0, 0.1);
}
/* 卡片头部 */
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx;
background: linear-gradient(135deg, #e8f4f8 0%, #f0f8fb 100%);
border-bottom: 1rpx solid #f2f2f2;
}
.apply-no {
display: flex;
align-items: center;
}
.label-text {
font-size: 26rpx;
color: #606266;
margin-right: 8rpx;
}
.no-text {
font-size: 28rpx;
font-weight: 600;
color: #303133;
}
.status-wrap {
display: flex;
align-items: center;
gap: 10rpx;
}
/* 状态标签样式 */
.status-label {
height: 44rpx;
line-height: 44rpx;
font-size: 24rpx;
padding: 0 16rpx;
border-radius: 22rpx;
color: #fff;
}
.status-success {
background-color: #67c23a;
}
.status-warning {
background-color: #e6a23c;
}
.status-error {
background-color: #f56c6c;
}
.status-info {
background-color: #909399;
}
.status-default {
background-color: #409eff;
}
/* 有效/到期标签 */
.valid-label {
height: 44rpx;
line-height: 44rpx;
font-size: 22rpx;
padding: 0 12rpx;
border-radius: 22rpx;
color: #fff;
}
.valid-success {
background-color: #67c23a;
}
.valid-info {
background-color: #909399;
}
/* 住宿费状态标签 */
.fee-label {
display: inline-block;
height: 36rpx;
line-height: 36rpx;
font-size: 22rpx;
padding: 0 12rpx;
border-radius: 18rpx;
color: #fff;
}
.fee-success {
background-color: #67c23a;
}
.fee-info {
background-color: #f56c6c;
}
/* 折叠面板 */
.collapse-item {
--uni-collapse-item-header-padding: 24rpx;
--uni-collapse-item-content-padding: 0;
--uni-collapse-item-header-font-size: 28rpx;
--uni-collapse-item-header-color: #303133;
}
/* 信息网格 */
.info-grid {
display: flex;
flex-wrap: wrap;
padding: 24rpx;
gap: 20rpx;
}
.info-grid .info-item {
width: calc(50% - 10rpx);
display: flex;
align-items: center;
font-size: 26rpx;
line-height: 1.6;
}
/* 信息表单 */
.info-form {
padding: 24rpx;
}
.info-form .form-item {
display: flex;
flex-direction: column;
margin-bottom: 24rpx;
font-size: 26rpx;
line-height: 1.6;
}
.info-form .form-item:last-child {
margin-bottom: 0;
}
.split-text {
margin: 0 10rpx;
color: #c0c4cc;
}
/* 标签和值样式 */
.label {
color: #606266;
margin-right: 12rpx;
font-weight: 500;
min-width: 120rpx;
}
.info-form .label {
margin-bottom: 8rpx;
font-size: 26rpx;
}
.value {
color: #303133;
flex: 1;
}
/* 审批记录 */
.approval-list {
padding: 24rpx;
}
.approval-item {
display: flex;
flex-wrap: wrap;
align-items: center;
padding: 12rpx 0;
border-bottom: 1rpx dashed #f2f2f2;
font-size: 26rpx;
}
.approval-item:last-child {
border-bottom: none;
}
.approval-node {
color: #606266;
margin-right: 8rpx;
}
.approval-result {
margin-right: 16rpx;
}
.result-pass {
color: #67c23a;
}
.result-reject {
color: #f56c6c;
}
.approval-time {
color: #909399;
font-size: 24rpx;
margin-right: 16rpx;
}
.approval-remark {
color: #303133;
font-size: 24rpx;
width: 100%;
margin-top: 8rpx;
}
.empty-approval {
padding: 24rpx;
text-align: center;
color: #909399;
font-size: 26rpx;
}
/* 卡片操作区 */
.card-actions {
display: flex;
justify-content: flex-end;
padding: 20rpx 24rpx;
border-top: 1rpx solid #f2f2f2;
background-color: #fafafa;
}
.detail-btn {
color: #409eff;
font-size: 26rpx;
}
/* 适配小屏幕 */
@media (max-width: 750rpx) {
.info-grid .info-item {
width: 100%;
}
.card-header {
flex-direction: column;
align-items: flex-start;
gap: 16rpx;
}
.status-wrap {
align-self: flex-start;
}
.list-container {
height: calc(100vh - 420rpx);
}
}
</style>