feat: 优化发票和证书弹窗组件,完善查看模式和文件预览功能

- 发票弹窗新增查看模式禁用状态,邮箱、文案和上传组件在查看模式下不可编辑
- 优化发票弹窗底部按钮,查看模式下仅显示"关闭"按钮
- 移除发票弹窗上传提示文案和上传区域样式冗余代码
- 证书弹窗新增图片组预览功能,支持图片文件点击放大查看
- 证书弹窗新增文件下载功能,支持所有文件类型下载
- 优化证书文
This commit is contained in:
Wei_佳 2025-11-17 14:48:34 +08:00
parent 67ac563ddb
commit 5cf79a3f7e
2 changed files with 105 additions and 38 deletions

View File

@ -135,6 +135,7 @@ const modalTitle = props.mode === 'invoice' ? '开票' : '查看发票'
v-model:value="formData.email" v-model:value="formData.email"
placeholder="请输入邮箱地址" placeholder="请输入邮箱地址"
clearable clearable
:disabled="mode === 'view'"
/> />
</NFormItem> </NFormItem>
@ -145,6 +146,7 @@ const modalTitle = props.mode === 'invoice' ? '开票' : '查看发票'
placeholder="请输入文案内容" placeholder="请输入文案内容"
:rows="4" :rows="4"
clearable clearable
:disabled="mode === 'view'"
/> />
</NFormItem> </NFormItem>
@ -158,6 +160,7 @@ const modalTitle = props.mode === 'invoice' ? '开票' : '查看发票'
:before-upload="beforeUpload" :before-upload="beforeUpload"
@change="handleUploadChange" @change="handleUploadChange"
@remove="handleRemove" @remove="handleRemove"
:disabled="mode === 'view'"
> >
<div class="upload-trigger"> <div class="upload-trigger">
<div class="upload-icon">+</div> <div class="upload-icon">+</div>
@ -166,20 +169,17 @@ const modalTitle = props.mode === 'invoice' ? '开票' : '查看发票'
</div> </div>
</NFormItem> </NFormItem>
<NFormItem>
<div style="width: 100%; text-align: left; padding-left: 100px">
<NText depth="3" style="font-size: 12px; color: #ff9800">
ps最多上传两张
</NText>
</div>
</NFormItem>
</NForm> </NForm>
<template #footer> <template #footer>
<div style="display: flex; justify-content: flex-end; gap: 12px"> <div v-if="mode !== 'view'" style="display: flex; justify-content: flex-end; gap: 12px">
<NButton @click="handleClose">取消</NButton> <NButton @click="handleClose">取消</NButton>
<NButton type="primary" @click="handleConfirm">确定</NButton> <NButton type="primary" @click="handleConfirm">确定</NButton>
</div> </div>
<div v-else style="display: flex; justify-content: flex-end; gap: 12px">
<NButton @click="handleClose">关闭</NButton>
</div>
</template> </template>
</NModal> </NModal>
</template> </template>
@ -192,8 +192,6 @@ const modalTitle = props.mode === 'invoice' ? '开票' : '查看发票'
.upload-trigger { .upload-trigger {
width: 100px; width: 100px;
height: 100px; height: 100px;
border: 2px dashed #d9d9d9;
border-radius: 4px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
@ -201,10 +199,6 @@ const modalTitle = props.mode === 'invoice' ? '开票' : '查看发票'
transition: all 0.3s; transition: all 0.3s;
} }
.upload-trigger:hover {
border-color: #40a9ff;
}
.upload-icon { .upload-icon {
font-size: 32px; font-size: 32px;
color: #d9d9d9; color: #d9d9d9;

View File

@ -6,7 +6,8 @@ import {
NButton, NButton,
NUpload, NUpload,
NText, NText,
NImage NImage,
NImageGroup
} from 'naive-ui' } from 'naive-ui'
// //
// import { DownloadIcon } from '@vicons/tabler' // import { DownloadIcon } from '@vicons/tabler'
@ -142,13 +143,32 @@ const handleDownloadReport = () => {
// //
const handlePreview = (file) => { const handlePreview = (file) => {
// PDF //
if (file.type?.startsWith('image/') || file.type === 'application/pdf') { if (!file.type?.startsWith('image/')) {
window.open(file.url || '', '_blank') $message.info('此文件类型不支持预览,请下载查看')
} else { return false
console.log('此文件类型不支持预览')
// TODO:
} }
// true NUpload 使
return true
}
//
const handleDownload = (file) => {
const link = document.createElement('a')
link.href = file.url || ''
link.download = file.name || 'download'
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
}
//
const customRequest = ({ file, onFinish, onError }) => {
//
//
setTimeout(() => {
onFinish()
}, 1000)
} }
const modalTitle = computed(() => { const modalTitle = computed(() => {
@ -187,6 +207,10 @@ const isUploadMode = computed(() => props.mode === 'upload')
:before-upload="beforeUpload" :before-upload="beforeUpload"
@change="handleReportUploadChange" @change="handleReportUploadChange"
@remove="handleRemove" @remove="handleRemove"
:custom-request="customRequest"
show-preview-button
show-download-button
@download="handleDownload"
> >
</NUpload> </NUpload>
</div> </div>
@ -203,6 +227,10 @@ const isUploadMode = computed(() => props.mode === 'upload')
:before-upload="beforeUpload" :before-upload="beforeUpload"
@change="handleCertificateUploadChange" @change="handleCertificateUploadChange"
@remove="handleRemove" @remove="handleRemove"
:custom-request="customRequest"
show-preview-button
show-download-button
@download="handleDownload"
> >
</NUpload> </NUpload>
</div> </div>
@ -239,24 +267,56 @@ const isUploadMode = computed(() => props.mode === 'upload')
<NText depth="3">暂无证书文件</NText> <NText depth="3">暂无证书文件</NText>
</div> </div>
<div v-else class="certificate-display"> <div v-else class="certificate-display">
<div <NImageGroup>
v-for="file in certificateFileList" <div
:key="file.id" v-for="file in certificateFileList"
class="certificate-image" :key="file.id"
@click="handlePreview(file)" class="certificate-item"
> >
<NImage <div class="certificate-image">
v-if="file.type?.startsWith('image/')" <NImage
:src="file.url" v-if="file.type?.startsWith('image/')"
width="120" :src="file.url"
height="120" width="120"
objectFit="cover" height="120"
preview-disabled object-fit="cover"
/> :preview-src="file.url"
<div v-else class="file-icon"> />
{{ file.name?.split('.').pop()?.toUpperCase() || 'FILE' }} <div v-else class="file-icon" @click="handlePreview(file)">
{{ file.name?.split('.').pop()?.toUpperCase() || 'FILE' }}
</div>
</div>
<div class="file-actions">
<NButton
v-if="file.type?.startsWith('image/')"
size="small"
type="primary"
text
@click="() => {}"
style="visibility: hidden;"
>
预览
</NButton>
<NButton
v-else
size="small"
type="primary"
text
disabled
>
不支持预览
</NButton>
<NButton
size="small"
type="primary"
text
@click="handleDownload(file)"
>
下载
</NButton>
</div>
</div> </div>
</div> </NImageGroup>
</div> </div>
</div> </div>
</div> </div>
@ -411,6 +471,13 @@ const isUploadMode = computed(() => props.mode === 'upload')
gap: 16px; gap: 16px;
} }
.certificate-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.certificate-image { .certificate-image {
cursor: pointer; cursor: pointer;
border-radius: 8px; border-radius: 8px;
@ -424,6 +491,12 @@ const isUploadMode = computed(() => props.mode === 'upload')
box-shadow: 0 4px 12px rgba(24, 144, 255, 0.15); box-shadow: 0 4px 12px rgba(24, 144, 255, 0.15);
} }
.file-actions {
display: flex;
gap: 8px;
align-items: center;
}
.empty-state { .empty-state {
padding: 40px 20px; padding: 40px 20px;
text-align: center; text-align: center;