Commit f1d4a358 by 周田

Merge branch 'liucan' into 'main'

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

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