mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 11:29:37 +08:00
update: 批量转存优化
This commit is contained in:
@@ -16,6 +16,7 @@ type TaskItemRepository interface {
|
||||
UpdateStatus(id uint, status string) error
|
||||
UpdateStatusAndOutput(id uint, status, outputData string) error
|
||||
GetStatsByTaskID(taskID uint) (map[string]int, error)
|
||||
ResetProcessingItems(taskID uint) error
|
||||
}
|
||||
|
||||
// TaskItemRepositoryImpl 任务项仓库实现
|
||||
@@ -135,3 +136,10 @@ func (r *TaskItemRepositoryImpl) GetStatsByTaskID(taskID uint) (map[string]int,
|
||||
|
||||
return stats, nil
|
||||
}
|
||||
|
||||
// ResetProcessingItems 重置处理中的任务项为pending状态
|
||||
func (r *TaskItemRepositoryImpl) ResetProcessingItems(taskID uint) error {
|
||||
return r.db.Model(&entity.TaskItem{}).
|
||||
Where("task_id = ? AND status = ?", taskID, "processing").
|
||||
Update("status", "pending").Error
|
||||
}
|
||||
|
||||
@@ -159,13 +159,8 @@ func GetSearchesTrend(c *gin.Context) {
|
||||
Where("DATE(date) = ?", dateStr).
|
||||
Count(&searches)
|
||||
|
||||
// 如果没有搜索记录,生成模拟数据
|
||||
if searches == 0 {
|
||||
// 基于当前时间的随机因子生成模拟搜索量
|
||||
baseSearches := int64(50 + utils.GetCurrentTime().Day()*2) // 基础搜索量
|
||||
randomFactor := float64(70+utils.GetCurrentTime().Hour()*i) / 100.0
|
||||
searches = int64(float64(baseSearches) * randomFactor)
|
||||
}
|
||||
// 如果没有搜索记录,返回0
|
||||
// 移除模拟数据生成逻辑,只返回真实数据
|
||||
|
||||
results = append(results, gin.H{
|
||||
"date": dateStr,
|
||||
|
||||
@@ -32,15 +32,17 @@ type BatchTransferResource struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
URL string `json:"url" binding:"required"`
|
||||
CategoryID uint `json:"category_id,omitempty"`
|
||||
PanID uint `json:"pan_id,omitempty"`
|
||||
Tags []uint `json:"tags,omitempty"`
|
||||
}
|
||||
|
||||
// CreateBatchTransferTask 创建批量转存任务
|
||||
func (h *TaskHandler) CreateBatchTransferTask(c *gin.Context) {
|
||||
var req struct {
|
||||
Title string `json:"title" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Resources []BatchTransferResource `json:"resources" binding:"required,min=1"`
|
||||
Title string `json:"title" binding:"required"`
|
||||
Description string `json:"description"`
|
||||
Resources []BatchTransferResource `json:"resources" binding:"required,min=1"`
|
||||
SelectedAccounts []uint `json:"selected_accounts,omitempty"`
|
||||
}
|
||||
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
@@ -48,7 +50,13 @@ func (h *TaskHandler) CreateBatchTransferTask(c *gin.Context) {
|
||||
return
|
||||
}
|
||||
|
||||
utils.Info("创建批量转存任务: %s,资源数量: %d", req.Title, len(req.Resources))
|
||||
utils.Info("创建批量转存任务: %s,资源数量: %d,选择账号数量: %d", req.Title, len(req.Resources), len(req.SelectedAccounts))
|
||||
|
||||
// 构建任务配置
|
||||
taskConfig := map[string]interface{}{
|
||||
"selected_accounts": req.SelectedAccounts,
|
||||
}
|
||||
configJSON, _ := json.Marshal(taskConfig)
|
||||
|
||||
// 创建任务
|
||||
newTask := &entity.Task{
|
||||
@@ -57,6 +65,7 @@ func (h *TaskHandler) CreateBatchTransferTask(c *gin.Context) {
|
||||
Type: "transfer",
|
||||
Status: "pending",
|
||||
TotalItems: len(req.Resources),
|
||||
Config: string(configJSON),
|
||||
CreatedAt: utils.GetCurrentTime(),
|
||||
UpdatedAt: utils.GetCurrentTime(),
|
||||
}
|
||||
@@ -75,6 +84,7 @@ func (h *TaskHandler) CreateBatchTransferTask(c *gin.Context) {
|
||||
Title: resource.Title,
|
||||
URL: resource.URL,
|
||||
CategoryID: resource.CategoryID,
|
||||
PanID: resource.PanID,
|
||||
Tags: resource.Tags,
|
||||
}
|
||||
|
||||
|
||||
@@ -201,6 +201,19 @@ func (tm *TaskManager) processTask(ctx context.Context, task *entity.Task, proce
|
||||
return
|
||||
}
|
||||
|
||||
// 获取任务项统计信息,用于计算正确的进度
|
||||
stats, err := tm.repoMgr.TaskItemRepository.GetStatsByTaskID(task.ID)
|
||||
if err != nil {
|
||||
utils.Error("获取任务项统计失败: %v", err)
|
||||
stats = map[string]int{
|
||||
"total": 0,
|
||||
"pending": 0,
|
||||
"processing": 0,
|
||||
"completed": 0,
|
||||
"failed": 0,
|
||||
}
|
||||
}
|
||||
|
||||
// 获取待处理的任务项
|
||||
items, err := tm.repoMgr.TaskItemRepository.GetByTaskIDAndStatus(task.ID, "pending")
|
||||
if err != nil {
|
||||
@@ -209,12 +222,35 @@ func (tm *TaskManager) processTask(ctx context.Context, task *entity.Task, proce
|
||||
return
|
||||
}
|
||||
|
||||
totalItems := len(items)
|
||||
processedItems := 0
|
||||
successItems := 0
|
||||
failedItems := 0
|
||||
// 计算总任务项数和已完成的项数
|
||||
totalItems := stats["total"]
|
||||
completedItems := stats["completed"]
|
||||
initialFailedItems := stats["failed"]
|
||||
processingItems := stats["processing"]
|
||||
|
||||
utils.Info("任务 %d 共有 %d 个待处理项", task.ID, totalItems)
|
||||
// 如果当前批次有处理中的任务项,重置它们为pending状态(服务器重启恢复)
|
||||
if processingItems > 0 {
|
||||
utils.Info("任务 %d 发现 %d 个处理中的任务项,重置为pending状态", task.ID, processingItems)
|
||||
err = tm.repoMgr.TaskItemRepository.ResetProcessingItems(task.ID)
|
||||
if err != nil {
|
||||
utils.Error("重置处理中任务项失败: %v", err)
|
||||
}
|
||||
// 重新获取待处理的任务项
|
||||
items, err = tm.repoMgr.TaskItemRepository.GetByTaskIDAndStatus(task.ID, "pending")
|
||||
if err != nil {
|
||||
utils.Error("重新获取任务项失败: %v", err)
|
||||
tm.markTaskFailed(task.ID, fmt.Sprintf("重新获取任务项失败: %v", err))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
currentBatchItems := len(items)
|
||||
processedItems := completedItems + initialFailedItems // 已经处理的项目数
|
||||
successItems := completedItems
|
||||
failedItems := initialFailedItems
|
||||
|
||||
utils.Info("任务 %d 统计信息: 总计=%d, 已完成=%d, 已失败=%d, 待处理=%d",
|
||||
task.ID, totalItems, completedItems, failedItems, currentBatchItems)
|
||||
|
||||
for _, item := range items {
|
||||
select {
|
||||
@@ -233,9 +269,11 @@ func (tm *TaskManager) processTask(ctx context.Context, task *entity.Task, proce
|
||||
successItems++
|
||||
}
|
||||
|
||||
// 更新任务进度
|
||||
progress := float64(processedItems) / float64(totalItems) * 100
|
||||
tm.updateTaskProgress(task.ID, progress, processedItems, successItems, failedItems)
|
||||
// 更新任务进度(基于总任务项数)
|
||||
if totalItems > 0 {
|
||||
progress := float64(processedItems) / float64(totalItems) * 100
|
||||
tm.updateTaskProgress(task.ID, progress, processedItems, successItems, failedItems)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -36,6 +36,7 @@ type TransferInput struct {
|
||||
Title string `json:"title"`
|
||||
URL string `json:"url"`
|
||||
CategoryID uint `json:"category_id"`
|
||||
PanID uint `json:"pan_id"`
|
||||
Tags []uint `json:"tags"`
|
||||
}
|
||||
|
||||
@@ -63,6 +64,22 @@ func (tp *TransferProcessor) Process(ctx context.Context, taskID uint, item *ent
|
||||
return fmt.Errorf("输入数据验证失败: %v", err)
|
||||
}
|
||||
|
||||
// 获取任务配置中的账号信息
|
||||
var selectedAccounts []uint
|
||||
task, err := tp.repoMgr.TaskRepository.GetByID(taskID)
|
||||
if err == nil && task.Config != "" {
|
||||
var taskConfig map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(task.Config), &taskConfig); err == nil {
|
||||
if accounts, ok := taskConfig["selected_accounts"].([]interface{}); ok {
|
||||
for _, acc := range accounts {
|
||||
if accID, ok := acc.(float64); ok {
|
||||
selectedAccounts = append(selectedAccounts, uint(accID))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 检查资源是否已存在
|
||||
exists, existingResource, err := tp.checkResourceExists(input.URL)
|
||||
if err != nil {
|
||||
@@ -92,7 +109,7 @@ func (tp *TransferProcessor) Process(ctx context.Context, taskID uint, item *ent
|
||||
}
|
||||
|
||||
// 执行转存操作
|
||||
resourceID, saveURL, err := tp.performTransfer(ctx, &input)
|
||||
resourceID, saveURL, err := tp.performTransfer(ctx, &input, selectedAccounts)
|
||||
if err != nil {
|
||||
// 转存失败,更新输出数据
|
||||
output := TransferOutput{
|
||||
@@ -180,7 +197,7 @@ func (tp *TransferProcessor) checkResourceExists(url string) (bool, *entity.Reso
|
||||
}
|
||||
|
||||
// performTransfer 执行转存操作
|
||||
func (tp *TransferProcessor) performTransfer(ctx context.Context, input *TransferInput) (uint, string, error) {
|
||||
func (tp *TransferProcessor) performTransfer(ctx context.Context, input *TransferInput, selectedAccounts []uint) (uint, string, error) {
|
||||
// 解析URL获取分享信息
|
||||
shareInfo, err := tp.parseShareURL(input.URL)
|
||||
if err != nil {
|
||||
@@ -188,7 +205,7 @@ func (tp *TransferProcessor) performTransfer(ctx context.Context, input *Transfe
|
||||
}
|
||||
|
||||
// 先执行转存操作
|
||||
saveURL, err := tp.transferToCloud(ctx, shareInfo)
|
||||
saveURL, err := tp.transferToCloud(ctx, shareInfo, selectedAccounts)
|
||||
if err != nil {
|
||||
utils.Error("云端转存失败: %v", err)
|
||||
return 0, "", fmt.Errorf("转存失败: %v", err)
|
||||
@@ -206,10 +223,28 @@ func (tp *TransferProcessor) performTransfer(ctx context.Context, input *Transfe
|
||||
categoryID = &input.CategoryID
|
||||
}
|
||||
|
||||
// 确定平台ID
|
||||
var panID uint
|
||||
if input.PanID != 0 {
|
||||
// 使用指定的平台ID
|
||||
panID = input.PanID
|
||||
utils.Info("使用指定的平台ID: %d", panID)
|
||||
} else {
|
||||
// 如果没有指定,默认使用夸克平台ID
|
||||
quarkPanID, err := tp.getQuarkPanID()
|
||||
if err != nil {
|
||||
utils.Error("获取夸克平台ID失败: %v", err)
|
||||
return 0, "", fmt.Errorf("获取夸克平台ID失败: %v", err)
|
||||
}
|
||||
panID = quarkPanID
|
||||
utils.Info("使用默认夸克平台ID: %d", panID)
|
||||
}
|
||||
|
||||
resource := &entity.Resource{
|
||||
Title: input.Title,
|
||||
URL: input.URL,
|
||||
CategoryID: categoryID,
|
||||
PanID: &panID, // 设置平台ID
|
||||
SaveURL: saveURL, // 直接设置转存链接
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
@@ -278,10 +313,55 @@ func (tp *TransferProcessor) addResourceTags(resourceID uint, tagIDs []uint) err
|
||||
}
|
||||
|
||||
// transferToCloud 执行云端转存
|
||||
func (tp *TransferProcessor) transferToCloud(ctx context.Context, shareInfo *ShareInfo) (string, error) {
|
||||
func (tp *TransferProcessor) transferToCloud(ctx context.Context, shareInfo *ShareInfo, selectedAccounts []uint) (string, error) {
|
||||
// 转存任务独立于自动转存开关,直接执行转存逻辑
|
||||
// 获取转存相关的配置(如最小存储空间等),但不检查自动转存开关
|
||||
|
||||
// 如果指定了账号,使用指定的账号
|
||||
if len(selectedAccounts) > 0 {
|
||||
utils.Info("使用指定的账号进行转存,账号数量: %d", len(selectedAccounts))
|
||||
|
||||
// 获取指定的账号
|
||||
var validAccounts []entity.Cks
|
||||
for _, accountID := range selectedAccounts {
|
||||
account, err := tp.repoMgr.CksRepository.FindByID(accountID)
|
||||
if err != nil {
|
||||
utils.Error("获取账号 %d 失败: %v", accountID, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !account.IsValid {
|
||||
utils.Error("账号 %d 无效", accountID)
|
||||
continue
|
||||
}
|
||||
|
||||
validAccounts = append(validAccounts, *account)
|
||||
}
|
||||
|
||||
if len(validAccounts) == 0 {
|
||||
return "", fmt.Errorf("指定的账号都无效或不存在")
|
||||
}
|
||||
|
||||
utils.Info("找到 %d 个有效账号,开始转存处理...", len(validAccounts))
|
||||
|
||||
// 使用第一个有效账号进行转存
|
||||
account := validAccounts[0]
|
||||
|
||||
// 创建网盘服务工厂
|
||||
factory := pan.NewPanFactory()
|
||||
|
||||
// 执行转存
|
||||
result := tp.transferSingleResource(shareInfo, account, factory)
|
||||
if !result.Success {
|
||||
return "", fmt.Errorf("转存失败: %s", result.ErrorMsg)
|
||||
}
|
||||
|
||||
return result.SaveURL, nil
|
||||
}
|
||||
|
||||
// 如果没有指定账号,使用原来的逻辑(自动选择)
|
||||
utils.Info("未指定账号,使用自动选择逻辑")
|
||||
|
||||
// 获取夸克平台ID
|
||||
quarkPanID, err := tp.getQuarkPanID()
|
||||
if err != nil {
|
||||
|
||||
@@ -46,6 +46,37 @@
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
网盘账号 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<n-select
|
||||
v-model:value="selectedAccounts"
|
||||
:options="accountOptions"
|
||||
placeholder="选择网盘账号"
|
||||
multiple
|
||||
filterable
|
||||
:loading="accountsLoading"
|
||||
@update:value="handleAccountChange"
|
||||
>
|
||||
<template #option="{ option: accountOption }">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-sm">{{ accountOption.label }}</span>
|
||||
<n-tag v-if="accountOption.is_valid" type="success" size="small">有效</n-tag>
|
||||
<n-tag v-else type="error" size="small">无效</n-tag>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
{{ formatSpace(accountOption.left_space) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</n-select>
|
||||
<div class="text-xs text-gray-500 mt-1">
|
||||
请选择要使用的网盘账号,系统将使用选中的账号进行转存操作
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="space-y-3 pt-4">
|
||||
<n-button
|
||||
@@ -53,7 +84,7 @@
|
||||
block
|
||||
size="large"
|
||||
:loading="processing"
|
||||
:disabled="!resourceText.trim() || processing"
|
||||
:disabled="!resourceText.trim() || !selectedAccounts.length || processing"
|
||||
@click="handleBatchTransfer"
|
||||
>
|
||||
<template #icon>
|
||||
@@ -115,7 +146,7 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted, onBeforeUnmount, h } from 'vue'
|
||||
import { usePanApi, useTaskApi } from '~/composables/useApi'
|
||||
import { usePanApi, useTaskApi, useCksApi } from '~/composables/useApi'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
// 数据状态
|
||||
@@ -142,13 +173,17 @@ const selectedPlatform = ref(null)
|
||||
const autoValidate = ref(true)
|
||||
const skipExisting = ref(true)
|
||||
const autoTransfer = ref(false)
|
||||
const selectedAccounts = ref<number[]>([])
|
||||
|
||||
// 选项数据
|
||||
const platformOptions = ref<any[]>([])
|
||||
const accountOptions = ref<any[]>([])
|
||||
const accountsLoading = ref(false)
|
||||
|
||||
// API实例
|
||||
const panApi = usePanApi()
|
||||
const taskApi = useTaskApi()
|
||||
const cksApi = useCksApi()
|
||||
const message = useMessage()
|
||||
|
||||
// 计算属性
|
||||
@@ -275,6 +310,11 @@ const handleBatchTransfer = async () => {
|
||||
return
|
||||
}
|
||||
|
||||
if (!selectedAccounts.value || selectedAccounts.value.length === 0) {
|
||||
message.warning('请选择至少一个网盘账号')
|
||||
return
|
||||
}
|
||||
|
||||
processing.value = true
|
||||
results.value = []
|
||||
|
||||
@@ -291,7 +331,7 @@ const handleBatchTransfer = async () => {
|
||||
const taskTitle = `批量转存任务_${new Date().toLocaleString('zh-CN')}`
|
||||
const taskData = {
|
||||
title: taskTitle,
|
||||
description: `批量转存 ${resourceList.length} 个资源`,
|
||||
description: `批量转存 ${resourceList.length} 个资源,使用 ${selectedAccounts.value.length} 个账号`,
|
||||
resources: resourceList.map(item => {
|
||||
const resource: any = {
|
||||
title: item.title,
|
||||
@@ -304,7 +344,9 @@ const handleBatchTransfer = async () => {
|
||||
resource.tags = selectedTags.value
|
||||
}
|
||||
return resource
|
||||
})
|
||||
}),
|
||||
// 添加选择的账号信息
|
||||
selected_accounts: selectedAccounts.value
|
||||
}
|
||||
|
||||
console.log('创建任务数据:', taskData)
|
||||
@@ -472,15 +514,55 @@ const updateResultsDisplay = () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取网盘账号选项
|
||||
const getAccountOptions = async () => {
|
||||
accountsLoading.value = true
|
||||
try {
|
||||
const response = await cksApi.getCks() as any
|
||||
const accounts = Array.isArray(response) ? response : []
|
||||
|
||||
accountOptions.value = accounts.map((account: any) => ({
|
||||
label: `${account.username || '未知用户'} (${account.pan?.name || '未知平台'})`,
|
||||
value: account.id,
|
||||
is_valid: account.is_valid,
|
||||
left_space: account.left_space,
|
||||
username: account.username,
|
||||
pan_name: account.pan?.name || '未知平台'
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('获取网盘账号选项失败:', error)
|
||||
message.error('获取网盘账号失败')
|
||||
} finally {
|
||||
accountsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理账号选择变化
|
||||
const handleAccountChange = (value: number[]) => {
|
||||
selectedAccounts.value = value
|
||||
console.log('选择的账号:', value)
|
||||
}
|
||||
|
||||
// 格式化空间大小
|
||||
const formatSpace = (bytes: number) => {
|
||||
if (bytes === 0) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
// 清空输入
|
||||
const clearInput = () => {
|
||||
resourceText.value = ''
|
||||
results.value = []
|
||||
selectedAccounts.value = []
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
fetchPlatforms()
|
||||
getAccountOptions()
|
||||
})
|
||||
|
||||
// 组件销毁时清理定时器
|
||||
|
||||
@@ -164,30 +164,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 操作按钮 -->
|
||||
<div class="flex flex-col space-y-2 ml-4">
|
||||
<n-button
|
||||
size="small"
|
||||
type="primary"
|
||||
:loading="item.transferring"
|
||||
@click="handleSingleTransfer(item)"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-exchange-alt"></i>
|
||||
</template>
|
||||
{{ item.transferring ? '转存中' : '立即转存' }}
|
||||
</n-button>
|
||||
|
||||
<n-button
|
||||
size="small"
|
||||
@click="viewResource(item)"
|
||||
>
|
||||
<template #icon>
|
||||
<i class="fas fa-eye"></i>
|
||||
</template>
|
||||
查看详情
|
||||
</n-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -211,6 +188,73 @@
|
||||
</div>
|
||||
</n-card>
|
||||
|
||||
<!-- 网盘账号选择模态框 -->
|
||||
<n-modal v-model:show="showAccountSelectionModal" preset="card" title="选择网盘账号" style="width: 600px">
|
||||
<div class="space-y-4">
|
||||
<div class="text-sm text-gray-600 dark:text-gray-400">
|
||||
请选择要使用的网盘账号进行批量转存操作
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||
网盘账号 <span class="text-red-500">*</span>
|
||||
</label>
|
||||
<n-select
|
||||
v-model:value="selectedAccounts"
|
||||
:options="accountOptions"
|
||||
placeholder="选择网盘账号"
|
||||
multiple
|
||||
filterable
|
||||
:loading="accountsLoading"
|
||||
@update:value="handleAccountChange"
|
||||
>
|
||||
<template #option="{ option: accountOption }">
|
||||
<div class="flex items-center justify-between w-full">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="text-sm">{{ accountOption.label }}</span>
|
||||
<n-tag v-if="accountOption.is_valid" type="success" size="small">有效</n-tag>
|
||||
<n-tag v-else type="error" size="small">无效</n-tag>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500">
|
||||
{{ formatSpace(accountOption.left_space) }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</n-select>
|
||||
<div class="text-xs text-gray-500 mt-1">
|
||||
请选择要使用的网盘账号,系统将使用选中的账号进行转存操作
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-yellow-50 dark:bg-yellow-900/20 p-3 rounded border border-yellow-200 dark:border-yellow-800">
|
||||
<div class="flex items-start space-x-2">
|
||||
<i class="fas fa-exclamation-triangle text-yellow-500 mt-0.5"></i>
|
||||
<div class="text-sm text-yellow-800 dark:text-yellow-200">
|
||||
<p>• 转存过程可能需要较长时间</p>
|
||||
<p>• 请确保选中的网盘账号有足够的存储空间</p>
|
||||
<p>• 转存完成后可在"已转存列表"中查看结果</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template #footer>
|
||||
<div class="flex justify-end space-x-3">
|
||||
<n-button @click="showAccountSelectionModal = false">
|
||||
取消
|
||||
</n-button>
|
||||
<n-button
|
||||
type="primary"
|
||||
:disabled="selectedAccounts.length === 0"
|
||||
:loading="batchTransferring"
|
||||
@click="confirmBatchTransfer"
|
||||
>
|
||||
{{ batchTransferring ? '创建任务中...' : '继续' }}
|
||||
</n-button>
|
||||
</div>
|
||||
</template>
|
||||
</n-modal>
|
||||
|
||||
<!-- 转存结果模态框 -->
|
||||
<n-modal v-model:show="showTransferResult" preset="card" title="转存结果" style="width: 600px">
|
||||
<div v-if="transferResults.length > 0" class="space-y-4">
|
||||
@@ -252,14 +296,15 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive, computed, onMounted } from 'vue'
|
||||
import { useResourceApi, useCategoryApi, useTagApi } from '~/composables/useApi'
|
||||
import { useResourceApi, useCategoryApi, useTagApi, useCksApi, useTaskApi } from '~/composables/useApi'
|
||||
import { useMessage } from 'naive-ui'
|
||||
|
||||
// 数据状态
|
||||
const loading = ref(false)
|
||||
const resources = ref([])
|
||||
const total = ref(0)
|
||||
const currentPage = ref(1)
|
||||
const pageSize = ref(10000)
|
||||
const pageSize = ref(2000)
|
||||
|
||||
// 搜索条件
|
||||
const searchQuery = ref('')
|
||||
@@ -274,6 +319,10 @@ const selectedResources = ref([])
|
||||
const batchTransferring = ref(false)
|
||||
const showTransferResult = ref(false)
|
||||
const transferResults = ref([])
|
||||
const showAccountSelectionModal = ref(false)
|
||||
const selectedAccounts = ref<number[]>([])
|
||||
const accountOptions = ref<any[]>([])
|
||||
const accountsLoading = ref(false)
|
||||
|
||||
// 选项数据
|
||||
const categoryOptions = ref([])
|
||||
@@ -288,6 +337,9 @@ const statusOptions = [
|
||||
const resourceApi = useResourceApi()
|
||||
const categoryApi = useCategoryApi()
|
||||
const tagApi = useTagApi()
|
||||
const cksApi = useCksApi()
|
||||
const taskApi = useTaskApi()
|
||||
const message = useMessage()
|
||||
|
||||
// 计算属性
|
||||
const isAllSelected = computed(() => {
|
||||
@@ -376,6 +428,44 @@ const fetchTags = async () => {
|
||||
}
|
||||
}
|
||||
|
||||
// 获取网盘账号选项
|
||||
const getAccountOptions = async () => {
|
||||
accountsLoading.value = true
|
||||
try {
|
||||
const response = await cksApi.getCks() as any
|
||||
const accounts = Array.isArray(response) ? response : []
|
||||
|
||||
accountOptions.value = accounts.map((account: any) => ({
|
||||
label: `${account.username || '未知用户'} (${account.pan?.name || '未知平台'})`,
|
||||
value: account.id,
|
||||
is_valid: account.is_valid,
|
||||
left_space: account.left_space,
|
||||
username: account.username,
|
||||
pan_name: account.pan?.name || '未知平台'
|
||||
}))
|
||||
} catch (error) {
|
||||
console.error('获取网盘账号选项失败:', error)
|
||||
message.error('获取网盘账号失败')
|
||||
} finally {
|
||||
accountsLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 处理账号选择变化
|
||||
const handleAccountChange = (value: number[]) => {
|
||||
selectedAccounts.value = value
|
||||
console.log('选择的账号:', value)
|
||||
}
|
||||
|
||||
// 格式化空间大小
|
||||
const formatSpace = (bytes: number) => {
|
||||
if (bytes === 0) return '0 B'
|
||||
const k = 1024
|
||||
const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k))
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]
|
||||
}
|
||||
|
||||
// 搜索处理
|
||||
const handleSearch = () => {
|
||||
currentPage.value = 1
|
||||
@@ -421,79 +511,23 @@ const toggleResourceSelection = (id: number, checked: boolean) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 单个转存
|
||||
const handleSingleTransfer = async (resource: any) => {
|
||||
resource.transferring = true
|
||||
|
||||
try {
|
||||
// 这里应该调用实际的转存API
|
||||
// 由于只是UI展示,模拟转存过程
|
||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
||||
|
||||
// 模拟随机成功/失败
|
||||
const isSuccess = Math.random() > 0.3
|
||||
|
||||
if (isSuccess) {
|
||||
$message.success(`${resource.title} 转存成功`)
|
||||
// 刷新列表
|
||||
refreshData()
|
||||
} else {
|
||||
$message.error(`${resource.title} 转存失败`)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('转存失败:', error)
|
||||
$message.error('转存失败')
|
||||
} finally {
|
||||
resource.transferring = false
|
||||
}
|
||||
}
|
||||
|
||||
// 批量转存
|
||||
const handleBatchTransfer = async () => {
|
||||
if (selectedResources.value.length === 0) {
|
||||
$message.warning('请选择要转存的资源')
|
||||
message.warning('请选择要转存的资源')
|
||||
return
|
||||
}
|
||||
|
||||
batchTransferring.value = true
|
||||
transferResults.value = []
|
||||
// 先获取网盘账号列表
|
||||
await getAccountOptions()
|
||||
|
||||
try {
|
||||
const selectedItems = resources.value.filter(r => selectedResources.value.includes(r.id))
|
||||
|
||||
// 这里应该调用实际的批量转存API
|
||||
// 由于只是UI展示,模拟批量转存过程
|
||||
for (const item of selectedItems) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
|
||||
const isSuccess = Math.random() > 0.3
|
||||
transferResults.value.push({
|
||||
id: item.id,
|
||||
title: item.title,
|
||||
url: item.url,
|
||||
success: isSuccess,
|
||||
message: isSuccess ? '转存成功' : '转存失败:网络错误'
|
||||
})
|
||||
}
|
||||
|
||||
showTransferResult.value = true
|
||||
|
||||
// 刷新列表
|
||||
refreshData()
|
||||
|
||||
} catch (error) {
|
||||
console.error('批量转存失败:', error)
|
||||
$message.error('批量转存失败')
|
||||
} finally {
|
||||
batchTransferring.value = false
|
||||
}
|
||||
// 显示账号选择模态框
|
||||
showAccountSelectionModal.value = true
|
||||
}
|
||||
|
||||
// 查看资源详情
|
||||
const viewResource = (resource: any) => {
|
||||
console.log('查看资源详情:', resource)
|
||||
// 这里可以打开资源详情模态框
|
||||
}
|
||||
|
||||
|
||||
// 获取状态类型
|
||||
const getStatusType = (resource: any) => {
|
||||
@@ -514,6 +548,47 @@ const formatDate = (dateString: string) => {
|
||||
return new Date(dateString).toLocaleDateString()
|
||||
}
|
||||
|
||||
// 确认批量转存
|
||||
const confirmBatchTransfer = async () => {
|
||||
if (selectedAccounts.value.length === 0) {
|
||||
message.warning('请选择至少一个网盘账号')
|
||||
return
|
||||
}
|
||||
|
||||
batchTransferring.value = true
|
||||
try {
|
||||
const selectedItems = resources.value.filter(r => selectedResources.value.includes(r.id))
|
||||
|
||||
const taskData = {
|
||||
title: `批量转存 ${selectedItems.length} 个资源`,
|
||||
description: `批量转存 ${selectedItems.length} 个资源,使用 ${selectedAccounts.value.length} 个账号`,
|
||||
resources: selectedItems.map(r => ({
|
||||
title: r.title,
|
||||
url: r.url,
|
||||
category_id: r.category_id || 0,
|
||||
pan_id: r.pan_id || 0
|
||||
})),
|
||||
selected_accounts: selectedAccounts.value
|
||||
}
|
||||
|
||||
const response = await taskApi.createBatchTransferTask(taskData) as any
|
||||
message.success(`批量转存任务已创建,共 ${selectedItems.length} 个资源`)
|
||||
|
||||
// 关闭模态框
|
||||
showAccountSelectionModal.value = false
|
||||
selectedAccounts.value = []
|
||||
|
||||
// 刷新列表
|
||||
refreshData()
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建批量转存任务失败:', error)
|
||||
message.error('创建批量转存任务失败')
|
||||
} finally {
|
||||
batchTransferring.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
onMounted(() => {
|
||||
fetchCategories()
|
||||
|
||||
@@ -33,22 +33,22 @@
|
||||
<!-- 自动处理状态 -->
|
||||
<div class="flex items-center gap-2 bg-gray-100 dark:bg-gray-700 rounded-lg px-3 py-2">
|
||||
<div class="w-2 h-2 rounded-full animate-pulse" :class="{
|
||||
'bg-red-400': !systemConfig?.auto_process_ready_resources,
|
||||
'bg-green-400': systemConfig?.auto_process_ready_resources
|
||||
'bg-red-400': !isAutoProcessEnabled,
|
||||
'bg-green-400': isAutoProcessEnabled
|
||||
}"></div>
|
||||
<span class="text-xs text-gray-700 dark:text-gray-300 font-medium">
|
||||
自动处理已<span>{{ systemConfig?.auto_process_ready_resources ? '开启' : '关闭' }}</span>
|
||||
自动处理已<span>{{ isAutoProcessEnabled ? '开启' : '关闭' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<!-- 自动转存状态 -->
|
||||
<div class="flex items-center gap-2 bg-gray-100 dark:bg-gray-700 rounded-lg px-3 py-2">
|
||||
<div class="w-2 h-2 rounded-full animate-pulse" :class="{
|
||||
'bg-red-400': !systemConfig?.auto_transfer_enabled,
|
||||
'bg-green-400': systemConfig?.auto_transfer_enabled
|
||||
'bg-red-400': !isAutoTransferEnabled,
|
||||
'bg-green-400': isAutoTransferEnabled
|
||||
}"></div>
|
||||
<span class="text-xs text-gray-700 dark:text-gray-300 font-medium">
|
||||
自动转存已<span>{{ systemConfig?.auto_transfer_enabled ? '开启' : '关闭' }}</span>
|
||||
自动转存已<span>{{ isAutoTransferEnabled ? '开启' : '关闭' }}</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -305,6 +305,7 @@ const systemConfigStore = useSystemConfigStore()
|
||||
const taskStore = useTaskStore()
|
||||
|
||||
// 初始化系统配置(管理员页面使用管理员API)
|
||||
// 在setup阶段初始化,确保数据可用
|
||||
await systemConfigStore.initConfig(false, true)
|
||||
|
||||
// 版本信息
|
||||
@@ -344,9 +345,23 @@ onBeforeUnmount(() => {
|
||||
const systemConfig = computed(() => {
|
||||
const config = systemConfigStore.config || {}
|
||||
console.log('顶部导航系统配置:', config)
|
||||
console.log('自动处理状态:', config.auto_process_ready_resources)
|
||||
console.log('自动转存状态:', config.auto_transfer_enabled)
|
||||
return config
|
||||
})
|
||||
|
||||
// 自动处理状态(确保布尔值)
|
||||
const isAutoProcessEnabled = computed(() => {
|
||||
const value = systemConfig.value?.auto_process_ready_resources
|
||||
return value === true || value === 'true' || value === '1'
|
||||
})
|
||||
|
||||
// 自动转存状态(确保布尔值)
|
||||
const isAutoTransferEnabled = computed(() => {
|
||||
const value = systemConfig.value?.auto_transfer_enabled
|
||||
return value === true || value === 'true' || value === '1'
|
||||
})
|
||||
|
||||
// 用户菜单状态
|
||||
const showUserMenu = ref(false)
|
||||
|
||||
|
||||
@@ -400,7 +400,8 @@ const singleTransfer = async (resource: any) => {
|
||||
resources: [{
|
||||
title: resource.title,
|
||||
url: resource.url,
|
||||
category_id: resource.category_id || 0
|
||||
category_id: resource.category_id || 0,
|
||||
pan_id: resource.pan_id || 0
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -443,7 +444,8 @@ const confirmBatchTransfer = async () => {
|
||||
resources: selectedResources.value.map(r => ({
|
||||
title: r.title,
|
||||
url: r.url,
|
||||
category_id: r.category_id || 0
|
||||
category_id: r.category_id || 0,
|
||||
pan_id: r.pan_id || 0
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { useApiFetch } from '~/composables/useApiFetch'
|
||||
import { parseApiResponse } from '~/composables/useApi'
|
||||
|
||||
export const useSystemConfigStore = defineStore('systemConfig', {
|
||||
state: () => ({
|
||||
@@ -15,9 +16,11 @@ export const useSystemConfigStore = defineStore('systemConfig', {
|
||||
const response = await useApiFetch(apiUrl)
|
||||
console.log('Store API响应:', response) // 调试信息
|
||||
|
||||
// 正确处理API响应结构
|
||||
const data = response.data || response
|
||||
// 使用parseApiResponse正确解析API响应
|
||||
const data = parseApiResponse(response)
|
||||
console.log('Store 处理后的数据:', data) // 调试信息
|
||||
console.log('Store 自动处理状态:', data.auto_process_ready_resources)
|
||||
console.log('Store 自动转存状态:', data.auto_transfer_enabled)
|
||||
|
||||
this.config = data
|
||||
this.initialized = true
|
||||
|
||||
Reference in New Issue
Block a user