This commit is contained in:
zlwu committed 2025-10-25 15:19:07 +08:00
1 parent f89d747952
commit 4c63668c19
10 files changed
+1608 -24

No files matched your search

+9
View File
@@ -150,6 +150,15 @@ page {
border-color: #007dfb !important;
}
/* Tabs组件主题色覆盖 - 全局统一底部方块颜色 */
.u-tabs__wrapper__nav__line {
background-color: #0051d9 !important;
}
.u-tabs .u-tabs__wrapper__nav__line {
background: #0051d9 !important;
}
/* ========== 全局温暖商务风格样式 ========== */
/* 全局页面背景 */
+180 -17
View File
@@ -73,18 +73,36 @@
<!-- 办理人信息 -->
<view class="approver-section">
<text class="section-label">办理人:</text>
<view class="approver-tags">
<template v-if="record.approveName">
<u-tag
v-for="(name, nameIndex) in record.approveName.split(
',',
)"
:key="nameIndex"
:text="name.trim()"
:type="getApproverType()"
size="mini"
class="approver-tag"
/>
<view class="approver-info-list">
<template
v-if="record.approverList && record.approverList.length > 0"
>
<view
v-for="(approver, approverIndex) in record.approverList"
:key="approverIndex"
class="approver-info-item"
>
<u-tag
:text="approver.name"
:type="getApproverType()"
size="mini"
class="approver-tag"
/>
<view
v-if="approver.phone"
class="approver-phone-wrapper"
@click="handleCallPhone(approver.phone)"
>
<u-icon
name="phone-fill"
size="28"
color="#1989fa"
/>
<text class="approver-phone">
{{ approver.phone }}
</text>
</view>
</view>
</template>
<u-tag
v-else
@@ -253,10 +271,43 @@ export default {
console.log('实例ID:', this.insId);
console.log('记录数量:', this.historyList.length);
// 处理附件信息(与web端保持一致)
// 处理附件信息和审批人信息(与web端保持一致)
if (this.historyList.length > 0) {
console.log('开始处理附件信息...');
console.log('开始处理附件信息和审批人信息...');
// 收集所有审批人ID
const allApproverIds = new Set();
for (const item of this.historyList) {
if (item.approver) {
// approver字段可能包含多个ID,用逗号分隔
const ids = item.approver
.split(',')
.map((id) => id.trim())
.filter((id) => id);
ids.forEach((id) => allApproverIds.add(id));
}
}
// 批量查询审批人信息(包含手机号码)
let userInfoMap = {};
if (allApproverIds.size > 0) {
console.log(
'开始批量查询审批人信息,ID列表:',
Array.from(allApproverIds),
);
try {
userInfoMap = await this.getUserInfoByIds(
Array.from(allApproverIds),
);
console.log('审批人信息查询完成:', userInfoMap);
} catch (error) {
console.error('批量查询审批人信息失败:', error);
}
}
// 处理每条审批记录
for (const item of this.historyList) {
// 处理附件信息
if (item.ext) {
try {
const attachmentRes = await this.getAttachmentsByIds(item.ext);
@@ -268,8 +319,31 @@ export default {
} else {
item.attachmentList = [];
}
// 处理审批人信息(包含姓名和手机号)
if (item.approver && item.approveName) {
const approverIds = item.approver
.split(',')
.map((id) => id.trim())
.filter((id) => id);
const approverNames = item.approveName
.split(',')
.map((name) => name.trim())
.filter((name) => name);
item.approverList = approverIds.map((id, index) => {
const userInfo = userInfoMap[id];
return {
id: id,
name: approverNames[index] || '未知',
phone: userInfo ? userInfo.phonenumber : null,
};
});
} else {
item.approverList = [];
}
}
console.log('附件信息处理完成');
console.log('附件信息和审批人信息处理完成');
}
} else {
console.error('API响应异常:', response);
@@ -300,6 +374,67 @@ export default {
return response;
},
// 批量查询用户信息(包含手机号码)
async getUserInfoByIds(userIds) {
try {
const response = await request({
url: '/system/user/optionselect',
method: 'GET',
params: {
userIds: userIds.join(','),
},
});
if (response.code === 200 && response.data) {
// 将数组转换为以userId为key的Map,方便后续查找
const userMap = {};
response.data.forEach((user) => {
userMap[user.userId] = user;
});
return userMap;
} else {
console.error('查询用户信息失败:', response.msg);
return {};
}
} catch (error) {
console.error('批量查询用户信息异常:', error);
return {};
}
},
// 拨打电话
handleCallPhone(phoneNumber) {
if (!phoneNumber) {
uni.showToast({
title: '手机号码为空',
icon: 'none',
});
return;
}
uni.showModal({
title: '拨打电话',
content: `确定要拨打 ${phoneNumber} 吗?`,
success: (res) => {
if (res.confirm) {
uni.makePhoneCall({
phoneNumber: phoneNumber,
success: () => {
console.log('拨打电话成功');
},
fail: (err) => {
console.error('拨打电话失败:', err);
uni.showToast({
title: '拨打电话失败',
icon: 'none',
});
},
});
}
},
});
},
// 获取状态样式类(使用字典方式)
getStatusClass(flowStatus) {
console.log('flowStatus:', flowStatus);
@@ -670,16 +805,44 @@ export default {
display: block;
}
.approver-tags {
.approver-info-list {
display: flex;
flex-direction: column;
gap: 12rpx;
}
.approver-info-item {
display: flex;
align-items: center;
gap: 12rpx;
flex-wrap: wrap;
gap: 8rpx;
}
.approver-tag {
margin: 0;
}
.approver-phone-wrapper {
display: flex;
align-items: center;
gap: 6rpx;
padding: 4rpx 8rpx;
border-radius: 8rpx;
transition: all 0.3s;
cursor: pointer;
}
.approver-phone-wrapper:active {
background-color: #e6f7ff;
transform: scale(0.98);
}
.approver-phone {
font-size: 26rpx;
color: #1989fa;
font-weight: 500;
}
.comment-text {
font-size: 26rpx;
color: #333;
+8
View File
@@ -333,6 +333,14 @@
"navigationBarBackgroundColor": "#0051d9",
"navigationBarTextStyle": "white"
}
},
{
"path": "pages/xg/studentDynamics/index",
"style": {
"navigationBarTitleText": "学生动向信息",
"navigationBarBackgroundColor": "#0051d9",
"navigationBarTextStyle": "white"
}
}
],
"globalStyle": {
+22
View File
@@ -221,6 +221,20 @@
</template>
</ServiceItem>
<ServiceItem
v-if="hasPermi(['student:dynamics:list'])"
title="学生动向信息(宿管)"
@click="gotoStudentDynamics"
>
<template #icon>
<image
src="/static/0011.png"
mode="aspectFit"
style="width: 24px; height: 24px"
/>
</template>
</ServiceItem>
<ServiceItem
v-if="hasPermi(['student:workstudy:list'])"
title="勤工俭学"
@@ -722,6 +736,7 @@ export default {
this.hasPermi(['student:dormviolation:listapp']) ||
this.hasPermi(['student:visit:list']) ||
this.hasPermi(['student:dormhygiene:listapp']) ||
this.hasPermi(['student:dynamics:list']) ||
this.hasPermi(['student:workstudy:list']) ||
this.hasPermi(['student:violation:listapp']) ||
this.hasPermi(['student:assessment:applyapp']) ||
@@ -988,6 +1003,13 @@ export default {
});
},
// 跳转到学生动向信息(宿管)页面
gotoStudentDynamics() {
uni.navigateTo({
url: '/pages/xg/studentDynamics/index',
});
},
// 跳转到修改密码页面
gotoResetPassword() {
uni.navigateTo({
@@ -0,0 +1,432 @@
<template>
<view class="dorm-application-tab">
<!-- 搜索区域 -->
<view class="search-container">
<u-search
v-model="searchKeyword"
placeholder="请输入姓名或学号"
:show-action="true"
action-text="搜索"
:animation="true"
@search="handleQuery"
@custom="handleQuery"
@clear="handleClear"
></u-search>
</view>
<!-- 加载状态 -->
<u-loading-page
:loading="loading && dataList.length === 0"
loading-text="加载中..."
></u-loading-page>
<!-- 列表内容 -->
<view v-if="!loading || dataList.length > 0" class="content-wrapper">
<!-- 空状态 -->
<view v-if="dataList.length === 0" class="empty-container">
<u-empty text="暂无调宿/退宿申请记录" mode="data"></u-empty>
</view>
<!-- 列表 -->
<scroll-view
v-else
scroll-y
class="scroll-view"
:lower-threshold="50"
@scrolltolower="handleLoadMore"
>
<view class="list-container">
<view
v-for="(item, index) in dataList"
:key="item.id || index"
class="list-card"
>
<!-- 卡片头部 -->
<view class="card-header">
<view class="student-info">
<text class="student-name">{{ item.studentName || '-' }}</text>
<text class="student-no">{{ item.studentNo || '-' }}</text>
</view>
<u-tag
:text="getApplicationTypeText(item.applicationType)"
:type="getApplicationTypeTagType(item.applicationType)"
size="mini"
></u-tag>
</view>
<!-- 卡片内容 -->
<view class="card-content">
<view class="info-row">
<text class="label">学院:</text>
<text class="value">{{ item.collegeName || '-' }}</text>
</view>
<view class="info-row">
<text class="label">专业:</text>
<text class="value">{{ item.majorName || '-' }}</text>
</view>
<view class="info-row">
<text class="label">班级:</text>
<text class="value">{{ item.className || '-' }}</text>
</view>
<view class="info-row">
<text class="label">当前宿舍:</text>
<text class="value">
{{ item.currentBuildingName }}{{ item.currentRoomNo
}}{{ item.currentBedNo }}
</text>
</view>
<view v-if="item.applicationType === '1'" class="info-row">
<text class="label">目标宿舍:</text>
<text class="value">
{{ item.targetBuildingName }}{{ item.targetRoomNo
}}{{ item.targetBedNo }}
</text>
</view>
<view class="info-row">
<text class="label">审批状态:</text>
<u-tag
:text="getApprovalStatusText(item.applicationStatus)"
:type="getApprovalStatusTagType(item.applicationStatus)"
size="mini"
></u-tag>
</view>
<view class="info-row">
<text class="label">申请时间:</text>
<text class="value">{{ formatTime(item.applyTime) }}</text>
</view>
</view>
<!-- 操作按钮 -->
<view class="card-actions">
<u-button
type="primary"
size="small"
plain
@click="handleViewApproval(item)"
>
<u-icon
name="list"
size="16"
color="#fff"
style="margin-right: 4px"
></u-icon>
审批记录
</u-button>
</view>
</view>
</view>
<!-- 加载更多提示 -->
<view class="load-more-wrapper">
<u-loadmore :status="loadMoreStatus" :load-text="loadMoreText" />
</view>
</scroll-view>
</view>
<!-- 审批记录组件 -->
<approval-record ref="approvalRecordRef"></approval-record>
</view>
</template>
<script>
import { request } from '@/common/request.js';
import { getDict, getDictLabel, getDictTagType } from '@/common/dict.js';
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
export default {
name: 'DormApplicationTab',
components: {
ApprovalRecord,
},
data() {
return {
loading: false,
dataList: [],
total: 0,
searchKeyword: '', // 搜索关键字(姓名或学号)
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
studentName: '',
studentNo: '',
},
// 加载更多状态
loadMoreStatus: 'loadmore',
loadMoreText: {
loadmore: '点击加载更多',
loading: '正在加载...',
nomore: '没有更多了',
},
// 字典数据
dictApplicationType: [],
dictApprovalStatus: [],
};
},
async created() {
await this.loadDictData();
await this.getList();
},
methods: {
// 加载字典数据
async loadDictData() {
try {
this.dictApplicationType = await getDict('dorm_application_type');
this.dictApprovalStatus = await getDict('student_leave_approval_status');
} catch (error) {
console.error('加载字典数据失败:', error);
}
},
// 获取列表数据
async getList() {
this.loading = true;
try {
// 根据搜索关键字设置查询参数
const params = {
...this.queryParams,
studentName: this.searchKeyword,
studentNo: this.searchKeyword,
};
const res = await request({
url: '/studentDynamics/dormApplicationList',
method: 'GET',
params: params,
});
if (res && res.code === 200) {
if (this.queryParams.pageNum === 1) {
this.dataList = res.rows || [];
} else {
this.dataList = [...this.dataList, ...(res.rows || [])];
}
this.total = res.total || 0;
// 更新加载更多状态
if (this.dataList.length >= this.total) {
this.loadMoreStatus = 'nomore';
} else {
this.loadMoreStatus = 'loadmore';
}
} else {
uni.showToast({
title: res?.msg || '获取数据失败',
icon: 'none',
});
}
} catch (error) {
console.error('获取调宿退宿申请列表失败:', error);
uni.showToast({
title: '获取数据失败',
icon: 'none',
});
} finally {
this.loading = false;
}
},
// 加载更多
handleLoadMore() {
if (this.loadMoreStatus === 'nomore' || this.loadMoreStatus === 'loading') {
return;
}
this.loadMoreStatus = 'loading';
this.queryParams.pageNum++;
this.getList();
},
// 搜索操作
handleQuery() {
this.queryParams.pageNum = 1;
this.dataList = [];
this.getList();
},
// 清空搜索
handleClear() {
this.searchKeyword = '';
this.handleQuery();
},
// 格式化时间
formatTime(timeStr) {
if (!timeStr) return '-';
try {
const date = new Date(timeStr);
if (isNaN(date.getTime())) return timeStr;
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
} catch (error) {
console.error('时间格式化失败:', error);
return timeStr;
}
},
// 获取申请类型文本
getApplicationTypeText(value) {
return getDictLabel(this.dictApplicationType, value) || '-';
},
// 获取申请类型标签类型
getApplicationTypeTagType(value) {
const fallbackMap = {
1: 'primary', // 调宿
2: 'warning', // 退宿
};
return getDictTagType(this.dictApplicationType, value, fallbackMap);
},
// 获取审批状态文本
getApprovalStatusText(value) {
return getDictLabel(this.dictApprovalStatus, value) || '-';
},
// 获取审批状态标签类型
getApprovalStatusTagType(value) {
const fallbackMap = {
0: 'warning', // 待审批
1: 'success', // 已通过
2: 'error', // 已驳回
};
return getDictTagType(this.dictApprovalStatus, value, fallbackMap);
},
// 查看审批记录
handleViewApproval(item) {
console.log('查看审批记录:', item);
if (!item.id) {
uni.showToast({
title: '数据异常,无法查看审批记录',
icon: 'none',
});
return;
}
// 调用审批记录组件,传入业务ID
this.$refs.approvalRecordRef.init(item.id);
},
},
};
</script>
<style lang="scss" scoped>
.dorm-application-tab {
background-color: #f5f6fa;
min-height: calc(100vh - 176rpx);
}
.search-container {
padding: 20rpx;
background-color: #fff;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.content-wrapper {
padding: 20rpx;
}
.empty-container {
padding: 100rpx 20rpx;
}
.scroll-view {
height: calc(100vh - 176rpx - 88rpx); // 减去搜索栏高度
}
.list-container {
display: flex;
flex-direction: column;
gap: 20rpx;
padding-bottom: 20rpx;
}
.list-card {
background-color: #fff;
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx;
border-bottom: 1px solid #f0f0f0;
}
.student-info {
display: flex;
align-items: center;
gap: 16rpx;
}
.student-name {
font-size: 32rpx;
font-weight: bold;
color: #303133;
}
.student-no {
font-size: 26rpx;
color: #909399;
}
.card-content {
padding: 24rpx;
}
.info-row {
display: flex;
align-items: center;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.label {
font-size: 28rpx;
color: #606266;
min-width: 140rpx;
margin-right: 16rpx;
}
.value {
font-size: 28rpx;
color: #303133;
flex: 1;
word-break: break-all;
}
.card-actions {
display: flex;
justify-content: flex-end;
align-items: center;
padding: 20rpx 24rpx;
border-top: 1px solid #f0f0f0;
}
.load-more-wrapper {
padding: 20rpx 0;
}
// uview组件样式调整
::v-deep .u-search {
background-color: #f5f6fa;
}
</style>
@@ -0,0 +1,392 @@
<template>
<view class="holiday-stay-tab">
<!-- 搜索区域 -->
<view class="search-container">
<u-search
v-model="searchKeyword"
placeholder="请输入姓名或学号"
:show-action="true"
action-text="搜索"
:animation="true"
@search="handleQuery"
@custom="handleQuery"
@clear="handleClear"
></u-search>
</view>
<!-- 加载状态 -->
<u-loading-page
:loading="loading && dataList.length === 0"
loading-text="加载中..."
></u-loading-page>
<!-- 列表内容 -->
<view v-if="!loading || dataList.length > 0" class="content-wrapper">
<!-- 空状态 -->
<view v-if="dataList.length === 0" class="empty-container">
<u-empty text="暂无假期留宿记录" mode="data"></u-empty>
</view>
<!-- 列表 -->
<scroll-view
v-else
scroll-y
class="scroll-view"
:lower-threshold="50"
@scrolltolower="handleLoadMore"
>
<view class="list-container">
<view
v-for="(item, index) in dataList"
:key="item.id || index"
class="list-card"
>
<!-- 卡片头部 -->
<view class="card-header">
<view class="student-info">
<text class="student-name">{{ item.studentName || '-' }}</text>
<text class="student-no">{{ item.studentNo || '-' }}</text>
</view>
<u-tag
:text="getHolidayTypeText(item.holidayType)"
:type="getHolidayTypeTagType(item.holidayType)"
size="mini"
></u-tag>
</view>
<!-- 卡片内容 -->
<view class="card-content">
<view class="info-row">
<text class="label">学院:</text>
<text class="value">{{ item.collegeName || '-' }}</text>
</view>
<view class="info-row">
<text class="label">专业:</text>
<text class="value">{{ item.majorName || '-' }}</text>
</view>
<view class="info-row">
<text class="label">班级:</text>
<text class="value">{{ item.className || '-' }}</text>
</view>
<view class="info-row">
<text class="label">宿舍信息:</text>
<text class="value">
{{ item.buildingName }}{{ item.roomName }}
</text>
</view>
<view class="info-row">
<text class="label">留宿时间:</text>
<text class="value">
{{ item.applyStartDate }} 至 {{ item.applyEndDate }}
</text>
</view>
<view class="info-row">
<text class="label">留宿原因:</text>
<u-tag
:text="getStayReasonTypeText(item.stayReasonType)"
:type="getStayReasonTypeTagType(item.stayReasonType)"
size="mini"
></u-tag>
</view>
<view class="info-row">
<text class="label">联系电话:</text>
<text class="value">{{ item.studentPhone || '-' }}</text>
</view>
<view class="info-row">
<text class="label">紧急联系人:</text>
<text class="value">{{ item.emergencyContact || '-' }}</text>
</view>
</view>
<!-- 操作按钮 - 暂不开发
<view class="card-actions">
<u-button
type="primary"
size="small"
@click="handleView(item)"
>查看</u-button>
</view>
-->
</view>
</view>
<!-- 加载更多提示 -->
<view class="load-more-wrapper">
<u-loadmore :status="loadMoreStatus" :load-text="loadMoreText" />
</view>
</scroll-view>
</view>
</view>
</template>
<script>
import { request } from '@/common/request.js';
import { getDict, getDictLabel, getDictTagType } from '@/common/dict.js';
export default {
name: 'HolidayStayTab',
data() {
return {
loading: false,
dataList: [],
total: 0,
searchKeyword: '', // 搜索关键字(姓名或学号)
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
studentName: '',
studentNo: '',
},
// 加载更多状态
loadMoreStatus: 'loadmore',
loadMoreText: {
loadmore: '点击加载更多',
loading: '正在加载...',
nomore: '没有更多了',
},
// 字典数据
dictHolidayType: [],
dictStayReasonType: [],
};
},
async created() {
await this.loadDictData();
await this.getList();
},
methods: {
// 加载字典数据
async loadDictData() {
try {
this.dictHolidayType = await getDict('xg_holiday_type');
this.dictStayReasonType = await getDict('xg_stay_reason_type');
} catch (error) {
console.error('加载字典数据失败:', error);
}
},
// 获取列表数据
async getList() {
this.loading = true;
try {
// 根据搜索关键字设置查询参数
const params = {
...this.queryParams,
studentName: this.searchKeyword,
studentNo: this.searchKeyword,
};
const res = await request({
url: '/studentDynamics/holidayStayList',
method: 'GET',
params: params,
});
if (res && res.code === 200) {
if (this.queryParams.pageNum === 1) {
this.dataList = res.rows || [];
} else {
this.dataList = [...this.dataList, ...(res.rows || [])];
}
this.total = res.total || 0;
// 更新加载更多状态
if (this.dataList.length >= this.total) {
this.loadMoreStatus = 'nomore';
} else {
this.loadMoreStatus = 'loadmore';
}
} else {
uni.showToast({
title: res?.msg || '获取数据失败',
icon: 'none',
});
}
} catch (error) {
console.error('获取假期留宿信息列表失败:', error);
uni.showToast({
title: '获取数据失败',
icon: 'none',
});
} finally {
this.loading = false;
}
},
// 加载更多
handleLoadMore() {
if (this.loadMoreStatus === 'nomore' || this.loadMoreStatus === 'loading') {
return;
}
this.loadMoreStatus = 'loading';
this.queryParams.pageNum++;
this.getList();
},
// 搜索操作
handleQuery() {
this.queryParams.pageNum = 1;
this.dataList = [];
this.getList();
},
// 清空搜索
handleClear() {
this.searchKeyword = '';
this.handleQuery();
},
// 获取假期类型文本
getHolidayTypeText(value) {
return getDictLabel(this.dictHolidayType, value) || '-';
},
// 获取假期类型标签类型
getHolidayTypeTagType(value) {
const fallbackMap = {
1: 'primary', // 寒假
2: 'warning', // 暑假
3: 'success', // 国庆
4: 'info', // 其他
};
return getDictTagType(this.dictHolidayType, value, fallbackMap);
},
// 获取留宿原因类型文本
getStayReasonTypeText(value) {
return getDictLabel(this.dictStayReasonType, value) || '-';
},
// 获取留宿原因类型标签类型
getStayReasonTypeTagType(value) {
const fallbackMap = {
1: 'primary', // 学习
2: 'warning', // 实习
3: 'success', // 工作
4: 'info', // 其他
};
return getDictTagType(this.dictStayReasonType, value, fallbackMap);
},
// 查看详情 - 暂不开发
// handleView(row) {
// console.log('查看详情:', row);
// },
},
};
</script>
<style lang="scss" scoped>
.holiday-stay-tab {
background-color: #f5f6fa;
min-height: calc(100vh - 176rpx);
}
.search-container {
padding: 20rpx;
background-color: #fff;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.content-wrapper {
padding: 20rpx;
}
.empty-container {
padding: 100rpx 20rpx;
}
.scroll-view {
height: calc(100vh - 176rpx - 88rpx); // 减去搜索栏高度
}
.list-container {
display: flex;
flex-direction: column;
gap: 20rpx;
padding-bottom: 20rpx;
}
.list-card {
background-color: #fff;
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx;
border-bottom: 1px solid #f0f0f0;
}
.student-info {
display: flex;
align-items: center;
gap: 16rpx;
}
.student-name {
font-size: 32rpx;
font-weight: bold;
color: #303133;
}
.student-no {
font-size: 26rpx;
color: #909399;
}
.card-content {
padding: 24rpx;
}
.info-row {
display: flex;
align-items: center;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.label {
font-size: 28rpx;
color: #606266;
min-width: 140rpx;
margin-right: 16rpx;
}
.value {
font-size: 28rpx;
color: #303133;
flex: 1;
word-break: break-all;
}
.card-actions {
display: flex;
justify-content: flex-end;
align-items: center;
padding: 20rpx 24rpx;
border-top: 1px solid #f0f0f0;
}
.load-more-wrapper {
padding: 20rpx 0;
}
// uview组件样式调整
::v-deep .u-search {
background-color: #f5f6fa;
}
</style>
@@ -0,0 +1,435 @@
<template>
<view class="student-leave-tab">
<!-- 搜索区域 -->
<view class="search-container">
<u-search
v-model="searchKeyword"
placeholder="请输入姓名或学号"
:show-action="true"
action-text="搜索"
:animation="true"
@search="handleQuery"
@custom="handleQuery"
@clear="handleClear"
></u-search>
</view>
<!-- 加载状态 -->
<u-loading-page
:loading="loading && dataList.length === 0"
loading-text="加载中..."
></u-loading-page>
<!-- 列表内容 -->
<view v-if="!loading || dataList.length > 0" class="content-wrapper">
<!-- 空状态 -->
<view v-if="dataList.length === 0" class="empty-container">
<u-empty text="暂无学生请假记录" mode="data"></u-empty>
</view>
<!-- 列表 -->
<scroll-view
v-else
scroll-y
class="scroll-view"
:lower-threshold="50"
@scrolltolower="handleLoadMore"
>
<view class="list-container">
<view
v-for="(item, index) in dataList"
:key="item.id || index"
class="list-card"
>
<!-- 卡片头部 -->
<view class="card-header">
<view class="student-info">
<text class="student-name">{{ item.studentName || '-' }}</text>
<text class="student-no">{{ item.studentNo || '-' }}</text>
</view>
<u-tag
:text="item.leaveTypeName || '-'"
type="primary"
size="mini"
></u-tag>
</view>
<!-- 卡片内容 -->
<view class="card-content">
<view class="info-row">
<text class="label">学院:</text>
<text class="value">{{ item.collegeName || '-' }}</text>
</view>
<view class="info-row">
<text class="label">专业:</text>
<text class="value">{{ item.majorName || '-' }}</text>
</view>
<view class="info-row">
<text class="label">班级:</text>
<text class="value">{{ item.className || '-' }}</text>
</view>
<view class="info-row">
<text class="label">请假时间:</text>
<text class="value">
{{ item.startTime }} 至 {{ item.endTime }}
</text>
</view>
<view class="info-row">
<text class="label">请假时长:</text>
<text class="value">{{ item.leaveDuration || 0 }}天</text>
</view>
<view class="info-row">
<text class="label">是否离校:</text>
<u-tag
:text="getIsLeaveSchoolText(item.isLeaveSchool)"
:type="getIsLeaveSchoolTagType(item.isLeaveSchool)"
size="mini"
></u-tag>
</view>
<view class="info-row">
<text class="label">审批状态:</text>
<u-tag
:text="getApprovalStatusText(item.approvalStatus)"
:type="getApprovalStatusTagType(item.approvalStatus)"
size="mini"
></u-tag>
</view>
<view class="info-row">
<text class="label">销假状态:</text>
<u-tag
:text="getCancelStatusText(item.cancelStatus)"
:type="getCancelStatusTagType(item.cancelStatus)"
size="mini"
></u-tag>
</view>
</view>
<!-- 操作按钮 -->
<view class="card-actions">
<u-button
type="primary"
size="small"
plain
@click="handleViewApproval(item)"
>
<u-icon
name="list"
size="16"
color="#fff"
style="margin-right: 4px"
></u-icon>
审批记录
</u-button>
</view>
</view>
</view>
<!-- 加载更多提示 -->
<view class="load-more-wrapper">
<u-loadmore :status="loadMoreStatus" :load-text="loadMoreText" />
</view>
</scroll-view>
</view>
<!-- 审批记录组件 -->
<approval-record ref="approvalRecordRef"></approval-record>
</view>
</template>
<script>
import { request } from '@/common/request.js';
import { getDict, getDictLabel, getDictTagType } from '@/common/dict.js';
import ApprovalRecord from '@/components/Process/approvalRecord.vue';
export default {
name: 'StudentLeaveTab',
components: {
ApprovalRecord,
},
data() {
return {
loading: false,
dataList: [],
total: 0,
searchKeyword: '', // 搜索关键字(姓名或学号)
// 查询参数
queryParams: {
pageNum: 1,
pageSize: 10,
studentName: '',
studentNo: '',
},
// 加载更多状态
loadMoreStatus: 'loadmore',
loadMoreText: {
loadmore: '点击加载更多',
loading: '正在加载...',
nomore: '没有更多了',
},
// 字典数据
dictYesNo: [],
dictApprovalStatus: [],
dictCancelStatus: [],
};
},
async created() {
await this.loadDictData();
await this.getList();
},
methods: {
// 加载字典数据
async loadDictData() {
try {
this.dictYesNo = await getDict('sys_yesno');
this.dictApprovalStatus = await getDict('student_leave_approval_status');
this.dictCancelStatus = await getDict('xg_xsqj_cancel_status');
} catch (error) {
console.error('加载字典数据失败:', error);
}
},
// 获取列表数据
async getList() {
this.loading = true;
try {
// 根据搜索关键字设置查询参数
const params = {
...this.queryParams,
studentName: this.searchKeyword,
studentNo: this.searchKeyword,
};
const res = await request({
url: '/studentDynamics/studentLeaveList',
method: 'GET',
params: params,
});
if (res && res.code === 200) {
if (this.queryParams.pageNum === 1) {
this.dataList = res.rows || [];
} else {
this.dataList = [...this.dataList, ...(res.rows || [])];
}
this.total = res.total || 0;
// 更新加载更多状态
if (this.dataList.length >= this.total) {
this.loadMoreStatus = 'nomore';
} else {
this.loadMoreStatus = 'loadmore';
}
} else {
uni.showToast({
title: res?.msg || '获取数据失败',
icon: 'none',
});
}
} catch (error) {
console.error('获取学生请假信息列表失败:', error);
uni.showToast({
title: '获取数据失败',
icon: 'none',
});
} finally {
this.loading = false;
}
},
// 加载更多
handleLoadMore() {
if (this.loadMoreStatus === 'nomore' || this.loadMoreStatus === 'loading') {
return;
}
this.loadMoreStatus = 'loading';
this.queryParams.pageNum++;
this.getList();
},
// 搜索操作
handleQuery() {
this.queryParams.pageNum = 1;
this.dataList = [];
this.getList();
},
// 清空搜索
handleClear() {
this.searchKeyword = '';
this.handleQuery();
},
// 获取是否离校文本
getIsLeaveSchoolText(value) {
return getDictLabel(this.dictYesNo, value) || '-';
},
// 获取是否离校标签类型
getIsLeaveSchoolTagType(value) {
const fallbackMap = {
Y: 'warning', // 是
N: 'info', // 否
};
return getDictTagType(this.dictYesNo, value, fallbackMap);
},
// 获取审批状态文本
getApprovalStatusText(value) {
return getDictLabel(this.dictApprovalStatus, value) || '-';
},
// 获取审批状态标签类型
getApprovalStatusTagType(value) {
const fallbackMap = {
0: 'warning', // 待审批
1: 'success', // 已通过
2: 'error', // 已驳回
};
return getDictTagType(this.dictApprovalStatus, value, fallbackMap);
},
// 获取销假状态文本
getCancelStatusText(value) {
return getDictLabel(this.dictCancelStatus, value) || '-';
},
// 获取销假状态标签类型
getCancelStatusTagType(value) {
const fallbackMap = {
0: 'warning', // 未销假
1: 'success', // 已销假
};
return getDictTagType(this.dictCancelStatus, value, fallbackMap);
},
// 查看审批记录
handleViewApproval(item) {
console.log('查看审批记录:', item);
if (!item.id) {
uni.showToast({
title: '数据异常,无法查看审批记录',
icon: 'none',
});
return;
}
// 调用审批记录组件,传入业务ID
this.$refs.approvalRecordRef.init(item.id);
},
},
};
</script>
<style lang="scss" scoped>
.student-leave-tab {
background-color: #f5f6fa;
min-height: calc(100vh - 176rpx);
}
.search-container {
padding: 20rpx;
background-color: #fff;
box-shadow: 0 2rpx 8rpx rgba(0, 0, 0, 0.1);
}
.content-wrapper {
padding: 20rpx;
}
.empty-container {
padding: 100rpx 20rpx;
}
.scroll-view {
height: calc(100vh - 176rpx - 88rpx); // 减去搜索栏高度
}
.list-container {
display: flex;
flex-direction: column;
gap: 20rpx;
padding-bottom: 20rpx;
}
.list-card {
background-color: #fff;
border-radius: 16rpx;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.08);
overflow: hidden;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 24rpx;
border-bottom: 1px solid #f0f0f0;
}
.student-info {
display: flex;
align-items: center;
gap: 16rpx;
}
.student-name {
font-size: 32rpx;
font-weight: bold;
color: #303133;
}
.student-no {
font-size: 26rpx;
color: #909399;
}
.card-content {
padding: 24rpx;
}
.info-row {
display: flex;
align-items: center;
margin-bottom: 16rpx;
&:last-child {
margin-bottom: 0;
}
}
.label {
font-size: 28rpx;
color: #606266;
min-width: 140rpx;
margin-right: 16rpx;
}
.value {
font-size: 28rpx;
color: #303133;
flex: 1;
word-break: break-all;
}
.card-actions {
display: flex;
justify-content: flex-end;
align-items: center;
padding: 20rpx 24rpx;
border-top: 1px solid #f0f0f0;
}
.load-more-wrapper {
padding: 20rpx 0;
}
// uview组件样式调整
::v-deep .u-search {
background-color: #f5f6fa;
}
</style>
@@ -0,0 +1,99 @@
<template>
<view class="student-dynamics-container">
<!-- 顶部导航 -->
<u-navbar title="学生动向信息" :border-bottom="false" bg-color="#0051d9" title-color="#fff">
<view slot="left" @click="goBack">
<u-icon name="arrow-left" color="#fff" size="22"></u-icon>
</view>
</u-navbar>
<!-- Tab切换 -->
<u-tabs
:list="tabList"
:current="currentTab"
@change="handleTabChange"
active-color="#0051d9"
inactive-color="#606266"
line-color="#0051d9"
:line-width="60"
:line-height="4"
:scrollable="false"
></u-tabs>
<!-- Tab内容区域 -->
<view class="tab-content">
<!-- 调宿/退宿信息 -->
<view v-show="currentTab === 0" class="tab-pane">
<dorm-application-tab ref="dormApplicationTabRef"></dorm-application-tab>
</view>
<!-- 假期留宿信息 -->
<view v-show="currentTab === 1" class="tab-pane">
<holiday-stay-tab ref="holidayStayTabRef"></holiday-stay-tab>
</view>
<!-- 学生请假信息 -->
<view v-show="currentTab === 2" class="tab-pane">
<student-leave-tab ref="studentLeaveTabRef"></student-leave-tab>
</view>
</view>
</view>
</template>
<script>
import DormApplicationTab from './components/DormApplicationTab.vue';
import HolidayStayTab from './components/HolidayStayTab.vue';
import StudentLeaveTab from './components/StudentLeaveTab.vue';
export default {
name: 'StudentDynamics',
components: {
DormApplicationTab,
HolidayStayTab,
StudentLeaveTab,
},
data() {
return {
// Tab列表
tabList: [
{ name: '调宿/退宿信息' },
{ name: '假期留宿信息' },
{ name: '学生请假信息' },
],
// 当前激活的Tab
currentTab: 0,
};
},
onLoad() {
console.log('学生动向信息管理页面已加载');
},
methods: {
// 返回上一页
goBack() {
uni.navigateBack();
},
// Tab切换处理
handleTabChange(item) {
console.log('切换到Tab页:', item);
// uview的@change事件返回的是对象,需要提取index属性
this.currentTab = item.index;
},
},
};
</script>
<style lang="scss" scoped>
.student-dynamics-container {
background-color: #f5f6fa;
min-height: 100vh;
}
.tab-content {
padding: 0;
}
.tab-pane {
min-height: calc(100vh - 88rpx - 88rpx); // 减去navbar和tabs的高度
}
</style>
@@ -17,7 +17,7 @@
:list="tabList"
:current="safeCurrentTab"
@click="handleTabClick"
active-color="#3c9cff"
active-color="#0051d9"
inactive-color="#606266"
:font-size="32"
:bar-width="60"