Commit 312aac18 by liucan

fix:修改导出数据为直接导出

parent e39bdbb5
...@@ -37,36 +37,31 @@ ...@@ -37,36 +37,31 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, watch } from "vue"; import { ref, watch } from "vue";
import { defineProps } from "vue";
import axios from "axios"; import axios from "axios";
import { ElLoading, ElMessage } from "element-plus"; import { ElLoading, ElMessage } from "element-plus";
import type { FormInstance, FormRules } from "element-plus"; import type { FormInstance, FormRules } from "element-plus";
// ---------- 表单类型 ----------
interface RuleForm { interface RuleForm {
//表单字段的类型 timeValue: string[];
timeValue: string; spiderType: string[];
spiderType: Array<string>;
} }
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
// ---------- 表单验证 ----------
const rules = ref<FormRules<RuleForm>>({ const rules = ref<FormRules<RuleForm>>({
timeValue: [ timeValue: [
{ {
// type: 'date',
required: true, required: true,
message: "请选择时间段", message: "请选择时间段",
trigger: "change", trigger: "change",
validator: (rule, value, callback) => { validator: (rule, value, callback) => {
// 检查是否为数组且长度为2
if (!Array.isArray(value) || value.length !== 2) { if (!Array.isArray(value) || value.length !== 2) {
callback(new Error("请选择完整的时间段")); callback(new Error("请选择完整的时间段"));
} else { } else {
// 检查日期格式是否有效(可选)
const isValid = value.every((date) => /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(date)); const isValid = value.every((date) => /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/.test(date));
if (!isValid) { isValid ? callback() : callback(new Error("时间格式不正确"));
callback(new Error("时间段格式不正确"));
} else {
callback();
}
} }
}, },
}, },
...@@ -80,228 +75,86 @@ const rules = ref<FormRules<RuleForm>>({ ...@@ -80,228 +75,86 @@ const rules = ref<FormRules<RuleForm>>({
}, },
], ],
}); });
//表单绑定的字段
// ---------- 表单绑定对象 ----------
const exportObject = ref({ const exportObject = ref({
timeValue: [], timeValue: [],
spiderType: [], spiderType: [],
}); });
const fullscreenLoading = ref(false); const fullscreenLoading = ref(false);
// ---------- 父传值 ----------
const props = defineProps({ const props = defineProps({
dialogVisible: { dialogVisible: { type: Boolean, default: false },
type: Boolean,
default: false,
},
}); });
const emit = defineEmits(["update:dialogVisible"]); const emit = defineEmits(["update:dialogVisible"]);
const exportDialogVisible = ref(props.dialogVisible); const exportDialogVisible = ref(props.dialogVisible);
// 导出方法 // ---------- 自动下载 Blob 文件 ----------
// const handleExport = async () => { const downloadBlob = (blob: Blob, fileName: string) => {
// // saveToFile('666') const url = window.URL.createObjectURL(blob);
// if (!formRef.value) return const a = document.createElement("a");
// await formRef.value.validate(async (valid, fields) => { a.href = url;
// console.log('开始校验'); a.download = fileName;
// if (valid) { document.body.appendChild(a);
// console.log('校验通过'); a.click();
// const loading = ElLoading.service({ document.body.removeChild(a);
// lock: true, window.URL.revokeObjectURL(url);
// text: 'Loading', };
// background: 'rgba(0, 0, 0, 0.7)',
// })
// try {
// const res = await axios.post(
// '/api/export/downloadFile',
// { times: exportObject.value.timeValue, filters: exportObject.value.spiderType },
// {
// responseType: 'blob',
// headers: {
// 'Token': localStorage.getItem('Admin-Token') || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6ImFkbWluIiwiZXhwIjoxNzU5MjA5NzM4fQ.6hVko0EQTuz7OYjXEafZYLpmkVEyiLhZ8aWHi0Pni_s', // 根据你的 token 存储方式调整
// },
// }
// )
// // 获取文件名
// // const disposition = res.headers['content-disposition']
// // let fileName = 'export.zip'
// // if (disposition) {
// // const match = disposition.match(/filename="?([^"]+)"?/)
// // if (match) fileName = decodeURIComponent(match[1])
// // }
// // const blob = new Blob([res.data], { type: 'application/zip' })
// // const url = window.URL.createObjectURL(blob)
// // const a = document.createElement('a')
// // a.href = url
// // a.download = fileName
// // document.body.appendChild(a)
// // a.click()
// // document.body.removeChild(a)
// // window.URL.revokeObjectURL(url)
// loading.close()
// // close()
// // ElMessage.success('导出成功');
// } catch (e) {
// console.error('导出失败', e)
// close()
// ElMessage.error('导出失败');
// }
// } else {
// console.log('校验不通过');
// }
// })
// }
// const handleExport = async () => {
// if (!formRef.value) return
// await formRef.value.validate(async (valid, fields) => {
// if (valid) {
// const loading = ElLoading.service({
// lock: true,
// text: '正在导出ZIP文件...',
// background: 'rgba(0, 0, 0, 0.7)',
// })
// try {
// const res = await axios.post(
// '/api/export/downloadFile',
// { times: exportObject.value.timeValue, filters: exportObject.value.spiderType },
// {
// responseType: 'blob', // 关键:指定接收二进制数据
// headers: {
// 'Token': localStorage.getItem('Admin-Token') || '',
// },
// }
// )
// // 调用保存方法,传入二进制数据和响应头
// saveZipFile(res.data, res.headers)
// loading.close()
// ElMessage.success('ZIP文件导出成功');
// } catch (e) {
// console.error('导出失败', e)
// loading.close()
// ElMessage.error('ZIP文件导出失败,请重试');
// }
// }
// })
// }
// const saveToFile = async (data:any)=> {
// const options = {
// suggestedName: "example.txt",
// types: [
// {
// description: 'Text Files',
// accept: { 'text/plain': ['.txt'] }
// }
// ]
// };
// try {
// const fileHandle = await window.showSaveFilePicker(options); // 打开保存对话框
// const writable = await fileHandle.createWritable(); // 创建可写流
// await writable.write(data); // 写入数据
// await writable.close(); // 关闭流
// console.log("文件保存成功!");
// } catch (error) {
// console.error("保存失败:", error);
// }
// }
// ---------- 处理导出 ----------
const handleExport = async () => { const handleExport = async () => {
if (!formRef.value) return; if (!formRef.value) return;
try { await formRef.value.validate(async (valid) => {
// 1. 先在用户点击时获取保存文件句柄(关键:在用户手势中执行) if (!valid) return;
const fileHandle = await getSaveFileHandle();
if (!fileHandle) {
ElMessage.info("已取消保存");
return;
}
// 2. 执行表单验证
await formRef.value.validate(async (valid, fields) => {
if (valid) {
const loading = ElLoading.service({
lock: true,
text: "正在导出文件...",
background: "rgba(0, 0, 0, 0.7)",
});
try {
// 3. 请求文件数据
const res = await axios.post(
"/api/export/downloadFile",
{ times: exportObject.value.timeValue, filters: exportObject.value.spiderType },
{
responseType: "blob",
headers: {
Token: localStorage.getItem("Admin-Token") || "",
},
}
);
// 4. 使用之前获取的句柄保存文件 const loading = ElLoading.service({
await saveToSelectedLocation(res.data, fileHandle); lock: true,
loading.close(); text: "正在导出文件...",
ElMessage.success("数据导出成功"); background: "rgba(0, 0, 0, 0.7)",
close();
} catch (e) {
console.error("导出失败", e);
loading.close();
ElMessage.error("ZIP文件导出失败,请重试");
}
}
}); });
} catch (error) {
console.error(error);
// ElMessage.error('无法打开保存对话框,请使用浏览器默认下载');
}
};
// 获取用户选择的保存位置
const getSaveFileHandle = async () => {
// 检查浏览器是否支持
if (!(window as any).showSaveFilePicker) {
ElMessage.warning("您的浏览器不支持选择保存位置,请使用最新版Chrome或Edge浏览器");
return null;
}
try { try {
// 弹出保存对话框(必须在用户直接交互中调用) // 请求后端 ZIP 文件
return await (window as any).showSaveFilePicker({ const res = await axios.post(
suggestedName: "export.zip", "/api/export/downloadFile",
types: [
{ {
description: "ZIP压缩文件", times: exportObject.value.timeValue,
accept: { "application/zip": [".zip"] }, filters: exportObject.value.spiderType,
}, },
], {
excludeAcceptAllOption: true, responseType: "blob",
}); headers: {
} catch (error: any) { Token: localStorage.getItem("Admin-Token") || "",
// 用户取消选择时也会触发错误,这里视为正常取消 },
if (error.name !== "AbortError") { }
console.error("获取保存位置失败", error); );
}
return null; // 获取后端文件名
} let fileName = "export.zip";
}; const disposition = res.headers["content-disposition"];
if (disposition) {
// 保存数据到用户选择的位置 const match = disposition.match(/filename="?([^"]+)"?/);
const saveToSelectedLocation = async (blobData: any, fileHandle: any) => { if (match) fileName = decodeURIComponent(match[1]);
if (!(blobData instanceof Blob) || blobData.size === 0) { }
throw new Error("无效的ZIP文件数据");
}
// 将blob转换为可写数据 // 自动下载
const arrayBuffer = await blobData.arrayBuffer(); downloadBlob(res.data, fileName);
// 写入到用户选择的文件 loading.close();
const writable = await fileHandle.createWritable(); ElMessage.success("导出成功");
await writable.write(arrayBuffer); close();
await writable.close(); } catch (e) {
console.error("导出失败", e);
loading.close();
ElMessage.error("导出失败,请重试");
}
});
}; };
// 关闭弹窗的方法 // ---------- 关闭弹窗 ----------
const close = () => { const close = () => {
exportObject.value = { exportObject.value = {
timeValue: [], timeValue: [],
...@@ -310,14 +163,15 @@ const close = () => { ...@@ -310,14 +163,15 @@ const close = () => {
formRef.value?.clearValidate(); formRef.value?.clearValidate();
exportDialogVisible.value = false; exportDialogVisible.value = false;
}; };
// 监听父组件传过来的值
// ---------- 同步父组件的 v-model ----------
watch( watch(
() => props.dialogVisible, () => props.dialogVisible,
(newVal) => { (newVal) => {
exportDialogVisible.value = newVal; exportDialogVisible.value = newVal;
} }
); );
// 监听组件内的值并向父组件更新
watch( watch(
() => exportDialogVisible.value, () => exportDialogVisible.value,
(newVal) => { (newVal) => {
......
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