Files
zhxg_app/pages/Approval/handleTask/processHandling/components/outsideAccommodation.vue
2025-12-19 18:56:39 +08:00

774 lines
19 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="container">
<!-- 主内容区 -->
<scroll-view class="content-scroll" scroll-y enhanced :show-scrollbar="false">
<!-- 申请概览顶部卡片 -->
<view class="card overview-card">
<view class="overview-header">
<view class="overview-left">
<text class="apply-no">申请编号{{ form.applyNo || '-' }}</text>
<text class="student-name">{{ form.studentName || '-' }} {{ form.studentNo || '-' }}</text>
</view>
<view class="status-tag" :class="getStatusClass(form.status)">
{{ getStatusText(form.status) }}
</view>
</view>
<view class="overview-info">
<view class="overview-item">
<text class="info-label">申请时间</text>
<text class="info-value">{{ formatDate(form.createTime) || '-' }}</text>
</view>
<view class="overview-item">
<text class="info-label">外宿周期</text>
<text class="info-value">{{ formatDate(form.startDate) || '-' }} {{ formatDate(form.endDate) || '-' }}</text>
</view>
<view class="overview-item">
<text class="info-label">状态有效性</text>
<text class="info-value" :class="form.isValid == 1 ? 'text-success' : 'text-danger'">
{{ form.isValid == 1 ? '有效' : '到期' }}
</text>
</view>
</view>
</view>
<!-- 学生基础信息 -->
<view class="card">
<view class="card-header">
<uni-icons type="contact" size="24" color="#409EFF"></uni-icons>
<text class="card-title">学生基础信息</text>
</view>
<view class="form-list">
<view class="form-item">
<text class="label">姓名</text>
<text class="value">{{ form.studentName || '-' }}</text>
</view>
<view class="form-item">
<text class="label">学号</text>
<text class="value">{{ form.studentNo || '-' }}</text>
</view>
<view class="form-item">
<text class="label">性别</text>
<text class="value">{{ form.gender == '1' ? '男' : form.gender == '0' ? '女' : '-' }}</text>
</view>
<view class="form-item">
<text class="label">班级</text>
<text class="value">{{ form.className || '-' }}</text>
</view>
<view class="form-item">
<text class="label">辅导员</text>
<text class="value">{{ form.teacherName || '-' }}</text>
</view>
<view class="form-item">
<text class="label">原宿舍</text>
<text class="value">{{ form.originalDormitory || '-' }}</text>
</view>
<view class="form-item">
<text class="label">住宿费</text>
<text class="value">
<view class="fee-tag" :class="form.accommodationFeeStatus == 1 ? 'fee-paid' : 'fee-unpaid'">
{{ form.accommodationFeeStatus == 1 ? '已交' : '未交' }}
</view>
{{ form.accommodationFee || '-' }}
</text>
</view>
<view class="form-item">
<text class="label">出生日期</text>
<text class="value">{{ formatDate(form.birthDate) || '-' }}</text>
</view>
<view class="form-item">
<text class="label">身份证号</text>
<text class="value">{{ form.idCard || '-' }}</text>
</view>
<view class="form-item">
<text class="label">联系电话</text>
<text class="value">{{ form.studentPhone || '-' }}</text>
</view>
<view class="form-item">
<text class="label">学院/专业</text>
<text class="value">{{ form.deptName || '-' }} / {{ form.majorName || '-' }}</text>
</view>
<!-- 学生电子签名 -->
<view class="form-item signature-item" v-if="form.studentSignature">
<text class="label">学生签名</text>
<image class="signature-img" :src="getFullUrl(form.studentSignature)" mode="aspectFit" @click="previewImage(form.studentSignature)"></image>
</view>
</view>
</view>
<!-- 外宿信息 -->
<view class="card">
<view class="card-header">
<uni-icons type="location" size="24" color="#409EFF"></uni-icons>
<text class="card-title">外宿信息</text>
</view>
<view class="form-list">
<view class="form-item">
<text class="label">外宿原因</text>
<text class="value">{{ form.applyReason || '-' }}</text>
</view>
<view class="form-item">
<text class="label">外宿地址</text>
<text class="value">{{ form.address || '-' }} {{ form.outsideAddress || '-' }}</text>
</view>
<view class="form-item">
<text class="label">紧急联系人</text>
<text class="value">{{ form.emergencyContact || '-' }} ({{ form.emergencyPhone || '-' }})</text>
</view>
</view>
</view>
<!-- 家长信息 -->
<view class="card">
<view class="card-header">
<uni-icons type="person-filled" size="24" color="#409EFF"></uni-icons>
<text class="card-title">家长信息</text>
</view>
<view class="form-list">
<view class="form-item">
<text class="label">家长意见</text>
<text class="value">
<view class="opinion-tag" :class="form.parentOpinion == 1 ? 'opinion-agree' : 'opinion-disagree'">
{{ form.parentOpinion == 1 ? '同意' : '不同意' }}
</view>
</text>
</view>
<view class="form-item">
<text class="label">家长电话</text>
<text class="value">{{ form.parentPhone || '-' }}</text>
</view>
<view class="form-item">
<text class="label">家长地址</text>
<text class="value">{{ form.parentAddress || '-' }} {{ form.parentDetailAddress || '-' }}</text>
</view>
<!-- 家长签字附件 -->
<view class="form-item signature-item" v-if="form.parentSignAttachment">
<text class="label">家长签字</text>
<image class="signature-img" :src="getFullUrl(form.parentSignAttachment)" mode="aspectFit" @click="previewImage(form.parentSignAttachment)"></image>
</view>
</view>
</view>
<!-- 承诺书信息 -->
<view class="card">
<view class="card-header">
<uni-icons type="gift-filled" size="24" color="#409EFF"></uni-icons>
<text class="card-title">外宿承诺书</text>
</view>
<view class="form-list">
<view class="form-item">
<text class="label">签署日期</text>
<text class="value">{{ formatDate(form.promiseDate) || '-' }}</text>
</view>
<view class="form-item promise-item">
<text class="label">承诺内容</text>
<view class="value promise-content" v-html="form.promiseContent || '-'"></view>
</view>
<!-- 承诺签名 -->
<view class="form-item signature-item" v-if="form.studentPromiseSign">
<text class="label">承诺签名</text>
<image class="signature-img" :src="getFullUrl(form.studentPromiseSign)" mode="aspectFit" @click="previewImage(form.studentPromiseSign)"></image>
</view>
</view>
</view>
<!-- 附件列表 -->
<view class="card" v-if="form.outsideAccommodationAttachments && form.outsideAccommodationAttachments.length">
<view class="card-header">
<uni-icons type="download-filled" size="24" color="#409EFF"></uni-icons>
<text class="card-title">佐证材料</text>
</view>
<view class="attachment-list">
<view class="attachment-item" v-for="(item, index) in form.outsideAccommodationAttachments" :key="index" @click="previewImage(item.attachmentUrl)">
<view class="file-icon">
<uni-icons type="file" size="32" color="#409EFF"></uni-icons>
</view>
<view class="file-info">
<text class="file-name">{{ item.attachmentName || '未命名文件' }}</text>
<text class="file-size">{{ formatFileSize(item.fileSize) }}</text>
</view>
<uni-icons type="right" size="24" color="#ccc"></uni-icons>
</view>
</view>
</view>
<!-- 审批记录 -->
<view class="card" v-if="form.outsideAccommodationApprovals && form.outsideAccommodationApprovals.length">
<view class="card-header">
<uni-icons type="checkbox" size="24" color="#409EFF"></uni-icons>
<text class="card-title">审批记录</text>
</view>
<view class="approval-list">
<view class="approval-item" v-for="(item, index) in form.outsideAccommodationApprovals" :key="index">
<view class="approval-line">
<view class="approval-dot" :class="item.approvalResult == 1 ? 'dot-success' : item.approvalResult == 0 ? 'dot-pending' : 'dot-danger'"></view>
<view class="approval-content">
<view class="approval-header">
<text class="approval-node">{{ item.approvalNode || '未知节点' }}</text>
<text class="approval-time">{{ formatDate(item.approvalTime) || '-' }}</text>
</view>
<view class="approval-body">
<view class="approval-info">
<text class="info-key">审批人</text>
<text class="info-val">{{ item.approverName || '-' }}</text>
</view>
<view class="approval-info">
<text class="info-key">审批结果</text>
<view class="approval-result-tag" :class="item.approvalResult == 1 ? 'result-approve' : item.approvalResult == 0 ? 'result-pending' : 'result-reject'">
{{ item.approvalResult == 1 ? '通过' : '驳回' }}
</view>
</view>
<view class="approval-info" v-if="item.approvalOpinion">
<text class="info-key">审批意见</text>
<text class="info-val">{{ item.approvalOpinion }}</text>
</view>
<!-- 审批签名 -->
<view class="approval-info" v-if="item.signature">
<text class="info-key">审批签名</text>
<!-- <image class="signature-img" :src="baseUrl + approval.signature" mode="aspectFit" @click="previewFile(approval, '签名')"></image> -->
<image style="width: 100px; height: 50px;border: 1px solid #eee" :src="getFullUrl(item.signature)" mode="aspectFit" @click="previewImage(item.signature)"></image>
</view>
</view>
</view>
</view>
</view>
</view>
</view>
<!-- 空审批记录提示 -->
<view class="card empty-card" v-if="form.outsideAccommodationApprovals && form.outsideAccommodationApprovals.length === 0">
<view class="card-header">
<uni-icons type="checkbox" size="24" color="#409EFF"></uni-icons>
<text class="card-title">审批记录</text>
</view>
<view class="empty-tip">
<uni-icons type="empty" size="60" color="#ccc"></uni-icons>
<text class="empty-text">暂无审批记录</text>
</view>
</view>
<!-- 底部留白 -->
<view class="bottom-space"></view>
</scroll-view>
</view>
</template>
<script>
import config from '@/config'
export default {
name: "OutsideAccommodationDetail",
props: {
form: {
type: Object,
default: () => ({})
}
},
data() {
return {
// 文件服务器基础地址
baseFileUrl: config.baseUrl
};
},
methods: {
/**
* 格式化日期
* @param {String} dateStr 日期字符串
* @returns {String} 格式化后的日期
*/
formatDate(dateStr) {
if (!dateStr) return '';
try {
const date = new Date(dateStr);
if (isNaN(date.getTime())) return dateStr;
return `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')}`;
} catch (e) {
return dateStr;
}
},
/**
* 格式化文件大小
* @param {Number} size 字节数
* @returns {String} 格式化后的大小
*/
formatFileSize(size) {
if (!size) return '0B';
if (size < 1024) return `${size}B`;
if (size < 1024 * 1024) return `${(size / 1024).toFixed(2)}KB`;
return `${(size / (1024 * 1024)).toFixed(2)}MB`;
},
/**
* 获取申请状态文本
* @param {Number} status 状态码
* @returns {String} 状态文本
*/
getStatusText(status) {
const statusMap = {
0: '待提交',
1: '待辅导员审批',
2: '待学院书记审批',
3: '待学工处审批',
4: '待学校领导审批',
5: '审核通过',
6: '已驳回'
};
return statusMap[status] || '未知状态';
},
/**
* 获取申请状态样式类
* @param {Number} status 状态码
* @returns {String} 样式类名
*/
getStatusClass(status) {
const classMap = {
0: 'status-pending',
1: 'status-processing',
2: 'status-processing',
3: 'status-processing',
4: 'status-processing',
5: 'status-success',
6: 'status-reject'
};
return classMap[status] || 'status-default';
},
/**
* 获取完整的文件URL
* @param {String} path 相对路径
* @returns {String} 完整URL
*/
getFullUrl(path) {
if (!path) return '';
// 如果是完整URL直接返回否则拼接基础地址
if (path.startsWith('http')) return path;
return this.baseFileUrl + path;
},
/**
* 预览图片
* @param {String} url 图片地址
*/
previewImage(url) {
if (!url) {
uni.showToast({
title: '图片地址不存在',
icon: 'none'
});
return;
}
const fullUrl = this.getFullUrl(url);
uni.previewImage({
urls: [fullUrl],
current: fullUrl
});
}
}
};
</script>
<style scoped lang="scss">
// 全局样式
.container {
width: 100%;
min-height: 100vh;
background-color: #f8f9fa;
}
// 页面头部
.page-header {
padding: 24rpx 30rpx;
background-color: #fff;
box-shadow: 0 2rpx 12rpx rgba(0, 0, 0, 0.05);
.page-title {
font-size: 36rpx;
font-weight: 600;
color: #333;
}
}
// 滚动容器
.content-scroll {
width: 100%;
height: calc(100vh - 96rpx);
}
// 通用卡片样式
.card {
margin: 20rpx 30rpx;
padding: 0;
background-color: #fff;
border-radius: 16rpx;
box-shadow: 0 4rpx 16rpx rgba(0, 0, 0, 0.04);
overflow: hidden;
&.overview-card {
padding: 30rpx;
background: linear-gradient(135deg, #e8f4fd 0%, #f0f8fb 100%);
.overview-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 24rpx;
.overview-left {
.apply-no {
font-size: 26rpx;
color: #666;
display: block;
margin-bottom: 8rpx;
}
.student-name {
font-size: 32rpx;
font-weight: 600;
color: #333;
}
}
}
.overview-info {
display: flex;
flex-wrap: wrap;
gap: 16rpx 0;
.overview-item {
width: 100%;
display: flex;
align-items: center;
.info-label {
font-size: 26rpx;
color: #666;
min-width: 120rpx;
}
.info-value {
font-size: 26rpx;
color: #333;
flex: 1;
}
}
}
}
&.empty-card {
.empty-tip {
padding: 60rpx 0;
text-align: center;
}
}
.card-header {
display: flex;
align-items: center;
padding: 24rpx 30rpx;
border-bottom: 1px solid #f5f5f5;
.card-title {
font-size: 30rpx;
font-weight: 600;
color: #333;
margin-left: 12rpx;
}
}
.form-list {
padding: 24rpx 30rpx;
.form-item {
display: flex;
align-items: center;
padding: 16rpx 0;
border-bottom: 1px solid #f9f9f9;
&:last-child {
border-bottom: none;
}
&.signature-item {
align-items: flex-start;
.signature-img {
width: 220rpx;
height: 88rpx;
border: 1px solid #eee;
border-radius: 8rpx;
margin-top: 4rpx;
}
}
&.promise-item {
align-items: flex-start;
.promise-content {
line-height: 1.8;
color: #333;
font-size: 26rpx;
flex: 1;
p {
margin: 8rpx 0;
}
}
}
.label {
font-size: 28rpx;
color: #666;
min-width: 120rpx;
flex-shrink: 0;
}
.value {
font-size: 28rpx;
color: #333;
flex: 1;
word-break: break-all;
}
}
}
.attachment-list {
padding: 24rpx 30rpx;
.attachment-item {
display: flex;
align-items: center;
padding: 20rpx 0;
border-bottom: 1px solid #f9f9f9;
&:last-child {
border-bottom: none;
}
.file-icon {
width: 60rpx;
height: 60rpx;
border-radius: 12rpx;
background-color: #e8f4fd;
display: flex;
align-items: center;
justify-content: center;
}
.file-info {
flex: 1;
margin: 0 20rpx;
.file-name {
font-size: 28rpx;
color: #333;
margin-bottom: 4rpx;
display: block;
}
.file-size {
font-size: 24rpx;
color: #999;
}
}
}
}
.approval-list {
padding: 24rpx 30rpx;
.approval-item {
position: relative;
padding-left: 20rpx;
margin-bottom: 20rpx;
&:last-child {
margin-bottom: 0;
}
.approval-line {
display: flex;
position: relative;
&::after {
content: '';
position: absolute;
left: 10rpx;
top: 30rpx;
bottom: -20rpx;
width: 2rpx;
background-color: #eee;
z-index: 1;
}
&:last-child::after {
display: none;
}
}
.approval-dot {
width: 20rpx;
height: 20rpx;
border-radius: 50%;
background-color: #ddd;
z-index: 2;
flex-shrink: 0;
&.dot-success {
background-color: #67c23a;
}
&.dot-danger {
background-color: #f56c6c;
}
&.dot-pending {
background-color: #e6a23c;
}
}
.approval-content {
flex: 1;
margin-left: 20rpx;
.approval-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 12rpx;
.approval-node {
font-size: 28rpx;
font-weight: 500;
color: #333;
}
.approval-time {
font-size: 24rpx;
color: #999;
}
}
.approval-body {
.approval-info {
display: flex;
align-items: center;
margin-bottom: 8rpx;
font-size: 26rpx;
.info-key {
color: #666;
min-width: 80rpx;
}
.info-val {
color: #333;
flex: 1;
}
}
}
}
}
}
}
// 标签样式
.status-tag, .fee-tag, .opinion-tag, .approval-result-tag {
padding: 8rpx 16rpx;
border-radius: 24rpx;
font-size: 24rpx;
font-weight: 500;
display: inline-block;
// 申请状态
&.status-pending {
background-color: #fdf6ec;
color: #e6a23c;
}
&.status-processing {
background-color: #e8f4fd;
color: #409eff;
}
&.status-success {
background-color: #f0f9eb;
color: #67c23a;
}
&.status-reject {
background-color: #fef0f0;
color: #f56c6c;
}
&.status-default {
background-color: #f5f5f5;
color: #909399;
}
// 费用状态
&.fee-paid {
background-color: #f0f9eb;
color: #67c23a;
margin-right: 10rpx;
}
&.fee-unpaid {
background-color: #fef0f0;
color: #f56c6c;
margin-right: 10rpx;
}
// 家长意见
&.opinion-agree {
background-color: #f0f9eb;
color: #67c23a;
}
&.opinion-disagree {
background-color: #fef0f0;
color: #f56c6c;
}
// 审批结果
&.result-approve {
background-color: #f0f9eb;
color: #67c23a;
}
&.result-reject {
background-color: #fef0f0;
color: #f56c6c;
}
&.result-pending {
background-color: #fdf6ec;
color: #e6a23c;
}
}
// 文本颜色
.text-success {
color: #67c23a !important;
}
.text-danger {
color: #f56c6c !important;
}
// 空提示
.empty-tip {
empty-text {
font-size: 28rpx;
color: #ccc;
margin-top: 20rpx;
}
}
// 底部留白
.bottom-space {
height: 40rpx;
}
</style>