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
85482980
Commit
85482980
authored
Sep 01, 2025
by
焦子成
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
1
parent
d36acf7e
Hide whitespace changes
Inline
Side-by-side
Showing
9 changed files
with
890 additions
and
615 deletions
+890
-615
src/api/api.js
+33
-0
src/main.js
+2
-0
src/router/index.js
+14
-14
src/stores/auth.js
+20
-18
src/utils/request.js
+34
-0
src/views/Downloader.vue
+645
-440
src/views/Login.vue
+131
-139
src/views/Settings.vue
+3
-3
vite.config.js
+8
-1
No files found.
src/api/api.js
0 → 100644
View file @
85482980
var
apiHost
=
""
;
var
domain
=
""
;
let
env
=
process
.
env
.
NODE_ENV
||
""
;
if
(
env
==
"production"
)
{
apiHost
=
"https://taskh5.htangcloud.com"
;
}
else
if
(
env
==
"test"
)
{
apiHost
=
"https://testapp.rongketech.com"
;
}
else
{
apiHost
=
"http://39.107.245.36:8081"
;
domain
=
'/api'
}
console
.
log
(
`-----env[
${
env
}
]-----, apiHost[
${
apiHost
}
]`
);
const
config
=
{
apiHost
,
domain
,
login
:
`
${
domain
}
/user/login`
,
register
:
`
${
domain
}
/user/register`
,
uploadBatchList
:
`
${
domain
}
/uploadBatch/pageList`
,
uploadBatchQueryUpload
:
`
${
domain
}
/uploadBatch/queryUpload`
,
uploadBatchDelete
:
`
${
domain
}
/uploadBatch/delete`
,
uploadDetailList
:
`
${
domain
}
/uploadDetail/pageList`
,
uploadBatchFileUpload
:
`
${
domain
}
/uploadBatch/fileUpload`
,
uploadDetailTotalNum
:
`
${
domain
}
/uploadDetail/totalNum`
,
uploadDetailDeleteAll
:
`
${
domain
}
/uploadDetail/deleteAll`
,
uploadDetailDelete
:
`
${
domain
}
/uploadDetail/delete`
,
downLoadStatus
:
`
${
domain
}
/uploadDetail/downLoadStatus`
,
fileUploadDetail
:
`
${
domain
}
/uploadBatch/fileUploadDetail`
,
};
export
default
config
;
src/main.js
View file @
85482980
...
...
@@ -7,6 +7,7 @@ import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import
App
from
'./App.vue'
import
router
from
'./router'
import
'./style.css'
import
axios
from
'axios'
const
app
=
createApp
(
App
)
const
pinia
=
createPinia
()
...
...
@@ -16,5 +17,6 @@ app.use(router)
app
.
use
(
ElementPlus
,
{
locale
:
zhCn
,
})
// app.prototype.$http = axios
app
.
mount
(
'#app'
)
src/router/index.js
View file @
85482980
...
...
@@ -42,21 +42,21 @@ const router = createRouter({
})
// 路由守卫
router
.
beforeEach
((
to
,
from
,
next
)
=>
{
const
authStore
=
useAuthStore
()
//
router.beforeEach((to, from, next) => {
//
const authStore = useAuthStore()
// 初始化认证状态
if
(
!
authStore
.
isAuthenticated
)
{
authStore
.
initialize
()
}
//
// 初始化认证状态
//
if (!authStore.isAuthenticated) {
//
authStore.initialize()
//
}
if
(
to
.
meta
.
requiresAuth
&&
!
authStore
.
isAuthenticated
)
{
next
(
'/login'
)
}
else
if
(
to
.
path
===
'/login'
&&
authStore
.
isAuthenticated
)
{
next
(
'/downloader'
)
}
else
{
next
()
}
})
//
if (to.meta.requiresAuth && !authStore.isAuthenticated) {
//
next('/login')
//
} else if (to.path === '/login' && authStore.isAuthenticated) {
//
next('/downloader')
//
} else {
//
next()
//
}
//
})
export
default
router
src/stores/auth.js
View file @
85482980
...
...
@@ -58,18 +58,18 @@ export const useAuthStore = defineStore('auth', () => {
}
// 检查用户名是否已存在
const
isUsernameExists
=
(
user
n
ame
)
=>
{
return
users
.
value
.
some
(
user
=>
user
.
user
name
===
usern
ame
)
const
isUsernameExists
=
(
user
N
ame
)
=>
{
return
users
.
value
.
some
(
user
=>
user
.
user
Name
===
userN
ame
)
}
// 用户注册
const
register
=
async
(
user
n
ame
,
password
,
email
=
''
)
=>
{
const
register
=
async
(
user
N
ame
,
password
,
email
=
''
)
=>
{
// 验证用户名
if
(
!
user
name
||
usern
ame
.
trim
().
length
<
3
)
{
if
(
!
user
Name
||
userN
ame
.
trim
().
length
<
3
)
{
throw
new
Error
(
'用户名至少需要3个字符'
)
}
if
(
isUsernameExists
(
user
n
ame
))
{
if
(
isUsernameExists
(
user
N
ame
))
{
throw
new
Error
(
'用户名已存在'
)
}
...
...
@@ -82,7 +82,7 @@ export const useAuthStore = defineStore('auth', () => {
// 创建新用户
const
newUser
=
{
id
:
Date
.
now
().
toString
(),
user
name
:
usern
ame
.
trim
(),
user
Name
:
userN
ame
.
trim
(),
password
:
btoa
(
password
),
// 简单的Base64编码(实际项目中应使用更安全的加密)
email
:
email
.
trim
(),
createdAt
:
new
Date
().
toISOString
(),
...
...
@@ -102,9 +102,9 @@ export const useAuthStore = defineStore('auth', () => {
}
// 用户登录
const
login
=
async
(
user
n
ame
,
password
)
=>
{
const
login
=
async
(
user
N
ame
,
password
)
=>
{
try
{
console
.
log
(
'Auth store login 被调用,用户名:'
,
user
n
ame
)
console
.
log
(
'Auth store login 被调用,用户名:'
,
user
N
ame
)
// 确保用户数据已加载
if
(
users
.
value
.
length
===
0
)
{
...
...
@@ -112,17 +112,19 @@ export const useAuthStore = defineStore('auth', () => {
loadUsersFromStorage
()
}
console
.
log
(
'当前用户列表:'
,
users
.
value
.
map
(
u
=>
u
.
user
n
ame
))
console
.
log
(
'当前用户列表:'
,
users
.
value
.
map
(
u
=>
u
.
user
N
ame
))
// 查找用户
const
user
=
users
.
value
.
find
(
u
=>
u
.
username
===
usern
ame
)
let
user
=
users
.
value
.
find
(
u
=>
u
.
userName
===
userN
ame
)
console
.
log
(
'--->查找用户:'
,
user
)
if
(
!
user
)
{
console
.
log
(
'未找到用户:'
,
username
)
throw
new
Error
(
'用户名或密码错误'
)
console
.
log
(
'未找到用户:'
,
userName
)
// user.value.userName = userName
// user.value.password = password
// throw new Error('用户名或密码错误')
}
console
.
log
(
'找到用户:'
,
user
.
user
n
ame
)
console
.
log
(
'找到用户:'
,
user
.
user
N
ame
)
console
.
log
(
'输入的密码:'
,
password
)
console
.
log
(
'输入的密码编码后:'
,
btoa
(
password
))
console
.
log
(
'存储的密码:'
,
user
.
password
)
...
...
@@ -130,7 +132,7 @@ export const useAuthStore = defineStore('auth', () => {
// 验证密码
if
(
user
.
password
!==
btoa
(
password
))
{
console
.
log
(
'密码不匹配'
)
throw
new
Error
(
'用户名或密码错误'
)
//
throw new Error('用户名或密码错误')
}
console
.
log
(
'密码验证成功'
)
...
...
@@ -272,7 +274,7 @@ export const useAuthStore = defineStore('auth', () => {
const
adminPassword
=
'123456'
const
adminUser
=
{
id
:
'admin'
,
user
n
ame
:
'admin'
,
user
N
ame
:
'admin'
,
password
:
btoa
(
adminPassword
),
email
:
''
,
createdAt
:
new
Date
().
toISOString
(),
...
...
@@ -290,7 +292,7 @@ export const useAuthStore = defineStore('auth', () => {
console
.
log
(
'管理员密码编码后:'
,
btoa
(
adminPassword
))
}
console
.
log
(
'当前用户列表:'
,
users
.
value
.
map
(
u
=>
u
.
user
n
ame
))
console
.
log
(
'当前用户列表:'
,
users
.
value
.
map
(
u
=>
u
.
user
N
ame
))
// 尝试恢复当前用户会话
try
{
...
...
@@ -301,7 +303,7 @@ export const useAuthStore = defineStore('auth', () => {
const
existingUser
=
users
.
value
.
find
(
u
=>
u
.
id
===
userData
.
id
)
if
(
existingUser
)
{
setCurrentUser
(
existingUser
)
console
.
log
(
'用户会话已恢复:'
,
existingUser
.
user
n
ame
)
console
.
log
(
'用户会话已恢复:'
,
existingUser
.
user
N
ame
)
}
}
}
catch
(
error
)
{
...
...
src/utils/request.js
0 → 100644
View file @
85482980
// import Vue from 'vue'
import
axios
from
'axios'
export
default
{
post
(
url
,
params
=
{})
{
return
new
Promise
((
resolve
,
reject
)
=>
{
axios
.
post
(
url
,
params
||
{},
{
headers
:
{
'content-type'
:
'application/json'
,
'Authorization'
:
sessionStorage
.
getItem
(
"token"
)
||
''
,
}
})
.
then
(
res
=>
{
resolve
(
res
.
data
)
})
.
catch
(
err
=>
{
reject
(
err
.
data
)
})
})
},
// get(url, params) {
// return new Promise((resolve, reject) => {
// axios.get(url, {
// params: params
// }).then(res => {
// resolve(res.data)
// }).catch(err => {
// reject(err.data)
// })
// })
// }
}
\ No newline at end of file
src/views/Downloader.vue
View file @
85482980
...
...
@@ -3,15 +3,21 @@
<!-- 头部 -->
<header
class=
"header"
>
<div
class=
"logo"
>
<el-icon><Download
/></el-icon>
<el-icon>
<Download
/>
</el-icon>
文件下载器
</div>
<div
style=
"display: flex; align-items: center; gap: 20px;"
>
<el-dropdown
@
command=
"handleUserCommand"
>
<span
class=
"user-info"
>
<el-icon><User
/></el-icon>
{{
authStore
.
user
?.
username
}}
<el-icon><ArrowDown
/></el-icon>
<el-icon>
<User
/>
</el-icon>
{{
authStore
.
user
?.
userName
}}
<el-icon>
<ArrowDown
/>
</el-icon>
</span>
<template
#
dropdown
>
<el-dropdown-menu>
...
...
@@ -26,162 +32,127 @@
<!-- 主要内容 -->
<div
class=
"main-content"
>
<!-- 配置区域(可折叠) -->
<el-collapse
v-model=
"activeCollapse"
class=
"config-collapse"
>
<el-collapse-item
title=
"📁 Excel文件管理"
name=
"upload"
>
<!-- Excel文件列表 -->
<div
v-if=
"downloadStore.excelFiles.length > 0"
style=
"margin-bottom: 20px;"
>
<h4
style=
"margin-bottom: 15px; color: #333;"
>
已上传的Excel文件 ({{ downloadStore.excelFiles.length }}个)
</h4>
<el-table
:data=
"downloadStore.excelFiles"
size=
"small"
border
style=
"width: 100%"
>
<el-table-column
prop=
"fileName"
label=
"文件名"
/>
<el-table-column
prop=
"totalRows"
label=
"总行数"
width=
"100"
align=
"center"
/>
<el-table-column
prop=
"uploadTime"
label=
"上传时间"
width=
"180"
>
<
template
#
default=
"{ row }"
>
{{
new
Date
(
row
.
uploadTime
).
toLocaleString
()
}}
</
template
>
</el-table-column>
<el-table-column
label=
"操作"
width=
"200"
align=
"center"
>
<
template
#
default=
"{ row }"
>
<el-button
@
click=
"selectExcelFile(row.id)"
type=
"primary"
size=
"small"
>
选择
</el-button>
<el-button
@
click=
"deleteExcelFile(row.id)"
type=
"danger"
size=
"small"
>
删除
</el-button>
</
template
>
</el-table-column>
</el-table>
<!-- 配置区域(可折叠) -->
<el-collapse
v-model=
"activeCollapse"
class=
"config-collapse"
>
<el-collapse-item
title=
"📁 Excel文件管理"
name=
"upload"
>
<!-- Excel文件列表 -->
<!-- v-if="downloadStore.excelFiles.length > 0" -->
<div
v-if=
"upTotal > 0"
style=
"margin-bottom: 20px;"
>
<h4
style=
"margin-bottom: 15px; color: #333;"
>
已上传的Excel文件 ({{ upTotal }}个)
</h4>
<el-table
:data=
"upTableData"
size=
"small"
border
style=
"width: 100%"
>
<el-table-column
prop=
"uploadName"
label=
"文件名"
/>
<el-table-column
prop=
"totalRows"
label=
"总行数"
width=
"100"
align=
"center"
/>
<el-table-column
prop=
"createTime"
label=
"上传时间"
width=
"180"
>
<!-- <template #default="{ row }">
{{ new Date(row.uploadTime).toLocaleString() }}
</template> -->
</el-table-column>
<el-table-column
label=
"操作"
width=
"200"
align=
"center"
>
<
template
#
default=
"{ row }"
>
<el-button
@
click=
"selectExcelFile(row.batchId)"
type=
"primary"
size=
"small"
>
选择
</el-button>
<el-button
@
click=
"deleteExcelFile(row.batchId)"
type=
"danger"
size=
"small"
>
删除
</el-button>
</
template
>
</el-table-column>
</el-table>
<div
class=
"pagination-container"
>
<el-pagination
v-model:current-page=
"upQuery.current"
v-model:page-size=
"upQuery.pageSize"
:page-sizes=
"[10, 20, 50, 100]"
:total=
"upTotal"
layout=
"total, sizes, prev, pager, next, jumper"
@
size-change=
"handleUpSizeChange"
@
current-change=
"handleUpCurrentChange"
/>
</div>
<el-divider
/>
<!-- Excel文件上传配置 -->
<div
class=
"upload-section"
>
<el-upload
ref=
"uploadRef"
:auto-upload=
"false"
:show-file-list=
"false"
accept=
".xlsx,.xls"
:on-change=
"handleFileChange"
>
</div>
<el-divider
/>
<!-- Excel文件上传配置 -->
<div
class=
"upload-section"
>
<el-upload
ref=
"uploadRef"
:auto-upload=
"false"
:show-file-list=
"false"
accept=
".xlsx,.xls"
:on-change=
"handleFileChange"
>
<el-button
type=
"primary"
size=
"large"
>
<el-icon><Upload
/></el-icon>
<el-icon>
<Upload
/>
</el-icon>
选择Excel文件
</el-button>
</el-upload>
<div
v-if=
"selectedFile"
style=
"margin-top: 15px;"
>
<el-alert
:title=
"`已选择文件: ${selectedFile.name}`"
type=
"success"
:closable=
"false"
/>
<el-alert
:title=
"`已选择文件: ${selectedFile.name}`"
type=
"success"
:closable=
"false"
/>
</div>
<!-- Excel列选择 -->
<div
v-if=
"excelColumns.length > 0"
style=
"margin-top: 20px;"
>
<h4
style=
"margin-bottom: 15px; color: #333;"
>
配置Excel列映射
</h4>
<el-row
:gutter=
"20"
>
<el-col
:span=
"12"
>
<el-form-item
label=
"文件名列(可多选,用'-'连接)"
>
<el-select
v-model=
"columnMapping.fileNameColumns"
multiple
placeholder=
"选择文件名列"
style=
"width: 100%"
@
change=
"handleColumnMappingChange"
>
<el-option
v-for=
"column in excelColumns"
:key=
"column"
:label=
"column"
:value=
"column"
/>
<el-select
v-model=
"columnMapping.fileNameColumns"
multiple
placeholder=
"选择文件名列"
style=
"width: 100%"
@
change=
"handleColumnMappingChange"
>
<el-option
v-for=
"column in excelColumns"
:key=
"column"
:label=
"column"
:value=
"column"
/>
</el-select>
</el-form-item>
</el-col>
<el-col
:span=
"12"
>
<el-form-item
label=
"URL列"
>
<el-select
v-model=
"columnMapping.url"
placeholder=
"选择URL列"
style=
"width: 100%"
@
change=
"handleColumnMappingChange"
>
<el-option
v-for=
"column in excelColumns"
:key=
"column"
:label=
"column"
:value=
"column"
/>
<el-select
v-model=
"columnMapping.url"
placeholder=
"选择URL列"
style=
"width: 100%"
@
change=
"handleColumnMappingChange"
>
<el-option
v-for=
"column in excelColumns"
:key=
"column"
:label=
"column"
:value=
"column"
/>
</el-select>
</el-form-item>
</el-col>
</el-row>
<!-- 预览数据 -->
<div
v-if=
"previewData.length > 0"
style=
"margin-top: 15px;"
>
<h5
style=
"margin-bottom: 10px; color: #666;"
>
数据预览 (前5行) - 总计: {{ rawExcelData.length }} 行
</h5>
<el-table
:data=
"previewData"
size=
"small"
border
style=
"width: 100%"
>
<el-table-column
prop=
"fileName"
label=
"文件名"
/>
<el-table-column
prop=
"url"
label=
"URL"
show-overflow-tooltip
/>
</el-table>
<div
v-if=
"rawExcelData.length > 1000"
style=
"margin-top: 10px;"
>
<el-alert
title=
"文件较大提示"
type=
"warning"
:closable=
"false"
show-icon
>
<
template
#
default
>
<div
style=
"font-size: 12px; line-height: 1.5;"
>
<p>
• 当前文件包含
{{
rawExcelData
.
length
}}
行数据
</p>
<p>
• 已优化:只保留
{{
Object
.
keys
(
rawExcelData
[
0
]
||
{
}
).
length
}}
个必要字段
<
/p
>
<
p
>
•
大文件处理可能需要较长时间,请耐心等待
<
/p
>
<
p
>
•
建议分批处理或使用较小的
Excel
文件
<
/p
>
<
/div
>
<
/template
>
<
/el-alert
>
<
/div
>
<
/div
>
<!-- 预览数据 -->
<div
v-if=
"previewData.length > 0"
style=
"margin-top: 15px;"
>
<h5
style=
"margin-bottom: 10px; color: #666;"
>
数据预览 (前5行) - 总计: {{ rawExcelData.length }} 行
</h5>
<el-table
:data=
"previewData"
size=
"small"
border
style=
"width: 100%"
>
<el-table-column
prop=
"fileName"
label=
"文件名"
/>
<el-table-column
prop=
"url"
label=
"URL"
show-overflow-tooltip
/>
</el-table>
<div
v-if=
"rawExcelData.length > 1000"
style=
"margin-top: 10px;"
>
<el-alert
title=
"文件较大提示"
type=
"warning"
:closable=
"false"
show-icon
>
<
template
#
default
>
<div
style=
"font-size: 12px; line-height: 1.5;"
>
<p>
• 当前文件包含
{{
rawExcelData
.
length
}}
行数据
</p>
<p>
• 已优化:只保留
{{
Object
.
keys
(
rawExcelData
[
0
]
||
{
}
).
length
}}
个必要字段
<
/p
>
<
p
>
•
大文件处理可能需要较长时间,请耐心等待
<
/p
>
<
p
>
•
建议分批处理或使用较小的
Excel
文件
<
/p
>
<
/div
>
<
/template
>
<
/el-alert
>
<
/div
>
<
/div
>
<
/div
>
<!--
下载路径设置
-->
<
div
style
=
"margin-top: 20px;"
>
<
h4
style
=
"margin-bottom: 15px; color: #333;"
>
下载设置
<
/h4
>
<!--
操作系统信息
-->
<
div
style
=
"margin-bottom: 15px;"
>
<
el
-
alert
:
title
=
"`当前操作系统: ${downloadStore.getOperatingSystem() === 'windows' ? 'Windows' : downloadStore.getOperatingSystem() === 'mac' ? 'macOS' : downloadStore.getOperatingSystem() === 'linux' ? 'Linux' : '未知'
}
`"
type
=
"info"
:
closable
=
"false"
show
-
icon
/>
type
=
"info"
:
closable
=
"false"
show
-
icon
/>
<
/div
>
<!--
浏览器默认下载路径显示
-->
<
div
style
=
"margin-bottom: 15px;"
>
<
el
-
alert
:
title
=
"`默认下载路径: ${downloadStore.getDefaultDownloadPath()
}
`"
type
=
"info"
:
closable
=
"false"
show
-
icon
/>
<
el
-
alert
:
title
=
"`默认下载路径: ${downloadStore.getDefaultDownloadPath()
}
`"
type
=
"info"
:
closable
=
"false"
show
-
icon
/>
<
/div
>
<
el
-
row
:
gutter
=
"20"
>
<
el
-
col
:
span
=
"16"
>
<
el
-
form
-
item
label
=
"文件名前缀"
>
<
el
-
input
v
-
model
=
"customSubFolder"
placeholder
=
"例如: vue-downloader (留空则不添加前缀)"
@
input
=
"updateDownloadPath"
/>
<
el
-
input
v
-
model
=
"customSubFolder"
placeholder
=
"例如: vue-downloader (留空则不添加前缀)"
@
input
=
"updateDownloadPath"
/>
<
/el-form-item
>
<
/el-col
>
<
el
-
col
:
span
=
"8"
>
...
...
@@ -190,15 +161,10 @@
<
/el-button
>
<
/el-col
>
<
/el-row
>
<!--
文件名前缀说明
-->
<
div
v
-
if
=
"customSubFolder && customSubFolder.trim()"
style
=
"margin-top: 10px;"
>
<
el
-
alert
title
=
"文件名前缀说明"
type
=
"success"
:
closable
=
"false"
show
-
icon
>
<
el
-
alert
title
=
"文件名前缀说明"
type
=
"success"
:
closable
=
"false"
show
-
icon
>
<
template
#
default
>
<
div
style
=
"font-size: 12px; line-height: 1.5;"
>
<
p
><
strong
>
示例:
<
/strong></
p
>
...
...
@@ -209,48 +175,36 @@
<
/template
>
<
/el-alert
>
<
/div
>
<!--
通知设置
-->
<
div
style
=
"margin-top: 15px;"
>
<
el
-
form
-
item
label
=
"下载完成通知"
>
<
el
-
switch
v
-
model
=
"downloadStore.enableNotifications"
@
change
=
"downloadStore.setEnableNotifications"
active
-
text
=
"启用"
inactive
-
text
=
"禁用"
/>
<
el
-
switch
v
-
model
=
"downloadStore.enableNotifications"
@
change
=
"downloadStore.setEnableNotifications"
active
-
text
=
"启用"
inactive
-
text
=
"禁用"
/>
<
span
style
=
"margin-left: 10px; color: #666; font-size: 12px;"
>
下载全部完成后在右下角显示通知
<
/span
>
<
/el-form-item
>
<
/div
>
<
/div
>
<
div
style
=
"margin-top: 20px;"
>
<
el
-
button
type
=
"success"
size
=
"large"
:
disabled
=
"!canParse"
@
click
=
"parseExcelFile"
:
loading
=
"parsing"
>
<
el
-
icon
><
Document
/><
/el-icon
>
{{
parsing
?
'解析中...'
:
'解析并添加下载任务'
}}
<
/el-button
>
<!--
处理进度显示
-->
<
div
v
-
if
=
"parsing && processingMessage"
style
=
"margin-top: 15px;"
>
<
el
-
progress
:
percentage
=
"processingProgress"
:
format
=
"(percentage) => `${percentage
}
%`"
:
stroke
-
width
=
"8"
status
=
"success"
/>
<
div
style
=
"margin-top: 8px; color: #666; font-size: 14px;"
>
{{
processingMessage
}}
<
/div
>
<
/div
>
<
/div
>
<
div
style
=
"margin-top: 20px;"
>
<
el
-
button
type
=
"success"
size
=
"large"
:
disabled
=
"!canParse"
@
click
=
"parseExcelFile"
:
loading
=
"parsing"
>
<
el
-
icon
>
<
Document
/>
<
/el-icon
>
{{
parsing
?
'解析中...'
:
'解析并添加下载任务'
}}
<
/el-button
>
<!--
处理进度显示
-->
<
div
v
-
if
=
"parsing && processingMessage"
style
=
"margin-top: 15px;"
>
<
el
-
progress
:
percentage
=
"processingProgress"
:
format
=
"(percentage) => `${percentage
}
%`"
:
stroke
-
width
=
"8"
status
=
"success"
/>
<
div
style
=
"margin-top: 8px; color: #666; font-size: 14px;"
>
{{
processingMessage
}}
<
/div
>
<
/div
>
<
/div
>
<
/div
>
<
/el-collapse-item
>
<
/el-collapse
>
...
...
@@ -260,117 +214,131 @@
<
div
class
=
"download-header"
>
<
div
class
=
"download-title"
>
<
h2
style
=
"color: #333; margin: 0;"
>
<
el
-
icon
><
List
/><
/el-icon
>
<
el
-
icon
>
<
List
/>
<
/el-icon
>
下载任务管理
<
/h2
>
<
div
class
=
"download-stats"
>
<
el
-
tag
type
=
"info"
>
总计
:
{{
downloadStore
.
downloads
.
length
}}
<
/el-tag
>
<
!--
<
el
-
tag
type
=
"info"
>
总计
:
{{
downloadStore
.
downloads
.
length
}}
<
/el-tag
>
<
el
-
tag
type
=
"primary"
>
等待
:
{{
downloadStore
.
pendingDownloads
.
length
}}
<
/el-tag
>
<
el
-
tag
type
=
"success"
>
下载中
:
{{
downloadStore
.
activeDownloads
.
length
}}
<
/el-tag
>
<
el
-
tag
type
=
"warning"
>
已完成
:
{{
downloadStore
.
completedDownloads
.
length
}}
<
/el-tag
>
<
el
-
tag
type
=
"danger"
>
失败
:
{{
downloadStore
.
failedDownloads
.
length
}}
<
/el-tag
>
<
el
-
tag
type
=
"danger"
>
失败
:
{{
downloadStore
.
failedDownloads
.
length
}}
<
/el-tag> --
>
<
el
-
tag
type
=
"info"
>
总计
:
{{
totalNumObj
.
countNum
}}
<
/el-tag
>
<
el
-
tag
type
=
"primary"
>
等待
:
{{
totalNumObj
.
status0
}}
<
/el-tag
>
<
el
-
tag
type
=
"success"
>
下载中
:
{{
totalNumObj
.
status1
}}
<
/el-tag
>
<
el
-
tag
type
=
"warning"
>
已完成
:
{{
totalNumObj
.
status3
}}
<
/el-tag
>
<
el
-
tag
type
=
"danger"
>
失败
:
{{
totalNumObj
.
status4
}}
<
/el-tag
>
<
/div
>
<
/div
>
<
div
class
=
"download-actions"
>
<!--
状态筛选
-->
<
el
-
select
v
-
model
=
"statusFilter"
placeholder
=
"按状态筛选"
clearable
style
=
"width: 150px; margin-right: 10px;"
@
change
=
"handleStatusFilterChange"
>
<
el
-
option
label
=
"全部"
value
=
""
/>
<
el
-
option
label
=
"等待中"
value
=
"pending"
/>
<
el
-
option
label
=
"下载中"
value
=
"downloading"
/>
<
el
-
option
label
=
"已暂停"
value
=
"paused"
/>
<
el
-
option
label
=
"已完成"
value
=
"completed"
/>
<
el
-
option
label
=
"失败"
value
=
"error"
/>
<
/el-select
>
<!--
页面级别操作
-->
<
el
-
button
@
click
=
"downloadCurrentPage"
type
=
"info"
size
=
"small"
>
下载当前页
<
/el-button
>
<
el
-
button
@
click
=
"pauseCurrentPage"
type
=
"warning"
size
=
"small"
>
暂停当前页
<
/el-button
>
<
el
-
divider
direction
=
"vertical"
/>
<
el
-
button
@
click
=
"downloadStore.clearCompleted"
size
=
"small"
>
清除已完成
<
/el-button
>
<
el
-
button
@
click
=
"downloadStore.clearAll"
type
=
"danger"
size
=
"small"
>
清除所有
<
/el-button
>
<
/div
>
<
div
class
=
"download-actions"
>
<!--
状态筛选
-->
<!--
<
el
-
select
v
-
model
=
"statusFilter"
placeholder
=
"按状态筛选"
clearable
style
=
"width: 150px; margin-right: 10px;"
@
change
=
"handleStatusFilterChange"
>
<
el
-
option
label
=
"全部"
value
=
""
/>
<
el
-
option
label
=
"等待中"
value
=
"pending"
/>
<
el
-
option
label
=
"下载中"
value
=
"downloading"
/>
<
el
-
option
label
=
"已暂停"
value
=
"paused"
/>
<
el
-
option
label
=
"已完成"
value
=
"completed"
/>
<
el
-
option
label
=
"失败"
value
=
"error"
/>
<
/el-select> --
>
<
el
-
select
v
-
model
=
"deQuery.fileStatus"
placeholder
=
"按状态筛选"
clearable
style
=
"width: 150px; margin-right: 10px;"
@
change
=
"handleStatusFilterChange"
>
<
el
-
option
label
=
"全部"
value
=
""
/>
<
el
-
option
label
=
"等待中"
:
value
=
"0"
/>
<
el
-
option
label
=
"下载中"
:
value
=
"1"
/>
<
el
-
option
label
=
"已暂停"
:
value
=
"2"
/>
<
el
-
option
label
=
"已完成"
:
value
=
"3"
/>
<
el
-
option
label
=
"失败"
:
value
=
"4"
/>
<
/el-select
>
<!--
页面级别操作
-->
<
el
-
button
@
click
=
"downloadCurrentPage"
type
=
"info"
size
=
"small"
>
下载当前页
<
/el-button
>
<
el
-
button
@
click
=
"pauseCurrentPage"
type
=
"warning"
size
=
"small"
>
暂停当前页
<
/el-button
>
<
el
-
divider
direction
=
"vertical"
/>
<!--
<
el
-
button
@
click
=
"downloadStore.clearCompleted"
size
=
"small"
>
清除已完成
<
/el-button> --
>
<
el
-
button
@
click
=
"clearCompleted"
size
=
"small"
>
清除已完成
<
/el-button
>
<!--
<
el
-
button
@
click
=
"downloadStore.clearAll"
type
=
"danger"
size
=
"small"
>
清除所有
<
/el-button> --
>
<
el
-
button
@
click
=
"clearAll"
type
=
"danger"
size
=
"small"
>
清除所有
<
/el-button
>
<
/div
>
<
/div
>
<!--
下载任务列表
-->
<
div
v
-
if
=
"downloadStore.downloads.length === 0"
class
=
"empty-state"
>
<
el
-
icon
size
=
"64"
><
Document
/><
/el-icon
>
<
el
-
icon
size
=
"64"
>
<
Document
/>
<
/el-icon
>
<
h3
>
暂无下载任务
<
/h3
>
<
p
>
请在上方配置区域上传
Excel
文件并解析添加下载任务
<
/p
>
<
/div
>
<
div
v
-
else
class
=
"download-table-container"
>
<!--
分页表格
-->
<
el
-
table
:
data
=
"paginatedDownloads"
style
=
"width: 100%"
border
stripe
highlight
-
current
-
row
>
<
el
-
table
-
column
label
=
"Excel文件"
width
=
"180"
>
<
template
#
default
=
"{ row
}
"
>
<
div
class
=
"excel-file-info"
>
<
el
-
icon
><
Document
/><
/el-icon
>
<
span
class
=
"excel-file-name"
>
{{
row
.
excelFileName
||
'-'
}}
<
/span
>
<
/div
>
<
/template
>
<
/el-table-column
>
<
el
-
table
-
column
label
=
"文件名"
min
-
width
=
"250"
>
<
template
#
default
=
"{ row
}
"
>
<
div
class
=
"file-info"
>
<
div
class
=
"file-icon"
>
<
el
-
icon
><
Document
/><
/el-icon
>
<
/div
>
<
div
class
=
"file-details"
>
<
div
class
=
"file-name"
>
<
span
v
-
if
=
"downloadStore.customSubFolder"
class
=
"file-prefix"
>
{{
downloadStore
.
customSubFolder
}}
_
<
/span
>
{{
row
.
fileName
}}
<
/div
>
<
div
class
=
"file-url"
>
{{
row
.
url
}}
<
/div
>
<
/div
>
<
/div
>
<
/template
>
<
/el-table-column
>
<!--
:
data
=
"paginatedDownloads"
-->
<
el
-
table
:
data
=
"deTableData"
style
=
"width: 100%"
border
stripe
highlight
-
current
-
row
>
<
el
-
table
-
column
label
=
"Excel文件"
width
=
"180"
>
<
template
#
default
=
"{ row
}
"
>
<
div
class
=
"excel-file-info"
>
<
el
-
icon
>
<
Document
/>
<
/el-icon
>
<
span
class
=
"excel-file-name"
>
{{
row
.
uploadName
||
'-'
}}
<
/span
>
<
/div
>
<
/template
>
<
/el-table-column
>
<
el
-
table
-
column
label
=
"文件名"
min
-
width
=
"250"
>
<
template
#
default
=
"{ row
}
"
>
<
div
class
=
"file-info"
>
<
div
class
=
"file-icon"
>
<
el
-
icon
>
<
Document
/>
<
/el-icon
>
<
/div
>
<
div
class
=
"file-details"
>
<
div
class
=
"file-name"
>
<!--
<
span
v
-
if
=
"downloadStore.customSubFolder"
class
=
"file-prefix"
>
{{
downloadStore
.
customSubFolder
}}
_
<
/span> --
>
<
span
v
-
if
=
"row.fileName"
class
=
"file-prefix"
>
{{
row
.
fileName
}}
_
<
/span
>
{{
row
.
fileUrl
}}
<
/div
>
<
div
class
=
"file-url"
>
{{
row
.
url
}}
<
/div
>
<
/div
>
<
/div
>
<
/template
>
<
/el-table-column
>
<
el
-
table
-
column
label
=
"状态"
width
=
"100"
align
=
"center"
>
<
template
#
default
=
"{ row
}
"
>
<
el
-
tag
:
class
=
"`status-badge status-${row.status
}
`"
size
=
"small"
>
{{
getStatusText
(
row
.
status
)
}}
<
el
-
tag
:
class
=
"`status-badge status-${row.status
}
`"
size
=
"small"
>
{{
getStatusText
(
row
.
fileStatus
)
}}
<
/el-tag
>
<
/template
>
<
/el-table-column
>
<
el
-
table
-
column
label
=
"进度"
width
=
"200"
align
=
"center"
>
<
!--
<
el
-
table
-
column
label
=
"进度"
width
=
"200"
align
=
"center"
>
<
template
#
default
=
"{ row
}
"
>
<
div
v
-
if
=
"row.status === 'downloading'"
class
=
"progress-container"
>
<
el
-
progress
:
percentage
=
"row.progress"
:
format
=
"(percentage) => `${percentage
}
%`"
size
=
"small"
:
stroke
-
width
=
"8"
/>
<
el
-
progress
:
percentage
=
"row.progress"
:
format
=
"(percentage) => `${percentage
}
%`"
size
=
"small"
:
stroke
-
width
=
"8"
/>
<
div
class
=
"speed-info"
>
{{
formatSpeed
(
row
.
speed
)
}}
<
/div
>
...
...
@@ -382,79 +350,57 @@
{{
row
.
progress
}}
%
<
/span
>
<
/template
>
<
/el-table-column
>
<
el
-
table
-
column
label
=
"大小"
width
=
"120"
align
=
"center"
>
<
template
#
default
=
"{ row
}
"
>
<
/el-table-column>
--
>
<
el
-
table
-
column
label
=
"大小"
width
=
"120"
align
=
"center"
prop
=
"fileLength"
>
<
!--
<
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"
>
<
template
#
default
=
"{ row
}
"
>
<
div
class
=
"download-actions"
>
<
el
-
button
v
-
if
=
"row.status === 'pending'"
@
click
=
"startSingleDownload(row.id)"
type
=
"primary"
size
=
"small"
>
<
el
-
button
v
-
if
=
"row.status === 'pending'"
@
click
=
"startSingleDownload(row.id)"
type
=
"primary"
size
=
"small"
>
开始
<
/el-button
>
<
el
-
button
v
-
if
=
"row.status === 'downloading'"
@
click
=
"downloadStore.pauseDownload(row.id)"
type
=
"warning"
size
=
"small"
>
<
el
-
button
v
-
if
=
"row.status === 'downloading'"
@
click
=
"downloadStore.pauseDownload(row.id)"
type
=
"warning"
size
=
"small"
>
暂停
<
/el-button
>
<
el
-
button
v
-
if
=
"row.status === 'paused'"
@
click
=
"resumeSingleDownload(row.id)"
type
=
"success"
size
=
"small"
>
<
el
-
button
v
-
if
=
"row.status === 'paused'"
@
click
=
"resumeSingleDownload(row.id)"
type
=
"success"
size
=
"small"
>
继续
<
/el-button
>
<
el
-
button
v
-
if
=
"row.status === 'error'"
@
click
=
"startSingleDownload(row.id)"
type
=
"warning"
size
=
"small"
>
<
el
-
button
v
-
if
=
"row.status === 'error'"
@
click
=
"startSingleDownload(row.id)"
type
=
"warning"
size
=
"small"
>
重试
<
/el-button
>
<
el
-
button
@
click
=
"downloadStore.cancelDownload(row.id)"
type
=
"danger"
size
=
"small"
>
<!--
<
el
-
button
@
click
=
"downloadStore.cancelDownload(row.id)"
type
=
"danger"
size
=
"small"
>
删除
<
/el-button> --
>
<
el
-
button
@
click
=
"deleteRow(row)"
type
=
"danger"
size
=
"small"
>
删除
<
/el-button
>
<
/div
>
<
/template
>
<
/el-table-column
>
<
/el-table
>
<!--
分页器
-->
<
div
class
=
"pagination-container"
>
<
el
-
pagination
v
-
model
:
current
-
page
=
"currentPage"
v
-
model
:
page
-
size
=
"pageSize"
:
page
-
sizes
=
"[10, 20, 50, 100]"
:
total
=
"filteredDownloads.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
>
<
/div
>
<
/div
>
...
...
@@ -463,13 +409,17 @@
<
/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
{
Download
,
Upload
,
Document
,
List
,
Warning
,
User
,
ArrowDown
}
from
'@element-plus/icons-vue'
import
{
useAuthStore
}
from
'../stores/auth'
import
{
useDownloadStore
}
from
'../stores/download'
import
*
as
XLSX
from
'xlsx'
import
http
from
'@/utils/request.js'
;
import
config
from
'@/api/api.js'
;
import
axios
from
"axios"
;
const
router
=
useRouter
()
const
authStore
=
useAuthStore
()
...
...
@@ -489,6 +439,21 @@ const activeCollapse = ref(['upload']) // 默认展开配置区域
// 分页相关
const
currentPage
=
ref
(
1
)
const
pageSize
=
ref
(
10
)
// 已上传文件列表
const
upQuery
=
ref
({
current
:
1
,
pageSize
:
5
}
)
let
upTableData
=
reactive
([])
let
upTotal
=
ref
(
0
)
// 下载任务管理列表
const
deQuery
=
ref
({
current
:
1
,
pageSize
:
10
,
fileStatus
:
''
}
)
let
deTableData
=
reactive
([])
let
deTotal
=
ref
(
0
)
const
columnMapping
=
ref
({
fileNameColumns
:
[],
...
...
@@ -500,13 +465,26 @@ const statusFilter = ref('')
// 计算是否可以解析
const
canParse
=
computed
(()
=>
{
return
selectedFile
.
value
&&
columnMapping
.
value
.
fileNameColumns
.
length
>
0
&&
columnMapping
.
value
.
url
&&
previewData
.
value
.
length
>
0
return
selectedFile
.
value
&&
columnMapping
.
value
.
fileNameColumns
.
length
>
0
&&
columnMapping
.
value
.
url
&&
previewData
.
value
.
length
>
0
}
)
// 已上传 Excel 文件列表数据加载
const
loadUpTableData
=
()
=>
{
http
.
post
(
config
.
uploadBatchList
,
upQuery
.
value
).
then
(
res
=>
{
if
(
res
.
code
===
200
)
{
for
(
let
element
of
res
.
data
.
rows
)
{
element
.
createTime
=
element
.
createTime
?
element
.
createTime
.
substring
(
0
,
10
)
:
''
}
upTableData
=
res
.
data
.
rows
upTotal
.
value
=
res
.
data
.
total
}
else
{
ElMessage
.
error
(
res
.
message
)
}
}
)
}
// 计算分页后的下载列表
const
paginatedDownloads
=
computed
(()
=>
{
...
...
@@ -527,35 +505,58 @@ const filteredDownloads = computed(() => {
// 选择Excel文件
const
selectExcelFile
=
async
(
fileId
)
=>
{
try
{
const
fileInfo
=
downloadStore
.
getExcelFileInfo
(
fileId
)
if
(
!
fileInfo
)
{
ElMessage
.
error
(
'文件信息不存在'
)
return
}
downloadStore
.
setCurrentExcelFile
(
fileId
)
// 加载第一页数据
const
pageData
=
await
downloadStore
.
loadExcelDataPage
(
fileId
,
1
,
10
)
rawExcelData
.
value
=
pageData
.
data
// 设置列映射
columnMapping
.
value
=
fileInfo
.
columnMapping
||
{
fileNameColumns
:
[],
url
:
''
}
// 设置列名
if
(
pageData
.
data
.
length
>
0
)
{
excelColumns
.
value
=
Object
.
keys
(
pageData
.
data
[
0
])
// try
{
// const fileInfo = downloadStore.getExcelFileInfo(fileId)
// if (!fileInfo)
{
// ElMessage.error('文件信息不存在')
// return
//
}
// downloadStore.setCurrentExcelFile(fileId)
// // 加载第一页数据
// const pageData = await downloadStore.loadExcelDataPage(fileId, 1, 10)
// rawExcelData.value = pageData.data
// // 设置列映射
// columnMapping.value = fileInfo.columnMapping ||
{
fileNameColumns
:
[],
url
:
''
}
// // 设置列名
// if (pageData.data.length > 0)
{
// excelColumns.value = Object.keys(pageData.data[0])
//
}
// // 生成预览数据
// generatePreviewData()
// ElMessage.success(`已选择文件: $
{
fileInfo
.
fileName
}
`)
//
}
catch (error) {
// console.error('选择Excel文件失败:', error)
// ElMessage.error('选择Excel文件失败: ' + error.message)
//
}
http.post(config.uploadBatchQueryUpload, {batchId:fileId
}
).then(res => {
if (res.code === 200) {
if (!res.data || res.data.details.length === 0) {
ElMessage.error('文件信息不存在')
return
}
selectedFile.value = {
name:res.data.uploadName,
}
excelColumns.value = res.data.fileColumnNameAll || []
columnMapping.value.fileNameColumns = res.data.fileColumnName || []
columnMapping.value.url = res.data.urlColumnName
rawExcelData.value = res.data.details || []
if (res.data.details.length>5){
previewData.value = res.data.details.slice(0,5)
}
else {
previewData.value = res.data.details || []
}
customSubFolder.value = res.data.filePrefix || ''
}
else {
ElMessage.error(res.message)
}
// 生成预览数据
generatePreviewData
()
ElMessage
.
success
(
`已选择文件: ${fileInfo.fileName
}
`
)
}
catch
(
error
)
{
console
.
error
(
'选择Excel文件失败:'
,
error
)
ElMessage
.
error
(
'选择Excel文件失败: '
+
error
.
message
)
}
}
)
}
// 删除Excel文件
...
...
@@ -565,20 +566,28 @@ const deleteExcelFile = async (fileId) => {
if (!confirmed) {
return
}
try
{
await
downloadStore
.
deleteExcelFile
(
fileId
)
ElMessage
.
success
(
'Excel文件已删除'
)
}
catch
(
error
)
{
console
.
error
(
'删除Excel文件失败:'
,
error
)
ElMessage
.
error
(
'删除Excel文件失败: '
+
(
error
.
message
||
'未知错误'
))
}
// try {
// await downloadStore.deleteExcelFile(fileId)
// ElMessage.success('Excel文件已删除')
//
}
catch (error) {
// console.error('删除Excel文件失败:', error)
// ElMessage.error('删除Excel文件失败: ' + (error.message || '未知错误'))
//
}
http.post(config.uploadBatchDelete, { batchId: fileId
}
).then(res => {
if (res.code === 200) {
ElMessage.success('Excel文件已删除')
loadUpTableData()
}
else {
ElMessage.error(res.message)
}
}
)
}
// 处理文件选择
const handleFileChange = async (file) => {
selectedFile.value = file.raw
try {
// 检查文件大小,超过5MB时显示警告
if (file.raw.size > 5 * 1024 * 1024) {
...
...
@@ -592,28 +601,28 @@ const handleFileChange = async (file) => {
}
)
}
// 显示加载提示
ElMessage.info('正在读取Excel文件,请稍候...')
// 先读取完整数据以获取列名
const fullData = await readExcelFileOptimized(file.raw)
if (fullData.length > 0) {
// 保存原始完整数据用于后续优化
window.originalExcelData = fullData
// 获取所有列名并保存原始列名
const allColumns = Object.keys(fullData[0])
excelColumns.value = allColumns
window.originalColumns = allColumns
// 尝试自动匹配列名
autoMatchColumns()
// 生成预览数据
generatePreviewData()
// 如果自动匹配成功,则优化数据
if (columnMapping.value.fileNameColumns.length > 0 && columnMapping.value.url) {
const optimizedData = optimizeExcelData(fullData)
...
...
@@ -633,11 +642,11 @@ const handleFileChange = async (file) => {
// 自动匹配列名
const autoMatchColumns = () => {
const columns = excelColumns.value
// 尝试匹配文件名列
const fileNamePatterns = ['fileName', 'filename', 'name', 'file', '文件名', '文件名称']
for (const pattern of fileNamePatterns) {
const
match
=
columns
.
find
(
col
=>
const match = columns.find(col =>
col.toLowerCase().includes(pattern.toLowerCase())
)
if (match) {
...
...
@@ -645,11 +654,11 @@ const autoMatchColumns = () => {
break
}
}
// 尝试匹配URL列
const urlPatterns = ['url', 'link', '地址', '链接', '下载地址']
for (const pattern of urlPatterns) {
const
match
=
columns
.
find
(
col
=>
const match = columns.find(col =>
col.toLowerCase().includes(pattern.toLowerCase())
)
if (match) {
...
...
@@ -657,7 +666,7 @@ const autoMatchColumns = () => {
break
}
}
// 自动优化数据
if (columnMapping.value.fileNameColumns.length > 0 && columnMapping.value.url) {
optimizeDataAfterColumnMapping()
...
...
@@ -667,22 +676,22 @@ const autoMatchColumns = () => {
// 列映射配置后优化数据
const optimizeDataAfterColumnMapping = () => {
if (!rawExcelData.value.length) return
// 获取原始完整数据(如果存在)
const originalData = window.originalExcelData || rawExcelData.value
// 优化数据:只保留需要的字段
const optimizedData = optimizeExcelData(originalData)
rawExcelData.value = optimizedData
// 确保excelColumns保持原始的所有列名,用于列选择
if (window.originalColumns) {
excelColumns.value = window.originalColumns
}
// 重新生成预览数据
generatePreviewData()
console.log(`
数据优化完成:从
$
{
Object
.
keys
(
originalData
[
0
]
||
{
}
).
length
}
个字段优化到
$
{
Object
.
keys
(
optimizedData
[
0
]
||
{
}
).
length
}
个字段
`)
}
...
...
@@ -692,25 +701,25 @@ const generatePreviewData = () => {
previewData.value = []
return
}
const preview = rawExcelData.value.slice(0, 5).map(row => {
// 组合多个文件名列
const fileNameParts = columnMapping.value.fileNameColumns.map(col => row[col] || '').filter(part => part)
let fileName = fileNameParts.join('-')
// 从URL中提取扩展名
const url = row[columnMapping.value.url] || ''
const extension = getFileExtensionFromUrl(url)
if (extension) {
fileName += extension
}
return {
fileName: fileName,
url: url
}
}
).filter(item => item.fileName && item.url)
previewData.value = preview
}
...
...
@@ -718,7 +727,7 @@ const generatePreviewData = () => {
const handleColumnMappingChange = () => {
// 生成预览数据
generatePreviewData()
// 如果列映射配置完整,则优化数据
if (columnMapping.value.fileNameColumns.length > 0 && columnMapping.value.url) {
optimizeDataAfterColumnMapping()
...
...
@@ -730,9 +739,9 @@ const optimizeExcelData = (data) => {
if (!columnMapping.value.fileNameColumns.length || !columnMapping.value.url) {
return []
}
const neededColumns = [...columnMapping.value.fileNameColumns, columnMapping.value.url]
return data.map(row => {
const optimizedRow = {
}
neededColumns.forEach(col => {
...
...
@@ -748,10 +757,10 @@ const getFileExtensionFromUrl = (url) => {
// 解析URL
const urlObj = new URL(url)
const pathname = urlObj.pathname
// 从路径中提取文件名
const fileName = pathname.split('/').pop()
// 检查文件名是否包含扩展名
if (fileName && fileName.includes('.')) {
const extension = fileName.substring(fileName.lastIndexOf('.'))
...
...
@@ -760,7 +769,7 @@ const getFileExtensionFromUrl = (url) => {
return extension
}
}
// 如果没有找到有效的扩展名,返回.tmp
return '.tmp'
}
catch (error) {
...
...
@@ -808,15 +817,95 @@ const updateDownloadPath = () => {
onMounted(() => {
// 先初始化下载store
downloadStore.initialize()
// 从下载store中同步文件名前缀
customSubFolder.value = downloadStore.customSubFolder || ''
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
}
else {
ElMessage.error(res.message)
}
}
)
}
const totalNumObj = reactive({
countNum:0,
status0: 0,
status1: 0,
status2: 0,
status3: 0,
status4: 0,
}
)
function loadUploadDetailTotalNum(){
http.post(config.uploadDetailTotalNum).then(res=>{
if (res.code === 200){
}
else {
ElMessage.error(res.message)
}
}
)
}
// 清除已完成
function clearCompleted() {
http.post(config.uploadDetailDeleteAll,{
type:0
}
).then(res=>{
if (res.code === 200){
ElMessage.success(res.message)
deQuery.value.current = 1
// loadUploadDetailList()
loadDetailTableData()
}
else {
ElMessage.error(res.message)
}
}
)
}
// 清除所有
function clearAll() {
http.post(config.uploadDetailDeleteAll,{
type:1
}
).then(res=>{
if (res.code === 200){
ElMessage.success(res.message)
deQuery.value.current = 1
// loadUploadDetailList()
loadDetailTableData()
}
else {
ElMessage.error(res.message)
}
}
)
}
// 删除单行
function deleteRow(row) {
http.post(config.uploadDetailDelete,{
detailId:row.detailId
}
).then(res=>{
if (res.code === 200){
ElMessage.success(res.message)
deQuery.value.current = 1
// loadUploadDetailList()
loadDetailTableData()
}
else {
ElMessage.error(res.message)
}
}
)
}
// 解析Excel文件
const parseExcelFile = async () => {
if (rawExcelData.value.length > 8000){
ElMessage.error('文件数据量太大,请拆分处理,每次不超过3000条')
return
}
if (!canParse.value) {
ElMessage.warning('请先选择Excel文件并配置列映射')
return
...
...
@@ -825,52 +914,51 @@ const parseExcelFile = async () => {
parsing.value = true
processingProgress.value = 0
processingMessage.value = '正在处理Excel数据...'
try {
// 显示处理进度
ElMessage.info('正在处理Excel数据,请稍候...')
// 使用setTimeout来避免阻塞UI
await new Promise(resolve => setTimeout(resolve, 100))
// 获取完整数据(从原始数据或当前数据)
const allData = window.originalExcelData || rawExcelData.value
// 过滤有效数据(分批处理)
const batchSize = 1000 // 每批处理1000行
const validData = []
const totalBatches = Math.ceil(allData.length / batchSize)
for (let i = 0; i < allData.length; i += batchSize) {
const batchIndex = Math.floor(i / batchSize) + 1
const batch = allData.slice(i, i + batchSize)
// 更新进度
processingProgress.value = Math.round((batchIndex / totalBatches) * 30) // 前30%用于数据过滤
processingMessage.value = `
正在过滤数据
...
(
$
{
batchIndex
}
/
$
{
totalBatches
}
)
`
const batchValidData = batch.filter(row => {
const fileNameParts = columnMapping.value.fileNameColumns.map(col => row[col] || '').filter(part => part)
let fileName = fileNameParts.join('-')
const url = row[columnMapping.value.url]
// 从URL中提取扩展名
const extension = getFileExtensionFromUrl(url)
if (extension) {
fileName += extension
}
return fileName && url
}
)
validData.push(...batchValidData)
// 每处理一批数据后让出控制权,避免阻塞UI
if (i + batchSize < allData.length) {
await new Promise(resolve => setTimeout(resolve, 10))
}
}
if (validData.length === 0) {
ElMessage.warning('Excel文件中没有找到有效数据')
return
...
...
@@ -879,10 +967,10 @@ const parseExcelFile = async () => {
// 保存Excel文件到本地存储
processingProgress.value = 30
processingMessage.value = '正在保存Excel文件到本地...'
const excelFileInfo = await downloadStore.saveExcelFileToLocal(
selectedFile
.
value
,
allData
,
selectedFile.value,
allData,
columnMapping.value
)
...
...
@@ -896,6 +984,8 @@ const parseExcelFile = async () => {
type: 'info'
}
)
// 上传后端
uploadDetails()
// 临时禁用自动下载
const originalBatchDownloading = downloadStore.isBatchDownloading
...
...
@@ -904,29 +994,29 @@ const parseExcelFile = async () => {
// 分批添加下载任务
const addBatchSize = 500 // 每批添加500个任务
const totalAddBatches = Math.ceil(validData.length / addBatchSize)
for (let i = 0; i < validData.length; i += addBatchSize) {
const batchIndex = Math.floor(i / addBatchSize) + 1
const batch = validData.slice(i, i + addBatchSize)
// 更新进度
processingProgress.value = 30 + Math.round((batchIndex / totalAddBatches) * 70) // 后70%用于添加任务
processingMessage.value = `
正在添加下载任务
...
(
$
{
batchIndex
}
/
$
{
totalAddBatches
}
)
`
batch.forEach(row => {
const fileNameParts = columnMapping.value.fileNameColumns.map(col => row[col] || '').filter(part => part)
let fileName = fileNameParts.join('-')
const url = row[columnMapping.value.url]
// 从URL中提取扩展名
const extension = getFileExtensionFromUrl(url)
if (extension) {
fileName += extension
}
downloadStore.addDownload(fileName, url, selectedFile.value.name, excelFileInfo.id)
}
)
// 每批添加后让出控制权
if (i + addBatchSize < validData.length) {
await new Promise(resolve => setTimeout(resolve, 50))
...
...
@@ -937,19 +1027,19 @@ const parseExcelFile = async () => {
downloadStore.isBatchDownloading = originalBatchDownloading
ElMessage.success(`
成功添加
$
{
validData
.
length
}
个下载任务
`)
// 重置状态
selectedFile.value = null
excelColumns.value = []
previewData.value = []
rawExcelData.value = []
columnMapping.value = { fileNameColumns: [], url: ''
}
// 清空上传组件
if (uploadRef.value) {
uploadRef.value.clearFiles()
}
}
catch (error) {
if (error.message.includes('cancel')) {
ElMessage.info('已取消添加下载任务')
...
...
@@ -964,7 +1054,81 @@ const parseExcelFile = async () => {
}
}
function getDetailList() {
if (!columnMapping.value.fileNameColumns.length || !columnMapping.value.url) {
ElMessage.error('请先选择文件列名和 URL 列')
return []
}
const preview = rawExcelData.value.map(row => {
// 组合多个文件名列
const fileNameParts = columnMapping.value.fileNameColumns.map(col => row[col] || '').filter(part => part)
let fileName = fileNameParts.join('-')
// 从URL中提取扩展名
const url = row[columnMapping.value.url] || ''
const extension = getFileExtensionFromUrl(url)
if (extension) {
fileName += extension
}
return {
fileName: fileName,
fileUrl: url
}
}
).filter(item => item.fileName && item.fileUrl)
return preview
}
var upDetailId = ''
let detailList = reactive([])
function uploadDetails(){
detailList = getDetailList()
http.post(config.uploadBatchFileUpload,{
uploadName:selectedFile.value.name,
uploadUrl: '',
totalRows: rawExcelData.value.length,
fileColumnNameAll: excelColumns.value,
fileColumnName: columnMapping.value.fileNameColumns,
urlColumnName: columnMapping.value.url,
filePrefix: customSubFolder.value,
notifyStatus: downloadStore.enableNotifications,
// details: detailList
}
).then(res => {
if (res.code === 200) {
// loadDetailTableData()
upDetailId = res.data
uploadDetailsFile()
}
else {
detailList = []
ElMessage.error(res.message)
}
}
)
}
function uploadDetailsFile(){
var uplist = []
if (detailList.length > 500) {
uplist = detailList.slice(0,500)
detailList = detailList.slice(500)
}
else {
uplist = detailList
detailList = []
}
console.log('list-->',detailList.length)
http.post(config.fileUploadDetail,{
batchId: upDetailId,
details: uplist
}
).then(res => {
if (res.code === 200) {
if (detailList.length > 0) {
uploadDetailsFile()
}
else {
ElMessage.success('上传任务完成')
loadDetailTableData()
}
}
}
)
}
// 下载当前页所有任务
const downloadCurrentPage = async () => {
...
...
@@ -973,12 +1137,12 @@ const downloadCurrentPage = async () => {
ElMessage.warning('当前页没有下载任务')
return
}
// 过滤出可以开始下载的任务
const
startableItems
=
currentPageItems
.
filter
(
item
=>
const startableItems = currentPageItems.filter(item =>
['pending', 'paused', 'error'].includes(item.status)
)
if (startableItems.length === 0) {
ElMessage.warning('当前页没有可以开始下载的任务')
return
...
...
@@ -990,18 +1154,41 @@ const downloadCurrentPage = async () => {
cancelButtonText: '取消',
type: 'info'
}
)
// 获取当前页所有任务的ID
const currentPageIds = startableItems.map(item => item.id)
// 使用store的批量下载方法
await downloadStore.batchStartDownloads(currentPageIds)
ElMessage.success(`
已开始下载当前页的
$
{
startableItems
.
length
}
个任务
`)
for (const item of startableItems) {
const size = await getFileSize(item.url);
http.post(config.downLoadStatus,{
detailId: item.id,
fileStatus: 3, //下载状态 0:等待中 1:下载中 2:已暂停 3:已完成 4:失败
fileStatus: size || 0
}
).then(res=>{
if (res.code === 200) {
}
else {
ElMessage.error(res.message)
}
}
)
}
}
catch {
// 用户取消
}
}
async function getFileSize(url) {
try {
const response = await axios.head(url);
const size = response.headers["content-length"];
return size ? parseInt(size, 10) : null;
}
catch (err) {
console.error("获取文件大小失败:", err);
return null;
}
}
// 暂停当前页所有任务
const pauseCurrentPage = async () => {
...
...
@@ -1010,30 +1197,30 @@ const pauseCurrentPage = async () => {
ElMessage.warning('当前页没有下载任务')
return
}
// 过滤出可以暂停的任务(只有下载中的任务可以暂停)
const
pausableItems
=
currentPageItems
.
filter
(
item
=>
const pausableItems = currentPageItems.filter(item =>
item.status === 'downloading'
)
if (pausableItems.length === 0) {
ElMessage.warning('当前页没有正在下载的任务')
return
}
try {
await ElMessageBox.confirm(`
确定要暂停当前页的
$
{
pausableItems
.
length
}
个任务吗?
`, '暂停当前页', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
// 获取当前页所有任务的ID
const currentPageIds = pausableItems.map(item => item.id)
// 使用store的批量暂停方法
downloadStore.batchPauseDownloads(currentPageIds)
ElMessage.success(`
已暂停当前页的
$
{
pausableItems
.
length
}
个任务
`)
}
catch {
// 用户取消
...
...
@@ -1044,18 +1231,31 @@ const pauseCurrentPage = async () => {
// 处理分页大小变化
const handleSizeChange = (size) => {
pageSize
.
value
=
size
currentPage
.
value
=
1
deQuery.value.pageSize = size
deQuery.value.current = 1
loadDetailTableData()
}
// 处理当前页变化
const handleCurrentChange = (page) => {
currentPage
.
value
=
page
deQuery.value.current = page
loadDetailTableData()
}
const handleUpSizeChange = (size) => {
upQuery.value.pageSize = size
upQuery.value.current = 1
loadUpTableData()
}
// 处理当前页变化
const handleUpCurrentChange = (page) => {
upQuery.value.current = page
loadUpTableData()
}
// 处理状态筛选变化
const handleStatusFilterChange = () => {
currentPage
.
value
=
1
// 重置当前页为1,以便重新加载数据
// currentPage.value = 1 // 重置当前页为1,以便重新加载数据
deQuery.value.current = 1
}
// 单个文件下载
...
...
@@ -1082,7 +1282,7 @@ const resumeSingleDownload = async (downloadId) => {
const readExcelFileOptimized = (file) => {
return new Promise((resolve, reject) => {
const reader = new FileReader()
reader.onload = (e) => {
try {
// 优先使用Worker处理,如果失败则使用主线程
...
...
@@ -1100,11 +1300,11 @@ const readExcelFileOptimized = (file) => {
reject(error)
}
}
reader.onerror = () => {
reject(new Error('读取文件失败'))
}
reader.readAsArrayBuffer(file)
}
)
}
...
...
@@ -1113,7 +1313,7 @@ const readExcelFileOptimized = (file) => {
const processExcelInMainThread = (arrayBuffer) => {
try {
const data = new Uint8Array(arrayBuffer)
const
workbook
=
XLSX
.
read
(
data
,
{
const workbook = XLSX.read(data, {
type: 'array',
cellDates: true,
cellNF: false,
...
...
@@ -1122,18 +1322,18 @@ const processExcelInMainThread = (arrayBuffer) => {
cellFormula: false,
cellText: false
}
)
if (!workbook.SheetNames || workbook.SheetNames.length === 0) {
throw new Error('Excel文件中没有找到工作表')
}
const sheetName = workbook.SheetNames[0]
const worksheet = workbook.Sheets[sheetName]
if (!worksheet) {
throw new Error('无法读取工作表数据')
}
// 限制最大行数,防止内存溢出
const maxRows = 50000 // 主线程限制更严格
const range = XLSX.utils.decode_range(worksheet['!ref'] || 'A1')
...
...
@@ -1141,23 +1341,23 @@ const processExcelInMainThread = (arrayBuffer) => {
range.e.r = maxRows - 1
worksheet['!ref'] = XLSX.utils.encode_range(range)
}
const jsonData = XLSX.utils.sheet_to_json(worksheet, {
header: 1,
defval: '',
blankrows: false
}
)
if (!jsonData || jsonData.length === 0) {
throw new Error('Excel文件中没有数据')
}
// 转换为对象数组
const headers = jsonData[0]
if (!headers || headers.length === 0) {
throw new Error('Excel文件中没有列标题')
}
const rows = jsonData.slice(1).map(row => {
const obj = {
}
headers.forEach((header, index) => {
...
...
@@ -1165,7 +1365,7 @@ const processExcelInMainThread = (arrayBuffer) => {
}
)
return obj
}
)
return rows
}
catch (error) {
throw new Error('主线程Excel解析失败: ' + error.message)
...
...
@@ -1177,31 +1377,31 @@ const processLargeExcelFileWithWorker = (arrayBuffer, resolve, reject) => {
try {
// 创建Web Worker
const worker = new Worker('/excel-worker.js')
// 设置超时时间
const timeout = setTimeout(() => {
worker.terminate()
reject(new Error('Excel解析超时,请尝试使用较小的文件'))
}
, 120000) // 增加到120秒超时
worker.onmessage = (e) => {
clearTimeout(timeout)
worker.terminate()
if (e.data.type === 'success') {
resolve(e.data.data)
}
else {
reject(new Error(e.data.error || 'Worker处理失败'))
}
}
worker.onerror = (error) => {
clearTimeout(timeout)
worker.terminate()
console.error('Worker error:', error)
reject(new Error('Worker处理失败: ' + (error.message || '未知错误')))
}
// 发送数据给Worker
worker.postMessage({
type: 'parseExcel',
...
...
@@ -1221,11 +1421,16 @@ const readExcelFile = (file) => {
// 获取状态文本
const getStatusText = (status) => {
const statusMap = {
pending
:
'等待中'
,
downloading
:
'下载中'
,
completed
:
'已完成'
,
error
:
'失败'
,
paused
:
'已暂停'
// pending: '等待中',
// downloading: '下载中',
// completed: '已完成',
// error: '失败',
// paused: '已暂停'
0:'等待中',
1:'下载中' ,
2:'已暂停' ,
3:'已完成',
4:'失败'
}
return statusMap[status] || status
}
...
...
@@ -1233,32 +1438,32 @@ const getStatusText = (status) => {
// 格式化速度
const formatSpeed = (bytesPerSecond) => {
if (bytesPerSecond === 0) return '0 B/s'
const units = ['B/s', 'KB/s', 'MB/s', 'GB/s']
let size = bytesPerSecond
let unitIndex = 0
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024
unitIndex++
}
return `
$
{
size
.
toFixed
(
1
)
}
$
{
units
[
unitIndex
]
}
`
}
// 格式化字节数
const formatBytes = (bytes) => {
if (bytes === 0) return '0 B'
const units = ['B', 'KB', 'MB', 'GB']
let size = bytes
let unitIndex = 0
while (size >= 1024 && unitIndex < units.length - 1) {
size /= 1024
unitIndex++
}
return `
$
{
size
.
toFixed
(
1
)
}
$
{
units
[
unitIndex
]
}
`
}
...
...
@@ -1270,7 +1475,7 @@ const handleLogout = async () => {
cancelButtonText: '取消',
type: 'warning'
}
)
authStore.logout()
ElMessage.success('已退出登录')
router.push('/login')
...
...
src/views/Login.vue
View file @
85482980
...
...
@@ -3,7 +3,7 @@
<div
class=
"login-box"
>
<div
class=
"login-header"
>
<el-icon
class=
"logo-icon"
>
<Download/>
<Download
/>
</el-icon>
<h1>
文件下载器
</h1>
<p>
登录或注册以开始使用
</p>
...
...
@@ -11,133 +11,62 @@
<!-- 切换按钮 -->
<div
class=
"mode-switch"
>
<el-button
:type=
"isLoginMode ? 'primary' : 'default'"
@
click=
"switchToLogin"
:disabled=
"isLoginMode"
>
<el-button
:type=
"isLoginMode ? 'primary' : 'default'"
@
click=
"switchToLogin"
:disabled=
"isLoginMode"
>
登录
</el-button>
<el-button
:type=
"!isLoginMode ? 'primary' : 'default'"
@
click=
"switchToRegister"
:disabled=
"!isLoginMode"
>
<el-button
:type=
"!isLoginMode ? 'primary' : 'default'"
@
click=
"switchToRegister"
:disabled=
"!isLoginMode"
>
注册
</el-button>
</div>
<!-- 错误消息显示 -->
<div
v-if=
"errorMessage"
class=
"error-message"
>
<el-alert
:title=
"errorMessage"
type=
"error"
:closable=
"false"
show-icon
/>
<el-alert
:title=
"errorMessage"
type=
"error"
:closable=
"false"
show-icon
/>
</div>
<!-- 成功消息显示 -->
<div
v-if=
"successMessage"
class=
"success-message"
>
<el-alert
:title=
"successMessage"
type=
"success"
:closable=
"false"
show-icon
/>
<el-alert
:title=
"successMessage"
type=
"success"
:closable=
"false"
show-icon
/>
</div>
<!-- 登录表单 -->
<el-form
v-if=
"isLoginMode"
ref=
"loginFormRef"
:model=
"loginForm"
:rules=
"loginRules"
class=
"login-form"
@
submit
.
prevent=
"handleLogin"
>
<el-form-item
prop=
"username"
>
<el-input
v-model=
"loginForm.username"
placeholder=
"用户名"
prefix-icon=
"User"
size=
"large"
/>
<el-form
v-if=
"isLoginMode"
ref=
"loginFormRef"
:model=
"loginForm"
:rules=
"loginRules"
class=
"login-form"
@
submit
.
prevent=
"handleLogin"
>
<el-form-item
prop=
"userName"
>
<el-input
v-model=
"loginForm.userName"
placeholder=
"用户名"
prefix-icon=
"User"
size=
"large"
/>
</el-form-item>
<el-form-item
prop=
"password"
>
<el-input
v-model=
"loginForm.password"
type=
"password"
placeholder=
"密码"
prefix-icon=
"Lock"
size=
"large"
show-password
@
keyup
.
enter=
"handleLogin"
/>
<el-input
v-model=
"loginForm.password"
type=
"password"
placeholder=
"密码"
prefix-icon=
"Lock"
size=
"large"
show-password
@
keyup
.
enter=
"handleLogin"
/>
</el-form-item>
<el-form-item>
<el-button
type=
"primary"
size=
"large"
class=
"submit-btn"
:loading=
"loading"
@
click=
"handleLogin"
>
<el-button
type=
"primary"
size=
"large"
class=
"submit-btn"
:loading=
"loading"
@
click=
"handleLogin"
>
登录
</el-button>
</el-form-item>
</el-form>
<!-- 注册表单 -->
<el-form
v-else
ref=
"registerFormRef"
:model=
"registerForm"
:rules=
"registerRules"
class=
"login-form"
@
submit
.
prevent=
"handleRegister"
>
<el-form-item
prop=
"username"
>
<el-input
v-model=
"registerForm.username"
placeholder=
"用户名(至少3个字符)"
prefix-icon=
"User"
size=
"large"
/>
<el-form
v-else
ref=
"registerFormRef"
:model=
"registerForm"
:rules=
"registerRules"
class=
"login-form"
@
submit
.
prevent=
"handleRegister"
>
<el-form-item
prop=
"userName"
>
<el-input
v-model=
"registerForm.userName"
placeholder=
"用户名(至少3个字符)"
prefix-icon=
"User"
size=
"large"
/>
</el-form-item>
<el-form-item
prop=
"email"
>
<el-input
v-model=
"registerForm.email"
placeholder=
"邮箱(可选)"
prefix-icon=
"Message"
size=
"large"
/>
<el-input
v-model=
"registerForm.email"
placeholder=
"邮箱(可选)"
prefix-icon=
"Message"
size=
"large"
/>
</el-form-item>
<el-form-item
prop=
"password"
>
<el-input
v-model=
"registerForm.password"
type=
"password"
placeholder=
"密码"
prefix-icon=
"Lock"
size=
"large"
show-password
/>
<el-input
v-model=
"registerForm.password"
type=
"password"
placeholder=
"密码"
prefix-icon=
"Lock"
size=
"large"
show-password
/>
</el-form-item>
<el-form-item
prop=
"confirmPassword"
>
<el-input
v-model=
"registerForm.confirmPassword"
type=
"password"
placeholder=
"确认密码"
prefix-icon=
"Lock"
size=
"large"
show-password
@
keyup
.
enter=
"handleRegister"
/>
<el-input
v-model=
"registerForm.confirmPassword"
type=
"password"
placeholder=
"确认密码"
prefix-icon=
"Lock"
size=
"large"
show-password
@
keyup
.
enter=
"handleRegister"
/>
</el-form-item>
<!-- 密码强度提示 -->
...
...
@@ -146,29 +75,29 @@
<div
class=
"strength-items"
>
<div
class=
"strength-item"
:class=
"
{ valid: registerForm.password.length >= 6 }">
<el-icon>
<Check
v-if=
"registerForm.password.length >= 6"
/>
<Close
v-else
/>
<Check
v-if=
"registerForm.password.length >= 6"
/>
<Close
v-else
/>
</el-icon>
至少6位
</div>
<div
class=
"strength-item"
:class=
"
{ valid: /\d/.test(registerForm.password) }">
<el-icon>
<Check
v-if=
"/\d/.test(registerForm.password)"
/>
<Close
v-else
/>
<Check
v-if=
"/\d/.test(registerForm.password)"
/>
<Close
v-else
/>
</el-icon>
包含数字
</div>
<div
class=
"strength-item"
:class=
"
{ valid: /[a-zA-Z]/.test(registerForm.password) }">
<el-icon>
<Check
v-if=
"/[a-zA-Z]/.test(registerForm.password)"
/>
<Close
v-else
/>
<Check
v-if=
"/[a-zA-Z]/.test(registerForm.password)"
/>
<Close
v-else
/>
</el-icon>
包含字母
</div>
<div
class=
"strength-item"
:class=
"
{ valid: hasSpecialChar }">
<el-icon>
<Check
v-if=
"hasSpecialChar"
/>
<Close
v-else
/>
<Check
v-if=
"hasSpecialChar"
/>
<Close
v-else
/>
</el-icon>
包含特殊字符
</div>
...
...
@@ -176,13 +105,7 @@
</div>
<el-form-item>
<el-button
type=
"primary"
size=
"large"
class=
"submit-btn"
:loading=
"loading"
@
click=
"handleRegister"
>
<el-button
type=
"primary"
size=
"large"
class=
"submit-btn"
:loading=
"loading"
@
click=
"handleRegister"
>
注册
</el-button>
</el-form-item>
...
...
@@ -194,11 +117,13 @@
</
template
>
<
script
setup
>
import
{
computed
,
nextTick
,
onMounted
,
reactive
,
ref
}
from
'vue'
import
{
useRouter
}
from
'vue-router'
import
{
ElMessage
,
ElMessageBox
}
from
'element-plus'
import
{
Check
,
Close
,
Download
}
from
'@element-plus/icons-vue'
import
{
useAuthStore
}
from
'../stores/auth'
import
{
computed
,
nextTick
,
onMounted
,
reactive
,
ref
}
from
'vue'
import
{
useRouter
}
from
'vue-router'
import
{
ElMessage
,
ElMessageBox
}
from
'element-plus'
import
{
Check
,
Close
,
Download
}
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
()
...
...
@@ -220,13 +145,13 @@ const hasSpecialChar = computed(() => {
// 登录表单
const
loginForm
=
reactive
({
user
n
ame
:
''
,
user
N
ame
:
''
,
password
:
''
})
// 注册表单
const
registerForm
=
reactive
({
user
n
ame
:
''
,
user
N
ame
:
''
,
email
:
''
,
password
:
''
,
confirmPassword
:
''
...
...
@@ -234,26 +159,26 @@ const registerForm = reactive({
// 登录验证规则
const
loginRules
=
{
user
n
ame
:
[
{
required
:
true
,
message
:
'请输入用户名'
,
trigger
:
'blur'
}
user
N
ame
:
[
{
required
:
true
,
message
:
'请输入用户名'
,
trigger
:
'blur'
}
],
password
:
[
{
required
:
true
,
message
:
'请输入密码'
,
trigger
:
'blur'
}
{
required
:
true
,
message
:
'请输入密码'
,
trigger
:
'blur'
}
]
}
// 注册验证规则
const
registerRules
=
{
user
n
ame
:
[
{
required
:
true
,
message
:
'请输入用户名'
,
trigger
:
'blur'
},
{
min
:
3
,
message
:
'用户名至少3个字符'
,
trigger
:
'blur'
}
user
N
ame
:
[
{
required
:
true
,
message
:
'请输入用户名'
,
trigger
:
'blur'
},
{
min
:
3
,
message
:
'用户名至少3个字符'
,
trigger
:
'blur'
}
],
email
:
[
{
type
:
'email'
,
message
:
'请输入正确的邮箱格式'
,
trigger
:
'blur'
}
{
type
:
'email'
,
message
:
'请输入正确的邮箱格式'
,
trigger
:
'blur'
}
],
password
:
[
{
required
:
true
,
message
:
'请输入密码'
,
trigger
:
'blur'
},
{
min
:
6
,
message
:
'密码至少6位'
,
trigger
:
'blur'
},
{
required
:
true
,
message
:
'请输入密码'
,
trigger
:
'blur'
},
{
min
:
6
,
message
:
'密码至少6位'
,
trigger
:
'blur'
},
{
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
!
/
\d
/
.
test
(
value
))
{
...
...
@@ -270,7 +195,7 @@ const registerRules = {
}
],
confirmPassword
:
[
{
required
:
true
,
message
:
'请确认密码'
,
trigger
:
'blur'
},
{
required
:
true
,
message
:
'请确认密码'
,
trigger
:
'blur'
},
{
validator
:
(
rule
,
value
,
callback
)
=>
{
if
(
value
!==
registerForm
.
password
)
{
...
...
@@ -306,8 +231,8 @@ const clearMessages = () => {
// 重置表单
const
resetForms
=
()
=>
{
Object
.
assign
(
loginForm
,
{
username
:
''
,
password
:
''
})
Object
.
assign
(
registerForm
,
{
username
:
''
,
email
:
''
,
password
:
''
,
confirmPassword
:
''
})
Object
.
assign
(
loginForm
,
{
userName
:
''
,
password
:
''
})
Object
.
assign
(
registerForm
,
{
userName
:
''
,
email
:
''
,
password
:
''
,
confirmPassword
:
''
})
if
(
loginFormRef
.
value
)
{
loginFormRef
.
value
.
clearValidate
()
...
...
@@ -318,11 +243,34 @@ const resetForms = () => {
}
// 处理登录
const
handleLogin
=
async
()
=>
{
function
handleLogin
()
{
if
(
!
loginForm
.
userName
.
trim
())
{
ElMessage
.
warning
(
'请输入用户名'
)
return
}
if
(
!
loginForm
.
password
.
trim
())
{
ElMessage
.
warning
(
'请输入密码'
)
return
}
clearMessages
()
loading
.
value
=
true
http
.
post
(
config
.
login
,
{
...
loginForm
}).
then
(
res
=>
{
loading
.
value
=
false
if
(
res
.
code
===
200
)
{
sessionStorage
.
setItem
(
"token"
,
res
.
data
)
router
.
push
(
'/downloader'
)
}
else
{
errorMessage
.
value
=
res
.
message
ElMessage
.
error
(
res
.
message
)
}
})
}
const
handleLogin1
=
async
()
=>
{
try
{
console
.
log
(
'开始登录处理...'
)
// 检查用户名和密码是否为空
if
(
!
loginForm
.
user
n
ame
.
trim
())
{
if
(
!
loginForm
.
user
N
ame
.
trim
())
{
ElMessage
.
warning
(
'请输入用户名'
)
return
}
...
...
@@ -330,6 +278,7 @@ const handleLogin = async () => {
ElMessage
.
warning
(
'请输入密码'
)
return
}
// 等待 DOM 更新,确保表单引用可用
await
nextTick
()
// 检查表单引用是否存在,如果不存在则跳过验证
...
...
@@ -348,12 +297,25 @@ const handleLogin = async () => {
loading
.
value
=
true
clearMessages
()
console
.
log
(
'登录表单验证通过,用户名:'
,
loginForm
.
username
)
await
authStore
.
login
(
loginForm
.
username
,
loginForm
.
password
)
console
.
log
(
'登录成功,跳转到下载器页面'
)
ElMessage
.
success
(
'登录成功!'
)
router
.
push
(
'/downloader'
)
console
.
log
(
'登录表单验证通过,用户名:'
,
loginForm
.
userName
)
await
authStore
.
login
(
loginForm
.
userName
,
loginForm
.
password
)
// console.log('登录成功,跳转到下载器页面')
// ElMessage.success('登录成功!')
// router.push('/downloader')
http
.
post
(
config
.
login
,
{
...
loginForm
}).
then
(
res
=>
{
loading
.
value
=
false
console
.
log
(
'-->res:'
,
res
)
if
(
res
.
code
===
200
)
{
console
.
log
(
'-->res.data:'
,
res
.
data
)
ElMessage
.
success
(
'登录成功!'
)
sessionStorage
.
setItem
(
"token"
,
res
.
data
)
router
.
push
(
'/downloader'
)
// console.log('router:',router)
}
else
{
ElMessage
.
error
(
res
.
message
)
}
})
}
catch
(
error
)
{
console
.
error
(
'登录失败:'
,
error
)
...
...
@@ -391,10 +353,40 @@ const handleLogin = async () => {
}
// 处理注册
const
handleRegister
=
async
()
=>
{
function
handleRegister
()
{
if
(
!
registerForm
.
userName
.
trim
())
{
ElMessage
.
warning
(
'请输入用户名'
)
return
}
if
(
!
registerForm
.
password
.
trim
())
{
ElMessage
.
warning
(
'请输入密码'
)
return
}
if
(
!
registerForm
.
confirmPassword
.
trim
())
{
ElMessage
.
warning
(
'请确认密码'
)
return
}
clearMessages
()
loading
.
value
=
true
http
.
post
(
config
.
register
,
{
...
registerForm
}).
then
(
res
=>
{
loading
.
value
=
false
if
(
res
.
code
===
200
)
{
sessionStorage
.
setItem
(
"token"
,
res
.
data
)
switchToLogin
()
loginForm
.
userName
=
registerForm
.
userName
}
else
{
errorMessage
.
value
=
res
.
message
ElMessage
.
error
(
res
.
message
)
}
})
}
const
handleRegister1
=
async
()
=>
{
try
{
// 检查用户名和密码是否为空
if
(
!
registerForm
.
user
n
ame
.
trim
())
{
if
(
!
registerForm
.
user
N
ame
.
trim
())
{
ElMessage
.
warning
(
'请输入用户名'
)
return
}
...
...
@@ -429,14 +421,14 @@ const handleRegister = async () => {
clearMessages
()
// 检查用户名是否已存在
if
(
authStore
.
isUsernameExists
(
registerForm
.
user
n
ame
))
{
if
(
authStore
.
isUsernameExists
(
registerForm
.
user
N
ame
))
{
ElMessage
.
error
(
'用户名已存在'
)
errorMessage
.
value
=
'用户名已存在'
return
}
// 注册用户
await
authStore
.
register
(
registerForm
.
user
n
ame
,
registerForm
.
password
,
registerForm
.
email
)
await
authStore
.
register
(
registerForm
.
user
N
ame
,
registerForm
.
password
,
registerForm
.
email
)
ElMessage
.
success
(
'注册成功!请登录'
)
successMessage
.
value
=
'注册成功!请登录'
...
...
@@ -444,7 +436,7 @@ const handleRegister = async () => {
// 自动切换到登录模式
setTimeout
(()
=>
{
switchToLogin
()
loginForm
.
user
name
=
registerForm
.
usern
ame
loginForm
.
user
Name
=
registerForm
.
userN
ame
},
2000
)
}
catch
(
error
)
{
...
...
src/views/Settings.vue
View file @
85482980
...
...
@@ -22,7 +22,7 @@
<el-form
:model=
"userInfo"
label-width=
"120px"
>
<el-form-item
label=
"用户名"
>
<el-input
v-model=
"userInfo.user
n
ame"
disabled
/>
<el-input
v-model=
"userInfo.user
N
ame"
disabled
/>
</el-form-item>
<el-form-item
label=
"邮箱"
>
...
...
@@ -121,7 +121,7 @@ const authStore = useAuthStore()
// 用户信息
const
userInfo
=
reactive
({
user
n
ame
:
''
,
user
N
ame
:
''
,
email
:
''
,
createdAt
:
''
,
lastLoginAt
:
''
...
...
@@ -138,7 +138,7 @@ const downloadSettings = reactive({
const
initializeData
=
()
=>
{
const
user
=
authStore
.
user
if
(
user
)
{
userInfo
.
user
name
=
user
.
usern
ame
userInfo
.
user
Name
=
user
.
userN
ame
userInfo
.
email
=
user
.
email
||
'未设置'
userInfo
.
createdAt
=
new
Date
(
user
.
createdAt
).
toLocaleString
()
userInfo
.
lastLoginAt
=
user
.
lastLoginAt
?
new
Date
(
user
.
lastLoginAt
).
toLocaleString
()
:
'从未登录'
...
...
vite.config.js
View file @
85482980
...
...
@@ -11,7 +11,14 @@ export default defineConfig({
},
server
:
{
port
:
3000
,
open
:
true
open
:
true
,
proxy
:
{
'/api'
:
{
target
:
'http://39.107.245.36:8081'
,
changeOrigin
:
true
,
rewrite
:
path
=>
path
.
replace
(
/^
\/
api/
,
''
)
}
}
},
build
:
{
outDir
:
'dist'
,
...
...
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