外宿申请-附件回显bug

This commit is contained in:
2025-12-12 11:51:17 +08:00
parent 8042c3d821
commit 2b7725ab91
4 changed files with 422 additions and 378 deletions

View File

@@ -1,46 +1,32 @@
<template> <template>
<div> <div>
<el-upload <el-upload :style="uploadStyle" action="" :class="['cm-affix', { 'is-disabled': inputDisabled }]"
:style="uploadStyle" :disabled="inputDisabled" :multiple="true" :http-request="handleUpload" :file-list="fileList" :accept="accpet"
action="" :show-file-list="false" :before-upload="handleBeforeUpload">
:class="['cm-affix', {'is-disabled': inputDisabled}]" <el-button v-if="inputDisabled !== true" id="affix1" :disabled="notupload" size="small" type="primary"><i
:disabled="inputDisabled" class='el-icon-upload2'></i>点击上传</el-button>
:multiple="true" <el-button v-if="this.affixId !== null && this.affixId !== '' && this.fileList.length > 0" id="affix2"
:http-request="handleUpload" size="small" @click.stop="downloadPack()">
:file-list="fileList" <div v-if="downloading" class="el-icon-loading file-upload"
:accept="accpet" style="margin-left: 0px;margin-right: 3px;font-size: 14px;" />
:show-file-list="false"
:before-upload="handleBeforeUpload"
>
<el-button v-if="inputDisabled !== true" id="affix1" :disabled="notupload" size="small" type="primary"><i class='el-icon-upload2'></i>点击上传</el-button>
<el-button
v-if="this.affixId !== null && this.affixId !== '' && this.fileList.length > 0"
id="affix2"
size="small"
@click.stop="downloadPack()"
>
<div v-if="downloading" class="el-icon-loading file-upload" style="margin-left: 0px;margin-right: 3px;font-size: 14px;" />
打包下载 打包下载
</el-button> </el-button>
</el-upload> </el-upload>
<div v-for="(item, index) in fileList" :key="index" class="file"> <div v-for="(item, index) in fileList" :key="index" class="file">
<div class="file-item" :class="[{'is-disabled': inputDisabled}]"> <div class="file-item" :class="[{ 'is-disabled': inputDisabled }]">
<div class="file-name">{{ item.name }}</div> <div class="file-name">{{ item.name }}</div>
<div v-if="item.status===1" class="el-icon-loading file-upload" title="上传中..." /> <div v-if="item.status === 1" class="el-icon-loading file-upload" title="上传中..." />
<div v-if="item.status===2" class="el-icon-download file-download" title="下载" @click="downloadFile(item)" /> <div v-if="item.status === 2" class="el-icon-download file-download" title="下载" @click="downloadFile(item)" />
<div v-if="item.status===2" class="el-icon-delete file-delete" title="删除" @click="deleteFile(item)" /> <div v-if="item.status === 2" class="el-icon-delete file-delete" title="删除" @click="deleteFile(item)" />
<div v-if="isImageURL(item.name) && item.status === 2" class="el-icon-picture" title="预览" @click="preview(item)" /> <div v-if="isImageURL(item.name) && item.status === 2" class="el-icon-picture" title="预览"
@click="preview(item)" />
</div> </div>
</div> </div>
<el-image v-show="false" ref="preview" <el-image v-show="false" ref="preview" class="preview" :src="hiddenSrc" :preview-src-list="previewList" />
class="preview"
:src="hiddenSrc"
:preview-src-list="previewList"
/>
</div> </div>
</template> </template>
@@ -56,19 +42,19 @@ export default {
} }
}, },
props: { props: {
notupload:{ notupload: {
type:Boolean, type: Boolean,
default:false default: false
}, },
value: String, // 父组件值 value: String, // 父组件值
disabled: Boolean, disabled: Boolean,
maxSize:{ maxSize: {
type: Number, type: Number,
default: 20 default: 20
}, },
accpet:{ accpet: {
type:String, type: String,
default:'*' default: '*'
}, },
uploadStyle: uploadStyle:
{ {
@@ -83,10 +69,13 @@ export default {
uploadCnt: 0, uploadCnt: 0,
affixId: '', affixId: '',
fileList: [], fileList: [],
downloading:false,//控制打包下载loading动画 downloading: false,//控制打包下载loading动画
baseurl: process.env.VUE_APP_BASE_API , baseurl: process.env.VUE_APP_BASE_API,
hiddenSrc:'', hiddenSrc: '',
previewList:[], previewList: [],
// 解决重复提交的核心变量
isQuerying: false, // 请求锁:标记是否正在查询附件
retryCount: 0 // 重试次数:捕获重复提交错误时重试
} }
}, },
computed: { computed: {
@@ -97,11 +86,18 @@ export default {
watch: { watch: {
// 父组件值监听事件 // 父组件值监听事件
value: { value: {
handler: function(newVal/*, oldVal*/) { immediate: true, // 初始化立即执行
if (newVal === undefined || newVal == null || newVal === '' handler: function (newVal, oldVal) {
|| this.affixId === undefined || this.affixId == null || this.affixId === '' // 新旧值不同时,先清空所有旧数据(核心解决数据残留)
|| newVal !== this.affixId) { if (newVal !== oldVal) {
this.loadData() this.fileList = [];
this.previewList = [];
this.affixId = '';
this.retryCount = 0;
}
// 满足条件才加载数据
if (newVal && newVal !== '' && newVal !== this.affixId) {
this.loadData();
} }
} }
} }
@@ -109,9 +105,17 @@ export default {
created() { created() {
this.loadData() this.loadData()
}, },
// 新增组件激活时清空旧数据适配keep-alive页面
activated() {
this.fileList = [];
this.previewList = [];
if (this.value) {
this.loadData();
}
},
methods: { methods: {
// 预览 // 预览
preview(item){ preview(item) {
this.hiddenSrc = item.savePath this.hiddenSrc = item.savePath
this.$refs.preview.showViewer = true this.$refs.preview.showViewer = true
@@ -123,20 +127,20 @@ export default {
}, },
handleUpload(file) { handleUpload(file) {
upload({'file': file.file, 'affixId': this.affixId}).then(res => { upload({ 'file': file.file, 'affixId': this.affixId }).then(res => {
this.uploadCnt-- this.uploadCnt--
if (res.code == 200) { if (res.code == 200) {
for (let i = 0; i < this.fileList.length; i++) { for (let i = 0; i < this.fileList.length; i++) {
let item = this.fileList[i] let item = this.fileList[i]
console.log(res.savePath) console.log(res.savePath)
if(item.name == res.trueName && item.status == 1){ if (item.name == res.trueName && item.status == 1) {
this.fileList[i].id = res.id this.fileList[i].id = res.id
this.fileList[i].status = 2 this.fileList[i].status = 2
this.fileList[i].savePath = this.baseurl + res.savePath this.fileList[i].savePath = this.baseurl + res.savePath
if(this.isImageURL(res.savePath)){ if (this.isImageURL(res.savePath)) {
this.previewList.push( this.fileList[i].savePath) this.previewList.push(this.fileList[i].savePath)
} }
// this.fileList[i].savePaths =[this.baseurl + res.savePath]; // this.fileList[i].savePaths =[this.baseurl + res.savePath];
@@ -177,19 +181,19 @@ export default {
let isLt20M = file.size / 1024 / 1024 < this.maxSize let isLt20M = file.size / 1024 / 1024 < this.maxSize
if (!isLt20M) { if (!isLt20M) {
this.$message.error('上传大小不能超过 '+this.maxSize+'MB!') this.$message.error('上传大小不能超过 ' + this.maxSize + 'MB!')
} else { } else {
this.fileList.push({name: file.name, status: 1}) this.fileList.push({ name: file.name, status: 1 })
this.uploadCnt++ this.uploadCnt++
} }
return isLt20M return isLt20M
}, },
getFileName(id){ getFileName(id) {
for (let i = 0; i < this.fileList.length; i++) { for (let i = 0; i < this.fileList.length; i++) {
let item = this.fileList[i] let item = this.fileList[i]
if(item.id == id){ if (item.id == id) {
console.log(item) console.log(item)
return item.name return item.name
} }
@@ -199,7 +203,7 @@ export default {
download(file.id).then((res) => { download(file.id).then((res) => {
console.log(res) console.log(res)
let fileName = this.getFileName(file.id).replace(/"/g, '') let fileName = this.getFileName(file.id).replace(/"/g, '')
var blob = new Blob([res], {type: 'application/octet-stream;'}) var blob = new Blob([res], { type: 'application/octet-stream;' })
var downloadElement = document.createElement('a') var downloadElement = document.createElement('a')
var href = window.URL.createObjectURL(blob) // 创建下载的链接 var href = window.URL.createObjectURL(blob) // 创建下载的链接
downloadElement.href = href downloadElement.href = href
@@ -219,13 +223,13 @@ export default {
return return
} }
//如果只有一个文件就不打包下载 //如果只有一个文件就不打包下载
if(this.fileList.length == 1){ if (this.fileList.length == 1) {
this.downloadFile(this.fileList[0]) this.downloadFile(this.fileList[0])
}else{ } else {
this.downloading = true this.downloading = true
downloadAll(this.affixId).then((res) => { downloadAll(this.affixId).then((res) => {
var blob = new Blob([res], {type: 'application/octet-stream'}) var blob = new Blob([res], { type: 'application/octet-stream' })
var downloadElement = document.createElement('a') var downloadElement = document.createElement('a')
var href = window.URL.createObjectURL(blob) // 创建下载的链接 var href = window.URL.createObjectURL(blob) // 创建下载的链接
downloadElement.href = href downloadElement.href = href
@@ -243,12 +247,12 @@ export default {
}, },
deleteFile(file) { deleteFile(file) {
this.$confirm('请确认是否删除此文件?', '提示', {type: 'info'}).then(() => { this.$confirm('请确认是否删除此文件?', '提示', { type: 'info' }).then(() => {
deleteAffix(file.id).then((res) => { deleteAffix(file.id).then((res) => {
if (res.code == 200) { if (res.code == 200) {
this.fileList = this.fileList.filter(item => item.id !== file.id) this.fileList = this.fileList.filter(item => item.id !== file.id)
this.previewList = this.previewList.filter(item => item !== file.savePath)//删除图片 this.previewList = this.previewList.filter(item => item !== file.savePath)//删除图片
if(this.fileList.length == 0){ if (this.fileList.length == 0) {
this.affixId = '' this.affixId = ''
} }
@@ -262,43 +266,83 @@ export default {
}) })
}, },
loadData() { loadData() {
this.fileList = [] // 1. 请求锁:防止同一时刻重复请求(解决重复提交)
if (typeof this.value !== 'undefined' && this.value !== null && this.value !== '') { if (this.isQuerying) return;
queryAffixs(this.value).then((res) => { // 2. 清空旧数据(双重保障解决数据残留)
if (res.code ==200) { this.fileList = [];
if(res.data.length > 0){ this.previewList = [];
this.affixId = this.value
for (var i=0;i<res.data.length;i++) {
if(this.isImageURL(res.data[i].savePath)){
this.previewList.push(this.baseurl+ res.data[i].savePath)
}
this.fileList.push({name: res.data[i].trueName, id: res.data[i].id, status: 2,savePath:this.baseurl+ res.data[i].savePath})
}
} else {
this.affixId = ''
}
} else { if (typeof this.value !== 'undefined' && this.value !== null && this.value !== '') {
this.$message.error(res.msg) this.isQuerying = true; // 加锁
// 带重试机制的查询方法
const queryWithRetry = () => {
queryAffixs(this.value).then((res) => {
this.isQuerying = false; // 解锁
if (res.code == 200) {
if (res.data.length > 0) {
this.affixId = this.value;
// 重新构建数组,避免引用污染
const newFileList = [];
const newPreviewList = [];
for (var i = 0; i < res.data.length; i++) {
const savePath = this.baseurl + res.data[i].savePath;
if (this.isImageURL(savePath)) {
newPreviewList.push(savePath);
} }
}).catch((res) => { newFileList.push({
this.editLoading = false name: res.data[i].trueName,
this.$message.error(res.message) id: res.data[i].id,
}) status: 2,
savePath: savePath
});
}
// 直接替换数组,彻底解决残留
this.fileList = newFileList;
this.previewList = newPreviewList;
} else { } else {
this.affixId = '' this.affixId = '';
}
} else {
// 捕获"重复提交"错误重试1次
if (res.msg.includes('请勿重复提交') && this.retryCount < 1) {
this.retryCount++;
this.isQuerying = true;
setTimeout(() => queryWithRetry(), 500); // 500ms后重试
return;
}
this.$message.error(res.msg);
}
}).catch((err) => {
this.isQuerying = false; // 解锁
// 捕获异常中的重复提交错误重试1次
if (err.message?.includes('请勿重复提交') && this.retryCount < 1) {
this.retryCount++;
setTimeout(() => queryWithRetry(), 500);
return;
}
this.$message.error(err.message || '查询附件失败');
});
};
// 执行查询
queryWithRetry();
} else {
this.affixId = '';
this.isQuerying = false;
} }
}, },
clearData() { clearData() {
this.affixId = '' this.affixId = '';
this.fileList = [] this.fileList = [];
this.previewList = [];
this.isQuerying = false;
this.retryCount = 0;
} }
} }
} }
</script> </script>
<style scoped> <style scoped>
.cm-affix >>>.el-upload{ .cm-affix>>>.el-upload {
padding: 0px 10px; padding: 0px 10px;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
@@ -306,53 +350,64 @@ export default {
align-items: center; align-items: center;
margin-bottom: 10px; margin-bottom: 10px;
} }
.file {
.file {
width: 100%; width: 100%;
} }
.file-item {
.file-item {
white-space: nowrap; white-space: nowrap;
display: flex; display: flex;
justify-content: start; justify-content: start;
align-items: center; align-items: center;
gap: 8px; gap: 8px;
} }
.file-item div {
.file-item div {
display: inline-block; display: inline-block;
white-space: nowrap; white-space: nowrap;
} }
.file-upload {
.file-upload {
font-size: 18px; font-size: 18px;
margin-left: 5px; margin-left: 5px;
} }
.file-download, .file-delete{
.file-download,
.file-delete {
font-size: 18px; font-size: 18px;
cursor: pointer; cursor: pointer;
} }
.file-download:hover, .file-delete:hover {
.file-download:hover,
.file-delete:hover {
color: #409EFF; color: #409EFF;
} }
.file-item[class~=is-disabled] .file-delete {
.file-item[class~=is-disabled] .file-delete {
display: none; display: none;
} }
.download-pack:hover {
.download-pack:hover {
background: #409eff; background: #409eff;
border-color: #3999a8; border-color: #3999a8;
color: #fafbfd; color: #fafbfd;
} }
.preview .file-preview{
.preview .file-preview {
font-size: 19px; font-size: 19px;
cursor: pointer; cursor: pointer;
} }
.preview >>>.el-image__inner{ .preview>>>.el-image__inner {
width: 18px; width: 18px;
height: 18px; height: 18px;
} }
</style> </style>

View File

@@ -274,7 +274,7 @@
<i class="el-icon-folder" /> <i class="el-icon-folder" />
保存 保存
</el-button> </el-button>
<el-button type="primary" size="medium" @click="submitForm(1)"> <el-button type="primary" size="medium" @click="submitForm(1)" v-if="form.id == ''">
<i class="el-icon-check" /> <i class="el-icon-check" />
提交申请 提交申请
</el-button> </el-button>
@@ -703,63 +703,52 @@ export default {
submitForm(status) { submitForm(status) {
this.$refs.formRef.validate((valid) => { this.$refs.formRef.validate((valid) => {
if (valid) { if (valid) {
// 生成申请编号 // 生成申请编号等逻辑不变
if (!this.form.applyNo) { if (!this.form.applyNo) {
const year = new Date().getFullYear(); const year = new Date().getFullYear();
const randomNo = Math.floor(Math.random() * 1000000).toString().padStart(6, '0'); const randomNo = Math.floor(Math.random() * 1000000).toString().padStart(6, '0');
this.form.applyNo = `WS${year}${randomNo}`; // 获取申请编号 this.form.applyNo = `WS${year}${randomNo}`;
} }
// 生成外宿结束时间默认次年8月31日
this.form.endDate = this.getOutsideDefaultEndTime() this.form.endDate = this.getOutsideDefaultEndTime()
// 生成本人承诺内容
this.form.promiseContent = ` this.form.promiseContent = `
<p>1.自觉遵守国家法律、法规;</p> <p>1.自觉遵守国家法律、法规;</p>
<p>2.自觉遵守学生行为规范和学校的规章制度,遵守社会公德;</p> <p>2.自觉遵守学生行为规范和学校的规章制度,遵守社会公德;</p>
<p>3.自觉遵守外宿住址所在社区的有关管理规定;</p> <p>3.自觉遵守外宿住址所在社区的有关管理规定;</p>
<p>4.本人申请外宿,属个人自愿行为,外宿期间发生的一切事故,造成本人、他人或集体的人身、财产损害的,学校不负责任。</p> <p>4.本人申请外宿,属个人自愿行为,外宿期间发生的一切事故,造成本人、他人或集体的人身、财产损害的,学校不负责任。</p>
` `
// 将地址数组转为字符串(用 / 拼接,后端可按此分隔解析)
const submitForm = { const submitForm = {
...this.form, ...this.form,
address: this.form.address ? this.form.address.join('/') : '', // 数组转字符串 address: this.form.address ? this.form.address.join('/') : '',
parentAddress: this.form.parentAddress ? this.form.parentAddress.join('/') : '', // 家长地址同理 parentAddress: this.form.parentAddress ? this.form.parentAddress.join('/') : '',
status: status status: this.form.status != 0 ? this.form.status : status
}; };
this.loading = true; this.loading = true;
if (this.form.id != null) { // 封装请求逻辑为Promise确保完成后再操作
updateOutsideAccommodationApply(submitForm).then((response) => { const requestPromise = this.form.id != null
// 批量新增材料附件 ? updateOutsideAccommodationApply(submitForm)
: addOutsideAccommodationApply(submitForm);
requestPromise.then((response) => {
// 附件处理逻辑不变
if (this.reasonFileList && this.reasonFileList.length > 0) { if (this.reasonFileList && this.reasonFileList.length > 0) {
batchAddOutsideAccommodationAttachment(this.reasonFileList)
this.resetForm()
}
this.loading = false;
this.$modal.msgSuccess('修改成功')
this.goBack()
}).catch(error => {
this.loading = false;
this.goBack()
})
} else {
addOutsideAccommodationApply(submitForm).then((response) => {
// 填写附件里面的申请编号
this.reasonFileList.forEach(element => { this.reasonFileList.forEach(element => {
element.applyId = response.data.id element.applyId = this.form.id || response.data.id;
}) });
// 批量新增材料附件 batchAddOutsideAccommodationAttachment(this.reasonFileList);
if (this.reasonFileList && this.reasonFileList.length > 0) {
batchAddOutsideAccommodationAttachment(this.reasonFileList)
this.resetForm()
} }
this.loading = false; this.loading = false;
this.$modal.msgSuccess('新增成功') this.$modal.msgSuccess(this.form.id ? '修改成功' : '新增成功');
this.goBack() // 延迟跳转:给后端数据落地留时间(关键!)
setTimeout(() => {
this.goBack();
}, 800); // 800ms延迟确保后端写入完成
}).catch(error => { }).catch(error => {
this.loading = false; this.loading = false;
this.goBack() this.$message.error('提交失败:' + (error.msg || '服务器处理异常'));
// 失败时不跳转,避免用户重复操作
// this.goBack();
}) })
}
} else { } else {
this.loading = false; this.loading = false;
this.$message.error('表单填写有误,请检查!') this.$message.error('表单填写有误,请检查!')

View File

@@ -44,8 +44,8 @@
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item span="4"> <el-descriptions-item span="4">
<template slot="label"> 宿费交纳情况只填写当年度交费情况 </template> <template slot="label"> 宿费交纳情况只填写当年度交费情况 </template>
<!-- {{ renderData.familyAddress }} --> {{ renderData.accommodationFee }}
已交 绑定当前学年年度住宿费 人民币 <!-- 已交 绑定当前学年年度住宿费 人民币 -->
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item span="2"> <el-descriptions-item span="2">

View File

@@ -150,7 +150,7 @@
<template slot-scope="scope"> <template slot-scope="scope">
<el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)" <el-button size="mini" type="text" icon="el-icon-edit" @click="handleUpdate(scope.row)"
v-hasPermi="['dormitory:outsideAccommodationApply:edit']" v-hasPermi="['dormitory:outsideAccommodationApply:edit']"
v-if="scope.row.status == 0 || getRejectInfo(scope.row.outsideAccommodationApprovals).isReject">修改</el-button> >修改</el-button>
<el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)" <el-button size="mini" type="text" icon="el-icon-delete" @click="handleDelete(scope.row)"
v-hasPermi="['dormitory:outsideAccommodationApply:remove']" v-if="scope.row.status == 0">删除</el-button> v-hasPermi="['dormitory:outsideAccommodationApply:remove']" v-if="scope.row.status == 0">删除</el-button>
<el-button v-if="scope.row.applyStatus != 0" size="mini" type="text" icon="el-icon-info" <el-button v-if="scope.row.applyStatus != 0" size="mini" type="text" icon="el-icon-info"