Files
gzh/src/views/Bayj_fysq.vue
2026-01-21 15:49:26 +08:00

1621 lines
47 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>
<div class="medical-form-page">
<div class="center-container">
<form class="medical-form" @submit.prevent="handleSubmit">
<!-- 标题区域 -->
<div class="form-header">
<h1 class="form-title">病案申请</h1>
<p class="form-desc">请填写以下信息我们将尽快为您处理</p>
</div>
<!-- 步骤条 -->
<div class="steps-container">
<div class="step-line"></div>
<div v-for="(step, index) in steps" :key="index" class="step-item" :class="{
'active': currentStep === index,
'completed': currentStep > index
}">
<div class="step-number">{{ completedStepNumbers[index] }}</div>
<div class="step-name">{{ step.name }}</div>
</div>
</div>
<!-- 步骤内容区域 -->
<div class="step-content">
<!-- 步骤1选择办理类型及领取方式 -->
<div v-if="currentStep === 0" class="step-panel">
<div class="form-section handling-type-section">
<h2 class="section-title">办理类型</h2>
<div class="handling-type-selector">
<label class="handling-option">
<input type="radio" name="handlingType" value="1" v-model="handlingType" class="handling-radio">
<span class="handling-text">本人办理</span>
</label>
<label class="handling-option">
<input type="radio" name="handlingType" value="2" v-model="handlingType" class="handling-radio">
<span class="handling-text">他人代办</span>
</label>
</div>
<span class="error-text" v-if="errors.handlingType">{{ errors.handlingType }}</span>
</div>
<div class="form-section delivery-type-section">
<h2 class="section-title">领取方式</h2>
<div class="delivery-type-selector">
<label class="delivery-option">
<input type="radio" name="deliveryType" value="1" v-model="deliveryType" class="delivery-radio">
<span class="delivery-text">到场自取</span>
</label>
<label class="delivery-option">
<input type="radio" name="deliveryType" value="2" v-model="deliveryType" class="delivery-radio">
<span class="delivery-text">快递邮寄</span>
</label>
</div>
<span class="error-text" v-if="errors.deliveryType">{{ errors.deliveryType }}</span>
</div>
</div>
<!-- 步骤2患者信息 -->
<div v-if="currentStep === 1" class="step-panel">
<div class="form-section">
<h2 class="section-title">患者信息</h2>
<div class="form-fields">
<div class="form-group">
<label for="patientName">病人姓名 <span class="required">*</span></label>
<input type="text" id="patientName" v-model="formData.patientName" placeholder="请输入病人姓名"
class="form-input" autocomplete="name" @input="syncRecipientName">
<span class="error-text" v-if="errors.patientName">{{ errors.patientName }}</span>
</div>
<div class="form-group">
<label for="patientId">就诊人卡号 <span class="required">*</span></label>
<input type="text" id="patientId" v-model="formData.patientId" placeholder="请输入就诊人卡号"
class="form-input">
<span class="error-text" v-if="errors.patientId">{{ errors.patientId }}</span>
</div>
<div class="form-group">
<label>就诊医院</label>
<div class="fixed-value">武警宁夏总队医院</div>
</div>
<div class="form-group">
<label for="phoneNumber">手机号码 <span class="required">*</span></label>
<input type="tel" id="phoneNumber" v-model="formData.phone" placeholder="请输入手机号码" maxlength="11"
class="form-input" autocomplete="tel">
<span class="error-text" v-if="errors.phone">{{ errors.phone }}</span>
</div>
<div class="form-group">
<label for="idNumber">身份证号 <span class="required">*</span></label>
<input type="text" id="idNumber" v-model="formData.idNumber" placeholder="请输入身份证号码" maxlength="18"
class="form-input">
<span class="error-text" v-if="errors.idNumber">{{ errors.idNumber }}</span>
</div>
<div class="form-group">
<label for="hospitalNumber">住院号 <span class="required">*</span></label>
<input type="text" id="hospitalNumber" v-model="formData.hospitalNumber" placeholder="请输入住院号"
class="form-input">
<span class="error-text" v-if="errors.hospitalNumber">{{ errors.hospitalNumber }}</span>
</div>
<div class="form-row">
<div class="form-group date-group">
<label>入院日期 <span class="required">*</span></label>
<div class="date-picker-wrapper">
<input type="text" v-model="formData.admissionDate" placeholder="选择入院日期"
class="form-input date-display" readonly @click="showAdmissionPicker = true">
<van-popup v-model="showAdmissionPicker" position="bottom">
<van-datetime-picker :value="admissionDate" type="date" title="选择入院日期" :min-date="minDate"
:max-date="new Date()" @confirm="handleAdmissionConfirm"
@cancel="showAdmissionPicker = false" />
</van-popup>
</div>
<span class="error-text" v-if="errors.admissionDate">{{ errors.admissionDate }}</span>
</div>
<div class="form-group date-group">
<label>出院日期 <span class="required">*</span></label>
<div class="date-picker-wrapper">
<input type="text" v-model="formData.dischargeDate" placeholder="选择出院日期"
class="form-input date-display" readonly
@click="formData.admissionDate && (showDischargePicker = true)"
:disabled="!formData.admissionDate">
<van-popup v-model="showDischargePicker" position="bottom">
<van-datetime-picker :value="dischargeDate" type="date" title="选择出院日期"
:min-date="getMinDischargeDate()" :max-date="new Date()" @confirm="handleDischargeConfirm"
@cancel="showDischargePicker = false" />
</van-popup>
</div>
<span class="error-text" v-if="errors.dischargeDate">{{ errors.dischargeDate }}</span>
</div>
</div>
</div>
</div>
</div>
<!-- 步骤3代办人信息 -->
<div v-if="currentStep === 2" class="step-panel">
<div class="form-section" v-if="handlingType === '2'">
<h2 class="section-title">代办人信息</h2>
<div class="form-fields">
<div class="form-group">
<label for="recipientName">代办人姓名 <span class="required">*</span></label>
<input type="text" id="recipientName" v-model="formData.recipientName" placeholder="请输入代办人姓名"
class="form-input" autocomplete="name">
<span class="error-text" v-if="errors.recipientName">{{ errors.recipientName }}</span>
</div>
<div class="form-group">
<label for="recipientPhone">代办人联系电话 <span class="required">*</span></label>
<input type="tel" id="recipientPhone" v-model="formData.recipientPhone" placeholder="请输入代办人联系电话"
maxlength="11" class="form-input" autocomplete="tel">
<span class="error-text" v-if="errors.recipientPhone">{{ errors.recipientPhone }}</span>
</div>
<div class="form-group">
<label for="recipientIdNumber">代办人身份证号 <span class="required">*</span></label>
<input type="text" id="recipientIdNumber" v-model="formData.recipientIdNumber"
placeholder="请输入代办人身份证号码" maxlength="18" class="form-input">
<span class="error-text" v-if="errors.recipientIdNumber">{{ errors.recipientIdNumber }}</span>
</div>
</div>
</div>
<div v-else class="self-handling-step3">
<p>您选择的是本人办理无需填写代办人信息</p>
<p>点击下一步继续上传证件</p>
</div>
</div>
<!-- 步骤4证件上传及确认 -->
<div v-if="currentStep === 3" class="step-panel">
<div class="form-section">
<h2 class="section-title">证件上传</h2>
<div class="upload-section">
<p class="upload-desc">
{{ handlingType === '1' ? '请上传患者本人证件照片' : '请上传患者及代办人证件照片' }}
</p>
<div class="upload-row">
<div class="upload-item">
<div class="upload-preview" :class="{ 'uploaded': formData.idFrontUrl }">
<img v-if="formData.idFrontUrl" :src="formData.idFrontUrl" alt="身份证正面" class="id-img">
<img v-else src="../assets/正面.png" alt="身份证正面示例" class="id-img">
</div>
<button type="button" class="upload-btn" @click="selectFile('idFront')">
上传患者身份证正面
</button>
<input type="file" ref="idFrontInput" accept="image/*" class="file-input"
@change="handleFileUpload('idFront', $event)">
</div>
<div class="upload-item">
<div class="upload-preview" :class="{ 'uploaded': formData.idBackUrl }">
<img v-if="formData.idBackUrl" :src="formData.idBackUrl" alt="身份证反面" class="id-img">
<img v-else src="../assets/反面.png" alt="身份证反面示例" class="id-img">
</div>
<button type="button" class="upload-btn" @click="selectFile('idBack')">
上传患者身份证反面
</button>
<input type="file" ref="idBackInput" accept="image/*" class="file-input"
@change="handleFileUpload('idBack', $event)">
</div>
</div>
<div v-if="handlingType === '2'" class="upload-row">
<div class="upload-item">
<div class="upload-preview" :class="{ 'uploaded': formData.recipientIdFrontUrl }">
<img v-if="formData.recipientIdFrontUrl" :src="formData.recipientIdFrontUrl" alt="代办人身份证正面"
class="id-img">
<img v-else src="../assets/正面.png" alt="代办人身份证正面示例" class="id-img">
</div>
<button type="button" class="upload-btn" @click="selectFile('recipientIdFront')">
上传代办人身份证正面
</button>
<input type="file" ref="recipientIdFrontInput" accept="image/*" class="file-input"
@change="handleFileUpload('recipientIdFront', $event)">
</div>
<div class="upload-item">
<div class="upload-preview" :class="{ 'uploaded': formData.recipientIdBackUrl }">
<img v-if="formData.recipientIdBackUrl" :src="formData.recipientIdBackUrl" alt="代办人身份证反面"
class="id-img">
<img v-else src="../assets/反面.png" alt="代办人身份证反面示例" class="id-img">
</div>
<button type="button" class="upload-btn" @click="selectFile('recipientIdBack')">
上传代办人身份证反面
</button>
<input type="file" ref="recipientIdBackInput" accept="image/*" class="file-input"
@change="handleFileUpload('recipientIdBack', $event)">
</div>
<div class="upload-item">
<div class="upload-preview" :class="{ 'uploaded': formData.powerOfAttorneyUrl }">
<img v-if="formData.powerOfAttorneyUrl" :src="formData.powerOfAttorneyUrl" alt="授权委托书"
class="id-img">
<img v-else src="../assets/示例书.png" alt="授权委托书示例" class="id-img">
</div>
<button type="button" class="upload-btn" @click="selectFile('powerOfAttorney')">
上传病案复印授权委托书
</button>
<input type="file" ref="powerOfAttorneyInput" accept="image/*" class="file-input"
@change="handleFileUpload('powerOfAttorney', $event)">
</div>
</div>
<div class="upload-notes">
<p>1支持上传身份证护照军官证户口本等有效证件</p>
<p>2请上传清晰可见的证件并确保证件内信息完整</p>
<p v-if="handlingType === '2'">3他人代办时需同时上传患者及代办人双方证件</p>
</div>
</div>
</div>
<div class="agreement-section">
<div class="agreement-check">
<div class="checkbox" :class="{ checked: hasAgreed }" @click="if (!hasAgreed) showAgreement = true">
<span v-if="hasAgreed"></span>
</div>
<p>我已阅读并同意<a href="javascript:;" @click="showAgreement = true">病案申请须知</a></p>
</div>
<span class="error-text" v-if="errors.agreement">{{ errors.agreement }}</span>
</div>
</div>
</div>
<!-- 步骤导航按钮 -->
<div class="step-navigation">
<button type="button" class="nav-btn prev-btn" @click="prevStep" :disabled="currentStep === 0">
上一步
</button>
<button type="button" class="nav-btn next-btn" @click="nextStep" :disabled="currentStep === totalSteps - 1">
下一步
</button>
<button type="submit" class="submit-btn" v-if="currentStep === totalSteps - 1" :disabled="!hasAgreed">
确认提交
</button>
</div>
</form>
</div>
<!-- 信息确认弹窗 -->
<div class="modal-backdrop" v-if="showConfirmModal">
<div class="confirm-modal">
<h3 class="modal-title">确认申请信息</h3>
<div class="confirm-details">
<div class="detail-item">
<span class="detail-label">办理类型</span>
<span class="detail-value">{{ handlingType === '1' ? '本人办理' : '他人代办' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">领取方式</span>
<span class="detail-value">{{ deliveryType === '1' ? '到场自取' : '快递邮寄' }}</span>
</div>
<div class="detail-item">
<span class="detail-label">打印份数</span>
<span class="detail-value">{{ formData.copies }}</span>
</div>
<div class="detail-item">
<span class="detail-label">病人姓名</span>
<span class="detail-value">{{ formData.patientName }}</span>
</div>
<div class="detail-item">
<span class="detail-label">就诊人卡号</span>
<span class="detail-value">{{ formData.patientId }}</span>
</div>
<div class="detail-item">
<span class="detail-label">就诊医院</span>
<span class="detail-value">武警宁夏总队医院</span>
</div>
<div class="detail-item">
<span class="detail-label">手机号码</span>
<span class="detail-value">{{ formData.phone }}</span>
</div>
<div class="detail-item">
<span class="detail-label">身份证号</span>
<span class="detail-value">{{ formData.idNumber }}</span>
</div>
<div class="detail-item">
<span class="detail-label">住院号</span>
<span class="detail-value">{{ formData.hospitalNumber }}</span>
</div>
<div class="detail-item">
<span class="detail-label">入院日期</span>
<span class="detail-value">{{ formData.admissionDate }}</span>
</div>
<div class="detail-item">
<span class="detail-label">出院日期</span>
<span class="detail-value">{{ formData.dischargeDate }}</span>
</div>
<template v-if="handlingType === '2'">
<div class="detail-item">
<span class="detail-label">代办人姓名</span>
<span class="detail-value">{{ formData.recipientName }}</span>
</div>
<div class="detail-item">
<span class="detail-label">代办人电话</span>
<span class="detail-value">{{ formData.recipientPhone }}</span>
</div>
<div class="detail-item">
<span class="detail-label">代办人身份证号</span>
<span class="detail-value">{{ formData.recipientIdNumber }}</span>
</div>
</template>
</div>
<div class="confirm-buttons">
<button class="cancel-btn" @click="showConfirmModal = false">取消</button>
<button class="confirm-btn" @click="submitConfirmed" :disabled="isSubmitting">确认提交</button>
</div>
</div>
</div>
<!-- 病案申请须知弹窗 -->
<div class="modal-backdrop" v-if="showAgreement">
<div class="agreement-modal">
<h3 class="modal-title">病案申请须知</h3>
<div class="agreement-content">
<div class="notice-content">
<p>根据卫健委颁发的医疗机构病历管理规定在申请病案复印前您需注意以下事项</p>
<ol>
<li>1申请人必须为患者本人或被委托代理人</li>
<li>2申请人为患者本人的需要提供患者身份证明材料申请人为被委托代理人的需要提供患者身份证明材料申请人身份证明材料及委托代理书</li>
<li>3公安司法保险等机构需到医院现场申请</li>
<li>4死亡患者只支持现场申请</li>
</ol>
<p><strong>委托代理书示例</strong></p>
<img src="../assets/示例书.png" alt="病案复印授权委托书示例"
style="max-width: 100%; border: 1px solid #ddd; border-radius: 0.05rem; margin: 0.2rem 0;">
</div>
</div>
<div class="agreement-footer">
<button class="agree-btn" @click="agreeAgreement">我已阅读并同意</button>
</div>
</div>
</div>
</div>
</template>
<script>
import { apiBayj } from "@/request/api.js";
import { Toast, DatetimePicker, Popup } from "vant";
export default {
components: {
[DatetimePicker.name]: DatetimePicker,
[Popup.name]: Popup
},
mounted() {
let card = JSON.parse(sessionStorage.getItem("card"))
console.log("card:",card)
// 将card对象的值赋给表单字段作为默认值允许用户自行修改
if (card) {
this.formData.patientName = card.name || '';
this.formData.patientId = card.patientId || '';
this.formData.idNumber = card.idNo || '';
this.formData.phone = card.phone || '';
}
},
data() {
return {
steps: [
{ name: '选择办理类型及领取方式' },
{ name: '填写患者信息' },
{ name: '填写代办人信息' },
{ name: '上传证件及确认' }
],
currentStep: 0,
card: {},
handlingType: '',
deliveryType: '',
formData: {
patientName: '',
patientId: '', // 新增就诊人卡号字段
phone: '',
idNumber: '',
hospitalNumber: '',
admissionDate: '',
dischargeDate: '',
recipientName: '',
recipientPhone: '',
recipientIdNumber: '',
copies: 1, // 打印份数默认1份
idFrontUrl: '',
idBackUrl: '',
recipientIdFrontUrl: '',
recipientIdBackUrl: '',
powerOfAttorneyUrl: '',
idFrontFile: null,
idBackFile: null,
recipientIdFrontFile: null,
recipientIdBackFile: null,
powerOfAttorneyFile: null,
},
errors: {},
showConfirmModal: false,
showAgreement: false,
hasAgreed: false,
isSubmitting: false, // 提交状态标记
// 日期选择器相关
showAdmissionPicker: false,
showDischargePicker: false,
admissionDate: new Date(),
dischargeDate: new Date(),
minDate: new Date(1999, 0, 1) // 最小日期设为1900年1月1日
};
},
computed: {
totalSteps() {
return this.steps.length;
},
completedStepNumbers() {
return this.steps.map((_, index) => {
if (this.currentStep > index) {
return '✓';
} else {
return index + 1;
}
});
}
},
methods: {
// 获取出院日期的最小值
getMinDischargeDate() {
if (this.formData.admissionDate) {
return new Date(this.formData.admissionDate);
}
return this.minDate;
},
syncRecipientName() {
if (this.handlingType === '1') {
this.formData.recipientName = this.formData.patientName;
}
},
// 处理入院日期确认
handleAdmissionConfirm(date) {
this.showAdmissionPicker = false;
// 确保 date 是有效日期对象
if (date instanceof Date && !isNaN(date.getTime())) {
this.admissionDate = date;
this.formData.admissionDate = this.formatDate(date);
// 如果出院日期早于入院日期,清空出院日期
if (this.formData.dischargeDate) {
const discharge = new Date(this.formData.dischargeDate);
if (discharge < date) {
this.formData.dischargeDate = '';
}
}
}
},
// 处理出院日期确认
handleDischargeConfirm(date) {
this.showDischargePicker = false;
// 确保 date 是有效日期对象
if (date instanceof Date && !isNaN(date.getTime())) {
this.dischargeDate = date;
this.formData.dischargeDate = this.formatDate(date);
}
},
// 格式化日期为YYYY-MM-DD
formatDate(date) {
if (!(date instanceof Date) || isNaN(date.getTime())) {
return '';
}
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
,
selectFile(type) {
if (type === 'idFront') {
this.$refs.idFrontInput.click();
} else if (type === 'idBack') {
this.$refs.idBackInput.click();
} else if (type === 'recipientIdFront') {
this.$refs.recipientIdFrontInput.click();
} else if (type === 'recipientIdBack') {
this.$refs.recipientIdBackInput.click();
} else if (type === 'powerOfAttorney') {
this.$refs.powerOfAttorneyInput.click();
}
},
handleFileUpload(type, e) {
const file = e.target.files[0];
if (!file) return;
if (!file.type.startsWith('image/')) {
Toast.fail('请上传图片文件');
return;
}
this.compressImage(file).then(compressedFile => {
if (compressedFile.size > 10 * 1024 * 1024) {
Toast.fail('图片大小不能超过10MB');
return;
}
if (type === 'idFront') {
this.formData.idFrontFile = compressedFile;
} else if (type === 'idBack') {
this.formData.idBackFile = compressedFile;
} else if (type === 'recipientIdFront') {
this.formData.recipientIdFrontFile = compressedFile;
} else if (type === 'recipientIdBack') {
this.formData.recipientIdBackFile = compressedFile;
} else if (type === 'powerOfAttorney') {
this.formData.powerOfAttorneyFile = compressedFile;
}
const reader = new FileReader();
reader.onload = (event) => {
if (type === 'idFront') {
this.formData.idFrontUrl = event.target.result;
} else if (type === 'idBack') {
this.formData.idBackUrl = event.target.result;
} else if (type === 'recipientIdFront') {
this.formData.recipientIdFrontUrl = event.target.result;
} else if (type === 'recipientIdBack') {
this.formData.recipientIdBackUrl = event.target.result;
} else if (type === 'powerOfAttorney') {
this.formData.powerOfAttorneyUrl = event.target.result;
}
};
reader.readAsDataURL(compressedFile);
});
},
compressImage(file) {
return new Promise((resolve) => {
if (file.size < 2 * 1024 * 1024) {
resolve(file);
return;
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = function () {
const maxSize = 1200;
let width = img.width;
let height = img.height;
if (width > height) {
if (width > maxSize) {
height = (height * maxSize) / width;
width = maxSize;
}
} else {
if (height > maxSize) {
width = (width * maxSize) / height;
height = maxSize;
}
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(img, 0, 0, width, height);
canvas.toBlob((blob) => {
const compressedFile = new File([blob], file.name, {
type: 'image/jpeg',
lastModified: Date.now()
});
resolve(compressedFile);
}, 'image/jpeg', 0.7);
};
img.src = URL.createObjectURL(file);
});
},
validateStep(step) {
const errors = {};
switch (step) {
case 0:
if (!this.handlingType) {
errors.handlingType = '请选择办理类型';
}
if (!this.deliveryType) {
errors.deliveryType = '请选择领取方式';
}
if (!this.formData.copies) {
errors.copies = '请选择打印份数';
}
break;
case 1:
if (!this.formData.patientName.trim()) {
errors.patientName = '请输入病人姓名';
}
// 新增就诊人卡号验证
if (!this.formData.patientId.trim()) {
errors.patientId = '请输入就诊人卡号';
}
if (!this.formData.phone.trim()) {
errors.phone = '请输入联系电话';
} else if (!/^1[3-9]\d{9}$/.test(this.formData.phone.trim())) {
errors.phone = '请输入有效的11位手机号码支持13-19开头';
}
if (!this.formData.idNumber.trim()) {
errors.idNumber = '请输入身份证号';
} else if (!this.validateIdCard(this.formData.idNumber.trim())) {
errors.idNumber = '请输入有效的18位身份证号码';
}
if (!this.formData.hospitalNumber.trim()) {
errors.hospitalNumber = '请输入住院号';
}
if (!this.formData.admissionDate) {
errors.admissionDate = '请选择入院日期';
}
if (!this.formData.dischargeDate) {
errors.dischargeDate = '请选择出院日期';
} else if (this.formData.admissionDate && new Date(this.formData.dischargeDate) < new Date(this.formData.admissionDate)) {
errors.dischargeDate = '出院日期不能早于入院日期';
}
break;
case 2:
if (this.handlingType === '2') {
if (!this.formData.recipientName.trim()) {
errors.recipientName = '请输入代办人姓名';
}
if (!this.formData.recipientPhone.trim()) {
errors.recipientPhone = '请输入代办人联系电话';
} else if (!/^1[3-9]\d{9}$/.test(this.formData.recipientPhone.trim())) {
errors.recipientPhone = '请输入有效的11位手机号码支持13-19开头';
}
if (!this.formData.recipientIdNumber.trim()) {
errors.recipientIdNumber = '请输入代办人身份证号';
} else if (!this.validateIdCard(this.formData.recipientIdNumber.trim())) {
errors.recipientIdNumber = '请输入有效的18位身份证号码';
}
}
break;
case 3:
if (!this.formData.idFrontFile) {
errors.idFront = '请上传患者身份证正面照片';
}
if (!this.formData.idBackFile) {
errors.idBack = '请上传患者身份证反面照片';
}
if (this.handlingType === '2' && !this.formData.powerOfAttorneyFile) {
errors.powerOfAttorney = '请上传病案复印授权委托书';
}
if (!this.hasAgreed) {
errors.agreement = '请阅读并同意病案申请须知';
}
break;
}
this.errors = errors;
return Object.keys(errors).length === 0;
},
validateIdCard(idCard) {
// 使用更严格的正则表达式验证身份证号码格式
const idCardRegex = /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/;
if (!idCardRegex.test(idCard)) {
return false;
}
// 校验码验证
const factor = [7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2];
const parity = ['1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2'];
let sum = 0;
let code = idCard.substring(17);
for (let i = 0; i < 17; i++) {
sum += parseInt(idCard[i], 10) * factor[i];
}
return parity[sum % 11].toUpperCase() === code.toUpperCase();
},
nextStep() {
if (this.validateStep(this.currentStep)) {
if (this.currentStep === 1 && this.handlingType === '1') {
this.syncRecipientName();
}
window.scrollTo({ top: 0, behavior: 'smooth' });
this.currentStep++;
} else {
if (this.currentStep === 3 && (this.errors.idFront || this.errors.idBack || this.errors.recipientIdFront || this.errors.recipientIdBack)) {
Toast.fail(this.handlingType === '2' ? '请上传完整的患者及代办人证件照片' : '请上传完整的身份证照片');
}
const firstError = Object.keys(this.errors)[0];
if (firstError) {
const elementIdMap = {
patientName: 'patientName',
patientId: 'patientId', // 新增就诊人卡号的元素ID映射
phone: 'phoneNumber',
idNumber: 'idNumber',
hospitalNumber: 'hospitalNumber',
admissionDate: 'admissionDate',
dischargeDate: 'dischargeDate',
recipientName: 'recipientName',
recipientPhone: 'recipientPhone',
recipientIdNumber: 'recipientIdNumber'
};
const elementId = elementIdMap[firstError];
if (elementId) {
document.getElementById(elementId)?.scrollIntoView({ behavior: 'smooth', block: 'center' });
}
}
}
},
prevStep() {
this.errors = {};
window.scrollTo({ top: 0, behavior: 'smooth' });
this.currentStep--;
},
handleSubmit() {
if (this.validateStep(this.currentStep)) {
this.showConfirmModal = true;
}
},
submitConfirmed() {
// 防止重复点击(如果用户快速点击)
if (this.isSubmitting) return;
this.isSubmitting = true; // 标记为提交中,禁用按钮
this.showConfirmModal = false;
const formData = new FormData();
formData.append('patientName', this.formData.patientName);
formData.append('patientId', this.formData.patientId); // 新增就诊人卡号字段
formData.append('idNumber', this.formData.idNumber);
formData.append('phone', this.formData.phone);
formData.append('hosNumber', this.formData.hospitalNumber);
formData.append('inhosDate', this.formData.admissionDate);
formData.append('outhosDate', this.formData.dischargeDate);
formData.append('handleType', this.handlingType);
formData.append('deliveryType', this.deliveryType);
formData.append('reName', this.formData.recipientName || '');
formData.append('reId', this.formData.recipientIdNumber || '');
formData.append('rePhone', this.formData.recipientPhone || '');
formData.append('copies', this.formData.copies);
const userId = localStorage.getItem("userid");
const token = localStorage.getItem("token");
if (userId && userId !== 'null') {
formData.append("userId", String(userId));
}
if (token && token !== 'null') {
formData.append("token", token);
}
if (this.formData.idFrontFile) {
formData.append('cardFrontFile', this.formData.idFrontFile);
}
if (this.formData.idBackFile) {
formData.append('cardBackFile', this.formData.idBackFile);
}
if (this.formData.recipientIdFrontFile) {
formData.append('recordFrontFile', this.formData.recipientIdFrontFile);
}
if (this.formData.recipientIdBackFile) {
formData.append('recordBackFile', this.formData.recipientIdBackFile);
}
if (this.formData.powerOfAttorneyFile) {
formData.append('powerOfAttorneyFile', this.formData.powerOfAttorneyFile);
}
const loading = Toast.loading({
message: '提交中...', // 加载提示文字
duration: 0, // 0 表示不会自动关闭,需要手动清除
forbidClick: true, // 禁止背景点击(防止重复提交)
loadingType: 'spinner' // 加载图标样式(可选,默认是 circular
});
apiBayj(formData).then(res => {
console.log('后台返回:', res);
// 关闭加载提示
loading.clear();
this.isSubmitting = false; // 恢复提交状态
if (res.code === 200) {
Toast.success('提交成功');
setTimeout(() => {
this.$router.push('/Bayj_wdsq');
}, 500);
this.resetForm();
} else {
Toast.fail(res.message || '提交失败,请重试');
}
}).catch(err => {
this.isSubmitting = false; // 恢复提交状态
// 关闭加载提示(错误情况也要关闭)
loading.clear();
console.error('申请提交失败:', err);
Toast.fail('网络异常,请检查网络后重试');
});
},
resetForm() {
this.currentStep = 0;
this.handlingType = '';
this.deliveryType = '';
this.hasAgreed = false;
this.formData = {
patientName: '',
patientId: '', // 新增就诊人卡号字段
phone: '',
idNumber: '',
hospitalNumber: '',
admissionDate: '',
dischargeDate: '',
recipientName: '',
recipientPhone: '',
recipientIdNumber: '',
copies: 1, // 打印份数默认1份
idFrontFile: null,
idBackFile: null,
recipientIdFrontFile: null,
recipientIdBackFile: null,
powerOfAttorneyFile: null,
idFrontUrl: '',
idBackUrl: '',
recipientIdFrontUrl: '',
recipientIdBackUrl: '',
powerOfAttorneyUrl: '',
};
// 重置日期选择器
this.admissionDate = new Date();
this.dischargeDate = new Date();
this.showAdmissionPicker = false;
this.showDischargePicker = false;
},
agreeAgreement() {
this.showAgreement = false;
this.hasAgreed = true;
}
}
}
</script>
<style scoped>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Segoe UI', 'Microsoft YaHei', 'PingFang SC', sans-serif;
}
.medical-form-page {
min-height: 100vh;
background-color: #f9f9f9;
color: #333;
padding: 0.4rem;
}
.center-container {
display: flex;
justify-content: center;
}
.medical-form {
width: 100%;
max-width: 950px;
display: flex;
flex-direction: column;
gap: 0.5rem;
padding: 0.6rem;
background-color: white;
border-radius: 0.1rem;
box-shadow: 0 0.02rem 0.08rem rgba(0, 0, 0, 0.08);
}
.form-header {
text-align: center;
margin-bottom: 0.2rem;
}
.form-title {
color: #333;
font-size: 0.42rem;
font-weight: 600;
margin-bottom: 0.2rem;
}
.form-desc {
color: #666;
font-size: 0.36rem;
line-height: 1.5;
}
.steps-container {
display: flex;
position: relative;
margin: 0.5rem 0;
padding: 0 0.2rem;
}
.step-line {
position: absolute;
top: 50%;
left: 0;
right: 0;
height: 0.02rem;
background-color: #e5e7eb;
transform: translateY(-50%);
z-index: 1;
}
.step-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
position: relative;
z-index: 2;
}
.step-number {
width: 0.6rem;
height: 0.6rem;
border-radius: 50%;
background-color: #e5e7eb;
color: #9ca3af;
display: flex;
align-items: center;
justify-content: center;
font-size: 0.36rem;
font-weight: 600;
margin-bottom: 0.15rem;
transition: all 0.3s ease;
}
.step-name {
font-size: 0.34rem;
color: #9ca3af;
transition: all 0.3s ease;
text-align: center;
}
.step-item.active .step-number {
background-color: #4299e1;
color: white;
}
.step-item.active .step-name {
color: #4299e1;
font-weight: 500;
}
.step-item.completed .step-number {
background-color: #4ade80;
color: white;
}
.step-item.completed .step-name {
color: #4ade80;
}
.step-content {
min-height: 3rem;
margin: 0.3rem 0;
}
.step-panel {
animation: fadeIn 0.3s ease;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(0.2rem);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.self-handling-step3 {
text-align: center;
padding: 1rem 0.5rem;
color: #666;
font-size: 0.38rem;
line-height: 1.8;
background-color: #f9f9f9;
border-radius: 0.05rem;
}
.step-navigation {
display: flex;
justify-content: space-between;
margin-top: 0.5rem;
gap: 0.3rem;
}
.nav-btn {
padding: 0.25rem 0;
font-size: 0.38rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border-radius: 0.05rem;
flex: 1;
}
.prev-btn {
background-color: #f5f5f5;
color: #555;
border: 1px solid #ddd;
}
.prev-btn:disabled {
background-color: #f9f9f9;
color: #ccc;
cursor: not-allowed;
}
.next-btn {
background-color: #4299e1;
color: white;
border: 1px solid #4299e1;
}
.next-btn:disabled {
background-color: #b3d4fc;
cursor: not-allowed;
}
.form-section {
display: flex;
flex-direction: column;
gap: 0.3rem;
margin-bottom: 0.4rem;
}
.section-title {
color: #333;
font-size: 0.4rem;
font-weight: 600;
padding-bottom: 0.2rem;
border-bottom: 1px solid #eee;
}
.form-fields {
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.form-row {
display: flex;
gap: 0.3rem;
width: 100%;
}
.date-group {
flex: 1;
}
.form-group {
display: flex;
flex-direction: column;
gap: 0.15rem;
}
.form-group label {
color: #555;
font-size: 0.38rem;
font-weight: 500;
display: flex;
align-items: center;
}
.required {
color: #DC2626;
margin-left: 0.05rem;
font-size: 0.38rem;
}
.form-input {
padding: 0.25rem 0.3rem;
border: 1px solid #ddd;
border-radius: 0.05rem;
font-size: 0.38rem;
transition: border-color 0.2s;
width: 100%;
}
.form-input:focus {
outline: none;
border-color: #4299e1;
}
.error-text {
color: #EF4444;
font-size: 0.36rem;
min-height: 0.4rem;
text-align: left;
}
.handling-type-section {
margin-bottom: 0.2rem;
}
.handling-type-selector {
display: flex;
gap: 0.3rem;
margin-top: 0.2rem;
width: 100%;
}
.handling-option {
display: flex;
align-items: center;
justify-content: center;
gap: 0.15rem;
cursor: pointer;
flex: 1;
padding: 0.25rem 0.3rem;
border-radius: 0.05rem;
transition: all 0.2s;
border: 1px solid #ddd;
background-color: white;
}
.handling-option:hover {
background-color: #f5f5f5;
}
.handling-radio {
width: 0.32rem;
height: 0.32rem;
}
.handling-text {
font-size: 0.38rem;
color: #555;
}
.delivery-type-section {
margin-bottom: 0.2rem;
}
.delivery-type-selector {
display: flex;
gap: 0.3rem;
margin-top: 0.2rem;
width: 100%;
}
.delivery-option {
display: flex;
align-items: center;
justify-content: center;
gap: 0.15rem;
cursor: pointer;
flex: 1;
padding: 0.25rem 0.3rem;
border-radius: 0.05rem;
transition: all 0.2s;
border: 1px solid #ddd;
background-color: white;
}
.delivery-option:hover {
background-color: #f5f5f5;
}
.delivery-radio {
width: 0.32rem;
height: 0.32rem;
}
.delivery-text {
font-size: 0.38rem;
color: #555;
}
.copies-options {
display: flex;
gap: 0.3rem;
margin-top: 0.2rem;
flex-wrap: wrap;
}
.copy-option {
display: flex;
align-items: center;
justify-content: center;
gap: 0.15rem;
cursor: pointer;
padding: 0.25rem 0.3rem;
border-radius: 0.05rem;
transition: all 0.2s;
border: 1px solid #ddd;
background-color: white;
}
.copy-option:hover {
background-color: #f5f5f5;
}
.copy-radio {
width: 0.32rem;
height: 0.32rem;
}
.copy-text {
font-size: 0.38rem;
color: #555;
}
.copy-option:has(.copy-radio:checked) {
border-color: #4299e1;
background-color: #f0f7ff;
}
.handling-option:has(.handling-radio:checked),
.delivery-option:has(.delivery-radio:checked) {
border-color: #4299e1;
background-color: #f0f7ff;
}
.fixed-value {
padding: 0.25rem 0.3rem;
border: 1px solid #ddd;
border-radius: 0.05rem;
font-size: 0.38rem;
background-color: #f9f9f9;
color: #333;
}
.upload-section {
display: flex;
flex-direction: column;
gap: 0.3rem;
}
.upload-row {
display: flex;
gap: 0.3rem;
width: 100%;
margin: 0.3rem 0;
}
.upload-item {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.2rem;
}
.upload-preview {
width: 100%;
height: 2.8rem;
border: 1px dashed #ddd;
border-radius: 0.05rem;
display: flex;
align-items: center;
justify-content: center;
background-color: #f9f9f9;
overflow: hidden;
}
.id-img {
width: 100%;
height: 100%;
object-fit: contain;
padding: 0.2rem;
}
.upload-preview.uploaded {
border-style: solid;
border-color: #ccc;
}
.upload-btn {
background-color: white;
color: #4299e1;
border: 1px solid #4299e1;
border-radius: 0.05rem;
padding: 0.15rem 0.25rem;
font-size: 0.32rem;
cursor: pointer;
transition: all 0.2s;
width: 100%;
}
.upload-btn:hover {
background-color: #4299e1;
color: white;
}
.file-input {
display: none;
}
.upload-desc {
color: #555;
font-size: 0.38rem;
font-weight: 500;
margin-top: 0.2rem;
}
.upload-notes {
color: #666;
font-size: 0.36rem;
line-height: 1.6;
padding: 0.2rem;
background-color: #f9f9f9;
border-radius: 0.05rem;
border: 1px solid #eee;
margin-top: 0.2rem;
}
@media (max-width: 768px) {
.upload-row {
gap: 0.2rem;
}
.upload-preview {
height: 2.2rem;
}
}
.agreement-section {
margin: 0.3rem 0;
}
.agreement-check {
display: flex;
align-items: center;
gap: 0.15rem;
color: #555;
font-size: 0.36rem;
}
.checkbox {
width: 0.36rem;
height: 0.36rem;
border: 1px solid #ddd;
border-radius: 0.05rem;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.2s;
}
.checkbox.checked {
background-color: #4299e1;
border-color: #4299e1;
color: white;
}
.agreement-check a {
color: #4299e1;
text-decoration: none;
}
.agreement-check a:hover {
text-decoration: underline;
}
.submit-btn {
background-color: #4299e1;
color: white;
border: none;
border-radius: 0.05rem;
padding: 0.25rem 0.4rem;
font-size: 0.4rem;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
width: 100%;
flex: 1;
}
.submit-btn:disabled {
background-color: #b3d4fc;
cursor: not-allowed;
}
.submit-btn:not(:disabled):hover {
background-color: #3182ce;
}
.modal-backdrop {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
padding: 0.4rem;
}
.confirm-modal,
.agreement-modal {
background-color: white;
border-radius: 0.1rem;
width: 100%;
box-shadow: 0 0.04rem 0.2rem rgba(0, 0, 0, 0.15);
}
.agreement-modal {
max-height: 90vh;
display: flex;
flex-direction: column;
}
.modal-title {
color: #333;
font-size: 0.4rem;
font-weight: 600;
padding: 0.3rem 0.4rem;
border-bottom: 1px solid #eee;
text-align: center;
}
.confirm-details {
padding: 0.4rem;
max-height: 60vh;
overflow-y: auto;
}
.detail-item {
display: flex;
justify-content: space-between;
padding: 0.2rem 0;
border-bottom: 1px solid #eee;
font-size: 0.36rem;
}
.detail-item:last-child {
border-bottom: none;
}
.detail-label {
color: #666;
flex: 0 0 35%;
text-align: left;
}
.detail-value {
color: #333;
flex: 0 0 65%;
text-align: right;
word-break: break-all;
}
.confirm-buttons {
display: flex;
border-top: 1px solid #eee;
}
.cancel-btn,
.confirm-btn {
padding: 0.25rem 0.4rem;
font-size: 0.38rem;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border: none;
flex: 1;
}
.cancel-btn {
background-color: #f5f5f5;
color: #555;
border-right: 1px solid #eee;
}
.confirm-btn {
background-color: #4299e1;
color: white;
}
.agreement-content {
flex: 1;
padding: 0.4rem;
overflow-y: auto;
line-height: 1.6;
font-size: 0.38rem;
color: #555;
}
.agreement-footer {
padding: 0.3rem 0.4rem;
border-top: 1px solid #eee;
}
.agree-btn {
background-color: #4299e1;
color: white;
border: none;
border-radius: 0.05rem;
padding: 0.25rem 0.4rem;
font-size: 0.38rem;
font-weight: 500;
cursor: pointer;
transition: background-color 0.2s;
width: 100%;
}
@media (max-width: 768px) {
.medical-form {
padding: 0.4rem;
gap: 0.4rem;
}
.form-row {
flex-direction: column;
gap: 0.3rem;
}
.handling-type-selector,
.delivery-type-selector {
gap: 0.2rem;
}
.detail-item {
flex-direction: column;
gap: 0.1rem;
}
.detail-label,
.detail-value {
flex: none;
width: 100%;
text-align: left;
}
.step-name {
font-size: 0.3rem;
}
}
.date-display {
cursor: pointer;
}
.date-display:disabled {
background-color: #f9f9f9;
cursor: not-allowed;
color: #999;
}
</style>