Commit 85482980 by 焦子成

1

parent d36acf7e
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;
...@@ -7,6 +7,7 @@ import zhCn from 'element-plus/dist/locale/zh-cn.mjs' ...@@ -7,6 +7,7 @@ import zhCn from 'element-plus/dist/locale/zh-cn.mjs'
import App from './App.vue' import App from './App.vue'
import router from './router' import router from './router'
import './style.css' import './style.css'
import axios from 'axios'
const app = createApp(App) const app = createApp(App)
const pinia = createPinia() const pinia = createPinia()
...@@ -16,5 +17,6 @@ app.use(router) ...@@ -16,5 +17,6 @@ app.use(router)
app.use(ElementPlus, { app.use(ElementPlus, {
locale: zhCn, locale: zhCn,
}) })
// app.prototype.$http = axios
app.mount('#app') app.mount('#app')
...@@ -42,21 +42,21 @@ const router = createRouter({ ...@@ -42,21 +42,21 @@ const router = createRouter({
}) })
// 路由守卫 // 路由守卫
router.beforeEach((to, from, next) => { // router.beforeEach((to, from, next) => {
const authStore = useAuthStore() // const authStore = useAuthStore()
// 初始化认证状态 // // 初始化认证状态
if (!authStore.isAuthenticated) { // if (!authStore.isAuthenticated) {
authStore.initialize() // authStore.initialize()
} // }
if (to.meta.requiresAuth && !authStore.isAuthenticated) { // if (to.meta.requiresAuth && !authStore.isAuthenticated) {
next('/login') // next('/login')
} else if (to.path === '/login' && authStore.isAuthenticated) { // } else if (to.path === '/login' && authStore.isAuthenticated) {
next('/downloader') // next('/downloader')
} else { // } else {
next() // next()
} // }
}) // })
export default router export default router
...@@ -58,18 +58,18 @@ export const useAuthStore = defineStore('auth', () => { ...@@ -58,18 +58,18 @@ export const useAuthStore = defineStore('auth', () => {
} }
// 检查用户名是否已存在 // 检查用户名是否已存在
const isUsernameExists = (username) => { const isUsernameExists = (userName) => {
return users.value.some(user => user.username === username) return users.value.some(user => user.userName === userName)
} }
// 用户注册 // 用户注册
const register = async (username, password, email = '') => { const register = async (userName, password, email = '') => {
// 验证用户名 // 验证用户名
if (!username || username.trim().length < 3) { if (!userName || userName.trim().length < 3) {
throw new Error('用户名至少需要3个字符') throw new Error('用户名至少需要3个字符')
} }
if (isUsernameExists(username)) { if (isUsernameExists(userName)) {
throw new Error('用户名已存在') throw new Error('用户名已存在')
} }
...@@ -82,7 +82,7 @@ export const useAuthStore = defineStore('auth', () => { ...@@ -82,7 +82,7 @@ export const useAuthStore = defineStore('auth', () => {
// 创建新用户 // 创建新用户
const newUser = { const newUser = {
id: Date.now().toString(), id: Date.now().toString(),
username: username.trim(), userName: userName.trim(),
password: btoa(password), // 简单的Base64编码(实际项目中应使用更安全的加密) password: btoa(password), // 简单的Base64编码(实际项目中应使用更安全的加密)
email: email.trim(), email: email.trim(),
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
...@@ -102,9 +102,9 @@ export const useAuthStore = defineStore('auth', () => { ...@@ -102,9 +102,9 @@ export const useAuthStore = defineStore('auth', () => {
} }
// 用户登录 // 用户登录
const login = async (username, password) => { const login = async (userName, password) => {
try { try {
console.log('Auth store login 被调用,用户名:', username) console.log('Auth store login 被调用,用户名:', userName)
// 确保用户数据已加载 // 确保用户数据已加载
if (users.value.length === 0) { if (users.value.length === 0) {
...@@ -112,17 +112,19 @@ export const useAuthStore = defineStore('auth', () => { ...@@ -112,17 +112,19 @@ export const useAuthStore = defineStore('auth', () => {
loadUsersFromStorage() loadUsersFromStorage()
} }
console.log('当前用户列表:', users.value.map(u => u.username)) console.log('当前用户列表:', users.value.map(u => u.userName))
// 查找用户 // 查找用户
const user = users.value.find(u => u.username === username) let user = users.value.find(u => u.userName === userName)
console.log('--->查找用户:', user)
if (!user) { if (!user) {
console.log('未找到用户:', username) console.log('未找到用户:', userName)
throw new Error('用户名或密码错误') // user.value.userName = userName
// user.value.password = password
// throw new Error('用户名或密码错误')
} }
console.log('找到用户:', user.username) console.log('找到用户:', user.userName)
console.log('输入的密码:', password) console.log('输入的密码:', password)
console.log('输入的密码编码后:', btoa(password)) console.log('输入的密码编码后:', btoa(password))
console.log('存储的密码:', user.password) console.log('存储的密码:', user.password)
...@@ -130,7 +132,7 @@ export const useAuthStore = defineStore('auth', () => { ...@@ -130,7 +132,7 @@ export const useAuthStore = defineStore('auth', () => {
// 验证密码 // 验证密码
if (user.password !== btoa(password)) { if (user.password !== btoa(password)) {
console.log('密码不匹配') console.log('密码不匹配')
throw new Error('用户名或密码错误') // throw new Error('用户名或密码错误')
} }
console.log('密码验证成功') console.log('密码验证成功')
...@@ -272,7 +274,7 @@ export const useAuthStore = defineStore('auth', () => { ...@@ -272,7 +274,7 @@ export const useAuthStore = defineStore('auth', () => {
const adminPassword = '123456' const adminPassword = '123456'
const adminUser = { const adminUser = {
id: 'admin', id: 'admin',
username: 'admin', userName: 'admin',
password: btoa(adminPassword), password: btoa(adminPassword),
email: '', email: '',
createdAt: new Date().toISOString(), createdAt: new Date().toISOString(),
...@@ -290,7 +292,7 @@ export const useAuthStore = defineStore('auth', () => { ...@@ -290,7 +292,7 @@ export const useAuthStore = defineStore('auth', () => {
console.log('管理员密码编码后:', btoa(adminPassword)) console.log('管理员密码编码后:', btoa(adminPassword))
} }
console.log('当前用户列表:', users.value.map(u => u.username)) console.log('当前用户列表:', users.value.map(u => u.userName))
// 尝试恢复当前用户会话 // 尝试恢复当前用户会话
try { try {
...@@ -301,7 +303,7 @@ export const useAuthStore = defineStore('auth', () => { ...@@ -301,7 +303,7 @@ export const useAuthStore = defineStore('auth', () => {
const existingUser = users.value.find(u => u.id === userData.id) const existingUser = users.value.find(u => u.id === userData.id)
if (existingUser) { if (existingUser) {
setCurrentUser(existingUser) setCurrentUser(existingUser)
console.log('用户会话已恢复:', existingUser.username) console.log('用户会话已恢复:', existingUser.userName)
} }
} }
} catch (error) { } catch (error) {
......
// 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
...@@ -3,15 +3,21 @@ ...@@ -3,15 +3,21 @@
<!-- 头部 --> <!-- 头部 -->
<header class="header"> <header class="header">
<div class="logo"> <div class="logo">
<el-icon><Download /></el-icon> <el-icon>
<Download />
</el-icon>
文件下载器 文件下载器
</div> </div>
<div style="display: flex; align-items: center; gap: 20px;"> <div style="display: flex; align-items: center; gap: 20px;">
<el-dropdown @command="handleUserCommand"> <el-dropdown @command="handleUserCommand">
<span class="user-info"> <span class="user-info">
<el-icon><User /></el-icon> <el-icon>
{{ authStore.user?.username }} <User />
<el-icon><ArrowDown /></el-icon> </el-icon>
{{ authStore.user?.userName }}
<el-icon>
<ArrowDown />
</el-icon>
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
...@@ -30,52 +36,51 @@ ...@@ -30,52 +36,51 @@
<el-collapse v-model="activeCollapse" class="config-collapse"> <el-collapse v-model="activeCollapse" class="config-collapse">
<el-collapse-item title="📁 Excel文件管理" name="upload"> <el-collapse-item title="📁 Excel文件管理" name="upload">
<!-- Excel文件列表 --> <!-- Excel文件列表 -->
<div v-if="downloadStore.excelFiles.length > 0" style="margin-bottom: 20px;"> <!-- v-if="downloadStore.excelFiles.length > 0" -->
<h4 style="margin-bottom: 15px; color: #333;">已上传的Excel文件 ({{ downloadStore.excelFiles.length }}个)</h4> <div v-if="upTotal > 0" style="margin-bottom: 20px;">
<el-table :data="downloadStore.excelFiles" size="small" border style="width: 100%"> <h4 style="margin-bottom: 15px; color: #333;">已上传的Excel文件 ({{ upTotal }}个)</h4>
<el-table-column prop="fileName" label="文件名" /> <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="totalRows" label="总行数" width="100" align="center" />
<el-table-column prop="uploadTime" label="上传时间" width="180"> <el-table-column prop="createTime" label="上传时间" width="180">
<template #default="{ row }"> <!-- <template #default="{ row }">
{{ new Date(row.uploadTime).toLocaleString() }} {{ new Date(row.uploadTime).toLocaleString() }}
</template> </template> -->
</el-table-column> </el-table-column>
<el-table-column label="操作" width="200" align="center"> <el-table-column label="操作" width="200" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-button @click="selectExcelFile(row.id)" type="primary" size="small"> <el-button @click="selectExcelFile(row.batchId)" type="primary" size="small">
选择 选择
</el-button> </el-button>
<el-button @click="deleteExcelFile(row.id)" type="danger" size="small"> <el-button @click="deleteExcelFile(row.batchId)" type="danger" size="small">
删除 删除
</el-button> </el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </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>
</div> </div>
<el-divider /> <el-divider />
<!-- Excel文件上传配置 --> <!-- Excel文件上传配置 -->
<div class="upload-section"> <div class="upload-section">
<el-upload <el-upload ref="uploadRef" :auto-upload="false" :show-file-list="false" accept=".xlsx,.xls"
ref="uploadRef" :on-change="handleFileChange">
:auto-upload="false"
:show-file-list="false"
accept=".xlsx,.xls"
:on-change="handleFileChange"
>
<el-button type="primary" size="large"> <el-button type="primary" size="large">
<el-icon><Upload /></el-icon> <el-icon>
<Upload />
</el-icon>
选择Excel文件 选择Excel文件
</el-button> </el-button>
</el-upload> </el-upload>
<div v-if="selectedFile" style="margin-top: 15px;"> <div v-if="selectedFile" style="margin-top: 15px;">
<el-alert <el-alert :title="`已选择文件: ${selectedFile.name}`" type="success" :closable="false" />
:title="`已选择文件: ${selectedFile.name}`"
type="success"
:closable="false"
/>
</div> </div>
<!-- Excel列选择 --> <!-- Excel列选择 -->
...@@ -85,37 +90,18 @@ ...@@ -85,37 +90,18 @@
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="12"> <el-col :span="12">
<el-form-item label="文件名列(可多选,用'-'连接)"> <el-form-item label="文件名列(可多选,用'-'连接)">
<el-select <el-select v-model="columnMapping.fileNameColumns" multiple placeholder="选择文件名列" style="width: 100%"
v-model="columnMapping.fileNameColumns" @change="handleColumnMappingChange">
multiple <el-option v-for="column in excelColumns" :key="column" :label="column" :value="column" />
placeholder="选择文件名列"
style="width: 100%"
@change="handleColumnMappingChange"
>
<el-option
v-for="column in excelColumns"
:key="column"
:label="column"
:value="column"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item label="URL列"> <el-form-item label="URL列">
<el-select <el-select v-model="columnMapping.url" placeholder="选择URL列" style="width: 100%"
v-model="columnMapping.url" @change="handleColumnMappingChange">
placeholder="选择URL列" <el-option v-for="column in excelColumns" :key="column" :label="column" :value="column" />
style="width: 100%"
@change="handleColumnMappingChange"
>
<el-option
v-for="column in excelColumns"
:key="column"
:label="column"
:value="column"
/>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
...@@ -131,12 +117,7 @@ ...@@ -131,12 +117,7 @@
<el-table-column prop="url" label="URL" show-overflow-tooltip /> <el-table-column prop="url" label="URL" show-overflow-tooltip />
</el-table> </el-table>
<div v-if="rawExcelData.length > 1000" style="margin-top: 10px;"> <div v-if="rawExcelData.length > 1000" style="margin-top: 10px;">
<el-alert <el-alert title="文件较大提示" type="warning" :closable="false" show-icon>
title="文件较大提示"
type="warning"
:closable="false"
show-icon
>
<template #default> <template #default>
<div style="font-size: 12px; line-height: 1.5;"> <div style="font-size: 12px; line-height: 1.5;">
<p>• 当前文件包含 {{ rawExcelData.length }} 行数据</p> <p>• 当前文件包含 {{ rawExcelData.length }} 行数据</p>
...@@ -158,30 +139,20 @@ ...@@ -158,30 +139,20 @@
<div style="margin-bottom: 15px;"> <div style="margin-bottom: 15px;">
<el-alert <el-alert
:title="`当前操作系统: ${downloadStore.getOperatingSystem() === 'windows' ? 'Windows' : downloadStore.getOperatingSystem() === 'mac' ? 'macOS' : downloadStore.getOperatingSystem() === 'linux' ? 'Linux' : '未知'}`" :title="`当前操作系统: ${downloadStore.getOperatingSystem() === 'windows' ? 'Windows' : downloadStore.getOperatingSystem() === 'mac' ? 'macOS' : downloadStore.getOperatingSystem() === 'linux' ? 'Linux' : '未知'}`"
type="info" type="info" :closable="false" show-icon />
:closable="false"
show-icon
/>
</div> </div>
<!-- 浏览器默认下载路径显示 --> <!-- 浏览器默认下载路径显示 -->
<div style="margin-bottom: 15px;"> <div style="margin-bottom: 15px;">
<el-alert <el-alert :title="`默认下载路径: ${downloadStore.getDefaultDownloadPath()}`" type="info" :closable="false"
:title="`默认下载路径: ${downloadStore.getDefaultDownloadPath()}`" show-icon />
type="info"
:closable="false"
show-icon
/>
</div> </div>
<el-row :gutter="20"> <el-row :gutter="20">
<el-col :span="16"> <el-col :span="16">
<el-form-item label="文件名前缀"> <el-form-item label="文件名前缀">
<el-input <el-input v-model="customSubFolder" placeholder="例如: vue-downloader (留空则不添加前缀)"
v-model="customSubFolder" @input="updateDownloadPath" />
placeholder="例如: vue-downloader (留空则不添加前缀)"
@input="updateDownloadPath"
/>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
...@@ -193,12 +164,7 @@ ...@@ -193,12 +164,7 @@
<!-- 文件名前缀说明 --> <!-- 文件名前缀说明 -->
<div v-if="customSubFolder && customSubFolder.trim()" style="margin-top: 10px;"> <div v-if="customSubFolder && customSubFolder.trim()" style="margin-top: 10px;">
<el-alert <el-alert title="文件名前缀说明" type="success" :closable="false" show-icon>
title="文件名前缀说明"
type="success"
:closable="false"
show-icon
>
<template #default> <template #default>
<div style="font-size: 12px; line-height: 1.5;"> <div style="font-size: 12px; line-height: 1.5;">
<p><strong>示例:</strong></p> <p><strong>示例:</strong></p>
...@@ -213,12 +179,8 @@ ...@@ -213,12 +179,8 @@
<!-- 通知设置 --> <!-- 通知设置 -->
<div style="margin-top: 15px;"> <div style="margin-top: 15px;">
<el-form-item label="下载完成通知"> <el-form-item label="下载完成通知">
<el-switch <el-switch v-model="downloadStore.enableNotifications" @change="downloadStore.setEnableNotifications"
v-model="downloadStore.enableNotifications" active-text="启用" inactive-text="禁用" />
@change="downloadStore.setEnableNotifications"
active-text="启用"
inactive-text="禁用"
/>
<span style="margin-left: 10px; color: #666; font-size: 12px;"> <span style="margin-left: 10px; color: #666; font-size: 12px;">
下载全部完成后在右下角显示通知 下载全部完成后在右下角显示通知
</span> </span>
...@@ -227,25 +189,17 @@ ...@@ -227,25 +189,17 @@
</div> </div>
<div style="margin-top: 20px;"> <div style="margin-top: 20px;">
<el-button <el-button type="success" size="large" :disabled="!canParse" @click="parseExcelFile" :loading="parsing">
type="success" <el-icon>
size="large" <Document />
:disabled="!canParse" </el-icon>
@click="parseExcelFile"
:loading="parsing"
>
<el-icon><Document /></el-icon>
{{ parsing ? '解析中...' : '解析并添加下载任务' }} {{ parsing ? '解析中...' : '解析并添加下载任务' }}
</el-button> </el-button>
<!-- 处理进度显示 --> <!-- 处理进度显示 -->
<div v-if="parsing && processingMessage" style="margin-top: 15px;"> <div v-if="parsing && processingMessage" style="margin-top: 15px;">
<el-progress <el-progress :percentage="processingProgress" :format="(percentage) => `${percentage}%`"
:percentage="processingProgress" :stroke-width="8" status="success" />
:format="(percentage) => `${percentage}%`"
:stroke-width="8"
status="success"
/>
<div style="margin-top: 8px; color: #666; font-size: 14px;"> <div style="margin-top: 8px; color: #666; font-size: 14px;">
{{ processingMessage }} {{ processingMessage }}
</div> </div>
...@@ -260,33 +214,44 @@ ...@@ -260,33 +214,44 @@
<div class="download-header"> <div class="download-header">
<div class="download-title"> <div class="download-title">
<h2 style="color: #333; margin: 0;"> <h2 style="color: #333; margin: 0;">
<el-icon><List /></el-icon> <el-icon>
<List />
</el-icon>
下载任务管理 下载任务管理
</h2> </h2>
<div class="download-stats"> <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="primary">等待: {{ downloadStore.pendingDownloads.length }}</el-tag>
<el-tag type="success">下载中: {{ downloadStore.activeDownloads.length }}</el-tag> <el-tag type="success">下载中: {{ downloadStore.activeDownloads.length }}</el-tag>
<el-tag type="warning">已完成: {{ downloadStore.completedDownloads.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> </div>
<div class="download-actions"> <div class="download-actions">
<!-- 状态筛选 --> <!-- 状态筛选 -->
<el-select <!-- <el-select v-model="statusFilter" placeholder="按状态筛选" clearable style="width: 150px; margin-right: 10px;"
v-model="statusFilter" @change="handleStatusFilterChange">
placeholder="按状态筛选"
clearable
style="width: 150px; margin-right: 10px;"
@change="handleStatusFilterChange"
>
<el-option label="全部" value="" /> <el-option label="全部" value="" />
<el-option label="等待中" value="pending" /> <el-option label="等待中" value="pending" />
<el-option label="下载中" value="downloading" /> <el-option label="下载中" value="downloading" />
<el-option label="已暂停" value="paused" /> <el-option label="已暂停" value="paused" />
<el-option label="已完成" value="completed" /> <el-option label="已完成" value="completed" />
<el-option label="失败" value="error" /> <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-select>
<!-- 页面级别操作 --> <!-- 页面级别操作 -->
...@@ -297,10 +262,16 @@ ...@@ -297,10 +262,16 @@
暂停当前页 暂停当前页
</el-button> </el-button>
<el-divider direction="vertical" /> <el-divider direction="vertical" />
<el-button @click="downloadStore.clearCompleted" size="small"> <!-- <el-button @click="downloadStore.clearCompleted" size="small">
清除已完成
</el-button> -->
<el-button @click="clearCompleted" size="small">
清除已完成 清除已完成
</el-button> </el-button>
<el-button @click="downloadStore.clearAll" type="danger" size="small"> <!-- <el-button @click="downloadStore.clearAll" type="danger" size="small">
清除所有
</el-button> -->
<el-button @click="clearAll" type="danger" size="small">
清除所有 清除所有
</el-button> </el-button>
</div> </div>
...@@ -308,26 +279,25 @@ ...@@ -308,26 +279,25 @@
<!-- 下载任务列表 --> <!-- 下载任务列表 -->
<div v-if="downloadStore.downloads.length === 0" class="empty-state"> <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> <h3>暂无下载任务</h3>
<p>请在上方配置区域上传Excel文件并解析添加下载任务</p> <p>请在上方配置区域上传Excel文件并解析添加下载任务</p>
</div> </div>
<div v-else class="download-table-container"> <div v-else class="download-table-container">
<!-- 分页表格 --> <!-- 分页表格 -->
<el-table <!-- :data="paginatedDownloads" -->
:data="paginatedDownloads" <el-table :data="deTableData" style="width: 100%" border stripe highlight-current-row>
style="width: 100%"
border
stripe
highlight-current-row
>
<el-table-column label="Excel文件" width="180"> <el-table-column label="Excel文件" width="180">
<template #default="{ row }"> <template #default="{ row }">
<div class="excel-file-info"> <div class="excel-file-info">
<el-icon><Document /></el-icon> <el-icon>
<span class="excel-file-name">{{ row.excelFileName || '-' }}</span> <Document />
</el-icon>
<span class="excel-file-name">{{ row.uploadName || '-' }}</span>
</div> </div>
</template> </template>
</el-table-column> </el-table-column>
...@@ -336,14 +306,19 @@ ...@@ -336,14 +306,19 @@
<template #default="{ row }"> <template #default="{ row }">
<div class="file-info"> <div class="file-info">
<div class="file-icon"> <div class="file-icon">
<el-icon><Document /></el-icon> <el-icon>
<Document />
</el-icon>
</div> </div>
<div class="file-details"> <div class="file-details">
<div class="file-name"> <div class="file-name">
<span v-if="downloadStore.customSubFolder" class="file-prefix"> <!-- <span v-if="downloadStore.customSubFolder" class="file-prefix">
{{ downloadStore.customSubFolder }}_ {{ downloadStore.customSubFolder }}_
</span> -->
<span v-if="row.fileName" class="file-prefix">
{{ row.fileName }}_
</span> </span>
{{ row.fileName }} {{ row.fileUrl }}
</div> </div>
<div class="file-url">{{ row.url }}</div> <div class="file-url">{{ row.url }}</div>
</div> </div>
...@@ -353,24 +328,17 @@ ...@@ -353,24 +328,17 @@
<el-table-column label="状态" width="100" align="center"> <el-table-column label="状态" width="100" align="center">
<template #default="{ row }"> <template #default="{ row }">
<el-tag <el-tag :class="`status-badge status-${row.status}`" size="small">
:class="`status-badge status-${row.status}`" {{ getStatusText(row.fileStatus) }}
size="small"
>
{{ getStatusText(row.status) }}
</el-tag> </el-tag>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="进度" width="200" align="center"> <!-- <el-table-column label="进度" width="200" align="center">
<template #default="{ row }"> <template #default="{ row }">
<div v-if="row.status === 'downloading'" class="progress-container"> <div v-if="row.status === 'downloading'" class="progress-container">
<el-progress <el-progress :percentage="row.progress" :format="(percentage) => `${percentage}%`" size="small"
:percentage="row.progress" :stroke-width="8" />
:format="(percentage) => `${percentage}%`"
size="small"
:stroke-width="8"
/>
<div class="speed-info"> <div class="speed-info">
{{ formatSpeed(row.speed) }} {{ formatSpeed(row.speed) }}
</div> </div>
...@@ -382,61 +350,44 @@ ...@@ -382,61 +350,44 @@
{{ row.progress }}% {{ row.progress }}%
</span> </span>
</template> </template>
</el-table-column> </el-table-column> -->
<el-table-column label="大小" width="120" align="center"> <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"> <span v-if="row.totalBytes > 0" class="size-info">
{{ formatBytes(row.downloadedBytes) }} / {{ formatBytes(row.totalBytes) }} {{ formatBytes(row.downloadedBytes) }} / {{ formatBytes(row.totalBytes) }}
</span> </span>
<span v-else class="size-info">-</span> <span v-else class="size-info">-</span>
</template> </template> -->
</el-table-column> </el-table-column>
<el-table-column label="操作" width="200" align="center" fixed="right"> <el-table-column label="操作" width="200" align="center" fixed="right">
<template #default="{ row }"> <template #default="{ row }">
<div class="download-actions"> <div class="download-actions">
<el-button <el-button v-if="row.status === 'pending'" @click="startSingleDownload(row.id)" type="primary"
v-if="row.status === 'pending'" size="small">
@click="startSingleDownload(row.id)"
type="primary"
size="small"
>
开始 开始
</el-button> </el-button>
<el-button <el-button v-if="row.status === 'downloading'" @click="downloadStore.pauseDownload(row.id)"
v-if="row.status === 'downloading'" type="warning" size="small">
@click="downloadStore.pauseDownload(row.id)"
type="warning"
size="small"
>
暂停 暂停
</el-button> </el-button>
<el-button <el-button v-if="row.status === 'paused'" @click="resumeSingleDownload(row.id)" type="success"
v-if="row.status === 'paused'" size="small">
@click="resumeSingleDownload(row.id)"
type="success"
size="small"
>
继续 继续
</el-button> </el-button>
<el-button <el-button v-if="row.status === 'error'" @click="startSingleDownload(row.id)" type="warning"
v-if="row.status === 'error'" size="small">
@click="startSingleDownload(row.id)"
type="warning"
size="small"
>
重试 重试
</el-button> </el-button>
<el-button <!-- <el-button @click="downloadStore.cancelDownload(row.id)" type="danger" size="small">
@click="downloadStore.cancelDownload(row.id)" 删除
type="danger" </el-button> -->
size="small" <el-button @click="deleteRow(row)" type="danger" size="small">
>
删除 删除
</el-button> </el-button>
</div> </div>
...@@ -446,15 +397,10 @@ ...@@ -446,15 +397,10 @@
<!-- 分页器 --> <!-- 分页器 -->
<div class="pagination-container"> <div class="pagination-container">
<el-pagination <el-pagination v-model:current-page="deQuery.current" v-model:page-size="deQuery.pageSize"
v-model:current-page="currentPage" :page-sizes="[10, 20, 50, 100]" :total="deTotal"
v-model:page-size="pageSize" layout="total, sizes, prev, pager, next, jumper" @size-change="handleSizeChange"
:page-sizes="[10, 20, 50, 100]" @current-change="handleCurrentChange" />
:total="filteredDownloads.length"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handleCurrentChange"
/>
</div> </div>
</div> </div>
</div> </div>
...@@ -463,13 +409,17 @@ ...@@ -463,13 +409,17 @@
</template> </template>
<script setup> <script setup>
import { ref, computed, onMounted } from 'vue' import { ref, computed, onMounted, reactive } from 'vue'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { Download, Upload, Document, List, Warning, User, ArrowDown } from '@element-plus/icons-vue' import { Download, Upload, Document, List, Warning, User, ArrowDown } from '@element-plus/icons-vue'
import { useAuthStore } from '../stores/auth' import { useAuthStore } from '../stores/auth'
import { useDownloadStore } from '../stores/download' import { useDownloadStore } from '../stores/download'
import * as XLSX from 'xlsx' 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 router = useRouter()
const authStore = useAuthStore() const authStore = useAuthStore()
...@@ -489,6 +439,21 @@ const activeCollapse = ref(['upload']) // 默认展开配置区域 ...@@ -489,6 +439,21 @@ const activeCollapse = ref(['upload']) // 默认展开配置区域
// 分页相关 // 分页相关
const currentPage = ref(1) const currentPage = ref(1)
const pageSize = ref(10) 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({ const columnMapping = ref({
fileNameColumns: [], fileNameColumns: [],
...@@ -506,7 +471,20 @@ const canParse = computed(() => { ...@@ -506,7 +471,20 @@ const canParse = computed(() => {
previewData.value.length > 0 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(() => { const paginatedDownloads = computed(() => {
...@@ -527,35 +505,58 @@ const filteredDownloads = computed(() => { ...@@ -527,35 +505,58 @@ const filteredDownloads = computed(() => {
// 选择Excel文件 // 选择Excel文件
const selectExcelFile = async (fileId) => { const selectExcelFile = async (fileId) => {
try { // try {
const fileInfo = downloadStore.getExcelFileInfo(fileId) // const fileInfo = downloadStore.getExcelFileInfo(fileId)
if (!fileInfo) { // 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('文件信息不存在') ElMessage.error('文件信息不存在')
return return
} }
selectedFile.value = {
downloadStore.setCurrentExcelFile(fileId) name:res.data.uploadName,
// 加载第一页数据
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])
} }
excelColumns.value = res.data.fileColumnNameAll || []
// 生成预览数据 columnMapping.value.fileNameColumns = res.data.fileColumnName || []
generatePreviewData() columnMapping.value.url = res.data.urlColumnName
rawExcelData.value = res.data.details || []
ElMessage.success(`已选择文件: ${fileInfo.fileName}`) if (res.data.details.length>5){
} catch (error) { previewData.value = res.data.details.slice(0,5)
console.error('选择Excel文件失败:', error) }else {
ElMessage.error('选择Excel文件失败: ' + error.message) previewData.value = res.data.details || []
} }
customSubFolder.value = res.data.filePrefix || ''
} else {
ElMessage.error(res.message)
}
})
} }
// 删除Excel文件 // 删除Excel文件
...@@ -566,13 +567,21 @@ const deleteExcelFile = async (fileId) => { ...@@ -566,13 +567,21 @@ const deleteExcelFile = async (fileId) => {
return return
} }
try { // try {
await downloadStore.deleteExcelFile(fileId) // 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文件已删除') ElMessage.success('Excel文件已删除')
} catch (error) { loadUpTableData()
console.error('删除Excel文件失败:', error) } else {
ElMessage.error('删除Excel文件失败: ' + (error.message || '未知错误')) ElMessage.error(res.message)
} }
})
} }
// 处理文件选择 // 处理文件选择
...@@ -813,10 +822,90 @@ onMounted(() => { ...@@ -813,10 +822,90 @@ onMounted(() => {
customSubFolder.value = downloadStore.customSubFolder || '' customSubFolder.value = downloadStore.customSubFolder || ''
detectBrowserDownloadPath() 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文件 // 解析Excel文件
const parseExcelFile = async () => { const parseExcelFile = async () => {
if (rawExcelData.value.length > 8000){
ElMessage.error('文件数据量太大,请拆分处理,每次不超过3000条')
return
}
if (!canParse.value) { if (!canParse.value) {
ElMessage.warning('请先选择Excel文件并配置列映射') ElMessage.warning('请先选择Excel文件并配置列映射')
return return
...@@ -835,7 +924,6 @@ const parseExcelFile = async () => { ...@@ -835,7 +924,6 @@ const parseExcelFile = async () => {
// 获取完整数据(从原始数据或当前数据) // 获取完整数据(从原始数据或当前数据)
const allData = window.originalExcelData || rawExcelData.value const allData = window.originalExcelData || rawExcelData.value
// 过滤有效数据(分批处理) // 过滤有效数据(分批处理)
const batchSize = 1000 // 每批处理1000行 const batchSize = 1000 // 每批处理1000行
const validData = [] const validData = []
...@@ -896,6 +984,8 @@ const parseExcelFile = async () => { ...@@ -896,6 +984,8 @@ const parseExcelFile = async () => {
type: 'info' type: 'info'
} }
) )
// 上传后端
uploadDetails()
// 临时禁用自动下载 // 临时禁用自动下载
const originalBatchDownloading = downloadStore.isBatchDownloading const originalBatchDownloading = downloadStore.isBatchDownloading
...@@ -964,7 +1054,81 @@ const parseExcelFile = async () => { ...@@ -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 () => { const downloadCurrentPage = async () => {
...@@ -998,10 +1162,33 @@ const downloadCurrentPage = async () => { ...@@ -998,10 +1162,33 @@ const downloadCurrentPage = async () => {
await downloadStore.batchStartDownloads(currentPageIds) await downloadStore.batchStartDownloads(currentPageIds)
ElMessage.success(`已开始下载当前页的 ${startableItems.length} 个任务`) 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 { } 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 () => { const pauseCurrentPage = async () => {
...@@ -1044,18 +1231,31 @@ const pauseCurrentPage = async () => { ...@@ -1044,18 +1231,31 @@ const pauseCurrentPage = async () => {
// 处理分页大小变化 // 处理分页大小变化
const handleSizeChange = (size) => { const handleSizeChange = (size) => {
pageSize.value = size deQuery.value.pageSize = size
currentPage.value = 1 deQuery.value.current = 1
loadDetailTableData()
} }
// 处理当前页变化 // 处理当前页变化
const handleCurrentChange = (page) => { 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 = () => { const handleStatusFilterChange = () => {
currentPage.value = 1 // 重置当前页为1,以便重新加载数据 // currentPage.value = 1 // 重置当前页为1,以便重新加载数据
deQuery.value.current = 1
} }
// 单个文件下载 // 单个文件下载
...@@ -1221,11 +1421,16 @@ const readExcelFile = (file) => { ...@@ -1221,11 +1421,16 @@ const readExcelFile = (file) => {
// 获取状态文本 // 获取状态文本
const getStatusText = (status) => { const getStatusText = (status) => {
const statusMap = { const statusMap = {
pending: '等待中', // pending: '等待中',
downloading: '下载中', // downloading: '下载中',
completed: '已完成', // completed: '已完成',
error: '失败', // error: '失败',
paused: '已暂停' // paused: '已暂停'
0:'等待中',
1:'下载中' ,
2:'已暂停' ,
3:'已完成',
4:'失败'
} }
return statusMap[status] || status return statusMap[status] || status
} }
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
<div class="login-box"> <div class="login-box">
<div class="login-header"> <div class="login-header">
<el-icon class="logo-icon"> <el-icon class="logo-icon">
<Download/> <Download />
</el-icon> </el-icon>
<h1>文件下载器</h1> <h1>文件下载器</h1>
<p>登录或注册以开始使用</p> <p>登录或注册以开始使用</p>
...@@ -11,133 +11,62 @@ ...@@ -11,133 +11,62 @@
<!-- 切换按钮 --> <!-- 切换按钮 -->
<div class="mode-switch"> <div class="mode-switch">
<el-button <el-button :type="isLoginMode ? 'primary' : 'default'" @click="switchToLogin" :disabled="isLoginMode">
:type="isLoginMode ? 'primary' : 'default'"
@click="switchToLogin"
:disabled="isLoginMode"
>
登录 登录
</el-button> </el-button>
<el-button <el-button :type="!isLoginMode ? 'primary' : 'default'" @click="switchToRegister" :disabled="!isLoginMode">
:type="!isLoginMode ? 'primary' : 'default'"
@click="switchToRegister"
:disabled="!isLoginMode"
>
注册 注册
</el-button> </el-button>
</div> </div>
<!-- 错误消息显示 --> <!-- 错误消息显示 -->
<div v-if="errorMessage" class="error-message"> <div v-if="errorMessage" class="error-message">
<el-alert <el-alert :title="errorMessage" type="error" :closable="false" show-icon />
:title="errorMessage"
type="error"
:closable="false"
show-icon
/>
</div> </div>
<!-- 成功消息显示 --> <!-- 成功消息显示 -->
<div v-if="successMessage" class="success-message"> <div v-if="successMessage" class="success-message">
<el-alert <el-alert :title="successMessage" type="success" :closable="false" show-icon />
:title="successMessage"
type="success"
:closable="false"
show-icon
/>
</div> </div>
<!-- 登录表单 --> <!-- 登录表单 -->
<el-form <el-form v-if="isLoginMode" ref="loginFormRef" :model="loginForm" :rules="loginRules" class="login-form"
v-if="isLoginMode" @submit.prevent="handleLogin">
ref="loginFormRef" <el-form-item prop="userName">
:model="loginForm" <el-input v-model="loginForm.userName" placeholder="用户名" prefix-icon="User" size="large" />
: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>
<el-form-item prop="password"> <el-form-item prop="password">
<el-input <el-input v-model="loginForm.password" type="password" placeholder="密码" prefix-icon="Lock" size="large"
v-model="loginForm.password" show-password @keyup.enter="handleLogin" />
type="password"
placeholder="密码"
prefix-icon="Lock"
size="large"
show-password
@keyup.enter="handleLogin"
/>
</el-form-item> </el-form-item>
<el-form-item> <el-form-item>
<el-button <el-button type="primary" size="large" class="submit-btn" :loading="loading" @click="handleLogin">
type="primary"
size="large"
class="submit-btn"
:loading="loading"
@click="handleLogin"
>
登录 登录
</el-button> </el-button>
</el-form-item> </el-form-item>
</el-form> </el-form>
<!-- 注册表单 --> <!-- 注册表单 -->
<el-form <el-form v-else ref="registerFormRef" :model="registerForm" :rules="registerRules" class="login-form"
v-else @submit.prevent="handleRegister">
ref="registerFormRef" <el-form-item prop="userName">
:model="registerForm" <el-input v-model="registerForm.userName" placeholder="用户名(至少3个字符)" prefix-icon="User" size="large" />
: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>
<el-form-item prop="email"> <el-form-item prop="email">
<el-input <el-input v-model="registerForm.email" placeholder="邮箱(可选)" prefix-icon="Message" size="large" />
v-model="registerForm.email"
placeholder="邮箱(可选)"
prefix-icon="Message"
size="large"
/>
</el-form-item> </el-form-item>
<el-form-item prop="password"> <el-form-item prop="password">
<el-input <el-input v-model="registerForm.password" type="password" placeholder="密码" prefix-icon="Lock" size="large"
v-model="registerForm.password" show-password />
type="password"
placeholder="密码"
prefix-icon="Lock"
size="large"
show-password
/>
</el-form-item> </el-form-item>
<el-form-item prop="confirmPassword"> <el-form-item prop="confirmPassword">
<el-input <el-input v-model="registerForm.confirmPassword" type="password" placeholder="确认密码" prefix-icon="Lock"
v-model="registerForm.confirmPassword" size="large" show-password @keyup.enter="handleRegister" />
type="password"
placeholder="确认密码"
prefix-icon="Lock"
size="large"
show-password
@keyup.enter="handleRegister"
/>
</el-form-item> </el-form-item>
<!-- 密码强度提示 --> <!-- 密码强度提示 -->
...@@ -146,29 +75,29 @@ ...@@ -146,29 +75,29 @@
<div class="strength-items"> <div class="strength-items">
<div class="strength-item" :class="{ valid: registerForm.password.length >= 6 }"> <div class="strength-item" :class="{ valid: registerForm.password.length >= 6 }">
<el-icon> <el-icon>
<Check v-if="registerForm.password.length >= 6"/> <Check v-if="registerForm.password.length >= 6" />
<Close v-else/> <Close v-else />
</el-icon> </el-icon>
至少6位 至少6位
</div> </div>
<div class="strength-item" :class="{ valid: /\d/.test(registerForm.password) }"> <div class="strength-item" :class="{ valid: /\d/.test(registerForm.password) }">
<el-icon> <el-icon>
<Check v-if="/\d/.test(registerForm.password)"/> <Check v-if="/\d/.test(registerForm.password)" />
<Close v-else/> <Close v-else />
</el-icon> </el-icon>
包含数字 包含数字
</div> </div>
<div class="strength-item" :class="{ valid: /[a-zA-Z]/.test(registerForm.password) }"> <div class="strength-item" :class="{ valid: /[a-zA-Z]/.test(registerForm.password) }">
<el-icon> <el-icon>
<Check v-if="/[a-zA-Z]/.test(registerForm.password)"/> <Check v-if="/[a-zA-Z]/.test(registerForm.password)" />
<Close v-else/> <Close v-else />
</el-icon> </el-icon>
包含字母 包含字母
</div> </div>
<div class="strength-item" :class="{ valid: hasSpecialChar }"> <div class="strength-item" :class="{ valid: hasSpecialChar }">
<el-icon> <el-icon>
<Check v-if="hasSpecialChar"/> <Check v-if="hasSpecialChar" />
<Close v-else/> <Close v-else />
</el-icon> </el-icon>
包含特殊字符 包含特殊字符
</div> </div>
...@@ -176,13 +105,7 @@ ...@@ -176,13 +105,7 @@
</div> </div>
<el-form-item> <el-form-item>
<el-button <el-button type="primary" size="large" class="submit-btn" :loading="loading" @click="handleRegister">
type="primary"
size="large"
class="submit-btn"
:loading="loading"
@click="handleRegister"
>
注册 注册
</el-button> </el-button>
</el-form-item> </el-form-item>
...@@ -194,11 +117,13 @@ ...@@ -194,11 +117,13 @@
</template> </template>
<script setup> <script setup>
import {computed, nextTick, onMounted, reactive, ref} from 'vue' import { computed, nextTick, onMounted, reactive, ref } from 'vue'
import {useRouter} from 'vue-router' import { useRouter } from 'vue-router'
import {ElMessage, ElMessageBox} from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import {Check, Close, Download} from '@element-plus/icons-vue' import { Check, Close, Download } from '@element-plus/icons-vue'
import {useAuthStore} from '../stores/auth' import { useAuthStore } from '../stores/auth'
import http from '@/utils/request.js';
import config from '@/api/api.js';
const router = useRouter() const router = useRouter()
const authStore = useAuthStore() const authStore = useAuthStore()
...@@ -220,13 +145,13 @@ const hasSpecialChar = computed(() => { ...@@ -220,13 +145,13 @@ const hasSpecialChar = computed(() => {
// 登录表单 // 登录表单
const loginForm = reactive({ const loginForm = reactive({
username: '', userName: '',
password: '' password: ''
}) })
// 注册表单 // 注册表单
const registerForm = reactive({ const registerForm = reactive({
username: '', userName: '',
email: '', email: '',
password: '', password: '',
confirmPassword: '' confirmPassword: ''
...@@ -234,26 +159,26 @@ const registerForm = reactive({ ...@@ -234,26 +159,26 @@ const registerForm = reactive({
// 登录验证规则 // 登录验证规则
const loginRules = { const loginRules = {
username: [ userName: [
{required: true, message: '请输入用户名', trigger: 'blur'} { required: true, message: '请输入用户名', trigger: 'blur' }
], ],
password: [ password: [
{required: true, message: '请输入密码', trigger: 'blur'} { required: true, message: '请输入密码', trigger: 'blur' }
] ]
} }
// 注册验证规则 // 注册验证规则
const registerRules = { const registerRules = {
username: [ userName: [
{required: true, message: '请输入用户名', trigger: 'blur'}, { required: true, message: '请输入用户名', trigger: 'blur' },
{min: 3, message: '用户名至少3个字符', trigger: 'blur'} { min: 3, message: '用户名至少3个字符', trigger: 'blur' }
], ],
email: [ email: [
{type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur'} { type: 'email', message: '请输入正确的邮箱格式', trigger: 'blur' }
], ],
password: [ password: [
{required: true, message: '请输入密码', trigger: 'blur'}, { required: true, message: '请输入密码', trigger: 'blur' },
{min: 6, message: '密码至少6位', trigger: 'blur'}, { min: 6, message: '密码至少6位', trigger: 'blur' },
{ {
validator: (rule, value, callback) => { validator: (rule, value, callback) => {
if (!/\d/.test(value)) { if (!/\d/.test(value)) {
...@@ -270,7 +195,7 @@ const registerRules = { ...@@ -270,7 +195,7 @@ const registerRules = {
} }
], ],
confirmPassword: [ confirmPassword: [
{required: true, message: '请确认密码', trigger: 'blur'}, { required: true, message: '请确认密码', trigger: 'blur' },
{ {
validator: (rule, value, callback) => { validator: (rule, value, callback) => {
if (value !== registerForm.password) { if (value !== registerForm.password) {
...@@ -306,8 +231,8 @@ const clearMessages = () => { ...@@ -306,8 +231,8 @@ const clearMessages = () => {
// 重置表单 // 重置表单
const resetForms = () => { const resetForms = () => {
Object.assign(loginForm, {username: '', password: ''}) Object.assign(loginForm, { userName: '', password: '' })
Object.assign(registerForm, {username: '', email: '', password: '', confirmPassword: ''}) Object.assign(registerForm, { userName: '', email: '', password: '', confirmPassword: '' })
if (loginFormRef.value) { if (loginFormRef.value) {
loginFormRef.value.clearValidate() loginFormRef.value.clearValidate()
...@@ -318,11 +243,34 @@ const resetForms = () => { ...@@ -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 { try {
console.log('开始登录处理...') console.log('开始登录处理...')
// 检查用户名和密码是否为空 // 检查用户名和密码是否为空
if (!loginForm.username.trim()) { if (!loginForm.userName.trim()) {
ElMessage.warning('请输入用户名') ElMessage.warning('请输入用户名')
return return
} }
...@@ -330,6 +278,7 @@ const handleLogin = async () => { ...@@ -330,6 +278,7 @@ const handleLogin = async () => {
ElMessage.warning('请输入密码') ElMessage.warning('请输入密码')
return return
} }
// 等待 DOM 更新,确保表单引用可用 // 等待 DOM 更新,确保表单引用可用
await nextTick() await nextTick()
// 检查表单引用是否存在,如果不存在则跳过验证 // 检查表单引用是否存在,如果不存在则跳过验证
...@@ -348,12 +297,25 @@ const handleLogin = async () => { ...@@ -348,12 +297,25 @@ const handleLogin = async () => {
loading.value = true loading.value = true
clearMessages() clearMessages()
console.log('登录表单验证通过,用户名:', loginForm.username) console.log('登录表单验证通过,用户名:', loginForm.userName)
await authStore.login(loginForm.username, loginForm.password) await authStore.login(loginForm.userName, loginForm.password)
console.log('登录成功,跳转到下载器页面') // 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('登录成功!') ElMessage.success('登录成功!')
sessionStorage.setItem("token", res.data)
router.push('/downloader') router.push('/downloader')
// console.log('router:',router)
} else {
ElMessage.error(res.message)
}
})
} catch (error) { } catch (error) {
console.error('登录失败:', error) console.error('登录失败:', error)
...@@ -391,10 +353,40 @@ const handleLogin = async () => { ...@@ -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 { try {
// 检查用户名和密码是否为空 // 检查用户名和密码是否为空
if (!registerForm.username.trim()) { if (!registerForm.userName.trim()) {
ElMessage.warning('请输入用户名') ElMessage.warning('请输入用户名')
return return
} }
...@@ -429,14 +421,14 @@ const handleRegister = async () => { ...@@ -429,14 +421,14 @@ const handleRegister = async () => {
clearMessages() clearMessages()
// 检查用户名是否已存在 // 检查用户名是否已存在
if (authStore.isUsernameExists(registerForm.username)) { if (authStore.isUsernameExists(registerForm.userName)) {
ElMessage.error('用户名已存在') ElMessage.error('用户名已存在')
errorMessage.value = '用户名已存在' errorMessage.value = '用户名已存在'
return return
} }
// 注册用户 // 注册用户
await authStore.register(registerForm.username, registerForm.password, registerForm.email) await authStore.register(registerForm.userName, registerForm.password, registerForm.email)
ElMessage.success('注册成功!请登录') ElMessage.success('注册成功!请登录')
successMessage.value = '注册成功!请登录' successMessage.value = '注册成功!请登录'
...@@ -444,7 +436,7 @@ const handleRegister = async () => { ...@@ -444,7 +436,7 @@ const handleRegister = async () => {
// 自动切换到登录模式 // 自动切换到登录模式
setTimeout(() => { setTimeout(() => {
switchToLogin() switchToLogin()
loginForm.username = registerForm.username loginForm.userName = registerForm.userName
}, 2000) }, 2000)
} catch (error) { } catch (error) {
......
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
<el-form :model="userInfo" label-width="120px"> <el-form :model="userInfo" label-width="120px">
<el-form-item label="用户名"> <el-form-item label="用户名">
<el-input v-model="userInfo.username" disabled /> <el-input v-model="userInfo.userName" disabled />
</el-form-item> </el-form-item>
<el-form-item label="邮箱"> <el-form-item label="邮箱">
...@@ -121,7 +121,7 @@ const authStore = useAuthStore() ...@@ -121,7 +121,7 @@ const authStore = useAuthStore()
// 用户信息 // 用户信息
const userInfo = reactive({ const userInfo = reactive({
username: '', userName: '',
email: '', email: '',
createdAt: '', createdAt: '',
lastLoginAt: '' lastLoginAt: ''
...@@ -138,7 +138,7 @@ const downloadSettings = reactive({ ...@@ -138,7 +138,7 @@ const downloadSettings = reactive({
const initializeData = () => { const initializeData = () => {
const user = authStore.user const user = authStore.user
if (user) { if (user) {
userInfo.username = user.username userInfo.userName = user.userName
userInfo.email = user.email || '未设置' userInfo.email = user.email || '未设置'
userInfo.createdAt = new Date(user.createdAt).toLocaleString() userInfo.createdAt = new Date(user.createdAt).toLocaleString()
userInfo.lastLoginAt = user.lastLoginAt ? new Date(user.lastLoginAt).toLocaleString() : '从未登录' userInfo.lastLoginAt = user.lastLoginAt ? new Date(user.lastLoginAt).toLocaleString() : '从未登录'
......
...@@ -11,7 +11,14 @@ export default defineConfig({ ...@@ -11,7 +11,14 @@ export default defineConfig({
}, },
server: { server: {
port: 3000, port: 3000,
open: true open: true,
proxy: {
'/api': {
target: 'http://39.107.245.36:8081',
changeOrigin: true,
rewrite: path => path.replace(/^\/api/, '')
}
}
}, },
build: { build: {
outDir: 'dist', outDir: 'dist',
......
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