2024 lines
56 KiB
Vue
2024 lines
56 KiB
Vue
<template>
|
||
<view class="app-container">
|
||
<!-- 顶部导航栏 -->
|
||
<!-- <view class="nav-bar" @click="goBack">
|
||
<uni-icons type="back" size="20" color="#fff"></uni-icons>
|
||
<text class="nav-title">外宿申请表单</text>
|
||
</view> -->
|
||
|
||
<!-- 表单主体容器 -->
|
||
<view class="main-container">
|
||
<!-- 选项卡容器 - 原生选项卡 -->
|
||
<view class="tabs-container">
|
||
<view class="tabs-header">
|
||
<!-- 详情模式下禁用选项卡切换 -->
|
||
<view class="tab-item" :class="{ active: activeTab === 0 }" @click="switchTab(0)">
|
||
<uni-icons type="contact" size="16" :color="activeTab === 0 ? '#409EFF' : '#666'"></uni-icons>
|
||
<text class="tab-text">基本信息</text>
|
||
</view>
|
||
<view class="tab-item" :class="{ active: activeTab === 1 }" @click="switchTab(1)">
|
||
<uni-icons type="chat" size="16" :color="activeTab === 1 ? '#409EFF' : '#666'"></uni-icons>
|
||
<text class="tab-text">外宿原因</text>
|
||
</view>
|
||
<view class="tab-item" :class="{ active: activeTab === 2 }" @click="switchTab(2)">
|
||
<uni-icons type="location" size="16" :color="activeTab === 2 ? '#409EFF' : '#666'"></uni-icons>
|
||
<text class="tab-text">地址信息</text>
|
||
</view>
|
||
<view class="tab-item" :class="{ active: activeTab === 3 }" @click="switchTab(3)">
|
||
<uni-icons type="person-filled" size="16"
|
||
:color="activeTab === 3 ? '#409EFF' : '#666'"></uni-icons>
|
||
<text class="tab-text">家长信息</text>
|
||
</view>
|
||
<view class="tab-item" :class="{ active: activeTab === 4 }" @click="switchTab(4)">
|
||
<uni-icons type="checkbox" size="16" :color="activeTab === 4 ? '#409EFF' : '#666'"></uni-icons>
|
||
<text class="tab-text">本人承诺</text>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 表单内容区域 -->
|
||
<scroll-view scroll-y class="form-scroll" :style="{ height: formScrollHeight }">
|
||
<view class="form-wrapper">
|
||
<!-- 1. 基本信息标签页 -->
|
||
<view v-show="activeTab === 0" class="tab-panel">
|
||
<view class="form-card">
|
||
<view class="card-title">基本信息</view>
|
||
<view class="form-item">
|
||
<label class="form-label">原宿舍号</label>
|
||
<!-- 详情模式下禁用输入框 -->
|
||
<input v-model="form.originalDormitory" placeholder="如:1栋302"
|
||
class="form-input" :disabled="isDetailMode"></input>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">姓名</label>
|
||
<input v-model="form.studentName" placeholder="请输入姓名" class="form-input" :disabled="isDetailMode"></input>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">性别</label>
|
||
<!-- 详情模式下禁用选择器 -->
|
||
<picker mode="selector" :range="genderOptions" :range-key="'text'" v-model="form.gender"
|
||
@change="handleGenderChange" :disabled="isDetailMode">
|
||
<view class="picker-input">
|
||
{{ form.gender ? getGenderText(form.gender) : '请选择性别' }}
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">出生年月</label>
|
||
<picker mode="date" :value="form.birthDate" @change="handleBirthDateChange" :disabled="isDetailMode">
|
||
<view class="picker-input">
|
||
{{ form.birthDate || '请选择出生年月' }}
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">专业系</label>
|
||
<input v-model="form.majorName" placeholder="请输入专业系" class="form-input" :disabled="isDetailMode"></input>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">班级</label>
|
||
<input v-model="form.className" placeholder="请输入班级" class="form-input" :disabled="isDetailMode"></input>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">学号</label>
|
||
<input v-model="form.studentNo" placeholder="请输入学号" class="form-input" :disabled="isDetailMode"></input>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">身份证号码</label>
|
||
<input v-model="form.idCard" placeholder="请输入身份证号码" class="form-input" :disabled="isDetailMode"></input>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">联系电话</label>
|
||
<input v-model="form.studentPhone" placeholder="请输入联系电话" class="form-input"
|
||
type="number" :disabled="isDetailMode"></input>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">宿费交纳情况</label>
|
||
<view class="fee-status">{{ form.accommodationFee || '暂无数据' }}</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 2. 外宿原因标签页 -->
|
||
<view v-show="activeTab === 1" class="tab-panel">
|
||
<view class="form-card">
|
||
<view class="card-title card-apply">外宿原因及附件</view>
|
||
<view class="form-item">
|
||
<label class="form-label">外宿原因</label>
|
||
<textarea v-model="form.applyReason" placeholder="请详细描述外宿原因" class="form-textarea"
|
||
rows="5" :disabled="isDetailMode"></textarea>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">佐证附件</label>
|
||
<!-- 详情模式下禁用文件上传 -->
|
||
<view class="upload-btn" @click="chooseAffixFile" v-if="!isDetailMode">
|
||
<text class="upload-icon">+</text>
|
||
<text>上传文件</text>
|
||
</view>
|
||
<view class="file-list" v-if="affixFiles.length">
|
||
<view class="file-item" v-for="(file, index) in affixFiles" :key="index">
|
||
<text class="file-name" @click="previewImage(baseUrl + file.savePath)">{{ file.attachmentName || file.trueName }}</text>
|
||
<!-- 详情模式下禁用删除按钮 -->
|
||
<!-- <button size="mini" type="warn" @click="deleteAffixFile(index)" v-if="!isDetailMode"
|
||
class="delete-btn" :disabled="isDetailMode">删除</button> -->
|
||
<uni-icons type="trash-filled" size="30" @click="deleteAffixFile(index)" v-if="!isDetailMode"
|
||
class="delete-btn"></uni-icons>
|
||
</view>
|
||
</view>
|
||
<!-- <uni-file-picker :auto-upload="false" @select="chooseAffixFile" @delete="deleteAffixFile"></uni-file-picker> -->
|
||
<view class="upload-tip">支持上传jpg/png/pdf格式文件,单个文件不超过10MB(如病例、住房证明等)</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">电子签名</label>
|
||
<view class="signature-container">
|
||
<!-- 详情模式下隐藏签名按钮 -->
|
||
<button size="mini" type="primary" @click="!isDetailMode && openSignature('student')"
|
||
class="sign-btn" v-if="!isDetailMode">
|
||
签署电子签名
|
||
</button>
|
||
<image v-if="form.studentSignature" :src="baseUrl + form.studentSignature"
|
||
class="signature-preview" mode="widthFix"
|
||
@click="previewImage(baseUrl + form.studentSignature)"></image>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 3. 地址信息标签页 -->
|
||
<view v-show="activeTab === 2" class="tab-panel">
|
||
<view class="form-card">
|
||
<view class="card-title card-area">外宿地址信息</view>
|
||
<!-- 替换为省市区选择器 -->
|
||
<view class="form-item">
|
||
<label class="form-label">外宿地区</label>
|
||
<!-- 详情模式下禁用省市区选择器 -->
|
||
<view class="area-picker" @click="!isDetailMode && (showAreaPicker = true)" :class="{disabled: isDetailMode}">
|
||
<text class="picker-text">{{ areaText || '请选择省/市/区' }}</text>
|
||
<uni-icons type="arrowright" size="16" color="#999" class="picker-icon"></uni-icons>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">详细门牌号</label>
|
||
<input v-model="form.outsideAddress" placeholder="请输入详细门牌号(如:XX小区3栋502)"
|
||
class="form-input" :disabled="isDetailMode"></input>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">紧急联系人</label>
|
||
<input v-model="form.emergencyContact" placeholder="请输入紧急联系人姓名"
|
||
class="form-input" :disabled="isDetailMode"></input>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">紧急联系电话</label>
|
||
<input v-model="form.emergencyPhone" placeholder="请输入紧急联系人电话" class="form-input"
|
||
type="number" :disabled="isDetailMode"></input>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 4. 家长信息标签页 -->
|
||
<view v-show="activeTab === 3" class="tab-panel">
|
||
<view class="form-card">
|
||
<view class="card-title card-parent">家长意见及联系方式</view>
|
||
<view class="form-item">
|
||
<label class="form-label">家长意见</label>
|
||
<!-- 详情模式下禁用选择器 -->
|
||
<picker mode="selector" :range="parentOpinionOptions" :range-key="'text'"
|
||
v-model="form.parentOpinion" @change="handleParentOpinionChange" :disabled="isDetailMode">
|
||
<view class="picker-input">
|
||
{{ form.parentOpinion ? getParentOpinionText(form.parentOpinion) : '请选择家长意见' }}
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">家长签字附件</label>
|
||
<!-- 详情模式下禁用图片上传 -->
|
||
<view class="upload-btn" @click="handleUpload" v-if="!isDetailMode">
|
||
<text class="upload-icon">+</text>
|
||
<text>上传图片</text>
|
||
</view>
|
||
<view class="upload-preview" v-if="form.parentSignAttachment">
|
||
<image :src="baseUrl + form.parentSignAttachment" mode="aspectFill" />
|
||
</view>
|
||
<view class="upload-tip">只能上传jpg/png文件,且不超过2M</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">家长联系电话</label>
|
||
<input v-model="form.parentPhone" placeholder="请输入家长联系电话" class="form-input"
|
||
type="number" :disabled="isDetailMode"></input>
|
||
</view>
|
||
|
||
<!-- 家长地址省市区选择器 -->
|
||
<view class="form-item">
|
||
<label class="form-label">家长通讯地区</label>
|
||
<!-- 详情模式下禁用省市区选择器 -->
|
||
<view class="area-picker" @click="!isDetailMode && (showParentAreaPicker = true)" :class="{disabled: isDetailMode}">
|
||
<text class="picker-text">{{ parentAreaText || '请选择省/市/区' }}</text>
|
||
<uni-icons type="arrowright" size="16" color="#999" class="picker-icon"></uni-icons>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">家长详细地址</label>
|
||
<input v-model="form.parentDetailAddress" placeholder="请输入家长详细通讯地址"
|
||
class="form-input" :disabled="isDetailMode"></input>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 5. 本人承诺标签页 -->
|
||
<view v-show="activeTab === 4" class="tab-panel">
|
||
<view class="form-card">
|
||
<view class="card-title">本人承诺</view>
|
||
<view class="form-item">
|
||
<label class="form-label">承诺内容</label>
|
||
<view class="promise-content">
|
||
<text>1.自觉遵守国家法律、法规;</text>
|
||
<text>2.自觉遵守学生行为规范和学校的规章制度,遵守社会公德;</text>
|
||
<text>3.自觉遵守外宿住址所在社区的有关管理规定;</text>
|
||
<text>4.本人申请外宿,属个人自愿行为,外宿期间发生的一切事故,造成本人、他人或集体的人身、财产损害的,学校不负责任。</text>
|
||
</view>
|
||
</view>
|
||
|
||
<view class="form-item">
|
||
<label class="form-label">承诺签名</label>
|
||
<view class="signature-container">
|
||
<!-- 详情模式下隐藏签名按钮 -->
|
||
<button size="mini" type="primary" @click="!isDetailMode && openSignature('promise')"
|
||
class="sign-btn" v-if="!isDetailMode">
|
||
签署承诺签名
|
||
</button>
|
||
<image v-if="form.studentPromiseSign" :src="baseUrl + form.studentPromiseSign"
|
||
class="signature-preview" mode="widthFix"
|
||
@click="previewImage(baseUrl + form.studentPromiseSign)"></image>
|
||
<view class="date-item">
|
||
<label class="form-label">签署日期</label>
|
||
<!-- 详情模式下禁用日期选择器 -->
|
||
<picker mode="date" :value="form.promiseDate" @change="handlePromiseDateChange" :disabled="isDetailMode">
|
||
<view class="picker-input">
|
||
{{ form.promiseDate || '请选择签署日期' }}
|
||
</view>
|
||
</picker>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 提交按钮区域 -->
|
||
<!-- 详情模式下隐藏所有操作按钮 -->
|
||
<view class="submit-container" v-if="!isDetailMode">
|
||
<button type="primary" @click="submitForm(0)" class="submit-btn">
|
||
<uni-icons type="folder" size="16"></uni-icons> 保存
|
||
</button>
|
||
<button type="primary" @click="submitForm(1)" class="submit-btn submit-primary"
|
||
v-if="form.status == 0">
|
||
<uni-icons type="checkmark" size="16"></uni-icons> 提交申请
|
||
</button>
|
||
<button type="default" @click="resetForm" class="reset-btn" v-if="!currentId">
|
||
<uni-icons type="refresh" size="16"></uni-icons> 重置
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</scroll-view>
|
||
</view>
|
||
|
||
<!-- 申请须知弹窗(5秒自动关闭) -->
|
||
<!-- 详情模式下不显示该弹窗 -->
|
||
<transition name="popup-fade" v-if="!isDetailMode">
|
||
<view class="mask" v-if="showNoticePopup" @click.stop="() => {}"></view>
|
||
</transition>
|
||
<transition name="popup-slide" v-if="!isDetailMode">
|
||
<view class="notice-popup" v-if="showNoticePopup">
|
||
<view class="popup-header">
|
||
<text class="popup-title">申请须知</text>
|
||
<text class="countdown" v-if="countdown > 0">剩余 {{countdown}} 秒</text>
|
||
</view>
|
||
<view class="popup-content">
|
||
<view class="notice-item">1. 仅限南宁市学生申请外宿,需完整填写所有必填项并上传对应附件</view>
|
||
<view class="notice-item">2. 外宿原因需上传佐证材料(因病提供病例,外宿居所提供住房证明等)</view>
|
||
<view class="notice-item">3. 家长意见需上传签字扫描件,本人承诺需完成电子签名</view>
|
||
<view class="notice-item">4. 审批通过后需经学院领导签署意见,方可办理退房手续</view>
|
||
</view>
|
||
<view class="popup-footer">
|
||
<button type="primary" @click="closeNoticePopup" :disabled="countdown > 0" class="popup-btn">
|
||
我已阅读并知晓
|
||
</button>
|
||
</view>
|
||
</view>
|
||
</transition>
|
||
|
||
<!-- 通用签名弹窗(使用 uni-app 内置绘图 API) -->
|
||
<!-- 详情模式下不显示签名弹窗 -->
|
||
<view class="mask" v-if="!isDetailMode && showSignPopup" @click="closeSignPopup"></view>
|
||
<view class="sign-popup" v-if="!isDetailMode && showSignPopup">
|
||
<view class="sign-popup-header">
|
||
<text class="sign-title">{{ currentSignType === 'student' ? '电子签名' : '承诺签名' }}</text>
|
||
<uni-icons type="close" size="20" @click="closeSignPopup" class="close-icon"></uni-icons>
|
||
</view>
|
||
<!-- 扩大的签名画布 -->
|
||
<canvas class="sign-canvas" canvas-id="commonSignCanvas" @touchstart="handleSignStart"
|
||
@touchmove="handleSignMove" @touchend="handleSignEnd" @mousedown="handleSignStart"
|
||
@mousemove="handleSignMove" @mouseup="handleSignEnd" @mouseleave="handleSignEnd"></canvas>
|
||
<view class="sign-popup-footer">
|
||
<button size="mini" type="default" @click="clearSign" class="sign-btn-default">清除</button>
|
||
<button size="mini" type="primary" @click="saveSign" class="sign-btn-primary">确认</button>
|
||
</view>
|
||
</view>
|
||
|
||
<!-- 外宿地址省市区选择器弹窗 -->
|
||
<!-- 详情模式下不显示省市区选择器弹窗 -->
|
||
<view class="mask" v-if="!isDetailMode && showAreaPicker" @click="showAreaPicker = false"></view>
|
||
<view class="area-popup" v-if="!isDetailMode && showAreaPicker">
|
||
<view class="area-popup-header">
|
||
<button size="mini" type="default" @click="showAreaPicker = false" class="btn-cancel">取消</button>
|
||
<button size="mini" type="primary" @click="confirmArea" class="btn-confirm">确认</button>
|
||
</view>
|
||
<picker-view :value="areaValue" @change="onAreaChange" class="picker-view" indicator-style="height: 50px;">
|
||
<picker-view-column>
|
||
<view class="picker-item" v-for="(item, index) in provinces" :key="index">{{ item.value }}</view>
|
||
</picker-view-column>
|
||
<picker-view-column>
|
||
<view class="picker-item" v-for="(item, index) in cities" :key="index">{{ item.value }}</view>
|
||
</picker-view-column>
|
||
<picker-view-column>
|
||
<view class="picker-item" v-for="(item, index) in areas" :key="index">{{ item.value }}</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
|
||
<!-- 家长地址省市区选择器弹窗 -->
|
||
<!-- 详情模式下不显示省市区选择器弹窗 -->
|
||
<view class="mask" v-if="!isDetailMode && showParentAreaPicker" @click="showParentAreaPicker = false"></view>
|
||
<view class="area-popup" v-if="!isDetailMode && showParentAreaPicker">
|
||
<view class="area-popup-header">
|
||
<button size="mini" type="default" @click="showParentAreaPicker = false" class="btn-cancel">取消</button>
|
||
<button size="mini" type="primary" @click="confirmParentArea" class="btn-confirm">确认</button>
|
||
</view>
|
||
<picker-view :value="parentAreaValue" @change="onParentAreaChange" class="picker-view"
|
||
indicator-style="height: 50px;">
|
||
<picker-view-column>
|
||
<view class="picker-item" v-for="(item, index) in provinces" :key="index">{{ item.value }}</view>
|
||
</picker-view-column>
|
||
<picker-view-column>
|
||
<view class="picker-item" v-for="(item, index) in parentCities" :key="index">{{ item.value }}</view>
|
||
</picker-view-column>
|
||
<picker-view-column>
|
||
<view class="picker-item" v-for="(item, index) in parentAreas" :key="index">{{ item.value }}</view>
|
||
</picker-view-column>
|
||
</picker-view>
|
||
</view>
|
||
|
||
</view>
|
||
</template>
|
||
|
||
<script>
|
||
import {
|
||
getOwnInfo,
|
||
listStudent,
|
||
getOutsideAccommodationApply,
|
||
updateOutsideAccommodationApply,
|
||
addOutsideAccommodationApply,
|
||
getOwnLog
|
||
} from '@/api/dms/outsideAccommodation/outsideAccommodationApply'
|
||
import {
|
||
batchAddOutsideAccommodationAttachment,
|
||
deleteOutsideAccommodationAttachmentNameAndStuName
|
||
} from "@/api/dms/outsideAccommodation/outsideAccommodationAttachment";
|
||
import {
|
||
getUserProfile
|
||
} from '@/api/system/user'
|
||
import area from '@/components/complex-picker-region/area.js'
|
||
import config from '@/config'
|
||
import {
|
||
checkPic
|
||
} from "@/utils/checkPic.js";
|
||
import uploadFile from "@/plugins/upload.js";
|
||
import {
|
||
getAffixItems, deleteAffix
|
||
} from "@/api/affix.js";
|
||
export default {
|
||
name: 'OutsideAccommodationApply',
|
||
data() {
|
||
return {
|
||
// 新增:是否为详情查看模式(核心控制变量)
|
||
isDetailMode: false,
|
||
// 激活的标签页索引
|
||
activeTab: 0,
|
||
// 表单滚动高度
|
||
formScrollHeight: '70vh',
|
||
// 申请须知弹窗控制
|
||
showNoticePopup: false,
|
||
countdown: 5,
|
||
// 通用签名弹窗控制
|
||
showSignPopup: false,
|
||
currentSignType: '', // student / promise
|
||
signCtx: null, // 签名绘图上下文
|
||
isSigning: false, // 是否正在签名
|
||
hasSigned: false, // 是否有签名内容
|
||
canvasWidth: 680, // 画布宽度扩大(rpx转px)
|
||
canvasHeight: 300, // 画布高度扩大(rpx转px)
|
||
// 省市区选择器控制
|
||
showAreaPicker: false,
|
||
showParentAreaPicker: false,
|
||
provinces: [],
|
||
cities: [],
|
||
areas: [],
|
||
parentCities: [],
|
||
parentAreas: [],
|
||
areaValue: [0, 0, 0],
|
||
parentAreaValue: [0, 0, 0],
|
||
areaText: '',
|
||
parentAreaText: '',
|
||
// 表单数据
|
||
form: {
|
||
originalDormitory: '',
|
||
studentName: '',
|
||
gender: '',
|
||
birthDate: '',
|
||
majorName: '',
|
||
className: '',
|
||
studentNo: '',
|
||
deptName: '',
|
||
teacherName: '',
|
||
idCard: '',
|
||
studentPhone: '',
|
||
accommodationFeeStatus: 1,
|
||
applyReason: '',
|
||
address: '', // 省市区拼接结果(格式:北京市/北京市/东城区)
|
||
outsideAddress: '',
|
||
emergencyContact: '',
|
||
emergencyPhone: '',
|
||
parentOpinion: 1, // 默认同意
|
||
parentPhone: '',
|
||
parentAddress: '', // 家长省市区拼接结果
|
||
parentDetailAddress: '',
|
||
studentSignature: '',
|
||
studentPromiseSign: '',
|
||
promiseDate: '',
|
||
parentSignAttachment: '',
|
||
accommodationFee: '',
|
||
affixId: '',
|
||
status: 0,
|
||
id: '',
|
||
applyNo: '',
|
||
endDate: '',
|
||
promiseContent: ''
|
||
},
|
||
// 下拉选项
|
||
genderOptions: [{
|
||
text: '男',
|
||
value: '1'
|
||
},
|
||
{
|
||
text: '女',
|
||
value: '0'
|
||
}
|
||
],
|
||
parentOpinionOptions: [{
|
||
text: '同意外宿',
|
||
value: 1
|
||
},
|
||
{
|
||
text: '不同意外宿',
|
||
value: 0
|
||
}
|
||
],
|
||
// 文件上传相关
|
||
affixFiles: [],
|
||
parentSignFiles: "",
|
||
// 页面状态
|
||
currentId: null,
|
||
loading: false,
|
||
roleGroup: null,
|
||
// 基础配置
|
||
baseUrl: config.baseUrl || '',
|
||
uploadImgUrl: (config.baseUrl || '') + '/common/upload'
|
||
}
|
||
},
|
||
onLoad(options) {
|
||
// 新增:判断是否为详情模式(核心逻辑)
|
||
this.isDetailMode = options.type === 'detail';
|
||
|
||
// 1. 初始化省市区数据
|
||
this.initAreaData();
|
||
// 2. 初始化页面高度
|
||
this.initPageHeight();
|
||
|
||
// 3. 加载表单数据
|
||
if (options.id) {
|
||
this.currentId = options.id;
|
||
this.loadFormData(options.id);
|
||
} else {
|
||
this.getUser();
|
||
}
|
||
},
|
||
onReady() {
|
||
// 新增:详情模式下不打开申请须知弹窗
|
||
if (!this.isDetailMode) {
|
||
// 1. 打开申请须知弹窗
|
||
this.openNoticePopup();
|
||
}
|
||
},
|
||
methods: {
|
||
// ========== 省市区选择器核心方法 ==========
|
||
initAreaData() {
|
||
this.provinces = area || [];
|
||
if (this.provinces.length > 0) {
|
||
this.cities = this.provinces[0].children || [];
|
||
this.parentCities = this.provinces[0].children || [];
|
||
if (this.cities.length > 0) {
|
||
this.areas = this.cities[0].children || [];
|
||
this.parentAreas = this.cities[0].children || [];
|
||
}
|
||
}
|
||
},
|
||
onAreaChange(e) {
|
||
const val = e.detail.value;
|
||
this.areaValue = val;
|
||
if (this.provinces[val[0]]) {
|
||
this.cities = this.provinces[val[0]].children || [];
|
||
if (this.cities[val[1]]) {
|
||
this.areas = this.cities[val[1]].children || [];
|
||
}
|
||
}
|
||
},
|
||
onParentAreaChange(e) {
|
||
const val = e.detail.value;
|
||
this.parentAreaValue = val;
|
||
if (this.provinces[val[0]]) {
|
||
this.parentCities = this.provinces[val[0]].children || [];
|
||
if (this.parentCities[val[1]]) {
|
||
this.parentAreas = this.parentCities[val[1]].children || [];
|
||
}
|
||
}
|
||
},
|
||
confirmArea() {
|
||
const [pIdx, cIdx, aIdx] = this.areaValue;
|
||
const province = this.provinces[pIdx]?.value || '';
|
||
const city = this.cities[cIdx]?.value || '';
|
||
const area = this.areas[aIdx]?.value || '';
|
||
this.areaText = `${province}/${city}/${area}`;
|
||
this.form.address = this.areaText;
|
||
this.showAreaPicker = false;
|
||
},
|
||
confirmParentArea() {
|
||
const [pIdx, cIdx, aIdx] = this.parentAreaValue;
|
||
const province = this.provinces[pIdx]?.value || '';
|
||
const city = this.parentCities[cIdx]?.value || '';
|
||
const area = this.parentAreas[aIdx]?.value || '';
|
||
this.parentAreaText = `${province}/${city}/${area}`;
|
||
this.form.parentAddress = this.parentAreaText;
|
||
this.showParentAreaPicker = false;
|
||
},
|
||
|
||
// ========== 表单基础方法 ==========
|
||
getGenderText(value) {
|
||
const item = this.genderOptions.find(item => item.value === value);
|
||
return item ? item.text : '';
|
||
},
|
||
getParentOpinionText(value) {
|
||
const item = this.parentOpinionOptions.find(item => item.value === value);
|
||
return item ? item.text : '';
|
||
},
|
||
handleGenderChange(e) {
|
||
this.form.gender = e.detail.value;
|
||
},
|
||
handleBirthDateChange(e) {
|
||
this.form.birthDate = e.detail.value;
|
||
},
|
||
handleParentOpinionChange(e) {
|
||
this.form.parentOpinion = e.detail.value;
|
||
},
|
||
handlePromiseDateChange(e) {
|
||
this.form.promiseDate = e.detail.value;
|
||
},
|
||
initPageHeight() {
|
||
try {
|
||
const systemInfo = uni.getSystemInfoSync();
|
||
const navHeight = systemInfo.statusBarHeight + 44;
|
||
const tabsHeight = 80;
|
||
const submitHeight = 100;
|
||
const totalFixedHeight = (navHeight * 2) + (tabsHeight + submitHeight) / 2;
|
||
this.formScrollHeight = 'calc(100vh - ' + totalFixedHeight + 'px)';
|
||
|
||
// 计算画布实际像素尺寸(rpx转px) - 扩大后的尺寸
|
||
const pxRatio = systemInfo.windowWidth / 750; // 750rpx = 屏幕宽度
|
||
this.canvasWidth = 645 * pxRatio; // 扩大签名画布宽度
|
||
this.canvasHeight = 300 * pxRatio; // 扩大签名画布高度
|
||
} catch (e) {
|
||
this.formScrollHeight = '80vh';
|
||
this.canvasWidth = 340; // 默认值扩大
|
||
this.canvasHeight = 150; // 默认值扩大
|
||
}
|
||
},
|
||
openNoticePopup() {
|
||
this.showNoticePopup = true;
|
||
this.startCountdown();
|
||
},
|
||
startCountdown() {
|
||
this.countdown = 5;
|
||
const timer = setInterval(() => {
|
||
this.countdown--;
|
||
if (this.countdown <= 0) {
|
||
clearInterval(timer);
|
||
}
|
||
}, 1000);
|
||
},
|
||
closeNoticePopup() {
|
||
this.showNoticePopup = false;
|
||
this.countdown = 0;
|
||
},
|
||
switchTab(index) {
|
||
this.activeTab = index;
|
||
uni.pageScrollTo({
|
||
scrollTop: 0,
|
||
duration: 0
|
||
});
|
||
},
|
||
|
||
// ========== 通用签名功能(修复无法签名+虚线问题) ==========
|
||
// 初始化签名画布(兼容所有端)
|
||
initSignCanvas() {
|
||
try {
|
||
// 获取绘图上下文(uni-app 兼容所有端的方式)
|
||
this.signCtx = uni.createCanvasContext('commonSignCanvas', this);
|
||
|
||
// 设置画笔样式(关键:调整线条参数确保实线效果)
|
||
this.signCtx.setStrokeStyle('#000000');
|
||
this.signCtx.setLineWidth(3); // 线条宽度
|
||
this.signCtx.setLineCap('round'); // 线条端点圆润
|
||
this.signCtx.setLineJoin('round'); // 线条拐角圆润
|
||
this.signCtx.setMiterLimit(1); // 限制斜接长度
|
||
|
||
// 清空画布
|
||
this.clearSign();
|
||
console.log('签名画布初始化成功');
|
||
} catch (e) {
|
||
console.error('初始化签名画布失败:', e);
|
||
uni.showToast({
|
||
title: '签名画布初始化失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
},
|
||
|
||
// 打开签名弹窗
|
||
openSignature(type) {
|
||
this.currentSignType = type;
|
||
this.showSignPopup = true;
|
||
this.hasSigned = false;
|
||
this.isSigning = false;
|
||
|
||
// 延迟初始化画布,确保 DOM 渲染完成
|
||
setTimeout(() => {
|
||
this.initSignCanvas();
|
||
}, 300);
|
||
},
|
||
|
||
// 关闭签名弹窗
|
||
closeSignPopup() {
|
||
this.showSignPopup = false;
|
||
this.currentSignType = '';
|
||
this.isSigning = false;
|
||
},
|
||
|
||
// 开始签名(修复核心:简化坐标计算)
|
||
handleSignStart(e) {
|
||
if (!this.signCtx) return;
|
||
|
||
try {
|
||
this.isSigning = true;
|
||
this.hasSigned = true;
|
||
|
||
// 获取触摸/鼠标位置
|
||
const touch = e.type.includes('touch') ? e.touches[0] : e;
|
||
|
||
// 直接使用页面坐标(更稳定)
|
||
const x = touch.x || touch.clientX;
|
||
const y = touch.y || touch.clientY;
|
||
|
||
// 开始新路径
|
||
this.signCtx.beginPath();
|
||
this.signCtx.moveTo(x, y);
|
||
|
||
} catch (e) {
|
||
console.error('开始签名失败:', e);
|
||
}
|
||
},
|
||
|
||
// 签名绘制(修复核心:增量绘制+实时stroke)
|
||
handleSignMove(e) {
|
||
if (!this.isSigning || !this.signCtx) return;
|
||
|
||
try {
|
||
// 获取触摸/鼠标位置
|
||
const touch = e.type.includes('touch') ? e.touches[0] : e;
|
||
const x = touch.x || touch.clientX;
|
||
const y = touch.y || touch.clientY;
|
||
|
||
// 绘制线条(增量方式,保证实线)
|
||
this.signCtx.lineTo(x, y);
|
||
this.signCtx.stroke(); // 实时绘制,确保线条连续
|
||
this.signCtx.draw(true); // 保留之前的绘制内容
|
||
|
||
// 重新开始路径,避免重复绘制
|
||
this.signCtx.beginPath();
|
||
this.signCtx.moveTo(x, y);
|
||
|
||
} catch (e) {
|
||
console.error('签名绘制失败:', e);
|
||
}
|
||
},
|
||
|
||
// 结束签名
|
||
handleSignEnd() {
|
||
this.isSigning = false;
|
||
// 最后一次绘制,确保所有线条都显示
|
||
if (this.signCtx) {
|
||
this.signCtx.stroke();
|
||
this.signCtx.draw(true);
|
||
}
|
||
},
|
||
|
||
// 清空签名(简化版,确保可用)
|
||
clearSign() {
|
||
if (!this.signCtx) return;
|
||
|
||
try {
|
||
// 清空画布 - 使用扩大后的尺寸
|
||
this.signCtx.clearRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||
this.signCtx.fillStyle = '#ffffff';
|
||
this.signCtx.fillRect(0, 0, this.canvasWidth, this.canvasHeight);
|
||
this.signCtx.draw(true);
|
||
|
||
this.hasSigned = false;
|
||
} catch (e) {
|
||
console.error('清空签名失败:', e);
|
||
// 降级方案
|
||
uni.createCanvasContext('commonSignCanvas', this).draw(true);
|
||
this.hasSigned = false;
|
||
}
|
||
},
|
||
|
||
// 保存签名(转换为图片并上传)
|
||
saveSign() {
|
||
if (!this.hasSigned) {
|
||
uni.showToast({
|
||
title: '请先完成签名',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
this.loading = true;
|
||
|
||
// 把画布内容转换为临时文件
|
||
uni.canvasToTempFilePath({
|
||
canvasId: 'commonSignCanvas',
|
||
quality: 1.0,
|
||
success: (res) => {
|
||
// 上传签名图片
|
||
this.uploadSignImage(res.tempFilePath);
|
||
},
|
||
fail: (err) => {
|
||
console.error('转换签名为图片失败:', err);
|
||
uni.showToast({
|
||
title: '签名保存失败',
|
||
icon: 'none'
|
||
});
|
||
this.loading = false;
|
||
}
|
||
}, this);
|
||
},
|
||
|
||
// 上传签名图片
|
||
uploadSignImage(filePath) {
|
||
uni.uploadFile({
|
||
url: this.uploadImgUrl,
|
||
filePath: filePath,
|
||
name: 'file',
|
||
formData: {
|
||
type: this.currentSignType + '_signature',
|
||
timestamp: new Date().getTime()
|
||
},
|
||
success: (res) => {
|
||
try {
|
||
const data = JSON.parse(res.data);
|
||
if (data.code === 200) {
|
||
// 保存签名地址到表单
|
||
if (this.currentSignType === 'student') {
|
||
this.form.studentSignature = data.fileName;
|
||
} else {
|
||
this.form.studentPromiseSign = data.fileName;
|
||
}
|
||
uni.showToast({
|
||
title: '签名保存成功',
|
||
icon: 'success'
|
||
});
|
||
this.closeSignPopup();
|
||
} else {
|
||
uni.showToast({
|
||
title: '签名上传失败: ' + data.msg,
|
||
icon: 'none'
|
||
});
|
||
}
|
||
} catch (e) {
|
||
console.error('解析签名上传结果失败:', e);
|
||
uni.showToast({
|
||
title: '签名上传失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
},
|
||
fail: (err) => {
|
||
console.error('上传签名失败:', err);
|
||
uni.showToast({
|
||
title: '签名上传失败',
|
||
icon: 'none'
|
||
});
|
||
},
|
||
complete: () => {
|
||
this.loading = false;
|
||
}
|
||
});
|
||
},
|
||
|
||
// ========== 文件上传相关 ==========
|
||
chooseAffixFile() {
|
||
// 1. 定义affixId生成工具函数(确保uuid唯一性)
|
||
const generateUUID = () => {
|
||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||
const r = Math.random() * 16 | 0;
|
||
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
||
return v.toString(16);
|
||
});
|
||
};
|
||
|
||
uni.chooseFile({
|
||
count: 10, // 最多选择10个文件
|
||
// extension: ['.jpg', '.png', '.pdf'], // 可根据需求放开文件类型限制
|
||
success: async (chooseRes) => {
|
||
// 2. 初始化affixId(如果为空则生成唯一ID)
|
||
if (!this.form.affixId) {
|
||
this.form.affixId = generateUUID();
|
||
}
|
||
|
||
// 3. 遍历选择的文件,逐个上传(支持多文件)
|
||
for (const file of chooseRes.tempFiles) {
|
||
try {
|
||
// 4. 构造上传参数(放在循环内,确保每个文件参数正确)
|
||
const formDataObj = {
|
||
affixId: this.form.affixId,
|
||
fileName: file.name,
|
||
fileSize: file.size
|
||
};
|
||
|
||
// 5. 上传文件(await确保上传完成后再处理下一步)
|
||
const uploadRes = await uploadFile('/affix/upload', file.path, formDataObj);
|
||
const result = typeof uploadRes === 'string' ? JSON.parse(uploadRes) :
|
||
uploadRes;
|
||
|
||
// 6. 上传结果校验
|
||
if (result && result.code === 200) {
|
||
// 7. 构造文件信息对象(去重逻辑优化)
|
||
const fileInfo = {
|
||
applyId: this.form.id || '',
|
||
attachmentName: file.name,
|
||
attachmentUrl: result.savePath, // 本地路径
|
||
serverUrl: result.savePath || '', // 服务器返回的文件路径
|
||
fileId: result.id || '', // 服务器返回的文件ID
|
||
fileSize: file.size,
|
||
fileSuffix: file.name.split('.').pop().toLowerCase(),
|
||
studentName: this.form.studentName,
|
||
studentNo: this.form.studentNo,
|
||
uploadStatus: 'success' // 上传状态标记
|
||
};
|
||
|
||
// 8. 去重逻辑(优化:通过文件名+大小双重校验,避免路径重复问题)
|
||
const isDuplicate = this.affixFiles.some(item =>
|
||
item.attachmentName === file.name && item.fileSize === file.size
|
||
);
|
||
|
||
if (!isDuplicate) {
|
||
this.affixFiles.push(fileInfo);
|
||
}
|
||
console.log(this.affixFiles);
|
||
uni.showToast({
|
||
title: `文件 ${file.name} 上传成功`,
|
||
icon: 'success',
|
||
duration: 1500
|
||
});
|
||
} else {
|
||
// 上传失败处理
|
||
uni.showToast({
|
||
title: `文件 ${file.name} 上传失败:${result.message || '未知错误'}`,
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
}
|
||
} catch (error) {
|
||
// 9. 异常捕获(网络错误/上传接口异常)
|
||
console.error(`文件 ${file.name} 上传异常:`, error);
|
||
uni.showToast({
|
||
title: `文件 ${file.name} 上传异常,请重试`,
|
||
icon: 'none',
|
||
duration: 2000
|
||
});
|
||
}
|
||
}
|
||
},
|
||
// 10. 取消选择文件的处理
|
||
fail: (err) => {
|
||
console.error('选择文件失败:', err);
|
||
uni.showToast({
|
||
title: '选择文件失败,请重试',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
});
|
||
},
|
||
deleteAffixFile(index) {
|
||
const deletedFile = this.affixFiles[index];
|
||
this.affixFiles.splice(index, 1);
|
||
// this.form.affixId = this.affixFiles.length ? 'uploaded' : '';
|
||
let fileId = deletedFile.id || deletedFile.fileId
|
||
deleteAffix(fileId).then(() => {
|
||
deleteOutsideAccommodationAttachmentNameAndStuName({
|
||
attachmentName: deletedFile.trueName || deletedFile.attachmentName,
|
||
studentName: this.form.studentName
|
||
}).then(() => {
|
||
uni.showToast({
|
||
title: '删除成功',
|
||
icon: 'success'
|
||
});
|
||
}).catch(() => {
|
||
uni.showToast({
|
||
title: '删除失败',
|
||
icon: 'none'
|
||
});
|
||
});
|
||
})
|
||
|
||
},
|
||
// 家长附件上传
|
||
handleUpload() {
|
||
uni.chooseImage({
|
||
count: 3,
|
||
success: async (img) => {
|
||
let bool = await checkPic(img.tempFiles[0]);
|
||
if (bool) {
|
||
uploadFile('/common/upload', img.tempFilePaths[0]).then((res) => {
|
||
// if (this.form.photo !== "") {
|
||
// this.form.photo = this.form.photo + "," + JSON.parse(res)
|
||
// .fileName;
|
||
// } else {
|
||
// this.form.photo = JSON.parse(res).fileName;
|
||
// }
|
||
this.parentSignFiles = img.tempFilePaths[0];
|
||
this.form.parentSignAttachment = JSON.parse(res).fileName;
|
||
})
|
||
}
|
||
|
||
}
|
||
})
|
||
|
||
},
|
||
previewImage(url) {
|
||
uni.previewImage({
|
||
urls: [url],
|
||
current: url
|
||
});
|
||
},
|
||
|
||
// ========== 表单数据加载与提交 ==========
|
||
loadFormData(id) {
|
||
this.loading = true;
|
||
getOutsideAccommodationApply(id).then(res => {
|
||
this.form = {
|
||
...res.data,
|
||
address: res.data.address || '',
|
||
parentAddress: res.data.parentAddress || '',
|
||
parentOpinion: res.data.parentOpinion || '1'
|
||
};
|
||
|
||
this.areaText = res.data.address || '';
|
||
this.parentAreaText = res.data.parentAddress || '';
|
||
|
||
// if (this.form.parentSignAttachment) {
|
||
// this.parentSignFiles = [{
|
||
// name: this.form.parentSignAttachment.split('/').pop(),
|
||
// url: this.baseUrl + this.form.parentSignAttachment
|
||
// }];
|
||
// }
|
||
|
||
this.getAffixs(this.form.affixId)
|
||
|
||
this.loading = false;
|
||
}).catch(() => {
|
||
this.loading = false;
|
||
uni.showToast({
|
||
title: '加载数据失败',
|
||
icon: 'none'
|
||
});
|
||
});
|
||
},
|
||
getUser() {
|
||
this.loading = true;
|
||
getUserProfile().then((response) => {
|
||
this.roleGroup = response.roleGroup;
|
||
if (response.data) {
|
||
this.form.studentName = response.data.nickName;
|
||
this.form.gender = response.data.sex || '';
|
||
|
||
getOwnInfo().then((res) => {
|
||
if (res.data) {
|
||
this.form.studentId = res.data.stuId;
|
||
this.form.studentNo = res.data.stuNo;
|
||
this.form.majorName = res.data.majorName;
|
||
this.form.deptName = res.data.deptName
|
||
this.form.className = res.data.className;
|
||
this.form.idCard = res.data.idCard;
|
||
this.form.teacherName = res.data.teacherName
|
||
this.form.studentPhone = res.data.stuPhone;
|
||
this.form.birthDate = res.data.birthday || '';
|
||
|
||
getOwnLog().then(response => {
|
||
try {
|
||
const currentDormId = response.data.dormStu.dormitoryId;
|
||
const matchedRecord = response.data.record.find(item =>
|
||
item.roomId === currentDormId);
|
||
|
||
if (matchedRecord) {
|
||
const baseStuYearName = matchedRecord.stuYearName ||
|
||
'';
|
||
if (baseStuYearName) {
|
||
const isAllSameYear = response.data.record.every(
|
||
item => (item.stuYearName || '') ===
|
||
baseStuYearName);
|
||
if (isAllSameYear) {
|
||
const totalMoney = response.data.record.reduce(
|
||
(sum, item) => {
|
||
return sum + Number(item
|
||
.needMoney || 0);
|
||
}, 0);
|
||
this.form.accommodationFee = '已交' +
|
||
baseStuYearName + '年度住宿费' + totalMoney +
|
||
'人民币';
|
||
} else {
|
||
this.form.accommodationFee = '已交' +
|
||
baseStuYearName + '年度住宿费 存在不同年度费用数据';
|
||
}
|
||
} else {
|
||
this.form.accommodationFee = '已交未知年度住宿费 暂无有效学年信息';
|
||
}
|
||
} else {
|
||
const firstStuYearName = response.data.record[0]
|
||
?.stuYearName || '';
|
||
this.form.accommodationFee = '已交' + (
|
||
firstStuYearName || '未知') + '年度住宿费 暂无匹配数据';
|
||
}
|
||
} catch (e) {
|
||
this.form.accommodationFee = '获取缴费信息失败';
|
||
}
|
||
}).catch(() => {
|
||
this.form.accommodationFee = '获取缴费信息失败';
|
||
});
|
||
|
||
this.getStuDom();
|
||
}
|
||
this.loading = false;
|
||
}).catch(() => {
|
||
this.loading = false;
|
||
uni.showToast({
|
||
title: '获取学生信息失败',
|
||
icon: 'none'
|
||
});
|
||
});
|
||
}
|
||
}).catch(() => {
|
||
this.loading = false;
|
||
uni.showToast({
|
||
title: '获取用户信息失败',
|
||
icon: 'none'
|
||
});
|
||
});
|
||
},
|
||
// 获取学生宿舍
|
||
getStuDom() {
|
||
listStudent({
|
||
stuNo: this.form.studentNo,
|
||
stuName: this.form.studentName
|
||
}).then((response) => {
|
||
if (response.rows && response.rows[0]) {
|
||
this.form.originalDormitory =
|
||
response.rows[0].campusName +
|
||
' ' +
|
||
response.rows[0].parkName +
|
||
' ' +
|
||
response.rows[0].buildingName +
|
||
response.rows[0].roomNo;
|
||
}
|
||
}).catch(() => {
|
||
uni.showToast({
|
||
title: '获取宿舍信息失败',
|
||
icon: 'none'
|
||
});
|
||
});
|
||
},
|
||
// 获取上传的附件数据
|
||
getAffixs(affix) {
|
||
getAffixItems({
|
||
affixId: affix
|
||
}).then(file => {
|
||
this.affixFiles = file.data;
|
||
})
|
||
},
|
||
submitForm(status) {
|
||
// 定义英文字段名和中文提示的映射关系
|
||
const fieldMap = {
|
||
'originalDormitory': '原宿舍号',
|
||
'studentName': '姓名',
|
||
'gender': '性别',
|
||
'birthDate': '出生日期',
|
||
'majorName': '专业',
|
||
'className': '班级',
|
||
'studentNo': '学号',
|
||
'idCard': '身份证',
|
||
'studentPhone': '手机号码',
|
||
'applyReason': '外宿原因',
|
||
'address': '外宿地址省市区',
|
||
'outsideAddress': '详细门牌号',
|
||
'emergencyContact': '紧急联系人',
|
||
'emergencyPhone': '紧急联系电话',
|
||
'parentOpinion': '家长意见',
|
||
'parentPhone': '家长联系电话',
|
||
'parentAddress': '家长地址省市区',
|
||
'parentDetailAddress': '家长地址详细',
|
||
'promiseDate': '签署日期'
|
||
};
|
||
|
||
// 提取所有需要验证的英文字段名
|
||
const requiredFields = Object.keys(fieldMap);
|
||
|
||
// 筛选出为空的英文字段
|
||
const emptyFields = requiredFields.filter(field => !this.form[field]);
|
||
|
||
if (emptyFields.length) {
|
||
// 根据空的英文字段,找到对应的中文提示
|
||
const emptyFieldCn = fieldMap[emptyFields[0]];
|
||
uni.showToast({
|
||
title: '请填写' + emptyFieldCn,
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!this.form.studentSignature) {
|
||
uni.showToast({
|
||
title: '请完成电子签名',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!this.form.studentPromiseSign) {
|
||
uni.showToast({
|
||
title: '请完成承诺签名',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!this.form.affixId) {
|
||
uni.showToast({
|
||
title: '请上传佐证材料',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!this.form.parentSignAttachment) {
|
||
uni.showToast({
|
||
title: '请上传家长签字附件',
|
||
icon: 'none'
|
||
});
|
||
return;
|
||
}
|
||
|
||
if (!this.form.applyNo) {
|
||
const year = new Date().getFullYear();
|
||
const randomNo = Math.floor(Math.random() * 1000000).toString().padStart(6, '0');
|
||
this.form.applyNo = 'WS' + year + randomNo;
|
||
}
|
||
|
||
this.form.endDate = this.getOutsideDefaultEndTime();
|
||
this.form.promiseContent = `
|
||
<p>1.自觉遵守国家法律、法规;</p>
|
||
<p>2.自觉遵守学生行为规范和学校的规章制度,遵守社会公德;</p>
|
||
<p>3.自觉遵守外宿住址所在社区的有关管理规定;</p>
|
||
<p>4.本人申请外宿,属个人自愿行为,外宿期间发生的一切事故,造成本人、他人或集体的人身、财产损害的,学校不负责任。</p>
|
||
`
|
||
const submitForm = {
|
||
...this.form,
|
||
status: this.form.status != 0 ? this.form.status : status
|
||
};
|
||
|
||
this.loading = true;
|
||
const requestPromise = this.form.id ?
|
||
updateOutsideAccommodationApply(submitForm) :
|
||
addOutsideAccommodationApply(submitForm);
|
||
|
||
requestPromise.then((response) => {
|
||
if (this.affixFiles.length > 0) {
|
||
this.affixFiles.forEach(item => {
|
||
item.applyId = this.form.id || response.data.id;
|
||
});
|
||
batchAddOutsideAccommodationAttachment(this.affixFiles);
|
||
}
|
||
|
||
uni.showToast({
|
||
title: this.form.id ? '修改成功' : '新增成功',
|
||
icon: 'success'
|
||
});
|
||
setTimeout(() => {
|
||
this.goBack();
|
||
}, 800);
|
||
}).catch(() => {
|
||
uni.showToast({
|
||
title: '提交失败',
|
||
icon: 'none'
|
||
});
|
||
}).finally(() => {
|
||
this.loading = false;
|
||
});
|
||
},
|
||
resetForm() {
|
||
try {
|
||
this.form = {
|
||
originalDormitory: '',
|
||
studentName: '',
|
||
gender: '',
|
||
birthDate: '',
|
||
majorName: '',
|
||
className: '',
|
||
studentNo: '',
|
||
idCard: '',
|
||
studentPhone: '',
|
||
accommodationFeeStatus: 1,
|
||
applyReason: '',
|
||
address: '',
|
||
outsideAddress: '',
|
||
emergencyContact: '',
|
||
emergencyPhone: '',
|
||
parentOpinion: '1',
|
||
parentPhone: '',
|
||
parentAddress: '',
|
||
parentDetailAddress: '',
|
||
studentSignature: '',
|
||
studentPromiseSign: '',
|
||
promiseDate: '',
|
||
parentSignAttachment: '',
|
||
accommodationFee: '',
|
||
affixId: '',
|
||
status: 0
|
||
};
|
||
|
||
this.affixFiles = [];
|
||
this.parentSignFiles = "";
|
||
this.areaText = '';
|
||
this.parentAreaText = '';
|
||
|
||
this.activeTab = 0;
|
||
|
||
uni.showToast({
|
||
title: '表单已重置',
|
||
icon: 'success'
|
||
});
|
||
} catch (e) {
|
||
console.warn('重置表单失败:', e);
|
||
uni.showToast({
|
||
title: '重置失败',
|
||
icon: 'none'
|
||
});
|
||
}
|
||
},
|
||
goBack() {
|
||
uni.navigateBack({
|
||
delta: 1
|
||
});
|
||
},
|
||
getOutsideDefaultEndTime() {
|
||
const now = new Date();
|
||
const nextYear = now.getFullYear() + 1;
|
||
const endDate = new Date(nextYear, 7, 31);
|
||
return this.formatDate(endDate);
|
||
},
|
||
formatDate(date) {
|
||
const year = date.getFullYear();
|
||
const month = (date.getMonth() + 1).toString().padStart(2, '0');
|
||
const day = date.getDate().toString().padStart(2, '0');
|
||
return year + '-' + month + '-' + day;
|
||
}
|
||
}
|
||
}
|
||
</script>
|
||
|
||
<style scoped>
|
||
/* 核心容器样式 */
|
||
.app-container {
|
||
background-color: #f8f9fa;
|
||
min-height: 100vh;
|
||
width: 100%;
|
||
box-sizing: border-box;
|
||
}
|
||
|
||
/* 自定义导航栏 */
|
||
.nav-bar {
|
||
height: 44px;
|
||
line-height: 44px;
|
||
background: linear-gradient(135deg, #409EFF 0%, #53a8ff 100%);
|
||
color: #fff;
|
||
padding: 0 15px;
|
||
display: flex;
|
||
align-items: center;
|
||
box-shadow: 0 2px 10px rgba(64, 158, 255, 0.1);
|
||
}
|
||
|
||
.nav-title {
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
margin-left: 10px;
|
||
}
|
||
|
||
.main-container {
|
||
width: 100%;
|
||
height: calc(100vh - 44px);
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
/* 原生选项卡样式 */
|
||
.tabs-container {
|
||
width: 100%;
|
||
background-color: #fff;
|
||
border-bottom: 1px solid #e8eaec;
|
||
}
|
||
|
||
.tabs-header {
|
||
display: flex;
|
||
overflow-x: auto;
|
||
white-space: nowrap;
|
||
padding: 0 5rpx;
|
||
justify-content: space-around;
|
||
}
|
||
|
||
.tab-item {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 0 15rpx;
|
||
height: 88rpx;
|
||
min-width: 120rpx;
|
||
flex-direction: column;
|
||
position: relative;
|
||
transition: all 0.3s ease;
|
||
}
|
||
|
||
.tab-item.active {
|
||
color: #409EFF;
|
||
}
|
||
|
||
.tab-item.active::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 50%;
|
||
transform: translateX(-50%);
|
||
width: 120rpx;
|
||
height: 6rpx;
|
||
background-color: #409EFF;
|
||
border-radius: 3rpx;
|
||
}
|
||
|
||
.tab-text {
|
||
font-size: 24rpx;
|
||
margin-top: 8rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
/* 表单滚动区域 */
|
||
.form-scroll {
|
||
flex: 1;
|
||
width: 100%;
|
||
padding: 15rpx;
|
||
box-sizing: border-box;
|
||
overflow-y: auto;
|
||
}
|
||
|
||
/* 标签页面板 */
|
||
.tab-panel {
|
||
width: 100%;
|
||
margin-bottom: 15rpx;
|
||
}
|
||
|
||
/* 表单卡片 */
|
||
.form-card {
|
||
background-color: #fff;
|
||
border-radius: 12rpx;
|
||
padding: 25rpx;
|
||
margin-bottom: 20rpx;
|
||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.03);
|
||
border: 1px solid #f0f2f5;
|
||
}
|
||
|
||
.card-title {
|
||
font-size: 32rpx;
|
||
font-weight: 600;
|
||
margin-bottom: 25rpx;
|
||
color: #1f2937;
|
||
padding-bottom: 15rpx;
|
||
border-bottom: 1px solid #f0f2f5;
|
||
position: relative;
|
||
}
|
||
|
||
.card-title::after {
|
||
content: '';
|
||
position: absolute;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 130rpx;
|
||
height: 4rpx;
|
||
background-color: #409EFF;
|
||
border-radius: 2rpx;
|
||
}
|
||
|
||
.card-parent::after {
|
||
width: 290rpx;
|
||
}
|
||
|
||
.card-area::after {
|
||
width: 200rpx;
|
||
}
|
||
|
||
.card-apply::after {
|
||
width: 230rpx;
|
||
}
|
||
|
||
/* 表单项 */
|
||
.form-item {
|
||
margin-bottom: 25rpx;
|
||
display: flex;
|
||
flex-direction: column;
|
||
}
|
||
|
||
.form-label {
|
||
font-size: 28rpx;
|
||
color: #374151;
|
||
margin-bottom: 12rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.form-input {
|
||
height: 80rpx;
|
||
line-height: 80rpx;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 8rpx;
|
||
padding: 0 20rpx;
|
||
font-size: 28rpx;
|
||
transition: border-color 0.3s ease;
|
||
}
|
||
|
||
.form-input:focus {
|
||
border-color: #409EFF;
|
||
box-shadow: 0 0 0 4rpx rgba(64, 158, 255, 0.1);
|
||
}
|
||
|
||
.form-textarea {
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 8rpx;
|
||
padding: 20rpx;
|
||
font-size: 28rpx;
|
||
line-height: 1.8;
|
||
transition: border-color 0.3s ease;
|
||
min-height: 200rpx;
|
||
width: 100%;
|
||
}
|
||
|
||
.form-textarea:focus {
|
||
border-color: #409EFF;
|
||
box-shadow: 0 0 0 4rpx rgba(64, 158, 255, 0.1);
|
||
}
|
||
|
||
// 图片上传样式
|
||
.upload-btn {
|
||
width: 200rpx;
|
||
height: 80rpx;
|
||
border: 1px dashed #ccc;
|
||
border-radius: 8rpx;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
gap: 10rpx;
|
||
font-size: 28rpx;
|
||
color: #666;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.upload-icon {
|
||
font-size: 40rpx;
|
||
font-weight: bold;
|
||
}
|
||
|
||
.upload-tip {
|
||
font-size: 22rpx;
|
||
color: #999;
|
||
margin-bottom: 20rpx;
|
||
}
|
||
|
||
.upload-preview {
|
||
width: 200rpx;
|
||
height: 200rpx;
|
||
border-radius: 8rpx;
|
||
overflow: hidden;
|
||
}
|
||
|
||
.upload-preview image {
|
||
width: 100%;
|
||
height: 100%;
|
||
}
|
||
|
||
/* 选择器样式 */
|
||
.picker-input {
|
||
height: 80rpx;
|
||
line-height: 80rpx;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 8rpx;
|
||
padding: 0 20rpx;
|
||
font-size: 28rpx;
|
||
color: #4b5563;
|
||
transition: border-color 0.3s ease;
|
||
}
|
||
|
||
.picker-input:active {
|
||
border-color: #409EFF;
|
||
}
|
||
|
||
/* 省市区选择器样式 */
|
||
.area-picker {
|
||
height: 80rpx;
|
||
line-height: 80rpx;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 8rpx;
|
||
padding: 0 20rpx;
|
||
font-size: 28rpx;
|
||
color: #4b5563;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
transition: border-color 0.3s ease;
|
||
}
|
||
|
||
.area-picker:active {
|
||
border-color: #409EFF;
|
||
}
|
||
|
||
/* 新增:禁用状态样式(可选) */
|
||
.area-picker.disabled {
|
||
opacity: 0.7;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.picker-text {
|
||
flex: 1;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
.picker-icon {
|
||
flex-shrink: 0;
|
||
color: #9ca3af;
|
||
}
|
||
|
||
/* 省市区选择器弹窗样式 */
|
||
.area-popup {
|
||
position: fixed;
|
||
bottom: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
background-color: #fff;
|
||
z-index: 1001;
|
||
border-radius: 16rpx 16rpx 0 0;
|
||
box-shadow: 0 -4rpx 20rpx rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
.area-popup-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
padding: 20rpx;
|
||
border-bottom: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.picker-view {
|
||
height: 400rpx;
|
||
width: 100%;
|
||
}
|
||
|
||
.picker-item {
|
||
height: 50px;
|
||
line-height: 50px;
|
||
text-align: center;
|
||
font-size: 28rpx;
|
||
color: #1f2937;
|
||
}
|
||
|
||
/* 文件列表 */
|
||
.file-list {
|
||
margin-top: 15rpx;
|
||
}
|
||
|
||
.file-item {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
padding: 15rpx;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 8rpx;
|
||
margin-bottom: 10rpx;
|
||
background-color: #f9fafb;
|
||
}
|
||
|
||
.file-name {
|
||
font-size: 26rpx;
|
||
color: #4b5563;
|
||
flex: 1;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
|
||
/* 上传按钮样式 */
|
||
.upload-btn {
|
||
background-color: #f0f9ff;
|
||
color: #409EFF;
|
||
border: 1px solid #dbeafe;
|
||
border-radius: 8rpx;
|
||
padding: 12rpx 24rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.delete-btn {
|
||
background-color: #fef2f2;
|
||
color: #ef4444;
|
||
border: 1px solid #fee2e2;
|
||
border-radius: 6rpx;
|
||
/* padding: 8rpx 16rpx; */
|
||
/* font-size: 24rpx; */
|
||
}
|
||
|
||
/* 上传提示 */
|
||
.upload-tip {
|
||
font-size: 24rpx;
|
||
color: #9ca3af;
|
||
margin-top: 12rpx;
|
||
line-height: 1.6;
|
||
}
|
||
|
||
/* 宿费状态 */
|
||
.fee-status {
|
||
line-height: 1.8;
|
||
color: #4b5563;
|
||
font-size: 28rpx;
|
||
padding: 15rpx;
|
||
background-color: #f9fafb;
|
||
border-radius: 8rpx;
|
||
border: 1px solid #e5e7eb;
|
||
}
|
||
|
||
/* 签名容器 */
|
||
.signature-container {
|
||
display: flex;
|
||
align-items: center;
|
||
flex-wrap: wrap;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.sign-btn {
|
||
background-color: #409EFF;
|
||
color: #fff;
|
||
border-radius: 8rpx;
|
||
padding: 12rpx 24rpx;
|
||
font-size: 26rpx;
|
||
margin: 0;
|
||
}
|
||
|
||
.signature-preview {
|
||
width: 220rpx;
|
||
height: 110rpx;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 8rpx;
|
||
padding: 5rpx;
|
||
background-color: #f9fafb;
|
||
}
|
||
|
||
/* 日期项 */
|
||
.date-item {
|
||
margin-top: 20rpx;
|
||
width: 100%;
|
||
}
|
||
|
||
/* 提交按钮区域 */
|
||
.submit-container {
|
||
display: flex;
|
||
justify-content: center;
|
||
gap: 20rpx;
|
||
margin-top: 30rpx;
|
||
padding: 25rpx;
|
||
background-color: #fff;
|
||
border-radius: 12rpx;
|
||
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.03);
|
||
border: 1px solid #f0f2f5;
|
||
}
|
||
|
||
.submit-btn {
|
||
width: 220rpx;
|
||
height: 88rpx;
|
||
line-height: 88rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.submit-primary {
|
||
background: linear-gradient(135deg, #409EFF 0%, #53a8ff 100%);
|
||
border: none;
|
||
}
|
||
|
||
.reset-btn {
|
||
width: 220rpx;
|
||
height: 88rpx;
|
||
line-height: 88rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
background-color: #f9fafb;
|
||
color: #4b5563;
|
||
border: 1px solid #e5e7eb;
|
||
}
|
||
|
||
/* 弹窗遮罩 */
|
||
.mask {
|
||
position: fixed;
|
||
top: 0;
|
||
left: 0;
|
||
width: 100%;
|
||
height: 100%;
|
||
background-color: rgba(0, 0, 0, 0.5);
|
||
z-index: 999;
|
||
backdrop-filter: blur(2rpx);
|
||
}
|
||
|
||
/* 申请须知弹窗样式 */
|
||
.notice-popup {
|
||
position: fixed;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 600rpx;
|
||
background-color: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 35rpx;
|
||
z-index: 1000;
|
||
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
.popup-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 25rpx;
|
||
padding-bottom: 15rpx;
|
||
border-bottom: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.popup-title {
|
||
font-size: 34rpx;
|
||
font-weight: 600;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.countdown {
|
||
font-size: 24rpx;
|
||
color: #f97316;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.popup-content {
|
||
max-height: 400rpx;
|
||
overflow-y: auto;
|
||
margin-bottom: 35rpx;
|
||
}
|
||
|
||
.notice-item {
|
||
line-height: 2;
|
||
margin-bottom: 15rpx;
|
||
color: #4b5563;
|
||
font-size: 28rpx;
|
||
}
|
||
|
||
.popup-btn {
|
||
width: 100%;
|
||
height: 88rpx;
|
||
line-height: 88rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
background: linear-gradient(135deg, #409EFF 0%, #53a8ff 100%);
|
||
border: none;
|
||
}
|
||
|
||
/* 签名弹窗样式 - 扩大尺寸 */
|
||
.sign-popup {
|
||
position: fixed;
|
||
top: 50%;
|
||
left: 50%;
|
||
transform: translate(-50%, -50%);
|
||
width: 700rpx;
|
||
/* 弹窗宽度扩大 */
|
||
background-color: #fff;
|
||
border-radius: 16rpx;
|
||
padding: 30rpx;
|
||
z-index: 1000;
|
||
box-shadow: 0 10rpx 40rpx rgba(0, 0, 0, 0.15);
|
||
}
|
||
|
||
.sign-popup-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: center;
|
||
margin-bottom: 25rpx;
|
||
}
|
||
|
||
.sign-title {
|
||
font-size: 34rpx;
|
||
font-weight: 600;
|
||
color: #1f2937;
|
||
}
|
||
|
||
.close-icon {
|
||
color: #6b7280;
|
||
transition: color 0.3s ease;
|
||
}
|
||
|
||
.close-icon:active {
|
||
color: #ef4444;
|
||
}
|
||
|
||
/* 签名画布样式 - 大幅扩大尺寸 */
|
||
.sign-canvas {
|
||
width: 645rpx;
|
||
/* 画布宽度大幅增加 */
|
||
height: 300rpx;
|
||
/* 画布高度大幅增加 */
|
||
border: 1px solid #d1d5db;
|
||
background-color: #fff;
|
||
touch-action: none;
|
||
cursor: crosshair;
|
||
user-select: none;
|
||
margin-bottom: 25rpx;
|
||
border-radius: 8rpx;
|
||
/* 增加点击区域响应 */
|
||
pointer-events: auto;
|
||
z-index: 1001;
|
||
}
|
||
|
||
.sign-popup-footer {
|
||
display: flex;
|
||
justify-content: flex-end;
|
||
gap: 20rpx;
|
||
}
|
||
|
||
.sign-btn-default {
|
||
padding: 12rpx 24rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
background-color: #f9fafb;
|
||
color: #4b5563;
|
||
border: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.sign-btn-primary {
|
||
padding: 12rpx 24rpx;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
background: linear-gradient(135deg, #409EFF 0%, #53a8ff 100%);
|
||
border: none;
|
||
color: #fff;
|
||
}
|
||
|
||
/* 承诺内容样式 */
|
||
.promise-content {
|
||
line-height: 2;
|
||
color: #4b5563;
|
||
padding: 20rpx;
|
||
background-color: #f9fafb;
|
||
border-radius: 8rpx;
|
||
font-size: 28rpx;
|
||
border: 1px solid #e5e7eb;
|
||
}
|
||
|
||
.promise-content text {
|
||
display: block;
|
||
margin-bottom: 15rpx;
|
||
}
|
||
|
||
/* 按钮通用样式优化 */
|
||
.btn-cancel {
|
||
background-color: #f9fafb;
|
||
color: #4b5563;
|
||
border: 1px solid #e5e7eb;
|
||
border-radius: 8rpx;
|
||
padding: 12rpx 24rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
.btn-confirm {
|
||
background: linear-gradient(135deg, #409EFF 0%, #53a8ff 100%);
|
||
color: #fff;
|
||
border: none;
|
||
border-radius: 8rpx;
|
||
padding: 12rpx 24rpx;
|
||
font-size: 26rpx;
|
||
}
|
||
|
||
/* 添加过渡样式 */
|
||
.popup-fade-enter-active,
|
||
.popup-fade-leave-active {
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
.popup-fade-enter-from,
|
||
.popup-fade-leave-to {
|
||
opacity: 0;
|
||
}
|
||
|
||
.popup-slide-enter-active,
|
||
.popup-slide-leave-active {
|
||
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
}
|
||
|
||
.popup-slide-enter-from {
|
||
opacity: 0;
|
||
transform: translate(-50%, -50%) translateY(50px);
|
||
}
|
||
|
||
.popup-slide-leave-to {
|
||
opacity: 0;
|
||
transform: translate(-50%, -50%) translateY(50px);
|
||
}
|
||
|
||
/* 蒙层渐入渐出动画 */
|
||
.popup-fade-enter-active,
|
||
.popup-fade-leave-active {
|
||
transition: opacity 0.3s ease;
|
||
}
|
||
|
||
/* 进入前/离开后状态(透明) */
|
||
.popup-fade-enter,
|
||
.popup-fade-leave-to {
|
||
opacity: 0;
|
||
}
|
||
|
||
/* 弹窗滑入滑出动画(核心) */
|
||
.popup-slide-enter-active,
|
||
.popup-slide-leave-active {
|
||
transition: all 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||
/* 缓动曲线更丝滑 */
|
||
}
|
||
|
||
/* 进入前状态:透明 + 向下偏移50px */
|
||
.popup-slide-enter {
|
||
opacity: 0;
|
||
transform: translate(-50%, -50%) translateY(50px);
|
||
}
|
||
|
||
/* 离开后状态:透明 + 向下偏移50px */
|
||
.popup-slide-leave-to {
|
||
opacity: 0;
|
||
transform: translate(-50%, -50%) translateY(50px);
|
||
}
|
||
</style> |