Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
F
fk-spider-web
Overview
Overview
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
周田
fk-spider-web
Commits
f1d4a358
Commit
f1d4a358
authored
Dec 10, 2025
by
周田
Browse files
Options
Browse Files
Download
Plain Diff
Merge branch 'liucan' into 'main'
fix:修改导出数据为直接导出 See merge request
!17
parents
ec252468
312aac18
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
49 additions
and
195 deletions
+49
-195
index.vue
src/components/Export/index.vue
+49
-195
No files found.
src/components/Export/index.vue
View file @
f1d4a358
...
...
@@ -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,146 +75,41 @@ 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
;
}
await
formRef
.
value
.
validate
(
async
(
valid
)
=>
{
if
(
!
valid
)
return
;
// 2. 执行表单验证
await
formRef
.
value
.
validate
(
async
(
valid
,
fields
)
=>
{
if
(
valid
)
{
const
loading
=
ElLoading
.
service
({
lock
:
true
,
text
:
"正在导出文件..."
,
...
...
@@ -227,10 +117,13 @@ const handleExport = async () => {
});
try
{
// 3. 请求文件数据
// 请求后端 ZIP 文件
const
res
=
await
axios
.
post
(
"/api/export/downloadFile"
,
{
times
:
exportObject
.
value
.
timeValue
,
filters
:
exportObject
.
value
.
spiderType
},
{
times
:
exportObject
.
value
.
timeValue
,
filters
:
exportObject
.
value
.
spiderType
,
},
{
responseType
:
"blob"
,
headers
:
{
...
...
@@ -239,69 +132,29 @@ const handleExport = async () => {
}
);
// 4. 使用之前获取的句柄保存文件
await
saveToSelectedLocation
(
res
.
data
,
fileHandle
);
// 获取后端文件名
let
fileName
=
"export.zip"
;
const
disposition
=
res
.
headers
[
"content-disposition"
];
if
(
disposition
)
{
const
match
=
disposition
.
match
(
/filename="
?([^
"
]
+
)
"
?
/
);
if
(
match
)
fileName
=
decodeURIComponent
(
match
[
1
]);
}
// 自动下载
downloadBlob
(
res
.
data
,
fileName
);
loading
.
close
();
ElMessage
.
success
(
"数据
导出成功"
);
ElMessage
.
success
(
"
导出成功"
);
close
();
}
catch
(
e
)
{
console
.
error
(
"导出失败"
,
e
);
loading
.
close
();
ElMessage
.
error
(
"ZIP文件导出失败,
请重试"
);
ElMessage
.
error
(
"导出失败,
请重试"
);
}
}
});
}
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
:
[
{
description
:
"ZIP压缩文件"
,
accept
:
{
"application/zip"
:
[
".zip"
]
},
},
],
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文件数据"
);
}
// 将blob转换为可写数据
const
arrayBuffer
=
await
blobData
.
arrayBuffer
();
// 写入到用户选择的文件
const
writable
=
await
fileHandle
.
createWritable
();
await
writable
.
write
(
arrayBuffer
);
await
writable
.
close
();
};
//
关闭弹窗的方法
//
---------- 关闭弹窗 ----------
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
)
=>
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment