mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-26 03:44:55 +08:00
update: 添加扩容UI
This commit is contained in:
@@ -23,6 +23,7 @@ type TaskType string
|
||||
|
||||
const (
|
||||
TaskTypeBatchTransfer TaskType = "batch_transfer" // 批量转存
|
||||
TaskTypeExpansion TaskType = "expansion" // 账号扩容
|
||||
)
|
||||
|
||||
// Task 任务表
|
||||
|
||||
@@ -58,7 +58,7 @@ func (r *TaskRepositoryImpl) GetList(page, pageSize int, taskType, status string
|
||||
|
||||
// 添加过滤条件
|
||||
if taskType != "" {
|
||||
query = query.Where("task_type = ?", taskType)
|
||||
query = query.Where("type = ?", taskType)
|
||||
}
|
||||
if status != "" {
|
||||
query = query.Where("status = ?", status)
|
||||
|
||||
@@ -2,6 +2,7 @@ package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
@@ -387,3 +388,149 @@ func (h *TaskHandler) DeleteTask(c *gin.Context) {
|
||||
"message": "任务删除成功",
|
||||
})
|
||||
}
|
||||
|
||||
// CreateExpansionTask 创建扩容任务
|
||||
func (h *TaskHandler) CreateExpansionTask(c *gin.Context) {
|
||||
var req struct {
|
||||
PanAccountID uint `json:"pan_account_id" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
ErrorResponse(c, "参数错误: "+err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
utils.Debug("创建扩容任务: 账号ID %d", req.PanAccountID)
|
||||
|
||||
// 获取账号信息,用于构建任务标题
|
||||
cks, err := h.repoMgr.CksRepository.FindByID(req.PanAccountID)
|
||||
if err != nil {
|
||||
utils.Error("获取账号信息失败: %v", err)
|
||||
ErrorResponse(c, "获取账号信息失败: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 构建账号名称
|
||||
accountName := cks.Username
|
||||
if accountName == "" {
|
||||
accountName = cks.Remark
|
||||
}
|
||||
if accountName == "" {
|
||||
accountName = fmt.Sprintf("账号%d", cks.ID)
|
||||
}
|
||||
|
||||
// 构建任务配置(存储账号ID)
|
||||
taskConfig := map[string]interface{}{
|
||||
"pan_account_id": req.PanAccountID,
|
||||
}
|
||||
configJSON, _ := json.Marshal(taskConfig)
|
||||
|
||||
// 创建任务标题,包含账号名称
|
||||
taskTitle := fmt.Sprintf("账号扩容 - %s", accountName)
|
||||
|
||||
// 创建任务
|
||||
newTask := &entity.Task{
|
||||
Title: taskTitle,
|
||||
Description: req.Description,
|
||||
Type: "expansion",
|
||||
Status: "pending",
|
||||
TotalItems: 1, // 扩容任务只有一个项目
|
||||
Config: string(configJSON),
|
||||
CreatedAt: utils.GetCurrentTime(),
|
||||
UpdatedAt: utils.GetCurrentTime(),
|
||||
}
|
||||
|
||||
if err := h.repoMgr.TaskRepository.Create(newTask); err != nil {
|
||||
utils.Error("创建扩容任务失败: %v", err)
|
||||
ErrorResponse(c, "创建任务失败: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 创建任务项
|
||||
expansionInput := task.ExpansionInput{
|
||||
PanAccountID: req.PanAccountID,
|
||||
}
|
||||
|
||||
inputJSON, _ := json.Marshal(expansionInput)
|
||||
|
||||
taskItem := &entity.TaskItem{
|
||||
TaskID: newTask.ID,
|
||||
Status: "pending",
|
||||
InputData: string(inputJSON),
|
||||
CreatedAt: utils.GetCurrentTime(),
|
||||
UpdatedAt: utils.GetCurrentTime(),
|
||||
}
|
||||
|
||||
err = h.repoMgr.TaskItemRepository.Create(taskItem)
|
||||
if err != nil {
|
||||
utils.Error("创建扩容任务项失败: %v", err)
|
||||
// 继续处理,不返回错误
|
||||
}
|
||||
|
||||
utils.Debug("扩容任务创建完成: %d", newTask.ID)
|
||||
|
||||
SuccessResponse(c, gin.H{
|
||||
"task_id": newTask.ID,
|
||||
"total_items": 1,
|
||||
"message": "扩容任务创建成功",
|
||||
})
|
||||
}
|
||||
|
||||
// GetExpansionAccounts 获取支持扩容的账号列表
|
||||
func (h *TaskHandler) GetExpansionAccounts(c *gin.Context) {
|
||||
// 获取所有有效的账号
|
||||
cksList, err := h.repoMgr.CksRepository.FindByIsValid(false)
|
||||
if err != nil {
|
||||
utils.Error("获取账号列表失败: %v", err)
|
||||
ErrorResponse(c, "获取账号列表失败: "+err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// 过滤出 quark 账号
|
||||
var expansionAccounts []gin.H
|
||||
tasks, _, _ := h.repoMgr.TaskRepository.GetList(1, 1000, "expansion", "completed")
|
||||
for _, ck := range cksList {
|
||||
if ck.ServiceType == "quark" {
|
||||
// 使用 Username 作为账号名称,如果为空则使用 Remark
|
||||
accountName := ck.Username
|
||||
if accountName == "" {
|
||||
accountName = ck.Remark
|
||||
}
|
||||
if accountName == "" {
|
||||
accountName = "账号 " + fmt.Sprintf("%d", ck.ID)
|
||||
}
|
||||
|
||||
// 检查是否已经扩容过
|
||||
expanded := false
|
||||
for _, task := range tasks {
|
||||
if task.Config != "" {
|
||||
var taskConfig map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(task.Config), &taskConfig); err == nil {
|
||||
if configAccountID, ok := taskConfig["pan_account_id"].(float64); ok {
|
||||
if uint(configAccountID) == ck.ID {
|
||||
expanded = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
expansionAccounts = append(expansionAccounts, gin.H{
|
||||
"id": ck.ID,
|
||||
"name": accountName,
|
||||
"service_type": ck.ServiceType,
|
||||
"expanded": expanded,
|
||||
"created_at": ck.CreatedAt,
|
||||
"updated_at": ck.UpdatedAt,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
SuccessResponse(c, gin.H{
|
||||
"accounts": expansionAccounts,
|
||||
"total": len(expansionAccounts),
|
||||
"message": "获取支持扩容账号列表成功",
|
||||
})
|
||||
}
|
||||
|
||||
6
main.go
6
main.go
@@ -92,6 +92,10 @@ func main() {
|
||||
transferProcessor := task.NewTransferProcessor(repoManager)
|
||||
taskManager.RegisterProcessor(transferProcessor)
|
||||
|
||||
// 注册扩容任务处理器
|
||||
expansionProcessor := task.NewExpansionProcessor(repoManager)
|
||||
taskManager.RegisterProcessor(expansionProcessor)
|
||||
|
||||
// 初始化Meilisearch管理器
|
||||
meilisearchManager := services.NewMeilisearchManager(repoManager)
|
||||
if err := meilisearchManager.Initialize(); err != nil {
|
||||
@@ -285,6 +289,8 @@ func main() {
|
||||
|
||||
// 任务管理路由
|
||||
api.POST("/tasks/transfer", middleware.AuthMiddleware(), middleware.AdminMiddleware(), taskHandler.CreateBatchTransferTask)
|
||||
api.POST("/tasks/expansion", middleware.AuthMiddleware(), middleware.AdminMiddleware(), taskHandler.CreateExpansionTask)
|
||||
api.GET("/tasks/expansion/accounts", middleware.AuthMiddleware(), middleware.AdminMiddleware(), taskHandler.GetExpansionAccounts)
|
||||
api.GET("/tasks", middleware.AuthMiddleware(), middleware.AdminMiddleware(), taskHandler.GetTasks)
|
||||
api.GET("/tasks/:id", middleware.AuthMiddleware(), middleware.AdminMiddleware(), taskHandler.GetTaskStatus)
|
||||
api.POST("/tasks/:id/start", middleware.AuthMiddleware(), middleware.AdminMiddleware(), taskHandler.StartTask)
|
||||
|
||||
190
task/expansion_processor.go
Normal file
190
task/expansion_processor.go
Normal file
@@ -0,0 +1,190 @@
|
||||
package task
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"github.com/ctwj/urldb/db/entity"
|
||||
"github.com/ctwj/urldb/db/repo"
|
||||
"github.com/ctwj/urldb/utils"
|
||||
)
|
||||
|
||||
// ExpansionProcessor 扩容任务处理器
|
||||
type ExpansionProcessor struct {
|
||||
repoMgr *repo.RepositoryManager
|
||||
}
|
||||
|
||||
// NewExpansionProcessor 创建扩容任务处理器
|
||||
func NewExpansionProcessor(repoMgr *repo.RepositoryManager) *ExpansionProcessor {
|
||||
return &ExpansionProcessor{
|
||||
repoMgr: repoMgr,
|
||||
}
|
||||
}
|
||||
|
||||
// GetTaskType 获取任务类型
|
||||
func (ep *ExpansionProcessor) GetTaskType() string {
|
||||
return "expansion"
|
||||
}
|
||||
|
||||
// ExpansionInput 扩容任务输入数据结构
|
||||
type ExpansionInput struct {
|
||||
PanAccountID uint `json:"pan_account_id"`
|
||||
}
|
||||
|
||||
// ExpansionOutput 扩容任务输出数据结构
|
||||
type ExpansionOutput struct {
|
||||
Success bool `json:"success"`
|
||||
Message string `json:"message"`
|
||||
Error string `json:"error,omitempty"`
|
||||
Time string `json:"time"`
|
||||
}
|
||||
|
||||
// Process 处理扩容任务项
|
||||
func (ep *ExpansionProcessor) Process(ctx context.Context, taskID uint, item *entity.TaskItem) error {
|
||||
utils.Info("开始处理扩容任务项: %d", item.ID)
|
||||
|
||||
// 解析输入数据
|
||||
var input ExpansionInput
|
||||
if err := json.Unmarshal([]byte(item.InputData), &input); err != nil {
|
||||
return fmt.Errorf("解析输入数据失败: %v", err)
|
||||
}
|
||||
|
||||
// 验证输入数据
|
||||
if err := ep.validateInput(&input); err != nil {
|
||||
return fmt.Errorf("输入数据验证失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查账号是否已经扩容过
|
||||
exists, err := ep.checkExpansionExists(input.PanAccountID)
|
||||
if err != nil {
|
||||
utils.Error("检查扩容记录失败: %v", err)
|
||||
return fmt.Errorf("检查扩容记录失败: %v", err)
|
||||
}
|
||||
|
||||
if exists {
|
||||
output := ExpansionOutput{
|
||||
Success: false,
|
||||
Message: "账号已扩容过",
|
||||
Error: "每个账号只能扩容一次",
|
||||
Time: utils.GetCurrentTimeString(),
|
||||
}
|
||||
|
||||
outputJSON, _ := json.Marshal(output)
|
||||
item.OutputData = string(outputJSON)
|
||||
|
||||
utils.Info("账号已扩容过,跳过扩容: 账号ID %d", input.PanAccountID)
|
||||
return fmt.Errorf("账号已扩容过")
|
||||
}
|
||||
|
||||
// 检查账号类型(只支持quark账号)
|
||||
if err := ep.checkAccountType(input.PanAccountID); err != nil {
|
||||
output := ExpansionOutput{
|
||||
Success: false,
|
||||
Message: "账号类型不支持扩容",
|
||||
Error: err.Error(),
|
||||
Time: utils.GetCurrentTimeString(),
|
||||
}
|
||||
|
||||
outputJSON, _ := json.Marshal(output)
|
||||
item.OutputData = string(outputJSON)
|
||||
|
||||
utils.Error("账号类型不支持扩容: %v", err)
|
||||
return err
|
||||
}
|
||||
|
||||
// 执行扩容操作(这里留空,直接返回成功)
|
||||
if err := ep.performExpansion(ctx, input.PanAccountID); err != nil {
|
||||
output := ExpansionOutput{
|
||||
Success: false,
|
||||
Message: "扩容失败",
|
||||
Error: err.Error(),
|
||||
Time: utils.GetCurrentTimeString(),
|
||||
}
|
||||
|
||||
outputJSON, _ := json.Marshal(output)
|
||||
item.OutputData = string(outputJSON)
|
||||
|
||||
utils.Error("扩容任务项处理失败: %d, 错误: %v", item.ID, err)
|
||||
return fmt.Errorf("扩容失败: %v", err)
|
||||
}
|
||||
|
||||
// 扩容成功
|
||||
output := ExpansionOutput{
|
||||
Success: true,
|
||||
Message: "扩容成功",
|
||||
Time: utils.GetCurrentTimeString(),
|
||||
}
|
||||
|
||||
outputJSON, _ := json.Marshal(output)
|
||||
item.OutputData = string(outputJSON)
|
||||
|
||||
utils.Info("扩容任务项处理完成: %d, 账号ID: %d", item.ID, input.PanAccountID)
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateInput 验证输入数据
|
||||
func (ep *ExpansionProcessor) validateInput(input *ExpansionInput) error {
|
||||
if input.PanAccountID == 0 {
|
||||
return fmt.Errorf("账号ID不能为空")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkExpansionExists 检查账号是否已经扩容过
|
||||
func (ep *ExpansionProcessor) checkExpansionExists(panAccountID uint) (bool, error) {
|
||||
// 查询所有expansion类型的任务
|
||||
tasks, _, err := ep.repoMgr.TaskRepository.GetList(1, 1000, "expansion", "completed")
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("获取扩容任务列表失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查每个任务的配置中是否包含该账号ID
|
||||
for _, task := range tasks {
|
||||
if task.Config != "" {
|
||||
var taskConfig map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(task.Config), &taskConfig); err == nil {
|
||||
if configAccountID, ok := taskConfig["pan_account_id"].(float64); ok {
|
||||
if uint(configAccountID) == panAccountID {
|
||||
// 找到了该账号的扩容任务,检查任务状态
|
||||
if task.Status == "completed" {
|
||||
// 如果任务已完成,说明已经扩容过
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// checkAccountType 检查账号类型(只支持quark账号)
|
||||
func (ep *ExpansionProcessor) checkAccountType(panAccountID uint) error {
|
||||
// 获取账号信息
|
||||
cks, err := ep.repoMgr.CksRepository.FindByID(panAccountID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("获取账号信息失败: %v", err)
|
||||
}
|
||||
|
||||
// 检查是否为quark账号
|
||||
if cks.ServiceType != "quark" {
|
||||
return fmt.Errorf("只支持quark账号扩容,当前账号类型: %s", cks.ServiceType)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// performExpansion 执行扩容操作
|
||||
func (ep *ExpansionProcessor) performExpansion(ctx context.Context, panAccountID uint) error {
|
||||
// 扩容逻辑暂时留空,直接返回成功
|
||||
// TODO: 实现具体的扩容逻辑
|
||||
|
||||
utils.Info("执行扩容操作,账号ID: %d", panAccountID)
|
||||
|
||||
// 模拟扩容操作延迟
|
||||
// time.Sleep(2 * time.Second)
|
||||
|
||||
return nil
|
||||
}
|
||||
4
web/components.d.ts
vendored
4
web/components.d.ts
vendored
@@ -15,11 +15,15 @@ declare module 'vue' {
|
||||
NButtonGroup: typeof import('naive-ui')['NButtonGroup']
|
||||
NCard: typeof import('naive-ui')['NCard']
|
||||
NCheckbox: typeof import('naive-ui')['NCheckbox']
|
||||
NCollapse: typeof import('naive-ui')['NCollapse']
|
||||
NCollapseItem: typeof import('naive-ui')['NCollapseItem']
|
||||
NDataTable: typeof import('naive-ui')['NDataTable']
|
||||
NDatePicker: typeof import('naive-ui')['NDatePicker']
|
||||
NDescriptions: typeof import('naive-ui')['NDescriptions']
|
||||
NDescriptionsItem: typeof import('naive-ui')['NDescriptionsItem']
|
||||
NDialogProvider: typeof import('naive-ui')['NDialogProvider']
|
||||
NDrawer: typeof import('naive-ui')['NDrawer']
|
||||
NDrawerContent: typeof import('naive-ui')['NDrawerContent']
|
||||
NEmpty: typeof import('naive-ui')['NEmpty']
|
||||
NForm: typeof import('naive-ui')['NForm']
|
||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||
|
||||
@@ -244,6 +244,8 @@ export const usePublicSystemConfigApi = () => {
|
||||
// 任务管理API
|
||||
export const useTaskApi = () => {
|
||||
const createBatchTransferTask = (data: any) => useApiFetch('/tasks/transfer', { method: 'POST', body: data }).then(parseApiResponse)
|
||||
const createExpansionTask = (data: any) => useApiFetch('/tasks/expansion', { method: 'POST', body: data }).then(parseApiResponse)
|
||||
const getExpansionAccounts = () => useApiFetch('/tasks/expansion/accounts').then(parseApiResponse)
|
||||
const getTasks = (params?: any) => useApiFetch('/tasks', { params }).then(parseApiResponse)
|
||||
const getTaskStatus = (id: number) => useApiFetch(`/tasks/${id}`).then(parseApiResponse)
|
||||
const startTask = (id: number) => useApiFetch(`/tasks/${id}/start`, { method: 'POST' }).then(parseApiResponse)
|
||||
@@ -251,7 +253,7 @@ export const useTaskApi = () => {
|
||||
const pauseTask = (id: number) => useApiFetch(`/tasks/${id}/pause`, { method: 'POST' }).then(parseApiResponse)
|
||||
const deleteTask = (id: number) => useApiFetch(`/tasks/${id}`, { method: 'DELETE' }).then(parseApiResponse)
|
||||
const getTaskItems = (id: number, params?: any) => useApiFetch(`/tasks/${id}/items`, { params }).then(parseApiResponse)
|
||||
return { createBatchTransferTask, getTasks, getTaskStatus, startTask, stopTask, pauseTask, deleteTask, getTaskItems }
|
||||
return { createBatchTransferTask, createExpansionTask, getExpansionAccounts, getTasks, getTaskStatus, startTask, stopTask, pauseTask, deleteTask, getTaskItems }
|
||||
}
|
||||
|
||||
// 日志函数:只在开发环境打印
|
||||
|
||||
427
web/pages/admin/accounts-expansion.vue
Normal file
427
web/pages/admin/accounts-expansion.vue
Normal file
@@ -0,0 +1,427 @@
|
||||
<template>
|
||||
<div class="max-w-7xl mx-auto space-y-6">
|
||||
<!-- 页面标题 -->
|
||||
<div class="flex items-center justify-between">
|
||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">账号扩容管理</h1>
|
||||
<p class="text-gray-600 dark:text-gray-400">管理账号扩容任务和状态</p>
|
||||
</div>
|
||||
|
||||
<!-- 提示信息 -->
|
||||
<n-alert type="info" :show-icon="false">
|
||||
<div class="flex items-center space-x-2">
|
||||
<i class="fas fa-info-circle text-blue-500"></i>
|
||||
<span class="text-sm">
|
||||
<strong>20T扩容说明:</strong>建议 <span @click="showImageModal = true" style="color:red" class="cursor-pointer">蜂小推</span> quark 账号扩容。<span @click="drawActive = true" style="color:blue" class="cursor-pointer">【什么推荐蜂小推】</span><br>
|
||||
1. 20T扩容 只支持新号,等到蜂小推首次 6T 奖励 到账后进行扩容<br>
|
||||
2. 账号需要处于关闭状态, 开启状态可能会被用于,自动转存等任务,存咋影响<br>
|
||||
3. <strong><n-text type="error">扩容完成后,并不直接获得容量</n-text>,账号将存储大量热门资源,<n-text type="error">需要手动推广</n-text></strong><br>
|
||||
4. 注意 推广获得20T容量,删除所有资源, 热门资源比较敏感,不建议,长期推广,仅用于扩容
|
||||
</span>
|
||||
</div>
|
||||
</n-alert>
|
||||
|
||||
<n-drawer v-model:show="drawActive" :width="502" closable placement="right">
|
||||
<n-drawer-content title="扩容说明">
|
||||
<div class="space-y-6 p-4">
|
||||
<div class="mb-4">
|
||||
<p class="text-gray-700 text-large dark:text-gray-300 leading-relaxed">
|
||||
扩容是网盘公司提供给推广用户的<n-text type="success">特权</n-text>!需要先注册推广平台并<n-text type="success">达标</n-text>即可获得权益。
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<n-collapse arrow-placement="right">
|
||||
<n-collapse-item title="达标要求" name="0">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3 flex items-center">
|
||||
<i class="fas fa-list-check text-blue-500 mr-2"></i>
|
||||
达标要求(以蜂小推为例)
|
||||
</h3>
|
||||
<span>首次账号累计7天转存 > 10 或 拉新 > 5</span>
|
||||
</n-collapse-item>
|
||||
<n-collapse-item title="注意事项" name="1">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3 flex items-center">
|
||||
<i class="fas fa-exclamation-triangle text-orange-500 mr-2"></i>
|
||||
注意事项
|
||||
</h3>
|
||||
<span>每个人的转存,只有当日第一次转存,且通过手机转存,才算有效转存。</span>
|
||||
</n-collapse-item>
|
||||
<n-collapse-item title="扩容原理" name="2">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3 flex items-center">
|
||||
<i class="fas fa-question-circle text-purple-500 mr-2"></i>
|
||||
扩容原理
|
||||
</h3>
|
||||
<span>大量转存热播资源,这样才能尽可能快的达标。</span>
|
||||
</n-collapse-item>
|
||||
<n-collapse-item title="为什么推荐蜂小推" name="3">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white mb-3 flex items-center">
|
||||
<i class="fas fa-thumbs-up text-green-500 mr-2"></i>
|
||||
为什么推荐蜂小推
|
||||
</h3>
|
||||
<p class="text-gray-600 dark:text-gray-400 leading-relaxed">
|
||||
登记后,第二天,即会发送 <strong class="text-blue-600">6T 空间</strong>,满足大量存储资源的前提条件。
|
||||
</p>
|
||||
</n-collapse-item>
|
||||
<n-collapse-item title="蜂小推怎么注册" name="3">
|
||||
<p class="text-gray-600 dark:text-gray-400">
|
||||
请扫描下方二维码进行注册。
|
||||
</p>
|
||||
<div class="mt-3 p-4 bg-gray-100 dark:bg-gray-800 rounded-lg text-center">
|
||||
<n-qr-code :value="qrCode" />
|
||||
</div>
|
||||
</n-collapse-item>
|
||||
</n-collapse>
|
||||
</div>
|
||||
</n-drawer-content>
|
||||
</n-drawer>
|
||||
|
||||
<!-- 图片模态框 -->
|
||||
<n-modal v-model:show="showImageModal" title="蜂小推" size="huge">
|
||||
<div class="text-center">
|
||||
<img src="/assets/images/fxt.jpg" alt="蜂小推" class="max-w-full max-h-screen object-contain rounded-lg shadow-lg" />
|
||||
</div>
|
||||
</n-modal>
|
||||
|
||||
<!-- 账号列表 -->
|
||||
<n-card>
|
||||
<template #header>
|
||||
<div class="flex items-center justify-between">
|
||||
<span class="text-lg font-semibold">支持扩容的账号列表</span>
|
||||
<div class="text-sm text-gray-500">
|
||||
共 {{ expansionAccounts.length }} 个账号
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- 加载状态 -->
|
||||
<div v-if="loading" class="flex items-center justify-center py-12">
|
||||
<n-spin size="large">
|
||||
<template #description>
|
||||
<span class="text-gray-500">加载中...</span>
|
||||
</template>
|
||||
</n-spin>
|
||||
</div>
|
||||
|
||||
<!-- 空状态 -->
|
||||
<div v-else-if="expansionAccounts.length === 0" class="flex flex-col items-center justify-center py-12">
|
||||
<n-empty description="暂无可扩容的账号,请先添加有效的 quark 账号">
|
||||
<template #icon>
|
||||
<i class="fas fa-user-circle text-4xl text-gray-400"></i>
|
||||
</template>
|
||||
</n-empty>
|
||||
</div>
|
||||
|
||||
<!-- 账号列表 -->
|
||||
<div v-else>
|
||||
<n-virtual-list :items="expansionAccounts" :item-size="80" style="max-height: 500px">
|
||||
<template #default="{ item }">
|
||||
<div class="border-b border-gray-200 dark:border-gray-700 p-4 hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors">
|
||||
<div class="flex items-center justify-between">
|
||||
<!-- 左侧信息 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="flex items-center space-x-4">
|
||||
<!-- 平台图标 -->
|
||||
<span v-html="getPlatformIcon(item.service_type === 'quark' ? '夸克网盘' : '其他')" class="text-lg"></span>
|
||||
|
||||
<!-- 账号信息 -->
|
||||
<div class="flex-1 min-w-0">
|
||||
<h3 class="text-sm font-medium text-gray-900 dark:text-gray-100 line-clamp-1">
|
||||
{{ item.name }}
|
||||
</h3>
|
||||
<p class="text-xs text-gray-500">
|
||||
{{ item.service_type === 'quark' ? '夸克网盘' : '其他平台' }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- 扩容状态 -->
|
||||
<div class="flex items-center space-x-2">
|
||||
<n-tag v-if="item.expanded" type="success" size="small">
|
||||
已扩容
|
||||
</n-tag>
|
||||
<n-tag v-else type="warning" size="small">
|
||||
可扩容
|
||||
</n-tag>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 创建时间 -->
|
||||
<div class="mt-2">
|
||||
<span class="text-xs text-gray-600 dark:text-gray-400">
|
||||
创建时间: {{ formatDate(item.created_at) }}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 右侧操作按钮 -->
|
||||
<div class="flex items-center space-x-2 ml-4">
|
||||
<n-button
|
||||
size="small"
|
||||
type="primary"
|
||||
:disabled="item.expanded"
|
||||
:loading="expandingAccountId === item.id"
|
||||
@click="handleExpansion(item)"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-expand"></i>
|
||||
</template>
|
||||
{{ item.expanded ? '已扩容' : '扩容' }}
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</n-virtual-list>
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 扩容任务列表 -->
|
||||
<n-card v-if="expansionTasks.length > 0" title="扩容任务列表">
|
||||
<n-data-table
|
||||
:columns="taskColumns"
|
||||
:data="expansionTasks"
|
||||
:pagination="false"
|
||||
max-height="400"
|
||||
size="small"
|
||||
/>
|
||||
</n-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
definePageMeta({
|
||||
layout: 'admin',
|
||||
middleware: ['auth']
|
||||
})
|
||||
|
||||
import { ref, onMounted, computed, h } from 'vue'
|
||||
import { useTaskApi } from '~/composables/useApi'
|
||||
import { useNotification, useDialog } from 'naive-ui'
|
||||
|
||||
// 响应式数据
|
||||
const expansionAccounts = ref([])
|
||||
const expansionTasks = ref([])
|
||||
const loading = ref(true)
|
||||
const expandingAccountId = ref(null)
|
||||
const drawActive = ref(false) // 侧边栏激活
|
||||
const qrCode = ref("https://app.fengtuiwl.com/#/pages/login/reg?p=22112503")
|
||||
const showImageModal = ref(false) // 图片模态框
|
||||
|
||||
// API实例
|
||||
const taskApi = useTaskApi()
|
||||
const notification = useNotification()
|
||||
|
||||
// 表格列配置
|
||||
const taskColumns = [
|
||||
{
|
||||
title: '任务ID',
|
||||
key: 'id',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '标题',
|
||||
key: 'title',
|
||||
ellipsis: {
|
||||
tooltip: true
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
width: 100,
|
||||
render: (row: any) => {
|
||||
const statusMap = {
|
||||
pending: { color: 'warning', text: '等待中', icon: 'fas fa-clock' },
|
||||
running: { color: 'info', text: '运行中', icon: 'fas fa-spinner fa-spin' },
|
||||
completed: { color: 'success', text: '已完成', icon: 'fas fa-check' },
|
||||
failed: { color: 'error', text: '失败', icon: 'fas fa-times' }
|
||||
}
|
||||
const status = statusMap[row.status as keyof typeof statusMap] || statusMap.failed
|
||||
return h('n-tag', { type: status.color }, {
|
||||
icon: () => h('i', { class: status.icon }),
|
||||
default: () => status.text
|
||||
})
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '创建时间',
|
||||
key: 'created_at',
|
||||
width: 150,
|
||||
render: (row: any) => formatDate(row.created_at)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'actions',
|
||||
width: 150,
|
||||
render: (row: any) => h('div', { class: 'flex space-x-2' }, [
|
||||
h('n-button', {
|
||||
size: 'small',
|
||||
type: 'primary',
|
||||
onClick: () => viewTaskDetails(row.id)
|
||||
}, '详情'),
|
||||
row.status === 'running' ? h('n-button', {
|
||||
size: 'small',
|
||||
type: 'warning',
|
||||
onClick: () => stopTask(row.id)
|
||||
}, '停止') : null
|
||||
].filter(Boolean))
|
||||
}
|
||||
]
|
||||
|
||||
// 获取支持扩容的账号列表
|
||||
const fetchExpansionAccounts = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const response = await taskApi.getExpansionAccounts()
|
||||
expansionAccounts.value = response.accounts || []
|
||||
} catch (error) {
|
||||
console.error('获取扩容账号列表失败:', error)
|
||||
notification.error({
|
||||
title: '失败',
|
||||
content: '获取扩容账号列表失败',
|
||||
duration: 3000
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 获取扩容任务列表
|
||||
const fetchExpansionTasks = async () => {
|
||||
try {
|
||||
const response = await taskApi.getTasks({ taskType: 'expansion' })
|
||||
expansionTasks.value = response.tasks || []
|
||||
} catch (error) {
|
||||
console.error('获取扩容任务列表失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理扩容操作
|
||||
const handleExpansion = async (account) => {
|
||||
const dialog = useDialog()
|
||||
|
||||
dialog.warning({
|
||||
title: '确认扩容',
|
||||
content: `确定要对账号 "${account.name}" 进行扩容操作吗?`,
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
draggable: true,
|
||||
onPositiveClick: async () => {
|
||||
expandingAccountId.value = account.id
|
||||
try {
|
||||
const response = await taskApi.createExpansionTask({
|
||||
pan_account_id: account.id,
|
||||
description: `对 ${account.name} 账号进行扩容操作`
|
||||
})
|
||||
|
||||
// 启动任务
|
||||
await taskApi.startTask(response.task_id)
|
||||
|
||||
notification.success({
|
||||
title: '成功',
|
||||
content: '扩容任务已创建并启动',
|
||||
duration: 3000
|
||||
})
|
||||
|
||||
// 刷新数据
|
||||
await Promise.all([
|
||||
fetchExpansionAccounts(),
|
||||
fetchExpansionTasks()
|
||||
])
|
||||
} catch (error) {
|
||||
console.error('创建扩容任务失败:', error)
|
||||
notification.error({
|
||||
title: '失败',
|
||||
content: '创建扩容任务失败: ' + (error.message || '未知错误'),
|
||||
duration: 3000
|
||||
})
|
||||
} finally {
|
||||
expandingAccountId.value = null
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 查看任务详情
|
||||
const viewTaskDetails = async (taskId) => {
|
||||
try {
|
||||
const status = await taskApi.getTaskStatus(taskId)
|
||||
console.log('任务详情:', status)
|
||||
|
||||
// 这里可以展示任务详情的模态框
|
||||
notification.info({
|
||||
title: '任务详情',
|
||||
content: `任务状态: ${status.status}, 总项目数: ${status.total_items}`,
|
||||
duration: 5000
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('获取任务详情失败:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 停止任务
|
||||
const stopTask = async (taskId) => {
|
||||
const dialog = useDialog()
|
||||
|
||||
dialog.warning({
|
||||
title: '确认停止',
|
||||
content: '确定要停止这个扩容任务吗?',
|
||||
positiveText: '确定',
|
||||
negativeText: '取消',
|
||||
onPositiveClick: async () => {
|
||||
try {
|
||||
await taskApi.stopTask(taskId)
|
||||
notification.success({
|
||||
title: '成功',
|
||||
content: '任务已停止',
|
||||
duration: 3000
|
||||
})
|
||||
await fetchExpansionTasks()
|
||||
} catch (error) {
|
||||
console.error('停止任务失败:', error)
|
||||
notification.error({
|
||||
title: '失败',
|
||||
content: '停止任务失败',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// 获取平台图标
|
||||
const getPlatformIcon = (platformName) => {
|
||||
const defaultIcons = {
|
||||
'夸克网盘': '<i class="fas fa-cloud text-blue-600"></i>',
|
||||
'其他': '<i class="fas fa-cloud text-gray-500"></i>'
|
||||
}
|
||||
return defaultIcons[platformName] || defaultIcons['其他']
|
||||
}
|
||||
|
||||
// 格式化日期
|
||||
const formatDate = (dateString) => {
|
||||
if (!dateString) return '-'
|
||||
const date = new Date(dateString)
|
||||
return date.toLocaleString('zh-CN', {
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
})
|
||||
}
|
||||
|
||||
// 页面加载
|
||||
onMounted(async () => {
|
||||
await Promise.all([
|
||||
fetchExpansionAccounts(),
|
||||
fetchExpansionTasks()
|
||||
])
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.line-clamp-1 {
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-box-orient: vertical;
|
||||
-webkit-line-clamp: 1;
|
||||
}
|
||||
</style>
|
||||
@@ -13,6 +13,12 @@
|
||||
</template>
|
||||
添加账号
|
||||
</n-button>
|
||||
<n-button @click="goToExpansionManagement" type="warning">
|
||||
<template #icon>
|
||||
<i class="fas fa-expand"></i>
|
||||
</template>
|
||||
账号扩容
|
||||
</n-button>
|
||||
<n-button @click="refreshData" type="info">
|
||||
<template #icon>
|
||||
<i class="fas fa-refresh"></i>
|
||||
@@ -623,6 +629,11 @@ const goToPage = (page) => {
|
||||
currentPage.value = page
|
||||
}
|
||||
|
||||
// 跳转到扩容管理页面
|
||||
const goToExpansionManagement = () => {
|
||||
router.push('/admin/accounts-expansion')
|
||||
}
|
||||
|
||||
// 页面加载
|
||||
onMounted(async () => {
|
||||
try {
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
import { ref, onMounted, computed, h } from 'vue'
|
||||
import { NButton } from 'naive-ui'
|
||||
import { useTaskStore } from '~/stores/task'
|
||||
import { useTaskApi } from '~/composables/useApi'
|
||||
import { useMessage, useDialog } from 'naive-ui'
|
||||
|
||||
// 任务状态管理
|
||||
@@ -107,7 +108,8 @@ const statusOptions = [
|
||||
|
||||
// 类型选项
|
||||
const typeOptions = [
|
||||
{ label: '转存任务', value: 'transfer' }
|
||||
{ label: '转存任务', value: 'transfer' },
|
||||
{ label: '扩容任务', value: 'expansion' }
|
||||
]
|
||||
|
||||
// 分页配置
|
||||
@@ -158,15 +160,16 @@ const taskColumns = [
|
||||
},
|
||||
{
|
||||
title: '类型',
|
||||
key: 'task_type',
|
||||
key: 'type',
|
||||
width: 80,
|
||||
minWidth: 80,
|
||||
maxWidth: 80,
|
||||
render: (row: any) => {
|
||||
const typeMap: Record<string, { text: string; color: string }> = {
|
||||
transfer: { text: '转存', color: 'blue' }
|
||||
transfer: { text: '转存', color: 'blue' },
|
||||
expansion: { text: '扩容', color: 'orange' }
|
||||
}
|
||||
const type = typeMap[row.task_type] || { text: row.task_type, color: 'gray' }
|
||||
const type = typeMap[row.type] || { text: row.type, color: 'gray' }
|
||||
return h('n-tag', { type: type.color, size: 'small' }, { default: () => type.text })
|
||||
}
|
||||
},
|
||||
@@ -347,7 +350,6 @@ const getRowClassName = (row: any) => {
|
||||
const fetchTasks = async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
const { useTaskApi } = await import('~/composables/useApi')
|
||||
const taskApi = useTaskApi()
|
||||
|
||||
const params: any = {
|
||||
@@ -360,13 +362,13 @@ const fetchTasks = async () => {
|
||||
params.status = statusFilter.value
|
||||
}
|
||||
if (typeFilter.value) {
|
||||
params.task_type = typeFilter.value
|
||||
params.taskType = typeFilter.value
|
||||
}
|
||||
|
||||
const response = await taskApi.getTasks(params) as any
|
||||
|
||||
if (response && response.items) {
|
||||
tasks.value = response.items
|
||||
if (response && response.page) {
|
||||
tasks.value = response.tasks || []
|
||||
total.value = response.total || 0
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -391,7 +393,7 @@ const onTypeFilterChange = () => {
|
||||
// 刷新任务列表
|
||||
const refreshTasks = async () => {
|
||||
// 强制刷新任务状态和列表
|
||||
await taskStore.fetchTaskStats()
|
||||
// await taskStore.fetchTaskStats()
|
||||
await fetchTasks()
|
||||
}
|
||||
|
||||
|
||||
BIN
web/public/assets/images/fxt.jpg
Normal file
BIN
web/public/assets/images/fxt.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 153 KiB |
@@ -65,7 +65,7 @@ export const useTaskStore = defineStore('task', () => {
|
||||
// 获取任务统计信息
|
||||
const fetchTaskStats = async () => {
|
||||
try {
|
||||
const response = await taskApi.getTasks() as any
|
||||
const response = await taskApi.getTasks({status: 'running'}) as any
|
||||
// console.log('原始任务API响应:', response)
|
||||
|
||||
// 处理API响应格式
|
||||
|
||||
Reference in New Issue
Block a user