| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743 |
- <script setup>
- import { ref, reactive, computed, watch, onMounted } from 'vue'
- import { showToast, showConfirmDialog } from 'vant'
- import TopNavBar from '@/business/top-nav-bar.vue'
- import { getStudentDetail, getStudentLeaveApplication, applyForResumption, withdrawApplication } from '@/services/student-prosthetics'
- import utils from '@/utils/utils'
- // 学生数据
- const students = ref([
- {
- id: 'ec03f17b3474469f99cf7039bb11e688',
- name: '李小明',
- gradeClass: '',
- schoolName: '',
- studentNo: '',
- studentStatusStr: '', // 状态:'在校' 或 '休学中'
- applying: false, // 是否正在申请
- applyStatusStr: '', // 申请状态
- leaveType: null, // 申请类型:'1 休学' 或 '2 复学'
- certNo: null // 存证id
- },
- {
- id: 'e909040887a644a4b1b7bf2f0d6e9460',
- name: '李飞',
- studentId: '',
- gradeClass: '',
- schoolName: '',
- studentNo: '',
- studentStatusStr: '休学中',
- applying: false,
- applyStatusStr: '',
- leaveType: 1,
- certNo: 'LV202401150001'
- },
- // {
- // id: 3,
- // name: '张小刚',
- // studentId: '2023030303',
- // gradeClass: '初三·2班',
- // schoolName: '太原市第五中学',
- // studentNo: '123',
- // studentStatusStr: '在校',
- // applying: true,
- // applyStatusStr: '审批中', // 申请状态
- // leaveType: 1,
- // certNo: null
- // }
- ])
- // 当前选中的学生
- const selectedStudent = ref(null)
- // 申请表单数据
- const formData = reactive({
- leaveType: 1, // 1'休学' 或 2'复学'
- diseaseDiagnosis: '',
- expectedStartDate: '',
- expectedEndDate: '',
- auditOpinion: '',
- stdLeaveAttachments: []
- })
- // 期限描述
- const periodDescription = computed(() => {
- if (!formData.expectedStartDate || !formData.expectedEndDate) return ''
- const start = new Date(formData.expectedStartDate)
- const end = new Date(formData.expectedEndDate)
- const days = Math.ceil((end - start) / (1000 * 60 * 60 * 24))
- return `共${days}天`
- })
- // 方法:选择学生
- const selectStudent = (student) => {
- if (student.applying) {
- showToast('该学生有正在审批中的申请,请等待审批完成')
- }
- selectedStudent.value = student
- // 重置表单
- resetForm()
- }
- // 重置表单
- const resetForm = () => {
- if (selectedStudent.value) {
- if (selectedStudent.value.studentStatusStr === '休学中') {
- formData.leaveType = 2
- } else {
- formData.leaveType = 1
- }
- formData.diseaseDiagnosis = ''
- formData.expectedStartDate = ''
- formData.expectedEndDate = ''
- formData.auditOpinion = ''
- formData.stdLeaveAttachments = []
- }
- }
- // 查看存证
- const viewEvidence = (student) => {
- localStorage.setItem('studentInfo', JSON.stringify(student))
- uni.navigateTo({
- url: `/modules/common/student-prosthetics/certificate`
- })
- }
- // 查看审批进度
- const viewProgress = (student) => {
- localStorage.setItem('studentInfo', JSON.stringify(student))
- uni.navigateTo({
- url: `/modules/common/student-prosthetics/approval-progress`
- })
- }
- const showDialog = ref(false)
- const auditOpinion = ref('')
- // 撤回申请
- const withdrawApply = () => {
- if (!selectedStudent.value?.applying) return
- auditOpinion.value = ''
- showDialog.value = true
- }
- const handleConfirm = async () => {
- if (!auditOpinion.value.trim()) {
- showToast('请输入撤回原因')
- return
- }
- try {
- await showConfirmDialog({
- title: '确认撤回',
- message: '确定要撤回申请吗?撤回后需要重新提交'
- })
- withdrawApplication({
- auditOpinion: auditOpinion.value,
- auditorId: selectedStudent.value.applicantId,
- auditorName: selectedStudent.value.applicantName,
- }).then(res => {
- if (res.success) {
- showToast('撤回成功')
- init(0)
- init(1)
- selectedStudent.value = null
- }
- })
- } catch (error) {
- // 用户取消
- }
- }
- // 提交申请
- const submitApply = async () => {
- // 表单验证
- if (!formData.diseaseDiagnosis.trim()) {
- showToast('请输入诊断记录')
- return
- }
- if (!formData.expectedStartDate) {
- showToast('请选择开始日期')
- return
- }
- if (!formData.expectedEndDate) {
- showToast('请选择结束日期')
- return
- }
- if (!formData.auditOpinion.trim()) {
- showToast('请输入申请原因')
- return
- }
- if (!formData.stdLeaveAttachments || !formData.stdLeaveAttachments.length) {
- showToast('请上传证明材料')
- return
- }
- let params = {
- studentId: selectedStudent.value.id,
- studentName: selectedStudent.value.name,
- applicantId: "02e168c19b7c456eafeb647853a079ae",
- applicantName: "15296619861",
- ...formData
- }
- params.expectedStartDate = params.expectedStartDate + ' 00:00:00'
- params.expectedEndDate = params.expectedEndDate + ' 00:00:00'
- if (params.stdLeaveAttachments && params.stdLeaveAttachments.length) {
- params.stdLeaveAttachments = params.stdLeaveAttachments.map(item => {
- return {
- fileName: item.fileName,
- fileUrl: item.fileUrl,
- fileType: item.fileType
- }
- })
- }
- applyForResumption(params).then(res => {
- if (res.success) {
- showToast('提交成功,等待审批')
- init(0)
- init(1)
- selectedStudent.value = null
- }
- })
- }
- // 上传文件
- const afterRead = async (file) => {
- let files = []
- if (Array.isArray(file)) {
- files = file
- } else {
- files = [file]
- }
- console.log('afterRead', files)
- const res = await utils.transferOssUrl(files) || []
- formData.stdLeaveAttachments = formData.stdLeaveAttachments.map(item => {
- if (item.file) {
- const index = res.findIndex(file => file.objectUrl === item.objectUrl)
- if (index !== -1) {
- return res[index]
- }
- }
- return item
- })
- }
- // 删除文件
- const removeFile = () => { }
- // 日期选择器相关
- const showStartDatePicker = ref(false)
- const showEndDatePicker = ref(false)
- const onStartDateConfirm = ({ selectedValues }) => {
- formData.expectedStartDate = selectedValues.join('-')
- showStartDatePicker.value = false
- }
- const onEndDateConfirm = ({ selectedValues }) => {
- formData.expectedEndDate = selectedValues.join('-')
- showEndDatePicker.value = false
- }
- const getStudentDetailFun = (id) => {
- return new Promise(async (resolve, reject) => {
- const res1 = await getStudentDetail({ id })
- const res2 = await getStudentLeaveApplication({ studentId: id })
- if (res1.success && res2.success) {
- const res1Info = res1.info || {}
- const res2Info = res2.info || {}
- resolve({
- success: res1.success && res2.success,
- info: {
- res1: {
- id: res1Info.id,
- name: res1Info.name,
- gradeClass: res1Info.gradeClass,
- schoolName: res1Info.schoolName,
- studentNo: res1Info.studentNo,
- },
- res2: {
- studentStatusStr: res2Info.studentStatusStr || '在校', // 状态:'在校' 或 '休学中'
- applying: res2Info.applyStatusStr ? true : false, // 是否正在申请
- applyStatusStr: res2Info.applyStatusStr, // 申请状态
- leaveType: res2Info.leaveType, // 申请类型:'1 休学' 或 '2 复学'
- certNo: res2Info.certNo, // 存证id
- createdTime: res2Info.createdTime || '', // 申请时间 存证时间
- details: {
- diseaseDiagnosis: res2Info.diseaseDiagnosis,
- expectedStartDate: res2Info.expectedStartDate,
- expectedEndDate: res2Info.expectedEndDate,
- auditOpinion: res2Info.auditOpinion
- },
- applicationId: res2Info.id,
- applicantId: res2Info.applicantId,
- applicantName: res2Info.applicantName,
- tenantId: res2Info.tenantId
- }
- }
- })
- } else {
- reject()
- }
- })
- }
- const init = (num = 0) => {
- const id = students.value[num]?.id
- getStudentDetailFun(id).then((res) => {
- if (res.success) {
- students.value[num] = Object.assign({}, students.value[num], res.info.res1, res.info.res2)
- // students.value[num].applying = false // 硬编码 需删除
- // students.value[num].certNo = '100000' // 硬编码 需删除
- // students.value[num].studentStatusStr = '休学中' // 硬编码 需删除
- }
- })
- }
- onMounted(() => {
- init(0)
- init(1)
- })
- </script>
- <template>
- <div class="apply-page">
- <TopNavBar></TopNavBar>
- <!-- 选择学生部分 -->
- <div class="section">
- <div class="section-title">选择学生</div>
- <div class="student-list">
- <div v-for="student in students" :key="student.id" class="student-item"
- :class="{ active: selectedStudent?.id === student.id }" @click="selectStudent(student)">
- <div class="student-info">
- <div class="name-status">
- <span class="name">{{ student.name }}</span>
- <span class="status-badge" :class="student.studentStatusStr">
- {{ student.studentStatusStr }}
- </span>
- </div>
- <div class="class-info">{{ student.gradeClass }}</div>
- <div v-if="student.certNo" class="extra-info">
- <div class="evidence-id">存证编号:{{ student.certNo }}</div>
- <van-button v-if="student.studentStatusStr === '休学中'" size="mini" type="primary"
- @click.stop="viewEvidence(student)">
- 查看存证
- </van-button>
- </div>
- <div v-if="student.applying" class="extra-info">
- <div class="applying-status">{{ student.applyStatusStr }}</div>
- <van-button size="mini" type="primary" @click.stop="viewProgress(student)">
- 查看进度
- </van-button>
- </div>
- </div>
- </div>
- </div>
- </div>
- <!-- 学生信息和申请表单(只在选中学生时显示) -->
- <div v-if="selectedStudent" class="form-container">
- <!-- 学生信息 -->
- <div class="section">
- <div class="section-title">学生信息</div>
- <div class="info-grid">
- <div class="info-row">
- <div class="info-label">姓名</div>
- <div class="info-value">{{ selectedStudent.name }}</div>
- </div>
- <div class="info-row">
- <div class="info-label">学号</div>
- <div class="info-value">{{ selectedStudent.studentNo }}</div>
- </div>
- <div class="info-row">
- <div class="info-label">所属学校</div>
- <div class="info-value">{{ selectedStudent.schoolName }}</div>
- </div>
- <div class="info-row">
- <div class="info-label">当前年级</div>
- <div class="info-value">{{ selectedStudent.gradeClass }}</div>
- </div>
- </div>
- </div>
- <!-- 审批中的提示 -->
- <div v-if="selectedStudent.applying" class="section warning">
- <div class="warning-text">
- 该学生有正在审批中的休学申请,请等待审批完成后再发起新的申请。
- </div>
- <div class="apply-info">
- <div class="apply-row">
- <div class="apply-label">申请类型</div>
- <div class="apply-value">{{ selectedStudent.leaveType == 1 ? '休学' : '复学' }}</div>
- </div>
- <div class="apply-row">
- <div class="apply-label">提交时间</div>
- <div class="apply-value">{{ selectedStudent?.createdTime || '' }}</div>
- </div>
- <div class="apply-row">
- <div class="apply-label">当前状态</div>
- <div class="apply-value status-text">{{ selectedStudent.applyStatusStr }}</div>
- </div>
- </div>
- <div class="action-buttons">
- <van-button type="primary" @click="viewProgress(selectedStudent)">查看审批进度</van-button>
- <van-button v-if="selectedStudent.leaveType === 1" type="default" @click="withdrawApply">
- 撤回申请
- </van-button>
- </div>
- </div>
- <!-- 申请表单(只在非审批中状态显示) -->
- <div v-else class="section">
- <div class="section-title">申请信息</div>
- <!-- 业务类型 -->
- <div class="form-group">
- <div class="form-label">业务类型</div>
- <div class="radio-group">
- <van-radio-group v-model="formData.leaveType" direction="horizontal">
- <van-radio :name="1" :disabled="selectedStudent.studentStatusStr === '休学中'">休学</van-radio>
- <van-radio :name="2" :disabled="selectedStudent.studentStatusStr !== '休学中'">复学</van-radio>
- </van-radio-group>
- </div>
- </div>
- <!-- 诊断记录 -->
- <div class="form-group">
- <div class="form-label">诊断记录</div>
- <van-field v-model="formData.diseaseDiagnosis" type="textarea" placeholder="请输入医院诊断结果" rows="2"
- maxlength="200" show-word-limit />
- </div>
- <van-notice-bar left-icon="info-o" class="tw-mt-[10rpx]">
- 请务必按照医院诊断证明书上的结论据实填写
- </van-notice-bar>
- <!-- 开始日期 -->
- <div class="form-group">
- <div class="form-label">开始日期</div>
- <van-field v-model="formData.expectedStartDate" readonly placeholder="请选择开始日期"
- @click="showStartDatePicker = true" />
- </div>
- <!-- 结束日期 -->
- <div class="form-group">
- <div class="form-label">结束日期</div>
- <van-field v-model="formData.expectedEndDate" readonly placeholder="请选择结束日期"
- @click="showEndDatePicker = true" />
- </div>
- <!-- 期限描述 -->
- <div class="form-group">
- <div class="form-label">期限描述</div>
- <van-field v-model="periodDescription" placeholder="系统将根据起止日期自动计算期限" readonly />
- </div>
- <van-notice-bar color="#1989fa" background="#ecf9ff" left-icon="info-o" class="tw-mt-[10rpx]">
- 系统将根据起止日期自动计算期限
- </van-notice-bar>
- <!-- 申请原因 -->
- <div class="form-group">
- <div class="form-label">申请原因</div>
- <van-field v-model="formData.auditOpinion" type="textarea" placeholder="请详细说明申请原因" rows="3"
- maxlength="200" show-word-limit />
- </div>
- <!-- 日期选择器 -->
- <van-popup v-model:show="showStartDatePicker" position="bottom">
- <van-date-picker title="选择开始日期" @confirm="onStartDateConfirm" @cancel="showStartDatePicker = false" />
- </van-popup>
- <van-popup v-model:show="showEndDatePicker" position="bottom">
- <van-date-picker title="选择结束日期" @confirm="onEndDateConfirm" @cancel="showEndDatePicker = false" />
- </van-popup>
- </div>
- <!-- 证明材料 -->
- <div v-if="!selectedStudent.applying" class="section">
- <div class="section-title">证明材料</div>
- <div class="upload-list">
- <!-- 病历资料 -->
- <div class="upload-item">
- <div class="upload-title">医院诊断证明(必传)</div>
- <div class="upload-title">病历资料(支持多页)</div>
- <div class="upload-title">家长身份证复印件</div>
- <van-uploader v-model="formData.stdLeaveAttachments" multiple :max-count="9" :after-read="afterRead"
- @delete="removeFile" />
- </div>
- </div>
- </div>
- <!-- 提交按钮 -->
- <div v-if="!selectedStudent.applying" class="submit-section">
- <van-button type="primary" size="large" @click="submitApply">提交申请</van-button>
- </div>
- </div>
- </div>
- <van-dialog v-model:show="showDialog" title="撤回申请原因" showConfirmButton showCancelButton @confirm="handleConfirm">
- <van-field v-model="auditOpinion" type="textarea" placeholder="请输入撤回申请原因" rows="3" maxlength="200"
- show-word-limit />
- </van-dialog>
- </template>
- <style lang="scss" scoped>
- .apply-page {
- padding: 24rpx;
- background-color: #f7f8fa;
- min-height: 100vh;
- padding-top: 120rpx;
- }
- .section {
- background: #fff;
- border-radius: 16rpx;
- padding: 24rpx;
- margin-bottom: 24rpx;
- &.warning {
- border-left: 8rpx solid #ff4444;
- background-color: #fff7f7;
- }
- }
- .section-title {
- font-size: 32rpx;
- font-weight: 600;
- color: #333;
- margin-bottom: 24rpx;
- padding-bottom: 16rpx;
- border-bottom: 1rpx solid #eee;
- }
- .student-list {
- .student-item {
- padding: 24rpx;
- margin-bottom: 16rpx;
- border-radius: 12rpx;
- border: 2rpx solid #eee;
- background: #fff;
- &.active {
- border-color: #1989fa;
- background-color: #f0f9ff;
- }
- &:last-child {
- margin-bottom: 0;
- }
- .student-info {
- .name-status {
- display: flex;
- align-items: center;
- justify-content: space-between;
- margin-bottom: 8rpx;
- .name {
- font-size: 32rpx;
- font-weight: 500;
- color: #333;
- }
- .status-badge {
- font-size: 24rpx;
- padding: 4rpx 12rpx;
- border-radius: 12rpx;
- &.在校 {
- background-color: #e8f5e9;
- color: #4caf50;
- }
- &.休学中 {
- background-color: #fff3e0;
- color: #ff9800;
- }
- }
- }
- .class-info {
- font-size: 28rpx;
- color: #666;
- margin-bottom: 12rpx;
- }
- .extra-info {
- display: flex;
- justify-content: space-between;
- align-items: center;
- margin-top: 12rpx;
- padding-top: 12rpx;
- border-top: 1rpx solid #eee;
- .evidence-id {
- font-size: 26rpx;
- color: #888;
- }
- .applying-status {
- font-size: 24rpx;
- padding: 4rpx 12rpx;
- border-radius: 12rpx;
- background-color: #fff3e0;
- color: #ff9800;
- }
- }
- }
- }
- }
- .info-grid {
- .info-row {
- display: flex;
- padding: 16rpx 0;
- border-bottom: 1rpx solid #f0f0f0;
- &:last-child {
- border-bottom: none;
- }
- .info-label {
- width: 200rpx;
- font-size: 28rpx;
- color: #666;
- }
- .info-value {
- flex: 1;
- font-size: 28rpx;
- color: #333;
- }
- }
- }
- .warning-text {
- font-size: 28rpx;
- color: #ff4444;
- margin-bottom: 24rpx;
- line-height: 1.4;
- }
- .apply-info {
- background: #fff;
- border-radius: 12rpx;
- padding: 16rpx;
- margin-bottom: 24rpx;
- .apply-row {
- display: flex;
- padding: 12rpx 0;
- .apply-label {
- width: 200rpx;
- font-size: 28rpx;
- color: #666;
- }
- .apply-value {
- flex: 1;
- font-size: 28rpx;
- color: #333;
- &.status-text {
- color: #ff9800;
- font-weight: 500;
- }
- }
- }
- }
- .action-buttons {
- display: flex;
- gap: 24rpx;
- .van-button {
- flex: 1;
- }
- }
- .form-container {
- .form-group {
- margin-top: 32rpx;
- .form-label {
- font-size: 28rpx;
- color: #333;
- margin-bottom: 12rpx;
- font-weight: 500;
- }
- .radio-group {
- .van-radio {
- margin-right: 48rpx;
- }
- }
- :deep(.van-field) {
- background-color: #fafafa;
- border-radius: 8rpx;
- .van-field__control {
- font-size: 28rpx;
- }
- }
- }
- }
- .upload-list {
- .upload-item {
- margin-bottom: 32rpx;
- &:last-child {
- margin-bottom: 0;
- }
- .upload-title {
- font-size: 28rpx;
- color: #333;
- margin-bottom: 16rpx;
- font-weight: 500;
- }
- :deep(.van-uploader) {
- .van-uploader__upload {
- width: 160rpx;
- height: 160rpx;
- margin: 0;
- }
- .van-uploader__preview {
- margin: 0 16rpx 16rpx 0;
- }
- .van-uploader__preview-image {
- width: 160rpx;
- height: 160rpx;
- border-radius: 8rpx;
- }
- }
- }
- }
- .submit-section {
- margin-top: 48rpx;
- padding: 24rpx;
- :deep(.van-button) {
- height: 88rpx;
- font-size: 32rpx;
- border-radius: 44rpx;
- }
- }
- </style>
|