任务管理页面后端优化:提升进度更新频率和状态详细程度
1. 提高爬取阶段进度更新频率:从每10个改为每5个代理更新一次 2. 提高验证阶段进度更新频率:从每5个改为每验证1个代理就更新一次 3. 添加进度百分比计算所需字段:在progress消息中添加current和total字段 4. 增强状态信息详细程度: - 添加connecting状态:正在连接插件源 - 添加starting状态:正在启动爬虫 - 添加crawling_start状态:开始爬取代理 - 添加validating_start状态:开始验证代理 - 在进度消息中添加message字段,显示更详细的进度描述 这些改进可以让前端显示更实时、更详细的任务进度和状态信息
This commit is contained in:
@@ -6,6 +6,19 @@ const api = axios.create({
|
||||
timeout: 30000
|
||||
})
|
||||
|
||||
api.interceptors.request.use(
|
||||
config => {
|
||||
const apiKey = localStorage.getItem('api_key')
|
||||
if (apiKey) {
|
||||
config.headers['X-API-Key'] = apiKey
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => {
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
api.interceptors.response.use(
|
||||
response => response.data,
|
||||
error => {
|
||||
@@ -20,14 +33,22 @@ export const statsAPI = {
|
||||
}
|
||||
|
||||
export const proxiesAPI = {
|
||||
getProxies: (params) => api.post('/api/proxies', params),
|
||||
getProxies: (params) => {
|
||||
const cleanedParams = {}
|
||||
Object.keys(params).forEach(key => {
|
||||
if (params[key] !== null && params[key] !== undefined && params[key] !== '') {
|
||||
cleanedParams[key] = params[key]
|
||||
}
|
||||
})
|
||||
return api.post('/api/proxies', cleanedParams)
|
||||
},
|
||||
getRandomProxy: () => api.get('/api/proxies/random'),
|
||||
getProxyDetail: (ip, port) => api.get(`/api/proxies/${ip}/${port}`),
|
||||
deleteProxy: (ip, port) => api.delete(`/api/proxies/${ip}/${port}`),
|
||||
batchDeleteProxies: (proxies) => api.post('/api/proxies/batch-delete', { proxies }),
|
||||
cleanInvalidProxies: () => api.delete('/api/proxies/clean-invalid'),
|
||||
exportProxies: (format, protocol) => api.get(`/api/proxies/export/${format}`, {
|
||||
params: { protocol },
|
||||
params: protocol ? { protocol } : {},
|
||||
responseType: 'blob'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { createApp } from 'vue'
|
||||
import { createPinia } from 'pinia'
|
||||
import ElementPlus from 'element-plus'
|
||||
import zhCn from 'element-plus/es/locale/lang/zh-cn'
|
||||
import 'element-plus/dist/index.css'
|
||||
import router from './router'
|
||||
import './style.css'
|
||||
@@ -12,6 +13,8 @@ const pinia = createPinia()
|
||||
|
||||
app.use(pinia)
|
||||
app.use(router)
|
||||
app.use(ElementPlus)
|
||||
app.use(ElementPlus, {
|
||||
locale: zhCn,
|
||||
})
|
||||
|
||||
app.mount('#app')
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<el-card class="filter-card" shadow="hover">
|
||||
<el-form :inline="true" :model="filterForm" class="form-row">
|
||||
<el-form-item label="协议类型">
|
||||
<el-select v-model="filterForm.protocol" placeholder="全部" clearable style="width: 120px">
|
||||
<el-select v-model="filterForm.protocol" placeholder="全部" clearable style="width: 120px" @change="handleSearch">
|
||||
<el-option label="全部" value=""></el-option>
|
||||
<el-option label="HTTP" value="http"></el-option>
|
||||
<el-option label="HTTPS" value="https"></el-option>
|
||||
@@ -14,24 +14,14 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="最低分数">
|
||||
<el-input-number v-model="filterForm.minScore" :min="0" :max="10" style="width: 120px" />
|
||||
<el-input-number v-model="filterForm.minScore" :min="0" :max="10" style="width: 120px" @change="handleSearch" />
|
||||
</el-form-item>
|
||||
<el-form-item label="排序方式">
|
||||
<el-select v-model="filterForm.sortBy" style="width: 140px">
|
||||
<el-select v-model="filterForm.sortBy" style="width: 140px" @change="handleSearch">
|
||||
<el-option label="更新时间" value="last_check"></el-option>
|
||||
<el-option label="分数" value="score"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" @click="handleSearch">
|
||||
<span class="btn-icon">🔍</span>
|
||||
搜索
|
||||
</el-button>
|
||||
<el-button @click="handleReset">
|
||||
<span class="btn-icon">🔄</span>
|
||||
重置
|
||||
</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
@@ -40,19 +30,24 @@
|
||||
<div class="card-header">
|
||||
<span class="card-title">代理详情</span>
|
||||
<div class="header-actions">
|
||||
<el-button type="danger" size="small" @click="handleBatchDelete" :disabled="selectedProxies.length === 0">
|
||||
批量删除
|
||||
</el-button>
|
||||
<el-dropdown @command="handleExport" split-button type="success">
|
||||
导出
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="txt">TXT格式</el-dropdown-item>
|
||||
<el-dropdown-item command="csv">CSV格式</el-dropdown-item>
|
||||
<el-dropdown-item command="json">JSON格式</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
<el-button-group>
|
||||
<el-button type="danger" @click="handleBatchDelete" :disabled="selectedProxies.length === 0">
|
||||
批量删除
|
||||
</el-button>
|
||||
<el-dropdown trigger="click" @command="handleExport">
|
||||
<el-button type="success">
|
||||
导出
|
||||
<el-icon class="el-icon--right"><component :is="ArrowDownIcon" /></el-icon>
|
||||
</el-button>
|
||||
<template #dropdown>
|
||||
<el-dropdown-menu>
|
||||
<el-dropdown-item command="txt">TXT格式</el-dropdown-item>
|
||||
<el-dropdown-item command="csv">CSV格式</el-dropdown-item>
|
||||
<el-dropdown-item command="json">JSON格式</el-dropdown-item>
|
||||
</el-dropdown-menu>
|
||||
</template>
|
||||
</el-dropdown>
|
||||
</el-button-group>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -76,15 +71,14 @@
|
||||
</el-table-column>
|
||||
<el-table-column prop="score" label="分数" width="100">
|
||||
<template #default="scope">
|
||||
<el-rate
|
||||
:model-value="scope.row.score || 0"
|
||||
disabled
|
||||
show-score
|
||||
:score-template="scope.row.score ? '{value}' : '0'"
|
||||
/>
|
||||
<span class="score-value">{{ scope.row.score || 0 }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="last_check" label="最后检查时间">
|
||||
<template #default="scope">
|
||||
{{ formatDateTime(scope.row.last_check) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="last_check" label="最后检查时间" />
|
||||
<el-table-column label="操作" width="200" fixed="right">
|
||||
<template #default="scope">
|
||||
<el-button
|
||||
@@ -123,10 +117,13 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { ArrowDown } from '@element-plus/icons-vue'
|
||||
import { useProxyStore } from '../stores/proxy'
|
||||
import PageHeader from '../components/PageHeader.vue'
|
||||
|
||||
const proxyStore = useProxyStore()
|
||||
const ArrowDownIcon = ArrowDown
|
||||
|
||||
const proxyStore = useProxyStore()
|
||||
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(20)
|
||||
@@ -149,11 +146,23 @@ function getProtocolType(protocol) {
|
||||
return types[protocol] || 'info'
|
||||
}
|
||||
|
||||
function formatDateTime(dateTimeStr) {
|
||||
if (!dateTimeStr) return '-'
|
||||
const date = new Date(dateTimeStr)
|
||||
const year = date.getFullYear()
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0')
|
||||
const day = String(date.getDate()).padStart(2, '0')
|
||||
const hours = String(date.getHours()).padStart(2, '0')
|
||||
const minutes = String(date.getMinutes()).padStart(2, '0')
|
||||
const seconds = String(date.getSeconds()).padStart(2, '0')
|
||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`
|
||||
}
|
||||
|
||||
async function fetchProxies() {
|
||||
await proxyStore.fetchProxies({
|
||||
page: currentPage.value,
|
||||
page_size: pageSize.value,
|
||||
protocol: filterForm.protocol || undefined,
|
||||
protocol: filterForm.protocol || null,
|
||||
min_score: filterForm.minScore,
|
||||
sort_by: filterForm.sortBy,
|
||||
sort_order: filterForm.sortOrder
|
||||
@@ -223,7 +232,7 @@ async function handleBatchDelete() {
|
||||
}
|
||||
|
||||
async function handleExport(format) {
|
||||
const success = await proxyStore.exportProxies(format, filterForm.protocol || undefined)
|
||||
const success = await proxyStore.exportProxies(format, filterForm.protocol || null)
|
||||
if (success) {
|
||||
ElMessage.success(`导出 ${format.toUpperCase()} 格式成功啦~`)
|
||||
}
|
||||
|
||||
@@ -14,6 +14,17 @@
|
||||
</template>
|
||||
|
||||
<el-form :model="settings" label-width="150px" class="settings-form">
|
||||
<el-form-item label="管理员API Key">
|
||||
<el-input
|
||||
v-model="settings.api_key"
|
||||
placeholder="请输入管理员API Key"
|
||||
type="password"
|
||||
show-password
|
||||
class="setting-input"
|
||||
/>
|
||||
<div class="setting-hint">用于执行管理操作的API Key</div>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item label="数据库路径">
|
||||
<el-input v-model="settings.db_path" placeholder="数据库文件路径" />
|
||||
</el-form-item>
|
||||
@@ -92,6 +103,7 @@ import PageHeader from '../components/PageHeader.vue'
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const settings = reactive({
|
||||
api_key: '',
|
||||
db_path: '',
|
||||
crawl_timeout: 30,
|
||||
validation_timeout: 10,
|
||||
@@ -109,6 +121,7 @@ async function fetchSettings() {
|
||||
const data = await response.json()
|
||||
Object.assign(settings, data)
|
||||
}
|
||||
settings.api_key = localStorage.getItem('api_key') || ''
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
@@ -117,12 +130,19 @@ async function fetchSettings() {
|
||||
async function handleSave() {
|
||||
saving.value = true
|
||||
try {
|
||||
if (settings.api_key) {
|
||||
localStorage.setItem('api_key', settings.api_key)
|
||||
} else {
|
||||
localStorage.removeItem('api_key')
|
||||
}
|
||||
|
||||
const { api_key, ...settingsToSend } = settings
|
||||
const response = await fetch('http://localhost:8923/api/settings', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(settings)
|
||||
body: JSON.stringify(settingsToSend)
|
||||
})
|
||||
|
||||
if (response.ok) {
|
||||
@@ -171,6 +191,13 @@ onMounted(() => {
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
|
||||
.setting-hint {
|
||||
margin-top: 8px;
|
||||
font-size: 12px;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.btn-icon {
|
||||
font-size: 20px;
|
||||
margin-right: 8px;
|
||||
|
||||
Reference in New Issue
Block a user