This commit is contained in:
zlwu committed 2026-05-17 23:08:38 +08:00
1 parent 135dd550b6
commit 2a4767a014
76 files changed
+25160 -2306

No files matched your search

@@ -1,7 +1,7 @@
<!-- 财务确认弹窗:附件上传 + 财务备注(对齐采集协议单附件 UI) -->
<template>
<div class="protocol-finance-confirm-fields">
<el-form-item label="财务备注" class="form-col-span-2">
<el-form-item :label="remarkLabel" class="form-col-span-2">
<el-input
:model-value="financeRemark"
type="textarea"
@@ -10,7 +10,7 @@
@update:model-value="emit('update:financeRemark', $event)"
/>
</el-form-item>
<el-form-item label="财务确认附件" class="form-col-span-2">
<el-form-item :label="attachmentLabel" class="form-col-span-2">
<div class="attachment-upload">
<div class="attachment-list">
<el-upload
@@ -94,8 +94,14 @@
financeRemark: string
attachments: FinanceAttachmentItem[]
uploadFolder?: string
remarkLabel?: string
attachmentLabel?: string
}>(),
{ uploadFolder: 'protocol-finance-confirm' }
{
uploadFolder: 'protocol-finance-confirm',
remarkLabel: '财务备注',
attachmentLabel: '财务确认附件'
}
)
const emit = defineEmits<{
@@ -144,6 +150,14 @@
emit('update:attachments', next)
}
/** Mutable snapshot used to avoid race conditions when multiple files are picked at once */
let _pendingList: FinanceAttachmentItem[] | null = null
function _getList(): FinanceAttachmentItem[] {
if (_pendingList) return _pendingList
return props.attachments
}
async function handleAttachmentChange(uploadFile: { raw?: File }) {
const file = uploadFile.raw
if (!file) return
@@ -152,7 +166,7 @@
ElMessage.error(`文件大小不能超过 ${maxSizeMb}MB`)
return
}
const tempId = `temp_${Date.now()}`
const tempId = `temp_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`
const uploading: FinanceAttachmentItem = {
id: tempId,
fileName: file.name,
@@ -160,29 +174,38 @@
fileType: file.type || '',
_uploading: true
}
const listWithTemp = [...props.attachments, uploading]
// Append to the latest snapshot so concurrent calls don't overwrite each other
const current = _getList()
const listWithTemp = [...current, uploading]
_pendingList = listWithTemp
emit('update:attachments', listWithTemp)
const idx = listWithTemp.length - 1
try {
const fd = new FormData()
fd.append('folder', props.uploadFolder)
fd.append('file', file)
const res = await fetchUploadAttachment(fd)
const updated = [...listWithTemp]
updated[idx] = {
id: res.id || tempId,
fileName: file.name || res.fileName,
fileUrl: res.fileUrl,
fileType: res.fileType || file.type || '',
_uploading: false
}
// Read from latest snapshot (NOT the closure-captured listWithTemp) to avoid
// overwriting sibling uploads that may have already completed while this one was in-flight
const latest = _pendingList || props.attachments
const updated = latest.map((a) =>
a.id === tempId
? {
id: res.id || tempId,
fileName: file.name || res.fileName,
fileUrl: res.fileUrl,
fileType: res.fileType || file.type || '',
_uploading: false
}
: a
)
_pendingList = updated
emit('update:attachments', updated)
} catch {
ElMessage.error('文件上传失败')
emit(
'update:attachments',
props.attachments.filter((a) => a.id !== tempId)
)
const latest = _pendingList || props.attachments
const remaining = latest.filter((a) => a.id !== tempId)
_pendingList = remaining.length > 0 ? remaining : null
emit('update:attachments', remaining)
}
}
</script>
@@ -203,7 +226,7 @@
}
.upload-card {
width: 120px;
width: 140px;
height: 96px;
border: 1px dashed #a7f3d0;
border-radius: 8px;
@@ -236,18 +259,26 @@
display: block;
font-size: 11px;
color: #94a3b8;
line-height: 1.4;
line-height: 1;
margin-top: 8px;
}
.attachment-item {
width: 120px;
width: 140px;
display: flex;
flex-direction: column;
border: 1px solid #e2e8f0;
border-radius: 8px;
background: #fff;
overflow: hidden;
transition:
border-color 0.15s,
box-shadow 0.15s;
&:hover {
border-color: #10b981;
box-shadow: 0 2px 8px rgba(16, 185, 129, 0.1);
}
&.is-uploading {
opacity: 0.6;
@@ -263,58 +294,84 @@
align-items: center;
justify-content: center;
background: #f8fafc;
overflow: hidden;
cursor: pointer;
img {
width: 100%;
height: 100%;
object-fit: cover;
display: block;
}
}
.attachment-preview__icon {
width: 48px;
height: 48px;
border-radius: 10px;
display: flex;
align-items: center;
justify-content: center;
color: #64748b;
&--video {
color: #10b981;
background: #ede9fe;
color: #7c3aed;
}
&--doc {
background: #e0f2fe;
color: #0284c7;
}
}
.attachment-footer {
display: flex;
align-items: center;
justify-content: space-between;
gap: 4px;
flex-direction: column;
flex: 1;
padding: 6px 8px;
min-height: 32px;
}
.attachment-name {
flex: 1;
min-width: 0;
font-size: 12px;
color: #334155;
display: -webkit-box;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
font-size: 11px;
color: #334155;
line-height: 1.4;
word-break: break-all;
min-height: calc(11px * 1.4 * 2);
}
.attachment-actions {
display: flex;
gap: 4px;
justify-content: center;
align-items: center;
gap: 8px;
margin-top: 4px;
flex-shrink: 0;
transition: opacity 0.15s;
}
.action-btn {
width: 22px;
height: 22px;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
color: #64748b;
cursor: pointer;
color: #94a3b8;
font-size: 16px;
background: #f1f5f9;
transition:
background 0.15s,
color 0.15s;
&:hover {
color: #10b981;
background: #10b981;
color: #fff;
}
}
</style>
@@ -46,7 +46,7 @@
<dd>{{ formatPaymentDueDate(detail?.installmentDueDate) }}</dd>
</div>
<div class="view-kv">
<dt>应缴费金额</dt>
<dt>应缴费金额(元)</dt>
<dd>{{ formatPaymentAmount(detail?.dueAmount) }}</dd>
</div>
<div class="view-kv">
@@ -54,7 +54,7 @@
<dd>{{ transferTypeLabel(String(detail?.transferType || '')) }}</dd>
</div>
<div class="view-kv">
<dt>转账金额</dt>
<dt>转账金额(元)</dt>
<dd>{{ formatPaymentAmount(detail?.transferAmount) }}</dd>
</div>
<div class="view-kv">
@@ -80,7 +80,7 @@
<h4 v-if="refundApplyTitle" class="detail-block-title">{{ refundApplyTitle }}</h4>
<dl class="view-kv-grid view-kv-grid--4">
<div class="view-kv">
<dt>退费金额</dt>
<dt>退费金额(元)</dt>
<dd>{{ formatPaymentAmount(detail?.refundAmount) }}</dd>
</div>
<div v-if="detail?.refundRemark" class="view-kv view-kv-span-2">