Commit c53ccfbd by yuzhenWang

修改预约附件可预览文件

parent 8e482ad9
...@@ -2,21 +2,14 @@ ...@@ -2,21 +2,14 @@
<div class="uploadContainer"> <div class="uploadContainer">
<CardOne title="材料信息"> <CardOne title="材料信息">
<template #headerRight> <template #headerRight>
<div style="margin-top: 20px;"> <div style="margin-top: 20px">
<el-button <el-button type="primary" :loading="downloading" @click="handleBatchDownloadSelected">
type="primary"
:loading="downloading"
@click="handleBatchDownloadSelected"
>
{{ downloading ? '正在打包中...' : '下载材料包' }} {{ downloading ? '正在打包中...' : '下载材料包' }}
</el-button> </el-button>
<div v-if="downloading" style="margin-top: 10px;"> <div v-if="downloading" style="margin-top: 10px">
<el-progress <el-progress :percentage="progressPercentage" :format="progressFormat" />
:percentage="progressPercentage" <div style="font-size: 12px; color: #666; margin-top: 5px">
:format="progressFormat"
/>
<div style="font-size: 12px; color: #666; margin-top: 5px;">
已处理文件:{{ currentCount }} / {{ totalCount }} 已处理文件:{{ currentCount }} / {{ totalCount }}
</div> </div>
</div> </div>
...@@ -120,7 +113,7 @@ ...@@ -120,7 +113,7 @@
</div> </div>
</el-upload> </el-upload>
</div> </div>
<div class="tip">(支持Word,Excel,PDF,图片格式)</div> <div class="tip">(支持PDF,图片格式)</div>
</div> </div>
<div class="dialogRight"> <div class="dialogRight">
<div <div
...@@ -136,14 +129,55 @@ ...@@ -136,14 +129,55 @@
class="uploaded-file-item" class="uploaded-file-item"
> >
<div class="fileName">{{ file.originalName }}</div> <div class="fileName">{{ file.originalName }}</div>
<el-icon color="red" size="18" @click="removeUploadedFile(file, index)" <div>
<el-button type="primary" size="small" link @click="previewFile(file)">
查看
</el-button>
<el-button type="danger" size="small" link @click="removeUploadedFile(file, index)">
删除
</el-button>
</div>
<!-- <el-icon color="red" size="18" @click="removeUploadedFile(file, index)"
><Delete ><Delete
/></el-icon> /></el-icon> -->
</div> </div>
</el-scrollbar> </el-scrollbar>
</div> </div>
</div> </div>
</CommonDialog> </CommonDialog>
<!-- 文件预览弹窗(页面内查看,不打开新窗口) -->
<el-dialog
v-model="previewDialogVisible"
:title="previewFileName"
width="70%"
:close-on-click-modal="false"
destroy-on-close
@close="previewUrl = ''"
>
<div class="preview-container">
<!-- 图片预览 -->
<div v-if="previewFileType === 'image'" class="preview-image-wrapper">
<img :src="previewUrl" class="preview-image" alt="预览图片" />
</div>
<!-- PDF 预览(使用 iframe 内嵌) -->
<iframe
v-else-if="previewFileType === 'pdf'"
:src="previewUrl"
class="preview-pdf"
frameborder="0"
></iframe>
<!-- 不支持预览的文件类型 -->
<div v-else-if="previewFileType === 'unsupported'" class="preview-unsupported">
<el-icon :size="48" color="#909399"><Document /></el-icon>
<p>暂不支持预览此类型文件</p>
<el-button type="primary" @click="previewDialogVisible = false"> 关闭 </el-button>
</div>
</div>
</el-dialog>
<el-dialog v-model="imageViewerVisible" title="图片预览" width="60%"> <el-dialog v-model="imageViewerVisible" title="图片预览" width="60%">
<div style="text-align: center"> <div style="text-align: center">
<el-image :src="imageUrl" fit="contain" /> <el-image :src="imageUrl" fit="contain" />
...@@ -153,9 +187,9 @@ ...@@ -153,9 +187,9 @@
</template> </template>
<script setup name="FileUpload"> <script setup name="FileUpload">
import { ref } from 'vue'; import { ref } from 'vue'
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus'
import { downloadFilesAsZip } from '@/utils/zipDownload'; // 引入刚才封装的工具 import { downloadFilesAsZip } from '@/utils/zipDownload' // 引入刚才封装的工具
import CommonDialog from '@/components/commonDialog' import CommonDialog from '@/components/commonDialog'
import CardOne from '@/components/formCard/cardOne' import CardOne from '@/components/formCard/cardOne'
import { getToken } from '@/utils/auth' import { getToken } from '@/utils/auth'
...@@ -188,6 +222,40 @@ const limit = ref(10) ...@@ -188,6 +222,40 @@ const limit = ref(10)
const fileSize = ref(10) const fileSize = ref(10)
const headers = ref({ Authorization: 'Bearer ' + getToken() }) const headers = ref({ Authorization: 'Bearer ' + getToken() })
const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + '/oss/api/oss/upload') // 上传的服务器地址 const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + '/oss/api/oss/upload') // 上传的服务器地址
// ==================== 文件预览弹窗 ====================
const previewDialogVisible = ref(false)
const previewUrl = ref('')
const previewFileName = ref('')
const previewFileType = ref('') // 'image', 'pdf', 'unsupported'
// 预览文件(页面内弹窗,不打开新窗口)
function previewFile(file) {
const url = file.url || file.fileUrl
if (!url) {
ElMessage.warning('文件地址不存在')
return
}
const ext = (file.originalName || '').split('.').pop().toLowerCase()
previewUrl.value = url
previewFileName.value = file.originalName || '文件'
if (['jpg', 'jpeg', 'png', 'webp', 'gif', 'bmp', 'svg'].includes(ext)) {
previewFileType.value = 'image'
previewDialogVisible.value = true
} else if (ext === 'pdf') {
previewFileType.value = 'pdf'
previewDialogVisible.value = true
} else {
// 不支持预览的文件类型,弹窗显示提示
previewFileType.value = 'unsupported'
previewDialogVisible.value = true
}
console.log('====================================')
console.log('previewUrl.value', previewUrl.value)
console.log('previewFileName.value', previewFileName.value)
console.log('previewFileType.value', previewFileType.value)
console.log('====================================')
}
// 图片查看相关状态 // 图片查看相关状态
const imageViewerVisible = ref(false) const imageViewerVisible = ref(false)
const imageUrl = ref('') const imageUrl = ref('')
...@@ -201,35 +269,39 @@ const data = reactive({ ...@@ -201,35 +269,39 @@ const data = reactive({
// 下载材料包 // 下载材料包
// 状态管理 // 状态管理
const downloading = ref(false); const downloading = ref(false)
const currentCount = ref(0); const currentCount = ref(0)
const totalCount = ref(0); const totalCount = ref(0)
const viewFile = file => {
console.log('====================================')
console.log('file', file)
console.log('====================================')
}
const progressPercentage = computed(() => { const progressPercentage = computed(() => {
if (totalCount.value === 0) return 0; if (totalCount.value === 0) return 0
return Math.floor((currentCount.value / totalCount.value) * 100); return Math.floor((currentCount.value / totalCount.value) * 100)
}); })
const progressFormat = (percentage) => `${currentCount.value}/${totalCount.value}`; const progressFormat = percentage => `${currentCount.value}/${totalCount.value}`
// 2. 核心处理方法 // 2. 核心处理方法
const handleBatchDownloadSelected = async () => { const handleBatchDownloadSelected = async () => {
let apiMaterialDtoList = [] let apiMaterialDtoList = []
if (!props.idsObj.appointmentBizId) { if (!props.idsObj.appointmentBizId) {
apiMaterialDtoList = fileTableList.value.map(item => { apiMaterialDtoList = fileTableList.value.map(item => {
return { return {
dataPerson: item.dataPerson, //资料人(字典) dataPerson: item.dataPerson, //资料人(字典)
dataPersonName: item.dataPersonName, //资料人(字典) dataPersonName: item.dataPersonName, //资料人(字典)
dataType: item.dataType, //资料类型(字典) dataType: item.dataType, //资料类型(字典)
dataTypeName: item.dataTypeName, //资料类型(字典) dataTypeName: item.dataTypeName, //资料类型(字典)
fileUrlList: item.fileBizIdList.map(item2 => ({ fileUrlList: item.fileBizIdList.map(item2 => ({
fileUrl: item2.url, fileUrl: item2.url,
fileName: item2.originalName fileName: item2.originalName
})) }))
} }
}) })
}else{ } else {
apiMaterialDtoList = fileTableList.value.map(item => { apiMaterialDtoList = fileTableList.value.map(item => {
return { return {
dataPerson: item.dataPerson, //资料人(字典) dataPerson: item.dataPerson, //资料人(字典)
dataPersonName: item.dataPersonName, //资料人(字典) dataPersonName: item.dataPersonName, //资料人(字典)
...@@ -238,85 +310,80 @@ if (!props.idsObj.appointmentBizId) { ...@@ -238,85 +310,80 @@ if (!props.idsObj.appointmentBizId) {
fileUrlList: item.fileUrlList fileUrlList: item.fileUrlList
} }
}) })
} }
if (!apiMaterialDtoList || apiMaterialDtoList.length === 0) { if (!apiMaterialDtoList || apiMaterialDtoList.length === 0) {
ElMessage.warning('没有要下载的材料'); ElMessage.warning('没有要下载的材料')
return; return
} }
// --- 步骤 1: 数据清洗与扁平化 --- // --- 步骤 1: 数据清洗与扁平化 ---
const flatFileList = []; const flatFileList = []
let hasFiles = false; let hasFiles = false
apiMaterialDtoList.forEach((item,index) => { apiMaterialDtoList.forEach((item, index) => {
// 安全检查:确保 fileUrlList 存在且是数组 // 安全检查:确保 fileUrlList 存在且是数组
const urls = item.fileUrlList; const urls = item.fileUrlList
if (!urls || !Array.isArray(urls) || urls.length === 0) { if (!urls || !Array.isArray(urls) || urls.length === 0) {
return; // 跳过没有文件的行 return // 跳过没有文件的行
} }
hasFiles = true; hasFiles = true
// 生成安全的业务前缀 // 生成安全的业务前缀
// 规则:[人员类型]_[资料类型]_[业务ID] // 规则:[人员类型]_[资料类型]_[业务ID]
// 例如:POLICYHOLDER_FRONT_2216 // 例如:POLICYHOLDER_FRONT_2216
const safePerson = (item.dataPersonName || 'UNKNOWN').replace(/[\/\\:*?"<>|]/g, '_'); const safePerson = (item.dataPersonName || 'UNKNOWN').replace(/[\/\\:*?"<>|]/g, '_')
const safeType = (item.dataTypeName || 'FILE').replace(/[\/\\:*?"<>|]/g, '_'); const safeType = (item.dataTypeName || 'FILE').replace(/[\/\\:*?"<>|]/g, '_')
const filePrefix = `${safePerson}_${safeType}`; const filePrefix = `${safePerson}_${safeType}`
urls.forEach((fileItem, fIndex) => { urls.forEach((fileItem, fIndex) => {
// 兼容 fileUrlList 可能是字符串数组 或 对象数组 // 兼容 fileUrlList 可能是字符串数组 或 对象数组
let fileUrl = fileItem.fileUrl; let fileUrl = fileItem.fileUrl
let originalFileName = fileItem.fileName; let originalFileName = fileItem.fileName
// --- 关键:构建最终文件名 --- // --- 关键:构建最终文件名 ---
const finalFileName = `${filePrefix}_${originalFileName}`; const finalFileName = `${filePrefix}_${originalFileName}`
if (fileUrl) { if (fileUrl) {
flatFileList.push({ flatFileList.push({
url: fileUrl, url: fileUrl,
name: finalFileName, name: finalFileName,
// 可选:保留元数据用于调试 // 可选:保留元数据用于调试
_meta: { _meta: {
type: item.dataType, type: item.dataType,
note: item.precautions note: item.precautions
} }
}); })
} }
}); })
}); })
if (!hasFiles) { if (!hasFiles) {
ElMessage.warning('选中的项中没有包含任何附件'); ElMessage.warning('选中的项中没有包含任何附件')
return; return
} }
// --- 步骤 2: 执行下载 --- // --- 步骤 2: 执行下载 ---
totalCount.value = flatFileList.length; totalCount.value = flatFileList.length
currentCount.value = 0; currentCount.value = 0
downloading.value = true; downloading.value = true
try { try {
const zipName = `预约附件包_${new Date().toISOString().slice(0, 10)}.zip`; const zipName = `预约附件包_${new Date().toISOString().slice(0, 10)}.zip`
await downloadFilesAsZip(flatFileList, zipName, (current, total) => { await downloadFilesAsZip(flatFileList, zipName, (current, total) => {
currentCount.value = current; currentCount.value = current
}); })
ElMessage.success(`成功打包 ${flatFileList.length} 个文件`); ElMessage.success(`成功打包 ${flatFileList.length} 个文件`)
} catch (error) { } catch (error) {
ElMessage.error('下载过程中出现异常,请查看控制台'); ElMessage.error('下载过程中出现异常,请查看控制台')
console.error(error); console.error(error)
} finally { } finally {
downloading.value = false; downloading.value = false
} }
}; }
const { queryParams, form } = toRefs(data) const { queryParams, form } = toRefs(data)
// 新增:用于存储已上传成功的文件列表 // 新增:用于存储已上传成功的文件列表
...@@ -444,7 +511,7 @@ const handleView = row => { ...@@ -444,7 +511,7 @@ const handleView = row => {
const downloadFile = () => { const downloadFile = () => {
let apiMaterialDtoList = [] let apiMaterialDtoList = []
let params = { let params = {
projectBizId:userStore.projectInfo.projectBizId || '', projectBizId: userStore.projectInfo.projectBizId || '',
objectName: '预约附件材料包', //对象名(包名) objectName: '预约附件材料包', //对象名(包名)
objectBizId: '' //对象业务ID objectBizId: '' //对象业务ID
} }
...@@ -522,7 +589,7 @@ function handleExceed() { ...@@ -522,7 +589,7 @@ function handleExceed() {
// 文件上传成功回调 // 文件上传成功回调
const uploadSuccess = (res, file, fileList) => { const uploadSuccess = (res, file, fileList) => {
console.log('上传成功', res, file) console.log('上传成功', res, file)
proxy.$modal.closeLoading(); proxy.$modal.closeLoading()
if (res.code === 200) { if (res.code === 200) {
// 构造前端使用的文件对象(保留原始 file 信息 + 后端返回的 url 等) // 构造前端使用的文件对象(保留原始 file 信息 + 后端返回的 url 等)
const uploadedFile = { const uploadedFile = {
...@@ -614,6 +681,32 @@ defineExpose({ ...@@ -614,6 +681,32 @@ defineExpose({
}) })
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
.preview-container {
display: flex;
justify-content: center;
align-items: center;
min-height: 400px;
}
.preview-image-wrapper {
text-align: center;
}
.preview-image {
max-width: 100%;
max-height: 70vh;
object-fit: contain;
}
.preview-pdf {
width: 100%;
height: 70vh;
}
.preview-unsupported {
text-align: center;
padding: 40px;
}
.preview-unsupported p {
margin: 16px 0;
color: #909399;
}
.uploadContainer { .uploadContainer {
padding-left: 10px; padding-left: 10px;
padding-top: 10px; padding-top: 10px;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment