Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
D
download-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
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
downloader
download-web
Commits
a2c01127
Commit
a2c01127
authored
Sep 01, 2025
by
焦子成
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
1
parent
85482980
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
166 additions
and
137 deletions
+166
-137
src/views/Downloader.vue
+14
-10
src/views/History.vue
+152
-127
No files found.
src/views/Downloader.vue
View file @
a2c01127
...
...
@@ -353,12 +353,12 @@
<
/el-table-column> --
>
<
el
-
table
-
column
label
=
"大小"
width
=
"120"
align
=
"center"
prop
=
"fileLength"
>
<
!--
<
template
#
default
=
"{ row
}
"
>
<
template
#
default
=
"{ row
}
"
>
<
span
v
-
if
=
"row.totalBytes > 0"
class
=
"size-info"
>
{{
formatBytes
(
row
.
downloadedBytes
)
}}
/
{{
formatBytes
(
row
.
totalBytes
)
}}
<
/span
>
<
span
v
-
else
class
=
"size-info"
>-<
/span
>
<
/template>
--
>
<
/template
>
<
/el-table-column
>
<
el
-
table
-
column
label
=
"操作"
width
=
"200"
align
=
"center"
fixed
=
"right"
>
...
...
@@ -440,14 +440,14 @@ const activeCollapse = ref(['upload']) // 默认展开配置区域
const
currentPage
=
ref
(
1
)
const
pageSize
=
ref
(
10
)
// 已上传文件列表
cons
t
upQuery
=
ref
({
le
t
upQuery
=
ref
({
current
:
1
,
pageSize
:
5
}
)
let
upTableData
=
reactive
([])
let
upTotal
=
ref
(
0
)
// 下载任务管理列表
cons
t
deQuery
=
ref
({
le
t
deQuery
=
ref
({
current
:
1
,
pageSize
:
10
,
fileStatus
:
''
...
...
@@ -455,13 +455,13 @@ const deQuery = ref({
let
deTableData
=
reactive
([])
let
deTotal
=
ref
(
0
)
cons
t
columnMapping
=
ref
({
le
t
columnMapping
=
ref
({
fileNameColumns
:
[],
url
:
''
}
)
// 状态筛选
cons
t
statusFilter
=
ref
(
''
)
le
t
statusFilter
=
ref
(
''
)
// 计算是否可以解析
const
canParse
=
computed
(()
=>
{
...
...
@@ -824,20 +824,21 @@ onMounted(() => {
detectBrowserDownloadPath()
loadUpTableData()
loadDetailTableData()
loadUploadDetailTotalNum()
}
)
function loadDetailTableData(){
http.post(config.uploadDetailList,deQuery.value).then(res=>{
if (res.code === 200){
deTableData = res.data.rows
deTotal.value = res.data.total
loadUploadDetailTotalNum()
}
else {
ElMessage.error(res.message)
}
}
)
}
const totalNumObj = reactive
({
let totalNumObj = ref
({
countNum:0,
fileLength: '',
status0: 0,
status1: 0,
status2: 0,
...
...
@@ -847,7 +848,10 @@ const totalNumObj = reactive({
function loadUploadDetailTotalNum(){
http.post(config.uploadDetailTotalNum).then(res=>{
if (res.code === 200){
totalNumObj.value.countNum = res.data.countNum
totalNumObj.value.status3 = res.data.status3
totalNumObj.value.status4 = res.data.status4
totalNumObj.value.fileLength = res.data.fileLength
}
else {
ElMessage.error(res.message)
}
...
...
@@ -902,7 +906,7 @@ function deleteRow(row) {
// 解析Excel文件
const parseExcelFile = async () => {
if (rawExcelData.value.length >
8
000){
if (rawExcelData.value.length >
3
000){
ElMessage.error('文件数据量太大,请拆分处理,每次不超过3000条')
return
}
...
...
src/views/History.vue
View file @
a2c01127
...
...
@@ -3,7 +3,9 @@
<!-- 头部 -->
<header
class=
"header"
>
<div
class=
"logo"
>
<el-icon><Clock
/></el-icon>
<el-icon>
<Clock
/>
</el-icon>
下载历史
</div>
<div
class=
"header-actions"
>
...
...
@@ -22,19 +24,19 @@
<el-card
class=
"stats-card"
>
<div
class=
"stats-grid"
>
<div
class=
"stat-item"
>
<div
class=
"stat-number"
>
{{
total
Records
}}
</div>
<div
class=
"stat-number"
>
{{
total
NumObj
.
countNum
}}
</div>
<div
class=
"stat-label"
>
总记录数
</div>
</div>
<div
class=
"stat-item"
>
<div
class=
"stat-number"
>
{{
completedCount
}}
</div>
<div
class=
"stat-number"
>
{{
totalNumObj
.
status3
}}
</div>
<div
class=
"stat-label"
>
已完成
</div>
</div>
<div
class=
"stat-item"
>
<div
class=
"stat-number"
>
{{
failedCount
}}
</div>
<div
class=
"stat-number"
>
{{
totalNumObj
.
status4
}}
</div>
<div
class=
"stat-label"
>
失败
</div>
</div>
<div
class=
"stat-item"
>
<div
class=
"stat-number"
>
{{
total
Size
}}
</div>
<div
class=
"stat-number"
>
{{
total
NumObj
.
fileLength
}}
</div>
<div
class=
"stat-label"
>
总大小
</div>
</div>
</div>
...
...
@@ -42,76 +44,68 @@
<!-- 历史记录列表 -->
<el-card
class=
"history-card"
>
<template
#
header
>
<div
class=
"card-header"
>
<span>
历史记录
</span>
<div
class=
"header-actions"
>
<el-select
v-model=
"statusFilter"
placeholder=
"状态筛选"
size=
"small"
style=
"width: 120px;"
>
<el-option
label=
"全部"
value=
""
/>
<el-option
label=
"已完成"
value=
"completed"
/>
<el-option
label=
"失败"
value=
"error"
/>
<el-option
label=
"暂停"
value=
"paused"
/>
</el-select>
<el-select
v-model=
"excelFileFilter"
placeholder=
"Excel文件筛选"
size=
"small"
style=
"width: 150px; margin-left: 10px;"
>
<el-option
label=
"全部Excel文件"
value=
""
/>
<el-option
v-for=
"excelFile in uniqueExcelFiles"
:key=
"excelFile"
:label=
"excelFile"
:value=
"excelFile"
/>
</el-select>
<el-input
v-model=
"searchKeyword"
placeholder=
"搜索文件名或Excel文件"
size=
"small"
style=
"width: 200px; margin-left: 10px;"
clearable
>
<template
#
prefix
>
<el-icon><Search
/></el-icon>
</
template
>
</el-input>
</div>
</div>
</template>
<template
#
header
>
<div
class=
"card-header"
>
<span>
历史记录
</span>
<div
class=
"header-actions"
>
<el-select
v-model=
"deQuery.fileStatus"
placeholder=
"状态筛选"
size=
"small"
style=
"width: 120px;"
>
<el-option
label=
"全部"
value=
""
/>
<el-option
label=
"已完成"
:value=
"3"
/>
<el-option
label=
"失败"
:value=
"4"
/>
<el-option
label=
"暂停"
:value=
"2"
/>
</el-select>
<el-select
v-model=
"excelFileFilter"
placeholder=
"Excel文件筛选"
size=
"small"
style=
"width: 150px; margin-left: 10px;"
>
<el-option
label=
"全部Excel文件"
value=
""
/>
<el-option
v-for=
"excelFile in uniqueExcelFiles"
:key=
"excelFile"
:label=
"excelFile"
:value=
"excelFile"
/>
</el-select>
<el-input
v-model=
"deQuery.fileName"
placeholder=
"搜索文件名或Excel文件"
size=
"small"
style=
"width: 200px; margin-left: 10px;"
clearable
>
<template
#
prefix
>
<el-icon>
<Search
/>
</el-icon>
</
template
>
</el-input>
</div>
</div>
</template>
<!-- 历史记录表格 -->
<el-table
:data=
"filteredHistory"
style=
"width: 100%"
:default-sort=
"{ prop: 'timestamp', order: 'descending' }"
stripe
>
<el-table-column
prop=
"timestamp"
label=
"时间"
width=
"180"
sortable
>
<
template
#
default=
"scope"
>
{{
formatDate
(
scope
.
row
.
timestamp
)
}}
</
template
>
</el-table-column>
<el-table-column
prop=
"excelFileName"
label=
"Excel文件"
width=
"180"
>
<
template
#
default=
"scope"
>
<div
class=
"excel-file-info"
>
<el-icon><Document
/></el-icon>
<span
class=
"excel-file-name"
>
{{
scope
.
row
.
excelFileName
||
'-'
}}
</span>
</div>
</
template
>
</el-table-column>
<el-table-column
prop=
"fileName"
label=
"文件名"
min-width=
"200"
>
<
template
#
default=
"scope"
>
<div
class=
"file-info"
>
<span
class=
"file-name"
>
<span
v-if=
"scope.row.fileNamePrefix"
class=
"file-prefix"
>
{{
scope
.
row
.
fileNamePrefix
}}
_
</span>
{{
scope
.
row
.
fileName
}}
</span>
<span
class=
"file-url"
>
{{
scope
.
row
.
url
}}
</span>
</div>
</
template
>
</el-table-column>
<el-table
:data=
"deTableData"
style=
"width: 100%"
:default-sort=
"{ prop: 'timestamp', order: 'descending' }"
stripe
>
<el-table-column
prop=
"timestamp"
label=
"时间"
width=
"180"
sortable
>
<
template
#
default=
"scope"
>
{{
formatDate
(
scope
.
row
.
timestamp
)
}}
</
template
>
</el-table-column>
<el-table-column
prop=
"excelFileName"
label=
"Excel文件"
width=
"180"
>
<
template
#
default=
"scope"
>
<div
class=
"excel-file-info"
>
<el-icon>
<Document
/>
</el-icon>
<span
class=
"excel-file-name"
>
{{
scope
.
row
.
excelFileName
||
'-'
}}
</span>
</div>
</
template
>
</el-table-column>
<el-table-column
prop=
"fileName"
label=
"文件名"
min-width=
"200"
>
<
template
#
default=
"scope"
>
<div
class=
"file-info"
>
<span
class=
"file-name"
>
<span
v-if=
"scope.row.fileNamePrefix"
class=
"file-prefix"
>
{{
scope
.
row
.
fileNamePrefix
}}
_
</span>
{{
scope
.
row
.
fileName
}}
</span>
<span
class=
"file-url"
>
{{
scope
.
row
.
url
}}
</span>
</div>
</
template
>
</el-table-column>
<el-table-column
prop=
"status"
label=
"状态"
width=
"100"
>
<
template
#
default=
"scope"
>
<el-tag
:type=
"getStatusType(scope.row.status)"
>
...
...
@@ -119,7 +113,7 @@
</el-tag>
</
template
>
</el-table-column>
<el-table-column
prop=
"progress"
label=
"进度"
width=
"120"
>
<
template
#
default=
"scope"
>
<div
v-if=
"scope.row.status === 'completed'"
>
...
...
@@ -133,34 +127,26 @@
</div>
</
template
>
</el-table-column>
<el-table-column
prop=
"fileSize"
label=
"文件大小"
width=
"120"
>
<
template
#
default=
"scope"
>
{{
formatFileSize
(
scope
.
row
.
fileSize
)
}}
</
template
>
</el-table-column>
<el-table-column
prop=
"downloadedBytes"
label=
"已下载"
width=
"120"
>
<
template
#
default=
"scope"
>
{{
formatFileSize
(
scope
.
row
.
downloadedBytes
)
}}
</
template
>
</el-table-column>
<el-table-column
label=
"操作"
width=
"120"
fixed=
"right"
>
<
template
#
default=
"scope"
>
<el-button
v-if=
"scope.row.status === 'error'"
@
click=
"retryDownload(scope.row)"
type=
"primary"
size=
"small"
>
<el-button
v-if=
"scope.row.status === 'error'"
@
click=
"retryDownload(scope.row)"
type=
"primary"
size=
"small"
>
重试
</el-button>
<el-button
@
click=
"viewDetails(scope.row)"
type=
"info"
size=
"small"
>
<el-button
@
click=
"viewDetails(scope.row)"
type=
"info"
size=
"small"
>
详情
</el-button>
</
template
>
...
...
@@ -169,15 +155,9 @@
<!-- 分页 -->
<div
class=
"pagination-container"
>
<el-pagination
v-model:current-page=
"currentPage"
v-model:page-size=
"pageSize"
:page-sizes=
"[10, 20, 50, 100]"
:total=
"filteredHistory.length"
layout=
"total, sizes, prev, pager, next, jumper"
@
size-change=
"handleSizeChange"
@
current-change=
"handleCurrentChange"
/>
<el-pagination
v-model:current-page=
"deQuery.current"
v-model:page-size=
"deQuery.pageSize"
:page-sizes=
"[10, 20, 50, 100]"
:total=
"deTotal"
layout=
"total, sizes, prev, pager, next, jumper"
@
size-change=
"handleSizeChange"
@
current-change=
"handleCurrentChange"
/>
</div>
</el-card>
</div>
...
...
@@ -185,15 +165,15 @@
<!-- 详情对话框 -->
<el-dialog
v-model=
"detailsVisible"
title=
"下载详情"
width=
"600px"
>
<div
v-if=
"selectedRecord"
class=
"details-content"
>
<el-descriptions
:column=
"1"
border
>
<el-descriptions-item
label=
"Excel文件"
>
{{ selectedRecord.excelFileName || '-' }}
</el-descriptions-item>
<el-descriptions-item
label=
"文件名"
>
<span
v-if=
"selectedRecord.fileNamePrefix"
class=
"file-prefix"
>
{{ selectedRecord.fileNamePrefix }}_
</span>
{{ selectedRecord.fileName }}
</el-descriptions-item>
<el-descriptions-item
label=
"下载地址"
>
{{ selectedRecord.url }}
</el-descriptions-item>
<el-descriptions
:column=
"1"
border
>
<el-descriptions-item
label=
"Excel文件"
>
{{ selectedRecord.excelFileName || '-' }}
</el-descriptions-item>
<el-descriptions-item
label=
"文件名"
>
<span
v-if=
"selectedRecord.fileNamePrefix"
class=
"file-prefix"
>
{{ selectedRecord.fileNamePrefix }}_
</span>
{{ selectedRecord.fileName }}
</el-descriptions-item>
<el-descriptions-item
label=
"下载地址"
>
{{ selectedRecord.url }}
</el-descriptions-item>
<el-descriptions-item
label=
"状态"
>
<el-tag
:type=
"getStatusType(selectedRecord.status)"
>
{{ getStatusText(selectedRecord.status) }}
...
...
@@ -214,11 +194,13 @@
</template>
<
script
setup
>
import
{
ref
,
computed
,
onMounted
}
from
'vue'
import
{
ref
,
computed
,
onMounted
,
reactive
}
from
'vue'
import
{
useRouter
}
from
'vue-router'
import
{
ElMessage
,
ElMessageBox
}
from
'element-plus'
import
{
Clock
,
Search
}
from
'@element-plus/icons-vue'
import
{
useAuthStore
}
from
'../stores/auth'
import
http
from
'@/utils/request.js'
;
import
config
from
'@/api/api.js'
;
const
router
=
useRouter
()
const
authStore
=
useAuthStore
()
...
...
@@ -233,56 +215,93 @@ const pageSize = ref(20)
const
detailsVisible
=
ref
(
false
)
const
selectedRecord
=
ref
(
null
)
const
deQuery
=
ref
({
current
:
1
,
pageSize
:
10
,
fileStatus
:
3
,
fileName
:
''
})
let
deTableData
=
reactive
([])
let
deTotal
=
ref
(
0
)
// 加载历史数据
const
loadHistory
=
()
=>
{
historyData
.
value
=
authStore
.
getDownloadHistory
()
// historyData.value = authStore.getDownloadHistory()
http
.
post
(
config
.
uploadDetailList
,
deQuery
.
value
).
then
(
res
=>
{
if
(
res
.
code
===
200
)
{
deTableData
=
res
.
data
.
rows
deTotal
.
value
=
res
.
data
.
total
}
else
{
ElMessage
.
error
(
res
.
message
)
}
})
}
let
totalNumObj
=
ref
({
"countNum"
:
0
,
"fileLength"
:
'0 KB'
,
"status0"
:
0
,
"status1"
:
0
,
"status2"
:
0
,
"status3"
:
0
,
"status4"
:
0
})
function
loadUploadDetailTotalNum
(){
http
.
post
(
config
.
uploadDetailTotalNum
).
then
(
res
=>
{
if
(
res
.
code
===
200
){
totalNumObj
.
value
.
countNum
=
res
.
data
.
countNum
totalNumObj
.
value
.
status3
=
res
.
data
.
status3
totalNumObj
.
value
.
status4
=
res
.
data
.
status4
totalNumObj
.
value
.
fileLength
=
res
.
data
.
fileLength
}
else
{
ElMessage
.
error
(
res
.
message
)
}
})
}
// 获取唯一的Excel文件列表
const
uniqueExcelFiles
=
computed
(()
=>
{
const
allDownloads
=
historyData
.
value
.
flatMap
(
record
=>
const
allDownloads
=
historyData
.
value
.
flatMap
(
record
=>
record
.
downloads
.
map
(
download
=>
({
...
download
,
timestamp
:
record
.
timestamp
}))
)
const
excelFiles
=
allDownloads
.
map
(
item
=>
item
.
excelFileName
)
.
filter
(
file
=>
file
&&
file
.
trim
())
return
[...
new
Set
(
excelFiles
)]
})
// 筛选历史记录
const
filteredHistory
=
computed
(()
=>
{
let
filtered
=
historyData
.
value
.
flatMap
(
record
=>
let
filtered
=
historyData
.
value
.
flatMap
(
record
=>
record
.
downloads
.
map
(
download
=>
({
...
download
,
timestamp
:
record
.
timestamp
}))
)
// 状态筛选
if
(
statusFilter
.
value
)
{
filtered
=
filtered
.
filter
(
item
=>
item
.
status
===
statusFilter
.
value
)
}
// Excel文件筛选
if
(
excelFileFilter
.
value
)
{
filtered
=
filtered
.
filter
(
item
=>
item
.
excelFileName
===
excelFileFilter
.
value
)
}
// 关键词搜索
if
(
searchKeyword
.
value
)
{
const
keyword
=
searchKeyword
.
value
.
toLowerCase
()
filtered
=
filtered
.
filter
(
item
=>
filtered
=
filtered
.
filter
(
item
=>
item
.
fileName
.
toLowerCase
().
includes
(
keyword
)
||
item
.
url
.
toLowerCase
().
includes
(
keyword
)
||
(
item
.
excelFileName
&&
item
.
excelFileName
.
toLowerCase
().
includes
(
keyword
))
)
}
return
filtered
})
...
...
@@ -313,11 +332,11 @@ const formatDate = (dateString) => {
// 格式化文件大小
const
formatFileSize
=
(
bytes
)
=>
{
if
(
!
bytes
||
bytes
===
0
)
return
'0 B'
const
k
=
1024
const
sizes
=
[
'B'
,
'KB'
,
'MB'
,
'GB'
,
'TB'
]
const
i
=
Math
.
floor
(
Math
.
log
(
bytes
)
/
Math
.
log
(
k
))
return
parseFloat
((
bytes
/
Math
.
pow
(
k
,
i
)).
toFixed
(
2
))
+
' '
+
sizes
[
i
]
}
...
...
@@ -360,7 +379,7 @@ const retryDownload = async (record) => {
type
:
'info'
}
)
// 这里可以调用下载store的方法重新下载
ElMessage
.
success
(
'已添加到下载队列'
)
}
catch
{
...
...
@@ -380,7 +399,7 @@ const clearAllHistory = async () => {
type
:
'warning'
}
)
authStore
.
clearDownloadHistory
()
loadHistory
()
ElMessage
.
success
(
'历史记录已清空'
)
...
...
@@ -391,12 +410,17 @@ const clearAllHistory = async () => {
// 分页处理
const
handleSizeChange
=
(
size
)
=>
{
pageSize
.
value
=
size
currentPage
.
value
=
1
// pageSize.value = size
// currentPage.value = 1
deQuery
.
value
.
pageSize
=
size
deQuery
.
value
.
current
=
1
loadHistory
()
}
const
handleCurrentChange
=
(
page
)
=>
{
currentPage
.
value
=
page
// currentPage.value = page
deQuery
.
value
.
current
=
page
loadHistory
()
}
// 返回下载器
...
...
@@ -407,6 +431,7 @@ const goBack = () => {
// 组件挂载时初始化
onMounted
(()
=>
{
loadHistory
()
loadUploadDetailTotalNum
()
})
</
script
>
...
...
@@ -525,17 +550,17 @@ onMounted(() => {
.main-content
{
padding
:
0
10px
;
}
.stats-grid
{
grid-template-columns
:
repeat
(
2
,
1
fr
);
}
.card-header
{
flex-direction
:
column
;
align-items
:
flex-start
;
gap
:
10px
;
}
.header-actions
{
flex-direction
:
column
;
}
...
...
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