feat: 支持多张支付凭证上传,更新了后端API、数据模型和前端上传组件。

This commit is contained in:
Wei_佳 2025-12-02 15:55:26 +08:00
parent 5093cf8146
commit 20d8f155c6
3 changed files with 56 additions and 26 deletions

View File

@ -116,6 +116,14 @@ async def create_with_receipt(payload: AppCreateInvoiceWithReceipt, current_user
wechat=getattr(current_user, "alias", None), wechat=getattr(current_user, "alias", None),
) )
inv = await invoice_controller.create(inv_data) inv = await invoice_controller.create(inv_data)
if payload.receipt_urls:
receipts = []
for url in payload.receipt_urls:
receipt = await invoice_controller.create_receipt(inv.id, PaymentReceiptCreate(url=url, note=payload.note))
detail = await invoice_controller.get_receipt_by_id(receipt.id)
if detail:
receipts.append(detail)
return Success(data={"invoice_id": inv.id, "receipts": receipts}, msg="创建并上传成功")
if payload.receipt_url: if payload.receipt_url:
receipt = await invoice_controller.create_receipt(inv.id, PaymentReceiptCreate(url=payload.receipt_url, note=payload.note)) receipt = await invoice_controller.create_receipt(inv.id, PaymentReceiptCreate(url=payload.receipt_url, note=payload.note))
detail = await invoice_controller.get_receipt_by_id(receipt.id) detail = await invoice_controller.get_receipt_by_id(receipt.id)

View File

@ -134,6 +134,7 @@ class AppCreateInvoiceWithReceipt(BaseModel):
# 兼容前端索引字段:"0"→normal"1"→special # 兼容前端索引字段:"0"→normal"1"→special
invoiceTypeIndex: Optional[str] = None invoiceTypeIndex: Optional[str] = None
receipt_url: Optional[str] = Field(None, max_length=512) receipt_url: Optional[str] = Field(None, max_length=512)
receipt_urls: Optional[List[str]] = None
note: Optional[str] = Field(None, max_length=256) note: Optional[str] = Field(None, max_length=256)
@field_validator('ticket_type', mode='before') @field_validator('ticket_type', mode='before')
@ -153,6 +154,21 @@ class AppCreateInvoiceWithReceipt(BaseModel):
return s or None return s or None
return v return v
@field_validator('receipt_urls', mode='before')
@classmethod
def _clean_receipt_urls(cls, v):
if v is None:
return v
if isinstance(v, str):
v = [v]
if isinstance(v, list):
cleaned = []
for item in v:
if isinstance(item, str) and item.strip():
cleaned.append(item.strip())
return cleaned or None
return None
@model_validator(mode='after') @model_validator(mode='after')
def _coerce_invoice_type(self): def _coerce_invoice_type(self):
if not self.invoice_type and self.invoiceTypeIndex is not None: if not self.invoice_type and self.invoiceTypeIndex is not None:

View File

@ -144,13 +144,15 @@
> >
<n-form-item label="上传支付凭证" required> <n-form-item label="上传支付凭证" required>
<n-upload <n-upload
v-model:file-list="fileList"
:action="actionUrl" :action="actionUrl"
list-type="image-card" list-type="image-card"
:max="1" :max="10"
multiple
accept="image/png,image/jpeg,image/jpg" accept="image/png,image/jpeg,image/jpg"
@before-upload="beforeUpload" @before-upload="beforeUpload"
@finish="handleUploadFinish" @finish="handleUploadFinish"
@remove="deleteUpload" @remove="handleRemove"
> >
<div> <div>
<img style="width: 24px; height: 24px" src="@/assets/images/upload.png" alt="" /> <img style="width: 24px; height: 24px" src="@/assets/images/upload.png" alt="" />
@ -158,7 +160,7 @@
</div> </div>
</n-upload> </n-upload>
<template #feedback> <template #feedback>
<div class="form-tip">支持jpgpngjpeg格式大小不超过5M</div> <div class="form-tip">支持JPGPNG格式大小不超过20M最多上传10张</div>
</template> </template>
</n-form-item> </n-form-item>
@ -256,9 +258,8 @@ const actionUrl = 'https://value.cdcee.net/api/v1/upload/file'
const currentStep = ref(1) const currentStep = ref(1)
// //
const fileInput = ref(null) const fileList = ref([])
const uploadedFile = ref(null) const uploadedFiles = ref([])
const uploadedFileUrl = ref('')
// //
@ -297,39 +298,43 @@ const message = useMessage()
// //
const isFormValid = computed(() => { const isFormValid = computed(() => {
console.log('isFormValid check:', {
uploadedFile: uploadedFile.value,
invoiceHeader: formModel.invoiceHeader,
invoiceType: formModel.invoiceType
})
return ( return (
!!uploadedFile.value && uploadedFiles.value.length > 0 &&
!!formModel.invoiceHeader && !!formModel.invoiceHeader &&
!!formModel.invoiceType !!formModel.invoiceType
) )
}) })
const handleUploadFinish = (file) => { const handleUploadFinish = ({ file, event }) => {
console.log('handleUploadFinish called:', file)
console.log('response:', file.event?.target?.response)
try { try {
let response = JSON.parse(file.event.target.response) const response = JSON.parse(event?.target?.response || '{}')
console.log('parsed response:', response) const fileUrl = response.data?.url
uploadedFile.value = response.data.url if (fileUrl) {
console.log('uploadedFile.value set to:', uploadedFile.value) file.url = fileUrl
file.name = response.data.filename || file.name
file.status = 'finished'
if (!uploadedFiles.value.includes(fileUrl)) {
uploadedFiles.value.push(fileUrl)
}
message.success('上传成功')
return file
}
message.error(response.message || '上传失败')
} catch (error) { } catch (error) {
console.error('Error parsing upload response:', error) console.error('Error parsing upload response:', error)
message.error('上传失败,请重试')
} }
} }
const deleteUpload = () => {
uploadedFile.value = '' const handleRemove = ({ file }) => {
uploadedFiles.value = uploadedFiles.value.filter((url) => url !== file?.url)
} }
const beforeUpload = (data) => { const beforeUpload = (data) => {
const file = data.file.file const file = data.file.file
const isLt10M = file.size / 1024 / 1024 < 10 const isLt20M = file.size / 1024 / 1024 <= 20
if (!isLt10M) { if (!isLt20M) {
message.error('图片大小不能超过10MB请重新上传') message.error('图片大小不能超过20MB请重新上传')
return false return false
} }
return true return true
@ -346,7 +351,7 @@ function handleUploadSubmit() {
header_id: formModel.invoiceHeader, header_id: formModel.invoiceHeader,
ticket_type: 'electronic', ticket_type: 'electronic',
invoice_type: formModel.invoiceType, invoice_type: formModel.invoiceType,
receipt_url: uploadedFile.value, receipt_urls: uploadedFiles.value,
}) })
currentStep.value = 3 currentStep.value = 3
} }
@ -501,7 +506,8 @@ defineExpose({
.form-tip { .form-tip {
font-size: 12px; font-size: 12px;
color: #c0c4cc; color: #c0c4cc;
margin-top: 8px; line-height: 18px;
margin: 12px 0 8px;
} }
.select-wrapper { .select-wrapper {