Commit 96e1897f by Sweet Zhang

前端下载材料包&新单跟进查看时不提示保存弹窗

parent 161113ab
...@@ -27,11 +27,12 @@ ...@@ -27,11 +27,12 @@
"dayjs": "^1.11.18", "dayjs": "^1.11.18",
"echarts": "5.6.0", "echarts": "5.6.0",
"element-plus": "^2.13.5", "element-plus": "^2.13.5",
"file-saver": "2.0.5", "file-saver": "^2.0.5",
"fuse.js": "6.6.2", "fuse.js": "6.6.2",
"js-beautify": "1.14.11", "js-beautify": "1.14.11",
"js-cookie": "3.0.5", "js-cookie": "3.0.5",
"jsencrypt": "3.3.2", "jsencrypt": "3.3.2",
"jszip": "^3.10.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
"p-limit": "^7.3.0", "p-limit": "^7.3.0",
"pinia": "3.0.2", "pinia": "3.0.2",
......
...@@ -505,17 +505,17 @@ function handleModelChange(value, item) { ...@@ -505,17 +505,17 @@ function handleModelChange(value, item) {
} }
} }
localModel.value = newModel localModel.value = newModel
console.log('子组件用户操作后,modelvalue值==', newModel) // console.log('子组件用户操作后,modelvalue值==', newModel)
nextTick(() => { nextTick(() => {
if (!isEqualShallow(props.modelValue, newModel)) { if (!isEqualShallow(props.modelValue, newModel)) {
console.log('如果新旧值不一样,反馈给父组件', newModel) // console.log('如果新旧值不一样,反馈给父组件', newModel)
emit('update:modelValue', newModel) emit('update:modelValue', newModel)
} else { } else {
console.log('🚫 跳过 emit:认为相等') console.log('🚫 跳过 emit:认为相等')
} }
}) })
if (item.type === 'select') { if (item.type === 'select') {
console.log('如果是select类型,反馈给父组件', item.prop, value, item) // console.log('如果是select类型,反馈给父组件', item.prop, value, item)
emit('selectChange', item.prop, value, item) emit('selectChange', item.prop, value, item)
} else if (item.type == 'upload') { } else if (item.type == 'upload') {
// 传给父组件最新的上传值newModel // 传给父组件最新的上传值newModel
...@@ -553,7 +553,7 @@ async function loadDictOptions(dictType) { ...@@ -553,7 +553,7 @@ async function loadDictOptions(dictType) {
dictStore.setDict(dictType, options) dictStore.setDict(dictType, options)
return options return options
} catch (err) { } catch (err) {
console.error(`加载字典 ${dictType} 失败`, err) // console.error(`加载字典 ${dictType} 失败`, err)
ElMessage.error(`字典 ${dictType} 加载失败`) ElMessage.error(`字典 ${dictType} 加载失败`)
return [] return []
} }
......
...@@ -114,7 +114,7 @@ const useDictStore = defineStore('dict', { ...@@ -114,7 +114,7 @@ const useDictStore = defineStore('dict', {
//设置最新的保险公司列表 //设置最新的保险公司列表
setAllInsuranceCompanyList(list) { setAllInsuranceCompanyList(list) {
this.allInsuranceCompanyList = list this.allInsuranceCompanyList = list
} },
} }
}) })
......
...@@ -82,3 +82,5 @@ export function useDictLists1(typeLists) { ...@@ -82,3 +82,5 @@ export function useDictLists1(typeLists) {
} }
}) })
} }
// src/utils/zipDownload.js
import JSZip from 'jszip';
import { saveAs } from 'file-saver';
import axios from 'axios';
/**
* 批量下载文件并打包成 ZIP
* @param {Array} files - 文件列表,每项包含 { url: '文件地址', name: '文件名(含后缀)' }
* @param {String} zipName - 生成的压缩包名称,默认 'attachments.zip'
* @param {Function} onProgress - 可选,进度回调 (current, total)
*/
export const downloadFilesAsZip = async (files, zipName = 'attachments.zip', onProgress) => {
if (!files || files.length === 0) {
console.warn('没有可下载的文件');
return;
}
const zip = new JSZip();
const total = files.length;
let loadedCount = 0;
// 创建加载提示(可选,结合 Element Plus ElLoading 使用)
// 这里只返回 Promise,UI 层的 loading 建议在调用处控制
try {
// 并行或串行请求文件内容
// 注意:如果文件非常多且大,建议限制并发数,避免浏览器卡顿或触发限流
const promises = files.map(async (file) => {
try {
// 关键点:响应类型必须为 arraybuffer
const response = await axios.get(file.url, {
responseType: 'arraybuffer',
// 如果涉及跨域或需要认证,需在此配置 headers
// headers: { 'Authorization': 'Bearer...' }
});
// 将文件内容添加到 zip
// file.name 应该包含扩展名,例如 "合同.pdf"
zip.file(file.name, response.data);
loadedCount++;
if (onProgress) {
onProgress(loadedCount, total);
}
} catch (error) {
console.error(`文件 ${file.name} 下载失败`, error);
// 可以选择抛出错误中断,或者继续处理其他文件
// throw error;
}
});
// 等待所有文件获取完成
await Promise.all(promises);
// 生成 ZIP 文件
const content = await zip.generateAsync({ type: 'blob' });
// 触发下载
saveAs(content, zipName);
return true;
} catch (error) {
console.error('打包下载失败:', error);
throw error;
}
};
\ No newline at end of file
...@@ -2,11 +2,30 @@ ...@@ -2,11 +2,30 @@
<div class="uploadContainer"> <div class="uploadContainer">
<CardOne title="材料信息"> <CardOne title="材料信息">
<template #headerRight> <template #headerRight>
<div> <div style="margin-top: 20px;">
<el-button
type="primary"
:loading="downloading"
@click="handleBatchDownloadSelected"
>
{{ downloading ? '正在打包中...' : '下载材料包' }}
</el-button>
<div v-if="downloading" style="margin-top: 10px;">
<el-progress
:percentage="progressPercentage"
:format="progressFormat"
/>
<div style="font-size: 12px; color: #666; margin-top: 5px;">
已处理文件:{{ currentCount }} / {{ totalCount }}
</div>
</div>
</div>
<!-- <div>
<el-button @click="downloadFile" type="primary" :loading="downLoading" <el-button @click="downloadFile" type="primary" :loading="downLoading"
>下载材料包</el-button >下载材料包</el-button
> >
</div> </div> -->
</template> </template>
<template #content> <template #content>
<el-table v-loading="loading" :data="fileTableList" boder> <el-table v-loading="loading" :data="fileTableList" boder>
...@@ -134,6 +153,9 @@ ...@@ -134,6 +153,9 @@
</template> </template>
<script setup name="FileUpload"> <script setup name="FileUpload">
import { ref } from 'vue';
import { ElMessage } from 'element-plus';
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'
...@@ -177,6 +199,105 @@ const data = reactive({ ...@@ -177,6 +199,105 @@ const data = reactive({
} }
}) })
// 下载材料包
// 状态管理
const downloading = ref(false);
const currentCount = ref(0);
const totalCount = ref(0);
const progressPercentage = computed(() => {
if (totalCount.value === 0) return 0;
return Math.floor((currentCount.value / totalCount.value) * 100);
});
const progressFormat = (percentage) => `${currentCount.value}/${totalCount.value}`;
// 2. 核心处理方法
const handleBatchDownloadSelected = async () => {
debugger;
const selectedRows = fileTableList.value;
if (!selectedRows || selectedRows.length === 0) {
ElMessage.warning('没有要下载的材料');
return;
}
// --- 步骤 1: 数据清洗与扁平化 ---
const flatFileList = [];
let hasFiles = false;
selectedRows.forEach((row, index) => {
// 安全检查:确保 fileUrlList 存在且是数组
const urls = row.fileUrlList;
if (!urls || !Array.isArray(urls) || urls.length === 0) {
return; // 跳过没有文件的行
}
hasFiles = true;
// 生成安全的业务前缀
// 规则:[人员类型]_[资料类型]_[业务ID]
// 例如:POLICYHOLDER_FRONT_2216
const safePerson = (row.dataPersonName || 'UNKNOWN').replace(/[\/\\:*?"<>|]/g, '_');
const safeType = (row.dataTypeName || 'FILE').replace(/[\/\\:*?"<>|]/g, '_');
const bizId = row.id || row.materialBizId || index; // 优先用数字ID
const filePrefix = `${safePerson}_${safeType}_${bizId}`;
urls.forEach((fileItem, fIndex) => {
// 兼容 fileUrlList 可能是字符串数组 或 对象数组
let fileUrl = fileItem.fileUrl;
let originalFileName = fileItem.fileName;
// --- 关键:构建最终文件名 ---
const finalFileName = `${filePrefix}_${originalFileName}`;
if (fileUrl) {
flatFileList.push({
url: fileUrl,
name: finalFileName,
// 可选:保留元数据用于调试
_meta: {
bizId: row.id,
type: row.dataType,
note: row.precautions
}
});
}
});
});
if (!hasFiles) {
ElMessage.warning('选中的项中没有包含任何附件');
return;
}
// --- 步骤 2: 执行下载 ---
totalCount.value = flatFileList.length;
currentCount.value = 0;
downloading.value = true;
try {
const zipName = `预约附件包_${new Date().toISOString().slice(0, 10)}.zip`;
await downloadFilesAsZip(flatFileList, zipName, (current, total) => {
currentCount.value = current;
});
ElMessage.success(`成功打包 ${flatFileList.length} 个文件`);
} catch (error) {
ElMessage.error('下载过程中出现异常,请查看控制台');
console.error(error);
} finally {
downloading.value = false;
}
};
const { queryParams, form } = toRefs(data) const { queryParams, form } = toRefs(data)
// 新增:用于存储已上传成功的文件列表 // 新增:用于存储已上传成功的文件列表
const uploadedFiles = ref([]) const uploadedFiles = ref([])
...@@ -298,6 +419,7 @@ const handleView = row => { ...@@ -298,6 +419,7 @@ const handleView = row => {
imageUrl.value = row.fileUrl imageUrl.value = row.fileUrl
imageViewerVisible.value = true imageViewerVisible.value = true
} }
// 下载材料包 // 下载材料包
const downloadFile = () => { const downloadFile = () => {
let apiMaterialDtoList = [] let apiMaterialDtoList = []
......
...@@ -1039,8 +1039,9 @@ const activeTab = ref('basic') ...@@ -1039,8 +1039,9 @@ const activeTab = ref('basic')
// ✅ 新增:切换前确认 // ✅ 新增:切换前确认
// ======================== // ========================
const handleBeforeLeave = async (newTabName, oldTabName) => { const handleBeforeLeave = async (newTabName, oldTabName) => {
console.log('切换前确认-----------------------', newTabName, oldTabName) // console.log('切换前确认-----------------------', newTabName, oldTabName)
console.log(tabDirty.value) // console.log(tabDirty.value)
if(props.mode === 'viewDetail'){return;}
if (tabDirty.value[oldTabName]) { if (tabDirty.value[oldTabName]) {
try { try {
await ElMessageBox.confirm(`“${getTabLabel(oldTabName)}” 未提交,确定要切换吗?`, '提示', { await ElMessageBox.confirm(`“${getTabLabel(oldTabName)}” 未提交,确定要切换吗?`, '提示', {
......
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