mirror of
https://github.com/ctwj/urldb.git
synced 2025-11-25 03:15:04 +08:00
update: 后台界面优化
This commit is contained in:
@@ -7,6 +7,8 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ctwj/urldb/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AlipanService 阿里云盘服务
|
// AlipanService 阿里云盘服务
|
||||||
@@ -428,7 +430,7 @@ func (a *AlipanService) manageAccessToken() (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 检查token是否过期
|
// 检查token是否过期
|
||||||
if time.Now().After(tokenInfo.ExpiresAt) {
|
if utils.GetCurrentTime().After(tokenInfo.ExpiresAt) {
|
||||||
return a.getNewAccessToken()
|
return a.getNewAccessToken()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/ctwj/urldb/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// QuarkPanService 夸克网盘服务
|
// QuarkPanService 夸克网盘服务
|
||||||
@@ -406,7 +408,7 @@ func (q *QuarkPanService) getShareSave(shareID, stoken string, fidList, fidToken
|
|||||||
|
|
||||||
// 生成指定长度的时间戳
|
// 生成指定长度的时间戳
|
||||||
func (q *QuarkPanService) generateTimestamp(length int) int64 {
|
func (q *QuarkPanService) generateTimestamp(length int) int64 {
|
||||||
timestamp := time.Now().UnixNano() / int64(time.Millisecond)
|
timestamp := utils.GetCurrentTime().UnixNano() / int64(time.Millisecond)
|
||||||
timestampStr := strconv.FormatInt(timestamp, 10)
|
timestampStr := strconv.FormatInt(timestamp, 10)
|
||||||
if len(timestampStr) > length {
|
if len(timestampStr) > length {
|
||||||
timestampStr = timestampStr[:length]
|
timestampStr = timestampStr[:length]
|
||||||
|
|||||||
@@ -219,8 +219,8 @@ func SystemConfigToPublicResponse(configs []entity.SystemConfig) map[string]inte
|
|||||||
|
|
||||||
// 设置时间戳(使用第一个配置的时间)
|
// 设置时间戳(使用第一个配置的时间)
|
||||||
if len(configs) > 0 {
|
if len(configs) > 0 {
|
||||||
response[entity.ConfigResponseFieldCreatedAt] = configs[0].CreatedAt.Format("2006-01-02 15:04:05")
|
response[entity.ConfigResponseFieldCreatedAt] = configs[0].CreatedAt.Format(utils.TimeFormatDateTime)
|
||||||
response[entity.ConfigResponseFieldUpdatedAt] = configs[0].UpdatedAt.Format("2006-01-02 15:04:05")
|
response[entity.ConfigResponseFieldUpdatedAt] = configs[0].UpdatedAt.Format(utils.TimeFormatDateTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|||||||
@@ -212,7 +212,7 @@ func (r *ResourceRepositoryImpl) SearchWithFilters(params map[string]interface{}
|
|||||||
var resources []entity.Resource
|
var resources []entity.Resource
|
||||||
var total int64
|
var total int64
|
||||||
|
|
||||||
db := r.db.Model(&entity.Resource{})
|
db := r.db.Model(&entity.Resource{}).Preload("Category").Preload("Pan").Preload("Tags")
|
||||||
|
|
||||||
// 处理参数
|
// 处理参数
|
||||||
for key, value := range params {
|
for key, value := range params {
|
||||||
@@ -258,6 +258,31 @@ func (r *ResourceRepositoryImpl) SearchWithFilters(params map[string]interface{}
|
|||||||
if isPublic, ok := value.(bool); ok {
|
if isPublic, ok := value.(bool); ok {
|
||||||
db = db.Where("is_public = ?", isPublic)
|
db = db.Where("is_public = ?", isPublic)
|
||||||
}
|
}
|
||||||
|
case "has_save_url": // 添加has_save_url参数支持
|
||||||
|
if hasSaveURL, ok := value.(bool); ok {
|
||||||
|
fmt.Printf("处理 has_save_url 参数: %v\n", hasSaveURL)
|
||||||
|
if hasSaveURL {
|
||||||
|
// 有转存链接:save_url不为空且不为空格
|
||||||
|
db = db.Where("save_url IS NOT NULL AND save_url != '' AND TRIM(save_url) != ''")
|
||||||
|
fmt.Printf("应用 has_save_url=true 条件: save_url IS NOT NULL AND save_url != '' AND TRIM(save_url) != ''\n")
|
||||||
|
} else {
|
||||||
|
// 没有转存链接:save_url为空、NULL或只有空格
|
||||||
|
db = db.Where("(save_url IS NULL OR save_url = '' OR TRIM(save_url) = '')")
|
||||||
|
fmt.Printf("应用 has_save_url=false 条件: (save_url IS NULL OR save_url = '' OR TRIM(save_url) = '')\n")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case "no_save_url": // 添加no_save_url参数支持(与has_save_url=false相同)
|
||||||
|
if noSaveURL, ok := value.(bool); ok && noSaveURL {
|
||||||
|
db = db.Where("(save_url IS NULL OR save_url = '' OR TRIM(save_url) = '')")
|
||||||
|
}
|
||||||
|
case "pan_name": // 添加pan_name参数支持
|
||||||
|
if panName, ok := value.(string); ok && panName != "" {
|
||||||
|
// 根据平台名称查找平台ID
|
||||||
|
var panEntity entity.Pan
|
||||||
|
if err := r.db.Where("name ILIKE ?", "%"+panName+"%").First(&panEntity).Error; err == nil {
|
||||||
|
db = db.Where("pan_id = ?", panEntity.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
package repo
|
package repo
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
|
||||||
"github.com/ctwj/urldb/db/entity"
|
"github.com/ctwj/urldb/db/entity"
|
||||||
|
"github.com/ctwj/urldb/utils"
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ func (r *ResourceViewRepositoryImpl) RecordView(resourceID uint, ipAddress, user
|
|||||||
|
|
||||||
// GetTodayViews 获取今日访问量
|
// GetTodayViews 获取今日访问量
|
||||||
func (r *ResourceViewRepositoryImpl) GetTodayViews() (int64, error) {
|
func (r *ResourceViewRepositoryImpl) GetTodayViews() (int64, error) {
|
||||||
today := time.Now().Format("2006-01-02")
|
today := utils.GetTodayString()
|
||||||
var count int64
|
var count int64
|
||||||
err := r.db.Model(&entity.ResourceView{}).
|
err := r.db.Model(&entity.ResourceView{}).
|
||||||
Where("DATE(created_at) = ?", today).
|
Where("DATE(created_at) = ?", today).
|
||||||
@@ -60,22 +60,22 @@ func (r *ResourceViewRepositoryImpl) GetViewsByDate(date string) (int64, error)
|
|||||||
// GetViewsTrend 获取访问量趋势数据
|
// GetViewsTrend 获取访问量趋势数据
|
||||||
func (r *ResourceViewRepositoryImpl) GetViewsTrend(days int) ([]map[string]interface{}, error) {
|
func (r *ResourceViewRepositoryImpl) GetViewsTrend(days int) ([]map[string]interface{}, error) {
|
||||||
var results []map[string]interface{}
|
var results []map[string]interface{}
|
||||||
|
|
||||||
for i := days - 1; i >= 0; i-- {
|
for i := days - 1; i >= 0; i-- {
|
||||||
date := time.Now().AddDate(0, 0, -i)
|
date := utils.GetCurrentTime().AddDate(0, 0, -i)
|
||||||
dateStr := date.Format("2006-01-02")
|
dateStr := date.Format(utils.TimeFormatDate)
|
||||||
|
|
||||||
count, err := r.GetViewsByDate(dateStr)
|
count, err := r.GetViewsByDate(dateStr)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
results = append(results, map[string]interface{}{
|
results = append(results, map[string]interface{}{
|
||||||
"date": dateStr,
|
"date": dateStr,
|
||||||
"views": count,
|
"views": count,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return results, nil
|
return results, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,4 +87,4 @@ func (r *ResourceViewRepositoryImpl) GetResourceViews(resourceID uint, limit int
|
|||||||
Limit(limit).
|
Limit(limit).
|
||||||
Find(&views).Error
|
Find(&views).Error
|
||||||
return views, err
|
return views, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package repo
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ctwj/urldb/db/entity"
|
"github.com/ctwj/urldb/db/entity"
|
||||||
|
"github.com/ctwj/urldb/utils"
|
||||||
|
|
||||||
"gorm.io/gorm"
|
"gorm.io/gorm"
|
||||||
)
|
)
|
||||||
@@ -37,7 +37,7 @@ func (r *SearchStatRepositoryImpl) RecordSearch(keyword, ip, userAgent string) e
|
|||||||
stat := entity.SearchStat{
|
stat := entity.SearchStat{
|
||||||
Keyword: keyword,
|
Keyword: keyword,
|
||||||
Count: 1,
|
Count: 1,
|
||||||
Date: time.Now(), // 可保留 date 字段,实际用 created_at 统计
|
Date: utils.GetCurrentTime(), // 可保留 date 字段,实际用 created_at 统计
|
||||||
IP: ip,
|
IP: ip,
|
||||||
UserAgent: userAgent,
|
UserAgent: userAgent,
|
||||||
}
|
}
|
||||||
@@ -124,9 +124,9 @@ func (r *SearchStatRepositoryImpl) GetKeywordTrend(keyword string, days int) ([]
|
|||||||
// GetSummary 获取搜索统计汇总
|
// GetSummary 获取搜索统计汇总
|
||||||
func (r *SearchStatRepositoryImpl) GetSummary() (map[string]int64, error) {
|
func (r *SearchStatRepositoryImpl) GetSummary() (map[string]int64, error) {
|
||||||
var total, today, week, month, keywords int64
|
var total, today, week, month, keywords int64
|
||||||
now := time.Now()
|
now := utils.GetCurrentTime()
|
||||||
todayStr := now.Format("2006-01-02")
|
todayStr := now.Format(utils.TimeFormatDate)
|
||||||
weekStart := now.AddDate(0, 0, -int(now.Weekday())+1).Format("2006-01-02") // 周一
|
weekStart := now.AddDate(0, 0, -int(now.Weekday())+1).Format(utils.TimeFormatDate) // 周一
|
||||||
monthStart := now.Format("2006-01") + "-01"
|
monthStart := now.Format("2006-01") + "-01"
|
||||||
|
|
||||||
// 总搜索次数
|
// 总搜索次数
|
||||||
|
|||||||
@@ -44,6 +44,21 @@ func GetResources(c *gin.Context) {
|
|||||||
utils.Error("解析分类ID失败: %v", err)
|
utils.Error("解析分类ID失败: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if hasSaveURL := c.Query("has_save_url"); hasSaveURL != "" {
|
||||||
|
if hasSaveURL == "true" {
|
||||||
|
params["has_save_url"] = true
|
||||||
|
} else if hasSaveURL == "false" {
|
||||||
|
params["has_save_url"] = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if noSaveURL := c.Query("no_save_url"); noSaveURL != "" {
|
||||||
|
if noSaveURL == "true" {
|
||||||
|
params["no_save_url"] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if panName := c.Query("pan_name"); panName != "" {
|
||||||
|
params["pan_name"] = panName
|
||||||
|
}
|
||||||
|
|
||||||
resources, total, err := repoManager.ResourceRepository.SearchWithFilters(params)
|
resources, total, err := repoManager.ResourceRepository.SearchWithFilters(params)
|
||||||
|
|
||||||
|
|||||||
@@ -23,12 +23,16 @@ func GetStats(c *gin.Context) {
|
|||||||
db.DB.Model(&entity.Resource{}).Select("COALESCE(SUM(view_count), 0)").Scan(&totalViews)
|
db.DB.Model(&entity.Resource{}).Select("COALESCE(SUM(view_count), 0)").Scan(&totalViews)
|
||||||
|
|
||||||
// 获取今日数据
|
// 获取今日数据
|
||||||
today := utils.GetCurrentTime().Format("2006-01-02")
|
today := utils.GetTodayString()
|
||||||
|
|
||||||
// 今日新增资源数量
|
// 今日新增资源数量
|
||||||
var todayResources int64
|
var todayResources int64
|
||||||
db.DB.Model(&entity.Resource{}).Where("DATE(created_at) = ?", today).Count(&todayResources)
|
db.DB.Model(&entity.Resource{}).Where("DATE(created_at) = ?", today).Count(&todayResources)
|
||||||
|
|
||||||
|
// 今日更新资源数量(包括新增和修改)
|
||||||
|
var todayUpdates int64
|
||||||
|
db.DB.Model(&entity.Resource{}).Where("DATE(updated_at) = ?", today).Count(&todayUpdates)
|
||||||
|
|
||||||
// 今日浏览量 - 使用访问记录表统计今日访问量
|
// 今日浏览量 - 使用访问记录表统计今日访问量
|
||||||
var todayViews int64
|
var todayViews int64
|
||||||
todayViews, err := repoManager.ResourceViewRepository.GetTodayViews()
|
todayViews, err := repoManager.ResourceViewRepository.GetTodayViews()
|
||||||
@@ -44,8 +48,8 @@ func GetStats(c *gin.Context) {
|
|||||||
// 添加调试日志
|
// 添加调试日志
|
||||||
utils.Info("统计数据 - 总资源: %d, 总分类: %d, 总标签: %d, 总浏览量: %d",
|
utils.Info("统计数据 - 总资源: %d, 总分类: %d, 总标签: %d, 总浏览量: %d",
|
||||||
totalResources, totalCategories, totalTags, totalViews)
|
totalResources, totalCategories, totalTags, totalViews)
|
||||||
utils.Info("今日数据 - 新增资源: %d, 今日浏览量: %d, 今日搜索: %d",
|
utils.Info("今日数据 - 新增资源: %d, 今日更新: %d, 今日浏览量: %d, 今日搜索: %d",
|
||||||
todayResources, todayViews, todaySearches)
|
todayResources, todayUpdates, todayViews, todaySearches)
|
||||||
|
|
||||||
SuccessResponse(c, gin.H{
|
SuccessResponse(c, gin.H{
|
||||||
"total_resources": totalResources,
|
"total_resources": totalResources,
|
||||||
@@ -53,6 +57,7 @@ func GetStats(c *gin.Context) {
|
|||||||
"total_tags": totalTags,
|
"total_tags": totalTags,
|
||||||
"total_views": totalViews,
|
"total_views": totalViews,
|
||||||
"today_resources": todayResources,
|
"today_resources": todayResources,
|
||||||
|
"today_updates": todayUpdates,
|
||||||
"today_views": todayViews,
|
"today_views": todayViews,
|
||||||
"today_searches": todaySearches,
|
"today_searches": todaySearches,
|
||||||
})
|
})
|
||||||
@@ -111,7 +116,7 @@ func GetPerformanceStats(c *gin.Context) {
|
|||||||
func GetSystemInfo(c *gin.Context) {
|
func GetSystemInfo(c *gin.Context) {
|
||||||
SuccessResponse(c, gin.H{
|
SuccessResponse(c, gin.H{
|
||||||
"uptime": time.Since(startTime).String(),
|
"uptime": time.Since(startTime).String(),
|
||||||
"start_time": utils.FormatTime(startTime, "2006-01-02 15:04:05"),
|
"start_time": utils.FormatTime(startTime, utils.TimeFormatDateTime),
|
||||||
"version": utils.Version,
|
"version": utils.Version,
|
||||||
"environment": gin.H{
|
"environment": gin.H{
|
||||||
"gin_mode": gin.Mode(),
|
"gin_mode": gin.Mode(),
|
||||||
@@ -146,7 +151,7 @@ func GetSearchesTrend(c *gin.Context) {
|
|||||||
// 生成最近7天的日期
|
// 生成最近7天的日期
|
||||||
for i := 6; i >= 0; i-- {
|
for i := 6; i >= 0; i-- {
|
||||||
date := utils.GetCurrentTime().AddDate(0, 0, -i)
|
date := utils.GetCurrentTime().AddDate(0, 0, -i)
|
||||||
dateStr := date.Format("2006-01-02")
|
dateStr := date.Format(utils.TimeFormatDate)
|
||||||
|
|
||||||
// 查询该日期的搜索量(从搜索统计表)
|
// 查询该日期的搜索量(从搜索统计表)
|
||||||
var searches int64
|
var searches int64
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ctwj/urldb/db/entity"
|
"github.com/ctwj/urldb/db/entity"
|
||||||
"github.com/ctwj/urldb/db/repo"
|
"github.com/ctwj/urldb/db/repo"
|
||||||
@@ -58,8 +57,8 @@ func (h *TaskHandler) CreateBatchTransferTask(c *gin.Context) {
|
|||||||
Type: "transfer",
|
Type: "transfer",
|
||||||
Status: "pending",
|
Status: "pending",
|
||||||
TotalItems: len(req.Resources),
|
TotalItems: len(req.Resources),
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: utils.GetCurrentTime(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: utils.GetCurrentTime(),
|
||||||
}
|
}
|
||||||
|
|
||||||
err := h.repoMgr.TaskRepository.Create(newTask)
|
err := h.repoMgr.TaskRepository.Create(newTask)
|
||||||
@@ -85,8 +84,8 @@ func (h *TaskHandler) CreateBatchTransferTask(c *gin.Context) {
|
|||||||
TaskID: newTask.ID,
|
TaskID: newTask.ID,
|
||||||
Status: "pending",
|
Status: "pending",
|
||||||
InputData: string(inputJSON),
|
InputData: string(inputJSON),
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: utils.GetCurrentTime(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: utils.GetCurrentTime(),
|
||||||
}
|
}
|
||||||
|
|
||||||
err = h.repoMgr.TaskItemRepository.Create(taskItem)
|
err = h.repoMgr.TaskItemRepository.Create(taskItem)
|
||||||
|
|||||||
@@ -257,7 +257,7 @@ func (a *AutoTransferScheduler) processAutoTransfer() {
|
|||||||
utils.Error(fmt.Sprintf("转存资源失败 (ID: %d): %v", res.ID, err))
|
utils.Error(fmt.Sprintf("转存资源失败 (ID: %d): %v", res.ID, err))
|
||||||
} else {
|
} else {
|
||||||
utils.Info(fmt.Sprintf("成功转存资源: %s", res.Title))
|
utils.Info(fmt.Sprintf("成功转存资源: %s", res.Title))
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(utils.GetCurrentTime().UnixNano())
|
||||||
sleepSec := rand.Intn(3) + 1 // 1,2,3
|
sleepSec := rand.Intn(3) + 1 // 1,2,3
|
||||||
time.Sleep(time.Duration(sleepSec) * time.Second)
|
time.Sleep(time.Duration(sleepSec) * time.Second)
|
||||||
}
|
}
|
||||||
@@ -289,7 +289,7 @@ func (a *AutoTransferScheduler) getQuarkPanID() (uint, error) {
|
|||||||
// getResourcesForTransfer 获取需要转存的资源
|
// getResourcesForTransfer 获取需要转存的资源
|
||||||
func (a *AutoTransferScheduler) getResourcesForTransfer(quarkPanID uint, limit int) ([]*entity.Resource, error) {
|
func (a *AutoTransferScheduler) getResourcesForTransfer(quarkPanID uint, limit int) ([]*entity.Resource, error) {
|
||||||
// 获取最近24小时内的资源
|
// 获取最近24小时内的资源
|
||||||
sinceTime := time.Now().Add(-24 * time.Hour)
|
sinceTime := utils.GetCurrentTime().Add(-24 * time.Hour)
|
||||||
|
|
||||||
// 使用资源仓库的方法获取需要转存的资源
|
// 使用资源仓库的方法获取需要转存的资源
|
||||||
repoImpl, ok := a.resourceRepo.(*repo.ResourceRepositoryImpl)
|
repoImpl, ok := a.resourceRepo.(*repo.ResourceRepositoryImpl)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/ctwj/urldb/db/entity"
|
"github.com/ctwj/urldb/db/entity"
|
||||||
"github.com/ctwj/urldb/db/repo"
|
"github.com/ctwj/urldb/db/repo"
|
||||||
@@ -275,7 +274,7 @@ func (tm *TaskManager) processTaskItem(ctx context.Context, taskID uint, item *e
|
|||||||
// 处理失败
|
// 处理失败
|
||||||
outputData := map[string]interface{}{
|
outputData := map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
"time": time.Now(),
|
"time": utils.GetCurrentTime(),
|
||||||
}
|
}
|
||||||
outputJSON, _ := json.Marshal(outputData)
|
outputJSON, _ := json.Marshal(outputData)
|
||||||
|
|
||||||
@@ -289,7 +288,7 @@ func (tm *TaskManager) processTaskItem(ctx context.Context, taskID uint, item *e
|
|||||||
// 处理成功
|
// 处理成功
|
||||||
outputData := map[string]interface{}{
|
outputData := map[string]interface{}{
|
||||||
"success": true,
|
"success": true,
|
||||||
"time": time.Now(),
|
"time": utils.GetCurrentTime(),
|
||||||
}
|
}
|
||||||
outputJSON, _ := json.Marshal(outputData)
|
outputJSON, _ := json.Marshal(outputData)
|
||||||
|
|
||||||
@@ -315,7 +314,7 @@ func (tm *TaskManager) updateTaskProgress(taskID uint, progress float64, process
|
|||||||
"processed": processed,
|
"processed": processed,
|
||||||
"success": success,
|
"success": success,
|
||||||
"failed": failed,
|
"failed": failed,
|
||||||
"time": time.Now(),
|
"time": utils.GetCurrentTime(),
|
||||||
}
|
}
|
||||||
|
|
||||||
progressJSON, _ := json.Marshal(progressData)
|
progressJSON, _ := json.Marshal(progressData)
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ func (tp *TransferProcessor) Process(ctx context.Context, taskID uint, item *ent
|
|||||||
ResourceID: existingResource.ID,
|
ResourceID: existingResource.ID,
|
||||||
SaveURL: existingResource.SaveURL,
|
SaveURL: existingResource.SaveURL,
|
||||||
Success: true,
|
Success: true,
|
||||||
Time: time.Now().Format("2006-01-02 15:04:05"),
|
Time: utils.GetCurrentTimeString(),
|
||||||
}
|
}
|
||||||
|
|
||||||
outputJSON, _ := json.Marshal(output)
|
outputJSON, _ := json.Marshal(output)
|
||||||
@@ -98,7 +98,7 @@ func (tp *TransferProcessor) Process(ctx context.Context, taskID uint, item *ent
|
|||||||
output := TransferOutput{
|
output := TransferOutput{
|
||||||
Error: err.Error(),
|
Error: err.Error(),
|
||||||
Success: false,
|
Success: false,
|
||||||
Time: time.Now().Format("2006-01-02 15:04:05"),
|
Time: utils.GetCurrentTimeString(),
|
||||||
}
|
}
|
||||||
|
|
||||||
outputJSON, _ := json.Marshal(output)
|
outputJSON, _ := json.Marshal(output)
|
||||||
@@ -113,7 +113,7 @@ func (tp *TransferProcessor) Process(ctx context.Context, taskID uint, item *ent
|
|||||||
output := TransferOutput{
|
output := TransferOutput{
|
||||||
Error: "转存成功但未获取到分享链接",
|
Error: "转存成功但未获取到分享链接",
|
||||||
Success: false,
|
Success: false,
|
||||||
Time: time.Now().Format("2006-01-02 15:04:05"),
|
Time: utils.GetCurrentTimeString(),
|
||||||
}
|
}
|
||||||
|
|
||||||
outputJSON, _ := json.Marshal(output)
|
outputJSON, _ := json.Marshal(output)
|
||||||
@@ -128,7 +128,7 @@ func (tp *TransferProcessor) Process(ctx context.Context, taskID uint, item *ent
|
|||||||
ResourceID: resourceID,
|
ResourceID: resourceID,
|
||||||
SaveURL: saveURL,
|
SaveURL: saveURL,
|
||||||
Success: true,
|
Success: true,
|
||||||
Time: time.Now().Format("2006-01-02 15:04:05"),
|
Time: utils.GetCurrentTimeString(),
|
||||||
}
|
}
|
||||||
|
|
||||||
outputJSON, _ := json.Marshal(output)
|
outputJSON, _ := json.Marshal(output)
|
||||||
|
|||||||
@@ -5,6 +5,13 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 时间格式常量
|
||||||
|
const (
|
||||||
|
TimeFormatDate = "2006-01-02"
|
||||||
|
TimeFormatDateTime = "2006-01-02 15:04:05"
|
||||||
|
TimeFormatRFC3339 = time.RFC3339
|
||||||
|
)
|
||||||
|
|
||||||
// InitTimezone 初始化时区设置
|
// InitTimezone 初始化时区设置
|
||||||
func InitTimezone() {
|
func InitTimezone() {
|
||||||
// 从环境变量获取时区配置
|
// 从环境变量获取时区配置
|
||||||
@@ -36,20 +43,35 @@ func GetCurrentTime() time.Time {
|
|||||||
|
|
||||||
// GetCurrentTimeString 获取当前时间字符串(使用配置的时区)
|
// GetCurrentTimeString 获取当前时间字符串(使用配置的时区)
|
||||||
func GetCurrentTimeString() string {
|
func GetCurrentTimeString() string {
|
||||||
return time.Now().Format("2006-01-02 15:04:05")
|
return time.Now().Format(TimeFormatDateTime)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetCurrentTimeRFC3339 获取当前时间RFC3339格式(使用配置的时区)
|
// GetCurrentTimeRFC3339 获取当前时间RFC3339格式(使用配置的时区)
|
||||||
func GetCurrentTimeRFC3339() string {
|
func GetCurrentTimeRFC3339() string {
|
||||||
return time.Now().Format(time.RFC3339)
|
return time.Now().Format(TimeFormatRFC3339)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ParseTime 解析时间字符串(使用配置的时区)
|
// ParseTime 解析时间字符串(使用配置的时区)
|
||||||
func ParseTime(timeStr string) (time.Time, error) {
|
func ParseTime(timeStr string) (time.Time, error) {
|
||||||
return time.Parse("2006-01-02 15:04:05", timeStr)
|
return time.Parse(TimeFormatDateTime, timeStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// FormatTime 格式化时间(使用配置的时区)
|
// FormatTime 格式化时间(使用配置的时区)
|
||||||
func FormatTime(t time.Time, layout string) string {
|
func FormatTime(t time.Time, layout string) string {
|
||||||
return t.Format(layout)
|
return t.Format(layout)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetTodayString 获取今日日期字符串
|
||||||
|
func GetTodayString() string {
|
||||||
|
return time.Now().Format(TimeFormatDate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentTimestamp 获取当前时间戳
|
||||||
|
func GetCurrentTimestamp() int64 {
|
||||||
|
return time.Now().Unix()
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetCurrentTimestampNano 获取当前纳秒时间戳
|
||||||
|
func GetCurrentTimestampNano() int64 {
|
||||||
|
return time.Now().UnixNano()
|
||||||
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ func GetFullVersionInfo() string {
|
|||||||
Node版本: %s
|
Node版本: %s
|
||||||
平台: %s/%s`,
|
平台: %s/%s`,
|
||||||
info.Version,
|
info.Version,
|
||||||
FormatTime(info.BuildTime, "2006-01-02 15:04:05"),
|
FormatTime(info.BuildTime, TimeFormatDateTime),
|
||||||
info.GitCommit,
|
info.GitCommit,
|
||||||
info.GitBranch,
|
info.GitBranch,
|
||||||
info.GoVersion,
|
info.GoVersion,
|
||||||
|
|||||||
1
web/components.d.ts
vendored
1
web/components.d.ts
vendored
@@ -24,6 +24,7 @@ declare module 'vue' {
|
|||||||
NForm: typeof import('naive-ui')['NForm']
|
NForm: typeof import('naive-ui')['NForm']
|
||||||
NFormItem: typeof import('naive-ui')['NFormItem']
|
NFormItem: typeof import('naive-ui')['NFormItem']
|
||||||
NInput: typeof import('naive-ui')['NInput']
|
NInput: typeof import('naive-ui')['NInput']
|
||||||
|
NInputNumber: typeof import('naive-ui')['NInputNumber']
|
||||||
NList: typeof import('naive-ui')['NList']
|
NList: typeof import('naive-ui')['NList']
|
||||||
NListItem: typeof import('naive-ui')['NListItem']
|
NListItem: typeof import('naive-ui')['NListItem']
|
||||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||||
|
|||||||
@@ -136,7 +136,7 @@ const systemConfigStore = useSystemConfigStore()
|
|||||||
const systemConfig = computed(() => systemConfigStore.config)
|
const systemConfig = computed(() => systemConfigStore.config)
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
systemConfigStore.initConfig()
|
systemConfigStore.initConfig(false, true)
|
||||||
})
|
})
|
||||||
|
|
||||||
// 退出登录
|
// 退出登录
|
||||||
|
|||||||
@@ -8,19 +8,16 @@
|
|||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
资源信息 <span class="text-red-500">*</span>
|
资源内容 <span class="text-red-500">*</span>
|
||||||
</label>
|
</label>
|
||||||
<n-input
|
<n-input
|
||||||
v-model:value="resourceText"
|
v-model:value="resourceText"
|
||||||
type="textarea"
|
type="textarea"
|
||||||
placeholder="请输入资源信息,每行格式:标题|链接地址 例如: 电影名称1|https://pan.quark.cn/s/xxx 电影名称2|https://pan.baidu.com/s/xxx"
|
placeholder="请输入资源内容,格式:标题和URL为一组..."
|
||||||
:rows="12"
|
:autosize="{ minRows: 10, maxRows: 15 }"
|
||||||
show-count
|
show-count
|
||||||
:maxlength="10000"
|
:maxlength="10000"
|
||||||
/>
|
/>
|
||||||
<p class="text-xs text-gray-500 mt-1">
|
|
||||||
每行一个资源,格式:标题|链接地址(用竖线分隔)
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -73,7 +70,7 @@
|
|||||||
<template #icon>
|
<template #icon>
|
||||||
<i class="fas fa-trash"></i>
|
<i class="fas fa-trash"></i>
|
||||||
</template>
|
</template>
|
||||||
清空输入
|
清空内容
|
||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -286,7 +283,7 @@ const handleBatchTransfer = async () => {
|
|||||||
const resourceList = parseResourceText(resourceText.value)
|
const resourceList = parseResourceText(resourceText.value)
|
||||||
|
|
||||||
if (resourceList.length === 0) {
|
if (resourceList.length === 0) {
|
||||||
message.warning('没有找到有效的资源信息,请按照"标题"和"链接"分行输入')
|
message.warning('没有找到有效的资源信息,请按照格式要求输入:标题和URL为一组,标题必填')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,22 +330,49 @@ const handleBatchTransfer = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析资源文本,按照 标题\n链接 的格式
|
// 解析资源文本,按照 标题\n链接 的格式(支持同一标题多个URL)
|
||||||
const parseResourceText = (text: string) => {
|
const parseResourceText = (text: string) => {
|
||||||
const lines = text.split('\n').filter((line: string) => line.trim())
|
const lines = text.split('\n').filter((line: string) => line.trim())
|
||||||
const resourceList = []
|
const resourceList = []
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i += 2) {
|
let currentTitle = ''
|
||||||
const title = lines[i]?.trim()
|
let currentUrls = []
|
||||||
const url = lines[i + 1]?.trim()
|
|
||||||
|
for (const line of lines) {
|
||||||
if (title && url && isValidUrl(url)) {
|
// 判断是否为 url(以 http/https 开头)
|
||||||
resourceList.push({
|
if (/^https?:\/\//i.test(line)) {
|
||||||
title,
|
currentUrls.push(line.trim())
|
||||||
url,
|
} else {
|
||||||
category_id: selectedCategory.value || 0,
|
// 新标题,先保存上一个
|
||||||
tags: selectedTags.value || []
|
if (currentTitle && currentUrls.length > 0) {
|
||||||
})
|
// 为每个URL创建一个资源项
|
||||||
|
for (const url of currentUrls) {
|
||||||
|
if (isValidUrl(url)) {
|
||||||
|
resourceList.push({
|
||||||
|
title: currentTitle,
|
||||||
|
url: url,
|
||||||
|
category_id: selectedCategory.value || 0,
|
||||||
|
tags: selectedTags.value || []
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentTitle = line.trim()
|
||||||
|
currentUrls = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理最后一组
|
||||||
|
if (currentTitle && currentUrls.length > 0) {
|
||||||
|
for (const url of currentUrls) {
|
||||||
|
if (isValidUrl(url)) {
|
||||||
|
resourceList.push({
|
||||||
|
title: currentTitle,
|
||||||
|
url: url,
|
||||||
|
category_id: selectedCategory.value || 0,
|
||||||
|
tags: selectedTags.value || []
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -92,14 +92,14 @@ const autoTransferEnabled = ref(false)
|
|||||||
// 获取系统配置状态
|
// 获取系统配置状态
|
||||||
const fetchSystemStatus = async () => {
|
const fetchSystemStatus = async () => {
|
||||||
try {
|
try {
|
||||||
await systemConfigStore.initConfig()
|
await systemConfigStore.initConfig(false, true)
|
||||||
|
|
||||||
// 从系统配置中获取自动处理和自动转存状态
|
// 从系统配置中获取自动处理和自动转存状态
|
||||||
const config = systemConfigStore.config
|
const config = systemConfigStore.config
|
||||||
|
|
||||||
if (config) {
|
if (config) {
|
||||||
// 检查自动处理状态
|
// 检查自动处理状态
|
||||||
autoProcessEnabled.value = config.auto_process_enabled === '1' || config.auto_process_enabled === true
|
autoProcessEnabled.value = config.auto_process_ready_resources === '1' || config.auto_process_ready_resources === true
|
||||||
|
|
||||||
// 检查自动转存状态
|
// 检查自动转存状态
|
||||||
autoTransferEnabled.value = config.auto_transfer_enabled === '1' || config.auto_transfer_enabled === true
|
autoTransferEnabled.value = config.auto_transfer_enabled === '1' || config.auto_transfer_enabled === true
|
||||||
|
|||||||
@@ -33,6 +33,11 @@
|
|||||||
</n-button>
|
</n-button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 调试信息 -->
|
||||||
|
<div class="text-sm text-gray-500 mb-2">
|
||||||
|
数据数量: {{ resources.length }}, 总数: {{ total }}, 加载状态: {{ loading }}
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 数据表格 -->
|
<!-- 数据表格 -->
|
||||||
<n-data-table
|
<n-data-table
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
@@ -51,10 +56,14 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted, h } from 'vue'
|
import { ref, reactive, computed, onMounted, h } from 'vue'
|
||||||
import { useResourceApi } from '~/composables/useApi'
|
import { useResourceApi } from '~/composables/useApi'
|
||||||
|
import { useMessage } from 'naive-ui'
|
||||||
|
|
||||||
|
// 消息提示
|
||||||
|
const $message = useMessage()
|
||||||
|
|
||||||
// 数据状态
|
// 数据状态
|
||||||
const loading = ref(false)
|
const loading = ref(false)
|
||||||
const resources = ref([])
|
const resources = ref<any[]>([])
|
||||||
const total = ref(0)
|
const total = ref(0)
|
||||||
const currentPage = ref(1)
|
const currentPage = ref(1)
|
||||||
const pageSize = ref(20)
|
const pageSize = ref(20)
|
||||||
@@ -79,12 +88,12 @@ const pagination = reactive({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// 表格列配置
|
// 表格列配置
|
||||||
const columns = [
|
const columns: any[] = [
|
||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
key: 'id',
|
key: 'id',
|
||||||
width: 60,
|
width: 60,
|
||||||
fixed: 'left'
|
fixed: 'left' as const
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '标题',
|
title: '标题',
|
||||||
@@ -101,11 +110,14 @@ const columns = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '平台',
|
title: '平台',
|
||||||
key: 'platform_name',
|
key: 'pan_name',
|
||||||
width: 80,
|
width: 80,
|
||||||
render: (row: any) => {
|
render: (row: any) => {
|
||||||
const platform = platformOptions.value.find((p: any) => p.value === row.pan_id)
|
if (row.pan_id) {
|
||||||
return platform?.label || '未知'
|
const platform = platformOptions.value.find((p: any) => p.value === row.pan_id)
|
||||||
|
return platform?.label || '未知'
|
||||||
|
}
|
||||||
|
return '未知'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -137,31 +149,39 @@ const columns = [
|
|||||||
width: 120,
|
width: 120,
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
render: (row: any) => {
|
render: (row: any) => {
|
||||||
return [
|
return h('div', { class: 'flex space-x-2' }, [
|
||||||
h('n-button', {
|
h('n-button', {
|
||||||
size: 'small',
|
size: 'small',
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
onClick: () => viewResource(row)
|
onClick: () => viewResource(row)
|
||||||
}, '查看'),
|
}, { default: () => '查看' }),
|
||||||
h('n-button', {
|
h('n-button', {
|
||||||
size: 'small',
|
size: 'small',
|
||||||
type: 'info',
|
type: 'info',
|
||||||
style: { marginLeft: '8px' },
|
|
||||||
onClick: () => copyLink(row.save_url)
|
onClick: () => copyLink(row.save_url)
|
||||||
}, '复制')
|
}, { default: () => '复制' })
|
||||||
]
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
// 平台选项
|
// 平台选项
|
||||||
const platformOptions = ref([])
|
const platformOptions = ref([
|
||||||
|
{ label: '夸克网盘', value: 1 },
|
||||||
|
{ label: '百度网盘', value: 2 },
|
||||||
|
{ label: '阿里云盘', value: 3 },
|
||||||
|
{ label: '天翼云盘', value: 4 },
|
||||||
|
{ label: '迅雷云盘', value: 5 },
|
||||||
|
{ label: '123云盘', value: 6 },
|
||||||
|
{ label: '115网盘', value: 7 },
|
||||||
|
{ label: 'UC网盘', value: 8 }
|
||||||
|
])
|
||||||
|
|
||||||
// 获取已转存资源
|
// 获取已转存资源
|
||||||
const fetchTransferredResources = async () => {
|
const fetchTransferredResources = async () => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
try {
|
try {
|
||||||
const params = {
|
const params: any = {
|
||||||
page: currentPage.value,
|
page: currentPage.value,
|
||||||
page_size: pageSize.value,
|
page_size: pageSize.value,
|
||||||
has_save_url: true // 筛选有转存链接的资源
|
has_save_url: true // 筛选有转存链接的资源
|
||||||
@@ -174,17 +194,36 @@ const fetchTransferredResources = async () => {
|
|||||||
params.category_id = selectedCategory.value
|
params.category_id = selectedCategory.value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('请求参数:', params)
|
||||||
const result = await resourceApi.getResources(params) as any
|
const result = await resourceApi.getResources(params) as any
|
||||||
console.log('已转存资源结果:', result)
|
console.log('已转存资源结果:', result)
|
||||||
|
console.log('结果类型:', typeof result)
|
||||||
|
console.log('结果结构:', Object.keys(result || {}))
|
||||||
|
|
||||||
if (result && result.resources) {
|
if (result && result.data) {
|
||||||
resources.value = result.resources
|
console.log('使用 resources 格式,数量:', result.data.length)
|
||||||
|
resources.value = result.data
|
||||||
total.value = result.total || 0
|
total.value = result.total || 0
|
||||||
pagination.itemCount = result.total || 0
|
pagination.itemCount = result.total || 0
|
||||||
} else if (Array.isArray(result)) {
|
} else if (Array.isArray(result)) {
|
||||||
|
console.log('使用数组格式,数量:', result.length)
|
||||||
resources.value = result
|
resources.value = result
|
||||||
total.value = result.length
|
total.value = result.length
|
||||||
pagination.itemCount = result.length
|
pagination.itemCount = result.length
|
||||||
|
} else {
|
||||||
|
console.log('未知格式,设置空数组')
|
||||||
|
resources.value = []
|
||||||
|
total.value = 0
|
||||||
|
pagination.itemCount = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('最终 resources.value:', resources.value)
|
||||||
|
console.log('最终 total.value:', total.value)
|
||||||
|
|
||||||
|
// 检查是否有资源没有 save_url
|
||||||
|
const resourcesWithoutSaveUrl = resources.value.filter((r: any) => !r.save_url || r.save_url.trim() === '')
|
||||||
|
if (resourcesWithoutSaveUrl.length > 0) {
|
||||||
|
console.warn('发现没有 save_url 的资源:', resourcesWithoutSaveUrl.map((r: any) => ({ id: r.id, title: r.title, save_url: r.save_url })))
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取已转存资源失败:', error)
|
console.error('获取已转存资源失败:', error)
|
||||||
|
|||||||
@@ -327,8 +327,8 @@ const fetchUntransferredResources = async () => {
|
|||||||
const result = await resourceApi.getResources(params) as any
|
const result = await resourceApi.getResources(params) as any
|
||||||
console.log('未转存资源结果:', result)
|
console.log('未转存资源结果:', result)
|
||||||
|
|
||||||
if (result && result.resources) {
|
if (result && result.data) {
|
||||||
resources.value = result.resources
|
resources.value = result.data
|
||||||
total.value = result.total || 0
|
total.value = result.total || 0
|
||||||
} else if (Array.isArray(result)) {
|
} else if (Array.isArray(result)) {
|
||||||
resources.value = result
|
resources.value = result
|
||||||
|
|||||||
@@ -26,6 +26,15 @@ export const parseApiResponse = <T>(response: any): T => {
|
|||||||
// 检查是否是包含success字段的响应格式(如登录接口)
|
// 检查是否是包含success字段的响应格式(如登录接口)
|
||||||
if (response && typeof response === 'object' && 'success' in response && 'data' in response) {
|
if (response && typeof response === 'object' && 'success' in response && 'data' in response) {
|
||||||
if (response.success) {
|
if (response.success) {
|
||||||
|
// 特殊处理资源接口返回的data格式,转换为resources格式
|
||||||
|
if (response.data && Array.isArray(response.data) && response.total !== undefined) {
|
||||||
|
return {
|
||||||
|
resources: response.data,
|
||||||
|
total: response.total,
|
||||||
|
page: response.page,
|
||||||
|
page_size: response.page_size
|
||||||
|
} as T
|
||||||
|
}
|
||||||
// 特殊处理资源接口返回的data.list格式,转换为resources格式
|
// 特殊处理资源接口返回的data.list格式,转换为resources格式
|
||||||
if (response.data && response.data.list && Array.isArray(response.data.list)) {
|
if (response.data && response.data.list && Array.isArray(response.data.list)) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
82
web/composables/useTimeFormat.ts
Normal file
82
web/composables/useTimeFormat.ts
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
// 统一的时间格式化工具函数
|
||||||
|
export const useTimeFormat = () => {
|
||||||
|
// 格式化日期时间(标准格式)
|
||||||
|
const formatDateTime = (dateString: string | Date) => {
|
||||||
|
if (!dateString) return '-'
|
||||||
|
const date = dateString instanceof Date ? dateString : new Date(dateString)
|
||||||
|
return date.toLocaleString('zh-CN')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化日期(仅日期)
|
||||||
|
const formatDate = (dateString: string | Date) => {
|
||||||
|
if (!dateString) return '-'
|
||||||
|
const date = dateString instanceof Date ? dateString : new Date(dateString)
|
||||||
|
return date.toLocaleDateString('zh-CN')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化时间(仅时间)
|
||||||
|
const formatTime = (dateString: string | Date) => {
|
||||||
|
if (!dateString) return '-'
|
||||||
|
const date = dateString instanceof Date ? dateString : new Date(dateString)
|
||||||
|
return date.toLocaleTimeString('zh-CN')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 格式化相对时间
|
||||||
|
const formatRelativeTime = (dateString: string | Date) => {
|
||||||
|
if (!dateString) return '-'
|
||||||
|
const date = dateString instanceof Date ? dateString : new Date(dateString)
|
||||||
|
const now = new Date()
|
||||||
|
const diffMs = now.getTime() - date.getTime()
|
||||||
|
const diffSec = Math.floor(diffMs / 1000)
|
||||||
|
const diffMin = Math.floor(diffSec / 60)
|
||||||
|
const diffHour = Math.floor(diffMin / 60)
|
||||||
|
const diffDay = Math.floor(diffHour / 24)
|
||||||
|
const diffWeek = Math.floor(diffDay / 7)
|
||||||
|
const diffMonth = Math.floor(diffDay / 30)
|
||||||
|
const diffYear = Math.floor(diffDay / 365)
|
||||||
|
|
||||||
|
const isToday = date.toDateString() === now.toDateString()
|
||||||
|
|
||||||
|
if (isToday) {
|
||||||
|
if (diffMin < 1) {
|
||||||
|
return '刚刚'
|
||||||
|
} else if (diffHour < 1) {
|
||||||
|
return `${diffMin}分钟前`
|
||||||
|
} else {
|
||||||
|
return `${diffHour}小时前`
|
||||||
|
}
|
||||||
|
} else if (diffDay < 1) {
|
||||||
|
return `${diffHour}小时前`
|
||||||
|
} else if (diffDay < 7) {
|
||||||
|
return `${diffDay}天前`
|
||||||
|
} else if (diffWeek < 4) {
|
||||||
|
return `${diffWeek}周前`
|
||||||
|
} else if (diffMonth < 12) {
|
||||||
|
return `${diffMonth}个月前`
|
||||||
|
} else {
|
||||||
|
return `${diffYear}年前`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取当前时间字符串
|
||||||
|
const getCurrentTimeString = () => {
|
||||||
|
return new Date().toLocaleString('zh-CN')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否为今天
|
||||||
|
const isToday = (dateString: string | Date) => {
|
||||||
|
if (!dateString) return false
|
||||||
|
const date = dateString instanceof Date ? dateString : new Date(dateString)
|
||||||
|
const now = new Date()
|
||||||
|
return date.toDateString() === now.toDateString()
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
formatDateTime,
|
||||||
|
formatDate,
|
||||||
|
formatTime,
|
||||||
|
formatRelativeTime,
|
||||||
|
getCurrentTimeString,
|
||||||
|
isToday
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -304,8 +304,8 @@ const systemConfigStore = useSystemConfigStore()
|
|||||||
// 任务状态管理
|
// 任务状态管理
|
||||||
const taskStore = useTaskStore()
|
const taskStore = useTaskStore()
|
||||||
|
|
||||||
// 初始化系统配置
|
// 初始化系统配置(管理员页面使用管理员API)
|
||||||
await systemConfigStore.initConfig()
|
await systemConfigStore.initConfig(false, true)
|
||||||
|
|
||||||
// 版本信息
|
// 版本信息
|
||||||
const versionInfo = ref({
|
const versionInfo = ref({
|
||||||
@@ -495,10 +495,10 @@ const operationItems = ref([
|
|||||||
active: (route: any) => route.path.startsWith('/admin/data-push')
|
active: (route: any) => route.path.startsWith('/admin/data-push')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: '/admin/auto-reply',
|
to: '/admin/bot',
|
||||||
label: '自动回复',
|
label: '机器人',
|
||||||
icon: 'fas fa-comments',
|
icon: 'fas fa-robot',
|
||||||
active: (route: any) => route.path.startsWith('/admin/auto-reply')
|
active: (route: any) => route.path.startsWith('/admin/bot')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
to: '/admin/seo',
|
to: '/admin/seo',
|
||||||
@@ -533,7 +533,7 @@ const autoExpandCurrentGroup = () => {
|
|||||||
expandedGroups.value.dataManagement = true
|
expandedGroups.value.dataManagement = true
|
||||||
} else if (currentPath.startsWith('/admin/site-config') || currentPath.startsWith('/admin/feature-config') || currentPath.startsWith('/admin/dev-config') || currentPath.startsWith('/admin/users') || currentPath.startsWith('/admin/version')) {
|
} else if (currentPath.startsWith('/admin/site-config') || currentPath.startsWith('/admin/feature-config') || currentPath.startsWith('/admin/dev-config') || currentPath.startsWith('/admin/users') || currentPath.startsWith('/admin/version')) {
|
||||||
expandedGroups.value.systemConfig = true
|
expandedGroups.value.systemConfig = true
|
||||||
} else if (currentPath.startsWith('/admin/data-transfer') || currentPath.startsWith('/admin/seo') || currentPath.startsWith('/admin/data-push') || currentPath.startsWith('/admin/auto-reply')) {
|
} else if (currentPath.startsWith('/admin/data-transfer') || currentPath.startsWith('/admin/seo') || currentPath.startsWith('/admin/data-push') || currentPath.startsWith('/admin/bot')) {
|
||||||
expandedGroups.value.operation = true
|
expandedGroups.value.operation = true
|
||||||
} else if (currentPath.startsWith('/admin/search-stats') || currentPath.startsWith('/admin/third-party-stats')) {
|
} else if (currentPath.startsWith('/admin/search-stats') || currentPath.startsWith('/admin/third-party-stats')) {
|
||||||
expandedGroups.value.statistics = true
|
expandedGroups.value.statistics = true
|
||||||
@@ -555,7 +555,7 @@ watch(() => useRoute().path, (newPath) => {
|
|||||||
expandedGroups.value.dataManagement = true
|
expandedGroups.value.dataManagement = true
|
||||||
} else if (newPath.startsWith('/admin/site-config') || newPath.startsWith('/admin/feature-config') || newPath.startsWith('/admin/dev-config') || newPath.startsWith('/admin/users') || newPath.startsWith('/admin/version')) {
|
} else if (newPath.startsWith('/admin/site-config') || newPath.startsWith('/admin/feature-config') || newPath.startsWith('/admin/dev-config') || newPath.startsWith('/admin/users') || newPath.startsWith('/admin/version')) {
|
||||||
expandedGroups.value.systemConfig = true
|
expandedGroups.value.systemConfig = true
|
||||||
} else if (newPath.startsWith('/admin/data-transfer') || newPath.startsWith('/admin/seo') || newPath.startsWith('/admin/data-push') || newPath.startsWith('/admin/auto-reply')) {
|
} else if (newPath.startsWith('/admin/data-transfer') || newPath.startsWith('/admin/seo') || newPath.startsWith('/admin/data-push') || newPath.startsWith('/admin/bot')) {
|
||||||
expandedGroups.value.operation = true
|
expandedGroups.value.operation = true
|
||||||
} else if (newPath.startsWith('/admin/search-stats') || newPath.startsWith('/admin/third-party-stats')) {
|
} else if (newPath.startsWith('/admin/search-stats') || newPath.startsWith('/admin/third-party-stats')) {
|
||||||
expandedGroups.value.statistics = true
|
expandedGroups.value.statistics = true
|
||||||
|
|||||||
@@ -1,198 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="space-y-6">
|
|
||||||
<!-- 页面标题 -->
|
|
||||||
<div class="flex items-center justify-between">
|
|
||||||
<div>
|
|
||||||
<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-button type="primary" @click="saveConfig" :loading="saving">
|
|
||||||
<template #icon>
|
|
||||||
<i class="fas fa-save"></i>
|
|
||||||
</template>
|
|
||||||
保存配置
|
|
||||||
</n-button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 配置表单 -->
|
|
||||||
<n-card>
|
|
||||||
<!-- 顶部Tabs -->
|
|
||||||
<n-tabs
|
|
||||||
v-model:value="activeTab"
|
|
||||||
type="line"
|
|
||||||
animated
|
|
||||||
class="mb-6"
|
|
||||||
>
|
|
||||||
<n-tab-pane name="qq" tab="QQ机器人">
|
|
||||||
|
|
||||||
<n-form
|
|
||||||
ref="formRef"
|
|
||||||
:model="configForm"
|
|
||||||
:rules="rules"
|
|
||||||
label-placement="left"
|
|
||||||
label-width="auto"
|
|
||||||
require-mark-placement="right-hanging"
|
|
||||||
>
|
|
||||||
<div class="space-y-6">
|
|
||||||
<!-- QQ机器人配置占位符 -->
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">QQ机器人开关</label>
|
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">开启QQ机器人自动回复功能</span>
|
|
||||||
</div>
|
|
||||||
<n-switch v-model:value="configForm.qq_bot_enabled" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 占位符内容 -->
|
|
||||||
<div class="p-8 text-center text-gray-500 dark:text-gray-400">
|
|
||||||
<i class="fas fa-cog text-4xl mb-4"></i>
|
|
||||||
<p class="text-lg font-medium mb-2">QQ机器人配置</p>
|
|
||||||
<p class="text-sm">QQ机器人自动回复功能配置区域</p>
|
|
||||||
<p class="text-xs mt-2">具体配置项待开发...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-form>
|
|
||||||
</n-tab-pane>
|
|
||||||
|
|
||||||
<n-tab-pane name="wechat" tab="微信公众号">
|
|
||||||
|
|
||||||
<n-form
|
|
||||||
ref="formRef"
|
|
||||||
:model="configForm"
|
|
||||||
:rules="rules"
|
|
||||||
label-placement="left"
|
|
||||||
label-width="auto"
|
|
||||||
require-mark-placement="right-hanging"
|
|
||||||
>
|
|
||||||
<div class="space-y-6">
|
|
||||||
<!-- 微信公众号配置占位符 -->
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">微信公众号开关</label>
|
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">开启微信公众号自动回复功能</span>
|
|
||||||
</div>
|
|
||||||
<n-switch v-model:value="configForm.wechat_mp_enabled" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 占位符内容 -->
|
|
||||||
<div class="p-8 text-center text-gray-500 dark:text-gray-400">
|
|
||||||
<i class="fas fa-comment-dots text-4xl mb-4"></i>
|
|
||||||
<p class="text-lg font-medium mb-2">微信公众号配置</p>
|
|
||||||
<p class="text-sm">微信公众号自动回复功能配置区域</p>
|
|
||||||
<p class="text-xs mt-2">具体配置项待开发...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-form>
|
|
||||||
</n-tab-pane>
|
|
||||||
|
|
||||||
<n-tab-pane name="wechat_open" tab="微信对话开放平台">
|
|
||||||
|
|
||||||
<n-form
|
|
||||||
ref="formRef"
|
|
||||||
:model="configForm"
|
|
||||||
:rules="rules"
|
|
||||||
label-placement="left"
|
|
||||||
label-width="auto"
|
|
||||||
require-mark-placement="right-hanging"
|
|
||||||
>
|
|
||||||
<div class="space-y-6">
|
|
||||||
<!-- 微信对话开放平台配置占位符 -->
|
|
||||||
<div class="space-y-2">
|
|
||||||
<div class="flex items-center space-x-2">
|
|
||||||
<label class="text-base font-semibold text-gray-800 dark:text-gray-200">微信对话开放平台开关</label>
|
|
||||||
<span class="text-xs text-gray-500 dark:text-gray-400">开启微信对话开放平台自动回复功能</span>
|
|
||||||
</div>
|
|
||||||
<n-switch v-model:value="configForm.wechat_open_enabled" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 占位符内容 -->
|
|
||||||
<div class="p-8 text-center text-gray-500 dark:text-gray-400">
|
|
||||||
<i class="fas fa-comments text-4xl mb-4"></i>
|
|
||||||
<p class="text-lg font-medium mb-2">微信对话开放平台配置</p>
|
|
||||||
<p class="text-sm">微信对话开放平台自动回复功能配置区域</p>
|
|
||||||
<p class="text-xs mt-2">具体配置项待开发...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</n-form>
|
|
||||||
</n-tab-pane>
|
|
||||||
</n-tabs>
|
|
||||||
</n-card>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
// 设置页面布局
|
|
||||||
definePageMeta({
|
|
||||||
layout: 'admin',
|
|
||||||
ssr: false
|
|
||||||
})
|
|
||||||
|
|
||||||
const notification = useNotification()
|
|
||||||
const formRef = ref()
|
|
||||||
const saving = ref(false)
|
|
||||||
const activeTab = ref('qq')
|
|
||||||
|
|
||||||
// 配置表单数据
|
|
||||||
const configForm = ref({
|
|
||||||
qq_bot_enabled: false,
|
|
||||||
wechat_mp_enabled: false,
|
|
||||||
wechat_open_enabled: false
|
|
||||||
})
|
|
||||||
|
|
||||||
// 表单验证规则
|
|
||||||
const rules = {
|
|
||||||
// 暂时为空,后续添加验证规则
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取配置
|
|
||||||
const fetchConfig = async () => {
|
|
||||||
try {
|
|
||||||
// 暂时使用模拟数据
|
|
||||||
configForm.value = {
|
|
||||||
qq_bot_enabled: false,
|
|
||||||
wechat_mp_enabled: false,
|
|
||||||
wechat_open_enabled: false
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('获取自动回复配置失败:', error)
|
|
||||||
notification.error({
|
|
||||||
content: '获取自动回复配置失败',
|
|
||||||
duration: 3000
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存配置
|
|
||||||
const saveConfig = async () => {
|
|
||||||
try {
|
|
||||||
saving.value = true
|
|
||||||
|
|
||||||
// 暂时使用模拟保存
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
|
||||||
|
|
||||||
notification.success({
|
|
||||||
content: '自动回复配置保存成功',
|
|
||||||
duration: 3000
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error('保存自动回复配置失败:', error)
|
|
||||||
notification.error({
|
|
||||||
content: '保存自动回复配置失败',
|
|
||||||
duration: 3000
|
|
||||||
})
|
|
||||||
} finally {
|
|
||||||
saving.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 页面加载时获取配置
|
|
||||||
onMounted(() => {
|
|
||||||
fetchConfig()
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
/* 自定义样式 */
|
|
||||||
</style>
|
|
||||||
@@ -1,26 +1,255 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-6">
|
<div class="space-y-6">
|
||||||
<div class="mb-6">
|
<!-- 页面标题 -->
|
||||||
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">机器人管理</h1>
|
<div class="flex items-center justify-between">
|
||||||
<p class="text-gray-600 dark:text-gray-400 mt-2">机器人配置与管理</p>
|
<div>
|
||||||
</div>
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">机器人管理</h1>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">管理各平台的机器人配置和自动回复功能</p>
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
|
||||||
<div class="text-center py-12">
|
|
||||||
<div class="text-gray-400 dark:text-gray-500 mb-4">
|
|
||||||
<i class="fas fa-robot text-4xl"></i>
|
|
||||||
</div>
|
|
||||||
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">功能开发中</h3>
|
|
||||||
<p class="text-gray-500 dark:text-gray-400">机器人管理功能正在开发中,敬请期待...</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 配置表单 -->
|
||||||
|
<n-card>
|
||||||
|
<!-- 顶部Tabs -->
|
||||||
|
<n-tabs
|
||||||
|
v-model:value="activeTab"
|
||||||
|
type="line"
|
||||||
|
animated
|
||||||
|
class="mb-6"
|
||||||
|
>
|
||||||
|
<n-tab-pane name="qq" tab="QQ机器人">
|
||||||
|
<div class="space-y-8">
|
||||||
|
<!-- 步骤1:Astrobot 安装指南 -->
|
||||||
|
<div class="bg-blue-50 dark:bg-blue-900/20 rounded-lg p-6">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="w-8 h-8 bg-blue-600 text-white rounded-full flex items-center justify-center mr-3">
|
||||||
|
<span class="text-sm font-bold">1</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">安装 Astrobot</h3>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex items-start space-x-3">
|
||||||
|
<i class="fas fa-github text-gray-600 dark:text-gray-400 mt-1"></i>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-gray-900 dark:text-white mb-1">开源地址</p>
|
||||||
|
<a
|
||||||
|
href="https://github.com/Astrian/astrobot"
|
||||||
|
target="_blank"
|
||||||
|
class="text-blue-600 dark:text-blue-400 hover:underline text-sm"
|
||||||
|
>
|
||||||
|
https://github.com/Astrian/astrobot
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-start space-x-3">
|
||||||
|
<i class="fas fa-book text-gray-600 dark:text-gray-400 mt-1"></i>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-gray-900 dark:text-white mb-1">安装教程</p>
|
||||||
|
<a
|
||||||
|
href="https://github.com/Astrian/astrobot/wiki"
|
||||||
|
target="_blank"
|
||||||
|
class="text-blue-600 dark:text-blue-400 hover:underline text-sm"
|
||||||
|
>
|
||||||
|
https://github.com/Astrian/astrobot/wiki
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 步骤2:插件安装 -->
|
||||||
|
<div class="bg-green-50 dark:bg-green-900/20 rounded-lg p-6">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="w-8 h-8 bg-green-600 text-white rounded-full flex items-center justify-center mr-3">
|
||||||
|
<span class="text-sm font-bold">2</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">安装插件</h3>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex items-start space-x-3">
|
||||||
|
<i class="fas fa-puzzle-piece text-gray-600 dark:text-gray-400 mt-1"></i>
|
||||||
|
<div>
|
||||||
|
<p class="text-sm font-medium text-gray-900 dark:text-white mb-1">插件地址</p>
|
||||||
|
<a
|
||||||
|
href="https://github.com/ctwj/astrbot_plugin_urldb"
|
||||||
|
target="_blank"
|
||||||
|
class="text-green-600 dark:text-green-400 hover:underline text-sm"
|
||||||
|
>
|
||||||
|
https://github.com/ctwj/astrbot_plugin_urldb
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-100 dark:bg-gray-800 rounded p-3">
|
||||||
|
<p class="text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
<strong>插件特性:</strong><br>
|
||||||
|
• 支持@机器人搜索功能<br>
|
||||||
|
• 可配置API域名和密钥<br>
|
||||||
|
• 自动格式化搜索结果<br>
|
||||||
|
• 支持超时时间配置<br><br>
|
||||||
|
<strong>安装步骤:</strong><br>
|
||||||
|
1. 下载插件文件<br>
|
||||||
|
2. 将插件放入 Astrobot 的 plugins 目录<br>
|
||||||
|
3. 重启 Astrobot<br>
|
||||||
|
4. 在配置文件中添加插件配置
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 步骤3:配置信息 -->
|
||||||
|
<div class="bg-purple-50 dark:bg-purple-900/20 rounded-lg p-6">
|
||||||
|
<div class="flex items-center mb-4">
|
||||||
|
<div class="w-8 h-8 bg-purple-600 text-white rounded-full flex items-center justify-center mr-3">
|
||||||
|
<span class="text-sm font-bold">3</span>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">配置信息</h3>
|
||||||
|
</div>
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<div>
|
||||||
|
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">网站域名</label>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<n-input
|
||||||
|
:value="siteDomain"
|
||||||
|
readonly
|
||||||
|
class="flex-1"
|
||||||
|
/>
|
||||||
|
<n-button
|
||||||
|
size="small"
|
||||||
|
@click="copyToClipboard(siteDomain)"
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</template>
|
||||||
|
复制
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label class="text-sm font-medium text-gray-700 dark:text-gray-300 mb-2 block">API Token</label>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<n-input
|
||||||
|
:value="apiToken"
|
||||||
|
readonly
|
||||||
|
type="password"
|
||||||
|
show-password-on="click"
|
||||||
|
class="flex-1"
|
||||||
|
/>
|
||||||
|
<n-button
|
||||||
|
size="small"
|
||||||
|
@click="copyToClipboard(apiToken)"
|
||||||
|
type="primary"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<i class="fas fa-copy"></i>
|
||||||
|
</template>
|
||||||
|
复制
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="bg-gray-100 dark:bg-gray-800 rounded p-3">
|
||||||
|
<p class="text-sm text-gray-700 dark:text-gray-300">
|
||||||
|
<strong>配置说明:</strong><br>
|
||||||
|
将上述信息配置到 Astrobot 的插件配置文件中,插件将自动连接到本系统进行资源搜索。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</n-tab-pane>
|
||||||
|
|
||||||
|
<n-tab-pane name="wechat" tab="微信公众号">
|
||||||
|
<div class="text-center py-12">
|
||||||
|
<i class="fas fa-lock text-4xl text-gray-400 mb-4"></i>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">功能暂未开放</h3>
|
||||||
|
<p class="text-gray-500 dark:text-gray-400">微信公众号机器人功能正在开发中,敬请期待</p>
|
||||||
|
</div>
|
||||||
|
</n-tab-pane>
|
||||||
|
|
||||||
|
<n-tab-pane name="telegram" tab="Telegram机器人">
|
||||||
|
<div class="text-center py-12">
|
||||||
|
<i class="fas fa-lock text-4xl text-gray-400 mb-4"></i>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">功能暂未开放</h3>
|
||||||
|
<p class="text-gray-500 dark:text-gray-400">Telegram机器人功能正在开发中,敬请期待</p>
|
||||||
|
</div>
|
||||||
|
</n-tab-pane>
|
||||||
|
|
||||||
|
<n-tab-pane name="wechat_open" tab="微信开放平台">
|
||||||
|
<div class="text-center py-12">
|
||||||
|
<i class="fas fa-lock text-4xl text-gray-400 mb-4"></i>
|
||||||
|
<h3 class="text-lg font-medium text-gray-900 dark:text-white mb-2">功能暂未开放</h3>
|
||||||
|
<p class="text-gray-500 dark:text-gray-400">微信开放平台机器人功能正在开发中,敬请期待</p>
|
||||||
|
</div>
|
||||||
|
</n-tab-pane>
|
||||||
|
</n-tabs>
|
||||||
|
</n-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
// 机器人管理页面
|
// 设置页面布局
|
||||||
definePageMeta({
|
definePageMeta({
|
||||||
layout: 'admin',
|
layout: 'admin',
|
||||||
middleware: ['auth']
|
ssr: false
|
||||||
})
|
})
|
||||||
</script>
|
|
||||||
|
const notification = useNotification()
|
||||||
|
const activeTab = ref('qq')
|
||||||
|
|
||||||
|
// 获取网站域名和API Token
|
||||||
|
const siteDomain = computed(() => {
|
||||||
|
if (process.client) {
|
||||||
|
return window.location.origin
|
||||||
|
}
|
||||||
|
return 'https://yourdomain.com'
|
||||||
|
})
|
||||||
|
|
||||||
|
const apiToken = ref('')
|
||||||
|
|
||||||
|
// 获取API Token
|
||||||
|
const fetchApiToken = async () => {
|
||||||
|
try {
|
||||||
|
const { useSystemConfigApi } = await import('~/composables/useApi')
|
||||||
|
const systemConfigApi = useSystemConfigApi()
|
||||||
|
const response = await systemConfigApi.getSystemConfig()
|
||||||
|
|
||||||
|
if (response && (response as any).api_token) {
|
||||||
|
apiToken.value = (response as any).api_token
|
||||||
|
} else {
|
||||||
|
apiToken.value = '未配置API Token'
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取API Token失败:', error)
|
||||||
|
apiToken.value = '获取失败'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制到剪贴板
|
||||||
|
const copyToClipboard = async (text: string) => {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text)
|
||||||
|
notification.success({
|
||||||
|
content: '已复制到剪贴板',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error('复制失败:', error)
|
||||||
|
notification.error({
|
||||||
|
content: '复制失败',
|
||||||
|
duration: 2000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载时获取配置
|
||||||
|
onMounted(() => {
|
||||||
|
fetchApiToken()
|
||||||
|
console.log('机器人管理页面已加载')
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 自定义样式 */
|
||||||
|
</style>
|
||||||
@@ -127,6 +127,11 @@ const saveConfig = async () => {
|
|||||||
content: '开发配置保存成功',
|
content: '开发配置保存成功',
|
||||||
duration: 3000
|
duration: 3000
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 刷新系统配置状态,确保顶部导航同步更新
|
||||||
|
const { useSystemConfigStore } = await import('~/stores/systemConfig')
|
||||||
|
const systemConfigStore = useSystemConfigStore()
|
||||||
|
await systemConfigStore.initConfig(true, true) // 强制刷新,使用管理员API
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存开发配置失败:', error)
|
console.error('保存开发配置失败:', error)
|
||||||
notification.error({
|
notification.error({
|
||||||
|
|||||||
@@ -229,6 +229,11 @@ const saveConfig = async () => {
|
|||||||
content: '功能配置保存成功',
|
content: '功能配置保存成功',
|
||||||
duration: 3000
|
duration: 3000
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 刷新系统配置状态,确保顶部导航同步更新
|
||||||
|
const { useSystemConfigStore } = await import('~/stores/systemConfig')
|
||||||
|
const systemConfigStore = useSystemConfigStore()
|
||||||
|
await systemConfigStore.initConfig(true, true) // 强制刷新,使用管理员API
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存功能配置失败:', error)
|
console.error('保存功能配置失败:', error)
|
||||||
notification.error({
|
notification.error({
|
||||||
|
|||||||
@@ -414,54 +414,54 @@ const linkColumns = [
|
|||||||
// 提交到百度
|
// 提交到百度
|
||||||
const submitToBaidu = () => {
|
const submitToBaidu = () => {
|
||||||
// 模拟提交
|
// 模拟提交
|
||||||
lastSubmitTime.value.baidu = new Date().toLocaleString()
|
lastSubmitTime.value.baidu = new Date().toLocaleString('zh-CN')
|
||||||
message.success('已提交到百度')
|
message.success('已提交到百度')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交到谷歌
|
// 提交到谷歌
|
||||||
const submitToGoogle = () => {
|
const submitToGoogle = () => {
|
||||||
// 模拟提交
|
// 模拟提交
|
||||||
lastSubmitTime.value.google = new Date().toLocaleString()
|
lastSubmitTime.value.google = new Date().toLocaleString('zh-CN')
|
||||||
message.success('已提交到谷歌')
|
message.success('已提交到谷歌')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交到必应
|
// 提交到必应
|
||||||
const submitToBing = () => {
|
const submitToBing = () => {
|
||||||
// 模拟提交
|
// 模拟提交
|
||||||
lastSubmitTime.value.bing = new Date().toLocaleString()
|
lastSubmitTime.value.bing = new Date().toLocaleString('zh-CN')
|
||||||
message.success('已提交到必应')
|
message.success('已提交到必应')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交到搜狗
|
// 提交到搜狗
|
||||||
const submitToSogou = () => {
|
const submitToSogou = () => {
|
||||||
// 模拟提交
|
// 模拟提交
|
||||||
lastSubmitTime.value.sogou = new Date().toLocaleString()
|
lastSubmitTime.value.sogou = new Date().toLocaleString('zh-CN')
|
||||||
message.success('已提交到搜狗')
|
message.success('已提交到搜狗')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交到神马搜索
|
// 提交到神马搜索
|
||||||
const submitToShenma = () => {
|
const submitToShenma = () => {
|
||||||
// 模拟提交
|
// 模拟提交
|
||||||
lastSubmitTime.value.shenma = new Date().toLocaleString()
|
lastSubmitTime.value.shenma = new Date().toLocaleString('zh-CN')
|
||||||
message.success('已提交到神马搜索')
|
message.success('已提交到神马搜索')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 提交到360搜索
|
// 提交到360搜索
|
||||||
const submitTo360 = () => {
|
const submitTo360 = () => {
|
||||||
// 模拟提交
|
// 模拟提交
|
||||||
lastSubmitTime.value.so360 = new Date().toLocaleString()
|
lastSubmitTime.value.so360 = new Date().toLocaleString('zh-CN')
|
||||||
message.success('已提交到360搜索')
|
message.success('已提交到360搜索')
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量提交
|
// 批量提交
|
||||||
const submitToAll = () => {
|
const submitToAll = () => {
|
||||||
// 模拟批量提交
|
// 模拟批量提交
|
||||||
lastSubmitTime.value.baidu = new Date().toLocaleString()
|
lastSubmitTime.value.baidu = new Date().toLocaleString('zh-CN')
|
||||||
lastSubmitTime.value.google = new Date().toLocaleString()
|
lastSubmitTime.value.google = new Date().toLocaleString('zh-CN')
|
||||||
lastSubmitTime.value.bing = new Date().toLocaleString()
|
lastSubmitTime.value.bing = new Date().toLocaleString('zh-CN')
|
||||||
lastSubmitTime.value.sogou = new Date().toLocaleString()
|
lastSubmitTime.value.sogou = new Date().toLocaleString('zh-CN')
|
||||||
lastSubmitTime.value.shenma = new Date().toLocaleString()
|
lastSubmitTime.value.shenma = new Date().toLocaleString('zh-CN')
|
||||||
lastSubmitTime.value.so360 = new Date().toLocaleString()
|
lastSubmitTime.value.so360 = new Date().toLocaleString('zh-CN')
|
||||||
message.success('已批量提交到所有搜索引擎')
|
message.success('已批量提交到所有搜索引擎')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -242,6 +242,11 @@ const saveConfig = async () => {
|
|||||||
content: '站点配置保存成功',
|
content: '站点配置保存成功',
|
||||||
duration: 3000
|
duration: 3000
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 刷新系统配置状态,确保顶部导航同步更新
|
||||||
|
const { useSystemConfigStore } = await import('~/stores/systemConfig')
|
||||||
|
const systemConfigStore = useSystemConfigStore()
|
||||||
|
await systemConfigStore.initConfig(true, true) // 强制刷新,使用管理员API
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('保存站点配置失败:', error)
|
console.error('保存站点配置失败:', error)
|
||||||
notification.error({
|
notification.error({
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="p-6 space-y-6">
|
<div class="p-4 space-y-4">
|
||||||
<!-- 页面标题和返回按钮 -->
|
<!-- 页面标题和返回按钮 -->
|
||||||
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between space-y-4 lg:space-y-0">
|
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between space-y-3 lg:space-y-0">
|
||||||
<div class="flex items-center space-x-4">
|
<div class="flex items-center space-x-3">
|
||||||
<n-button
|
<n-button
|
||||||
quaternary
|
quaternary
|
||||||
size="small"
|
size="small"
|
||||||
@@ -11,12 +11,11 @@
|
|||||||
<template #icon>
|
<template #icon>
|
||||||
<i class="fas fa-arrow-left"></i>
|
<i class="fas fa-arrow-left"></i>
|
||||||
</template>
|
</template>
|
||||||
<span class="hidden sm:inline">返回任务列表</span>
|
<span class="hidden sm:inline">返回</span>
|
||||||
<span class="sm:hidden">返回</span>
|
|
||||||
</n-button>
|
</n-button>
|
||||||
<div>
|
<div>
|
||||||
<h1 class="text-xl md:text-2xl font-bold text-gray-900 dark:text-white">任务详情</h1>
|
<h1 class="text-lg md:text-xl font-bold text-gray-900 dark:text-white">任务详情</h1>
|
||||||
<p class="text-sm text-gray-600 dark:text-gray-400 mt-1">查看任务的详细信息和执行状态</p>
|
<p class="text-xs text-gray-600 dark:text-gray-400">查看任务的详细信息和执行状态</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -75,102 +74,96 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 加载状态 -->
|
<!-- 加载状态 -->
|
||||||
<div v-if="loading" class="flex justify-center py-8">
|
<div v-if="loading" class="flex justify-center py-4">
|
||||||
<n-spin size="large" />
|
<n-spin size="medium" />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 任务详情 -->
|
<!-- 任务详情 -->
|
||||||
<div v-else-if="task" class="space-y-6">
|
<div v-else-if="task" class="space-y-4">
|
||||||
<!-- 基本信息卡片 -->
|
<!-- 整合的任务信息卡片 -->
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-4 md:p-6">
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-4">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">基本信息</h2>
|
<div class="flex flex-col lg:flex-row lg:items-center lg:justify-between space-y-4 lg:space-y-0">
|
||||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
<!-- 左侧:基本信息 -->
|
||||||
<div>
|
<div class="flex-1">
|
||||||
<label class="text-sm font-medium text-gray-500 dark:text-gray-400">任务ID</label>
|
<div class="flex items-center space-x-4 mb-3">
|
||||||
<p class="text-sm text-gray-900 dark:text-white mt-1">{{ task.id }}</p>
|
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">{{ task.title }}</h2>
|
||||||
</div>
|
<n-tag :type="getTaskStatusColor(task.status)" size="small">
|
||||||
<div>
|
{{ getTaskStatusText(task.status) }}
|
||||||
<label class="text-sm font-medium text-gray-500 dark:text-gray-400">任务标题</label>
|
</n-tag>
|
||||||
<p class="text-sm text-gray-900 dark:text-white mt-1 break-words">{{ task.title }}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="text-sm font-medium text-gray-500 dark:text-gray-400">任务类型</label>
|
|
||||||
<div class="mt-1">
|
|
||||||
<n-tag :type="getTaskTypeColor(task.task_type)" size="small">
|
<n-tag :type="getTaskTypeColor(task.task_type)" size="small">
|
||||||
{{ getTaskTypeText(task.task_type) }}
|
{{ getTaskTypeText(task.task_type) }}
|
||||||
</n-tag>
|
</n-tag>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
<div>
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 text-sm">
|
||||||
<label class="text-sm font-medium text-gray-500 dark:text-gray-400">任务状态</label>
|
<div>
|
||||||
<div class="mt-1">
|
<span class="text-gray-500 dark:text-gray-400">ID:</span>
|
||||||
<n-tag :type="getTaskStatusColor(task.status)" size="small">
|
<span class="ml-1 text-gray-900 dark:text-white">{{ task.id }}</span>
|
||||||
{{ getTaskStatusText(task.status) }}
|
</div>
|
||||||
</n-tag>
|
<div>
|
||||||
|
<span class="text-gray-500 dark:text-gray-400">总项目:</span>
|
||||||
|
<span class="ml-1 text-gray-900 dark:text-white">{{ task.total_items || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text-gray-500 dark:text-gray-400">已处理:</span>
|
||||||
|
<span class="ml-1 text-blue-600 dark:text-blue-400 font-medium">{{ task.processed_items || 0 }}</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<span class="text-gray-500 dark:text-gray-400">成功率:</span>
|
||||||
|
<span class="ml-1 text-green-600 dark:text-green-400 font-medium">
|
||||||
|
{{ task.total_items > 0 ? Math.round((task.success_items / task.total_items) * 100) : 0 }}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 进度条 -->
|
||||||
|
<div class="mt-3" v-if="task.total_items > 0">
|
||||||
|
<div class="flex items-center justify-between mb-1">
|
||||||
|
<span class="text-xs text-gray-500 dark:text-gray-400">进度</span>
|
||||||
|
<span class="text-xs text-gray-500 dark:text-gray-400">
|
||||||
|
{{ Math.round((task.processed_items / task.total_items) * 100) }}%
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<n-progress
|
||||||
|
type="line"
|
||||||
|
:percentage="Math.round((task.processed_items / task.total_items) * 100)"
|
||||||
|
:height="6"
|
||||||
|
:show-indicator="false"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
|
||||||
<label class="text-sm font-medium text-gray-500 dark:text-gray-400">创建时间</label>
|
<!-- 右侧:统计信息 -->
|
||||||
<p class="text-sm text-gray-900 dark:text-white mt-1">{{ formatDate(task.created_at) }}</p>
|
<div class="flex items-center space-x-6">
|
||||||
</div>
|
<div class="text-center">
|
||||||
<div>
|
<div class="text-lg font-bold text-green-600 dark:text-green-400">{{ task.success_items || 0 }}</div>
|
||||||
<label class="text-sm font-medium text-gray-500 dark:text-gray-400">更新时间</label>
|
<div class="text-xs text-gray-500 dark:text-gray-400">成功</div>
|
||||||
<p class="text-sm text-gray-900 dark:text-white mt-1">{{ formatDate(task.updated_at) }}</p>
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-lg font-bold text-red-600 dark:text-red-400">{{ task.failed_items || 0 }}</div>
|
||||||
|
<div class="text-xs text-gray-500 dark:text-gray-400">失败</div>
|
||||||
|
</div>
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="text-lg font-bold text-gray-600 dark:text-gray-400">{{ task.total_items - task.processed_items || 0 }}</div>
|
||||||
|
<div class="text-xs text-gray-500 dark:text-gray-400">待处理</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div v-if="task.description" class="mt-4">
|
<!-- 任务描述(如果有) -->
|
||||||
<label class="text-sm font-medium text-gray-500 dark:text-gray-400">任务描述</label>
|
<div v-if="task.description" class="mt-3 pt-3 border-t border-gray-200 dark:border-gray-700">
|
||||||
<p class="text-sm text-gray-900 dark:text-white mt-1 break-words">{{ task.description }}</p>
|
<p class="text-sm text-gray-600 dark:text-gray-400">{{ task.description }}</p>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 进度信息卡片 -->
|
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm p-4 md:p-6">
|
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white mb-4">进度信息</h2>
|
|
||||||
<div class="grid grid-cols-2 md:grid-cols-4 gap-4">
|
|
||||||
<div>
|
|
||||||
<label class="text-sm font-medium text-gray-500 dark:text-gray-400">总项目数</label>
|
|
||||||
<p class="text-xl md:text-2xl font-bold text-gray-900 dark:text-white mt-1">{{ task.total_items || 0 }}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="text-sm font-medium text-gray-500 dark:text-gray-400">已处理</label>
|
|
||||||
<p class="text-xl md:text-2xl font-bold text-blue-600 dark:text-blue-400 mt-1">{{ task.processed_items || 0 }}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="text-sm font-medium text-gray-500 dark:text-gray-400">成功</label>
|
|
||||||
<p class="text-xl md:text-2xl font-bold text-green-600 dark:text-green-400 mt-1">{{ task.success_items || 0 }}</p>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label class="text-sm font-medium text-gray-500 dark:text-gray-400">失败</label>
|
|
||||||
<p class="text-xl md:text-2xl font-bold text-red-600 dark:text-red-400 mt-1">{{ task.failed_items || 0 }}</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 进度条 -->
|
|
||||||
<div class="mt-4" v-if="task.total_items > 0">
|
|
||||||
<div class="flex items-center justify-between mb-2">
|
|
||||||
<span class="text-sm font-medium text-gray-700 dark:text-gray-300">总体进度</span>
|
|
||||||
<span class="text-sm text-gray-500 dark:text-gray-400">
|
|
||||||
{{ Math.round((task.processed_items / task.total_items) * 100) }}%
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<n-progress
|
|
||||||
type="line"
|
|
||||||
:percentage="Math.round((task.processed_items / task.total_items) * 100)"
|
|
||||||
:height="8"
|
|
||||||
:show-indicator="false"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 任务项列表 -->
|
<!-- 任务项列表 -->
|
||||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm">
|
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm">
|
||||||
<div class="p-4 md:p-6 border-b border-gray-200 dark:border-gray-700">
|
<div class="p-3 border-b border-gray-200 dark:border-gray-700 flex items-center justify-between">
|
||||||
<h2 class="text-lg font-semibold text-gray-900 dark:text-white">任务项列表</h2>
|
<h2 class="text-base font-semibold text-gray-900 dark:text-white">任务项列表</h2>
|
||||||
|
<span class="text-sm text-gray-500 dark:text-gray-400">共 {{ total }} 项</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="p-4 md:p-6 overflow-x-auto">
|
<div class="overflow-x-auto">
|
||||||
<n-data-table
|
<n-data-table
|
||||||
:columns="taskItemColumns"
|
:columns="taskItemColumns"
|
||||||
:data="taskItems"
|
:data="taskItems"
|
||||||
@@ -178,13 +171,14 @@
|
|||||||
:pagination="itemsPaginationConfig"
|
:pagination="itemsPaginationConfig"
|
||||||
size="small"
|
size="small"
|
||||||
:scroll-x="600"
|
:scroll-x="600"
|
||||||
|
:bordered="false"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 错误状态 -->
|
<!-- 错误状态 -->
|
||||||
<div v-else class="text-center py-8">
|
<div v-else class="text-center py-4">
|
||||||
<n-empty description="任务不存在或已被删除" />
|
<n-empty description="任务不存在或已被删除" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -237,26 +231,30 @@ const taskItemColumns = [
|
|||||||
{
|
{
|
||||||
title: 'ID',
|
title: 'ID',
|
||||||
key: 'id',
|
key: 'id',
|
||||||
width: 80,
|
width: 60,
|
||||||
minWidth: 80
|
minWidth: 60
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '输入数据',
|
title: '输入数据',
|
||||||
key: 'input',
|
key: 'input',
|
||||||
minWidth: 200,
|
minWidth: 250,
|
||||||
ellipsis: {
|
ellipsis: {
|
||||||
tooltip: true
|
tooltip: true
|
||||||
},
|
},
|
||||||
render: (row: any) => {
|
render: (row: any) => {
|
||||||
if (!row.input) return h('span', { class: 'text-sm text-gray-500' }, '无输入数据')
|
if (!row.input) return h('span', { class: 'text-sm text-gray-500' }, '无输入数据')
|
||||||
return h('span', { class: 'text-sm' }, row.input.title || row.input.url || '无标题')
|
const title = row.input.title || row.input.url || '无标题'
|
||||||
|
return h('div', { class: 'text-sm' }, [
|
||||||
|
h('div', { class: 'font-medium' }, title),
|
||||||
|
h('div', { class: 'text-xs text-gray-500 mt-1' }, row.input.url || '')
|
||||||
|
])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '状态',
|
title: '状态',
|
||||||
key: 'status',
|
key: 'status',
|
||||||
width: 100,
|
width: 80,
|
||||||
minWidth: 100,
|
minWidth: 80,
|
||||||
render: (row: any) => {
|
render: (row: any) => {
|
||||||
const statusMap: Record<string, { text: string; color: string }> = {
|
const statusMap: Record<string, { text: string; color: string }> = {
|
||||||
pending: { text: '待处理', color: 'warning' },
|
pending: { text: '待处理', color: 'warning' },
|
||||||
@@ -269,7 +267,7 @@ const taskItemColumns = [
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '输出数据',
|
title: '结果',
|
||||||
key: 'output',
|
key: 'output',
|
||||||
minWidth: 200,
|
minWidth: 200,
|
||||||
ellipsis: {
|
ellipsis: {
|
||||||
@@ -277,16 +275,28 @@ const taskItemColumns = [
|
|||||||
},
|
},
|
||||||
render: (row: any) => {
|
render: (row: any) => {
|
||||||
if (!row.output) return h('span', { class: 'text-sm text-gray-500' }, '无输出')
|
if (!row.output) return h('span', { class: 'text-sm text-gray-500' }, '无输出')
|
||||||
return h('span', { class: 'text-sm' }, row.output.error || row.output.save_url || '处理完成')
|
if (row.output.error) {
|
||||||
|
return h('div', { class: 'text-sm' }, [
|
||||||
|
h('div', { class: 'text-red-600 font-medium' }, '失败'),
|
||||||
|
h('div', { class: 'text-xs text-gray-500 mt-1' }, row.output.error)
|
||||||
|
])
|
||||||
|
}
|
||||||
|
return h('div', { class: 'text-sm' }, [
|
||||||
|
h('div', { class: 'text-green-600 font-medium' }, '成功'),
|
||||||
|
h('div', { class: 'text-xs text-gray-500 mt-1' }, row.output.save_url || '处理完成')
|
||||||
|
])
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '创建时间',
|
title: '时间',
|
||||||
key: 'created_at',
|
key: 'created_at',
|
||||||
width: 160,
|
width: 120,
|
||||||
minWidth: 160,
|
minWidth: 120,
|
||||||
render: (row: any) => {
|
render: (row: any) => {
|
||||||
return new Date(row.created_at).toLocaleString('zh-CN')
|
return h('div', { class: 'text-sm' }, [
|
||||||
|
h('div', new Date(row.created_at).toLocaleDateString('zh-CN')),
|
||||||
|
h('div', { class: 'text-xs text-gray-500' }, new Date(row.created_at).toLocaleTimeString('zh-CN'))
|
||||||
|
])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
486
web/pages/admin/untransferred-resources.vue
Normal file
486
web/pages/admin/untransferred-resources.vue
Normal file
@@ -0,0 +1,486 @@
|
|||||||
|
<template>
|
||||||
|
<div class="space-y-6">
|
||||||
|
<!-- 页面标题 -->
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div>
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900 dark:text-white">未转存列表</h1>
|
||||||
|
<p class="text-gray-600 dark:text-gray-400">显示夸克网盘中尚未转存的资源</p>
|
||||||
|
</div>
|
||||||
|
<div class="flex space-x-3">
|
||||||
|
<n-button @click="refreshData" type="info">
|
||||||
|
<template #icon>
|
||||||
|
<i class="fas fa-refresh"></i>
|
||||||
|
</template>
|
||||||
|
刷新
|
||||||
|
</n-button>
|
||||||
|
<n-button @click="batchTransfer" type="primary" :disabled="selectedResources.length === 0">
|
||||||
|
<template #icon>
|
||||||
|
<i class="fas fa-cloud-upload-alt"></i>
|
||||||
|
</template>
|
||||||
|
批量转存 ({{ selectedResources.length }})
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 统计信息 -->
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-3 gap-4">
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i class="fas fa-clock text-orange-500 text-2xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<div class="text-sm font-medium text-gray-500 dark:text-gray-400">待转存总数</div>
|
||||||
|
<div class="text-2xl font-bold text-gray-900 dark:text-white">{{ total }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i class="fas fa-check-circle text-green-500 text-2xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<div class="text-sm font-medium text-gray-500 dark:text-gray-400">已选择</div>
|
||||||
|
<div class="text-2xl font-bold text-gray-900 dark:text-white">{{ selectedResources.length }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-white dark:bg-gray-800 rounded-lg p-4 border border-gray-200 dark:border-gray-700">
|
||||||
|
<div class="flex items-center">
|
||||||
|
<div class="flex-shrink-0">
|
||||||
|
<i class="fas fa-quark text-blue-500 text-2xl"></i>
|
||||||
|
</div>
|
||||||
|
<div class="ml-4">
|
||||||
|
<div class="text-sm font-medium text-gray-500 dark:text-gray-400">夸克网盘</div>
|
||||||
|
<div class="text-2xl font-bold text-gray-900 dark:text-white">{{ total }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- 搜索和筛选 -->
|
||||||
|
<n-card>
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-4 gap-4">
|
||||||
|
<n-input
|
||||||
|
v-model:value="searchQuery"
|
||||||
|
placeholder="搜索资源标题..."
|
||||||
|
@keyup.enter="handleSearch"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
</template>
|
||||||
|
</n-input>
|
||||||
|
|
||||||
|
<n-select
|
||||||
|
v-model:value="selectedCategory"
|
||||||
|
placeholder="选择分类"
|
||||||
|
:options="categoryOptions"
|
||||||
|
clearable
|
||||||
|
/>
|
||||||
|
|
||||||
|
<n-select
|
||||||
|
v-model:value="sortBy"
|
||||||
|
placeholder="排序方式"
|
||||||
|
:options="sortOptions"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<n-button type="primary" @click="handleSearch">
|
||||||
|
<template #icon>
|
||||||
|
<i class="fas fa-search"></i>
|
||||||
|
</template>
|
||||||
|
搜索
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 资源列表 -->
|
||||||
|
<n-card>
|
||||||
|
<template #header>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<span class="text-lg font-semibold">未转存资源列表</span>
|
||||||
|
<div class="flex items-center space-x-2">
|
||||||
|
<n-checkbox
|
||||||
|
:checked="isAllSelected"
|
||||||
|
@update:checked="toggleSelectAll"
|
||||||
|
:indeterminate="isIndeterminate"
|
||||||
|
/>
|
||||||
|
<span class="text-sm text-gray-500">全选</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<span class="text-sm text-gray-500">共 {{ total }} 个资源,已选择 {{ selectedResources.length }} 个</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<div v-if="loading" class="flex items-center justify-center py-8">
|
||||||
|
<n-spin size="large" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="resources.length === 0" class="text-center py-8">
|
||||||
|
<i class="fas fa-check-circle text-4xl text-green-400 mb-4"></i>
|
||||||
|
<p class="text-gray-500">暂无未转存的资源</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else>
|
||||||
|
<n-data-table
|
||||||
|
:columns="columns"
|
||||||
|
:data="resources"
|
||||||
|
:pagination="paginationConfig"
|
||||||
|
:bordered="false"
|
||||||
|
size="small"
|
||||||
|
:scroll-x="800"
|
||||||
|
@update:checked-row-keys="handleSelectionChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</n-card>
|
||||||
|
|
||||||
|
<!-- 批量转存确认对话框 -->
|
||||||
|
<n-modal v-model:show="showBatchTransferModal" preset="dialog" title="确认批量转存">
|
||||||
|
<div class="space-y-4">
|
||||||
|
<p>确定要将选中的 <strong>{{ selectedResources.length }}</strong> 个资源进行批量转存吗?</p>
|
||||||
|
<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 #action>
|
||||||
|
<div class="flex space-x-2">
|
||||||
|
<n-button @click="showBatchTransferModal = false">取消</n-button>
|
||||||
|
<n-button type="primary" @click="confirmBatchTransfer" :loading="transferring">
|
||||||
|
{{ transferring ? '转存中...' : '确认转存' }}
|
||||||
|
</n-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</n-modal>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
// 设置页面布局
|
||||||
|
definePageMeta({
|
||||||
|
layout: 'admin'
|
||||||
|
})
|
||||||
|
|
||||||
|
import { ref, computed, onMounted } from 'vue'
|
||||||
|
import { useResourceApi, useCategoryApi, useTaskApi } from '~/composables/useApi'
|
||||||
|
|
||||||
|
// API实例
|
||||||
|
const resourceApi = useResourceApi()
|
||||||
|
const categoryApi = useCategoryApi()
|
||||||
|
const taskApi = useTaskApi()
|
||||||
|
|
||||||
|
// 数据状态
|
||||||
|
const resources = ref([])
|
||||||
|
const categories = ref([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const transferring = ref(false)
|
||||||
|
const total = ref(0)
|
||||||
|
const selectedResourceIds = ref([])
|
||||||
|
|
||||||
|
// 搜索和筛选
|
||||||
|
const searchQuery = ref('')
|
||||||
|
const selectedCategory = ref(null)
|
||||||
|
const sortBy = ref('created_at')
|
||||||
|
|
||||||
|
// 分页
|
||||||
|
const currentPage = ref(1)
|
||||||
|
const pageSize = ref(20)
|
||||||
|
|
||||||
|
// 模态框
|
||||||
|
const showBatchTransferModal = ref(false)
|
||||||
|
|
||||||
|
// 排序选项
|
||||||
|
const sortOptions = [
|
||||||
|
{ label: '创建时间 (最新)', value: 'created_at' },
|
||||||
|
{ label: '创建时间 (最早)', value: 'created_at_asc' },
|
||||||
|
{ label: '更新时间 (最新)', value: 'updated_at' },
|
||||||
|
{ label: '标题 (A-Z)', value: 'title' },
|
||||||
|
{ label: '标题 (Z-A)', value: 'title_desc' }
|
||||||
|
]
|
||||||
|
|
||||||
|
// 分类选项
|
||||||
|
const categoryOptions = computed(() => {
|
||||||
|
return categories.value.map(cat => ({
|
||||||
|
label: cat.name,
|
||||||
|
value: cat.id
|
||||||
|
}))
|
||||||
|
})
|
||||||
|
|
||||||
|
// 分页配置
|
||||||
|
const paginationConfig = computed(() => ({
|
||||||
|
page: currentPage.value,
|
||||||
|
pageSize: pageSize.value,
|
||||||
|
itemCount: total.value,
|
||||||
|
showSizePicker: true,
|
||||||
|
pageSizes: [10, 20, 50, 100],
|
||||||
|
onChange: (page: number) => {
|
||||||
|
currentPage.value = page
|
||||||
|
fetchResources()
|
||||||
|
},
|
||||||
|
onUpdatePageSize: (size: number) => {
|
||||||
|
pageSize.value = size
|
||||||
|
currentPage.value = 1
|
||||||
|
fetchResources()
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
|
// 选择状态
|
||||||
|
const selectedResources = computed(() => {
|
||||||
|
return resources.value.filter(r => selectedResourceIds.value.includes(r.id))
|
||||||
|
})
|
||||||
|
|
||||||
|
const isAllSelected = computed(() => {
|
||||||
|
return resources.value.length > 0 && selectedResourceIds.value.length === resources.value.length
|
||||||
|
})
|
||||||
|
|
||||||
|
const isIndeterminate = computed(() => {
|
||||||
|
return selectedResourceIds.value.length > 0 && selectedResourceIds.value.length < resources.value.length
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表格列定义
|
||||||
|
const columns = [
|
||||||
|
{
|
||||||
|
type: 'selection',
|
||||||
|
width: 50
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'ID',
|
||||||
|
key: 'id',
|
||||||
|
width: 80,
|
||||||
|
minWidth: 80
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '标题',
|
||||||
|
key: 'title',
|
||||||
|
minWidth: 200,
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '分类',
|
||||||
|
key: 'category',
|
||||||
|
width: 120,
|
||||||
|
render: (row: any) => {
|
||||||
|
if (!row.category) return '-'
|
||||||
|
return h('n-tag', { type: 'info', size: 'small' }, { default: () => row.category.name })
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '原始链接',
|
||||||
|
key: 'url',
|
||||||
|
minWidth: 200,
|
||||||
|
ellipsis: {
|
||||||
|
tooltip: true
|
||||||
|
},
|
||||||
|
render: (row: any) => {
|
||||||
|
return h('a', {
|
||||||
|
href: row.url,
|
||||||
|
target: '_blank',
|
||||||
|
class: 'text-blue-600 hover:text-blue-800 text-xs break-all'
|
||||||
|
}, row.url)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '创建时间',
|
||||||
|
key: 'created_at',
|
||||||
|
width: 160,
|
||||||
|
render: (row: any) => {
|
||||||
|
return new Date(row.created_at).toLocaleString('zh-CN')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '更新时间',
|
||||||
|
key: 'updated_at',
|
||||||
|
width: 160,
|
||||||
|
render: (row: any) => {
|
||||||
|
return new Date(row.updated_at).toLocaleString('zh-CN')
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '操作',
|
||||||
|
key: 'actions',
|
||||||
|
width: 120,
|
||||||
|
fixed: 'right',
|
||||||
|
render: (row: any) => {
|
||||||
|
return h('div', { class: 'flex space-x-1' }, [
|
||||||
|
h('n-button', {
|
||||||
|
size: 'small',
|
||||||
|
type: 'primary',
|
||||||
|
onClick: () => singleTransfer(row)
|
||||||
|
}, { default: () => '转存' }),
|
||||||
|
h('n-button', {
|
||||||
|
size: 'small',
|
||||||
|
type: 'default',
|
||||||
|
onClick: () => viewResource(row.id)
|
||||||
|
}, { default: () => '查看' })
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 获取未转存资源
|
||||||
|
const fetchResources = async () => {
|
||||||
|
loading.value = true
|
||||||
|
try {
|
||||||
|
const params = {
|
||||||
|
page: currentPage.value,
|
||||||
|
page_size: pageSize.value,
|
||||||
|
pan_name: 'quark', // 只获取夸克网盘的资源
|
||||||
|
has_save_url: false, // 只获取没有转存链接的资源
|
||||||
|
search: searchQuery.value,
|
||||||
|
category_id: selectedCategory.value,
|
||||||
|
sort_by: sortBy.value
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await resourceApi.getResources(params)
|
||||||
|
resources.value = response.resources || []
|
||||||
|
total.value = response.total || 0
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取未转存资源失败:', error)
|
||||||
|
notification.error({
|
||||||
|
content: '获取未转存资源失败',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取分类列表
|
||||||
|
const fetchCategories = async () => {
|
||||||
|
try {
|
||||||
|
const response = await categoryApi.getCategories()
|
||||||
|
categories.value = response || []
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取分类失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 搜索处理
|
||||||
|
const handleSearch = () => {
|
||||||
|
currentPage.value = 1
|
||||||
|
fetchResources()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 刷新数据
|
||||||
|
const refreshData = () => {
|
||||||
|
fetchResources()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择处理
|
||||||
|
const handleSelectionChange = (keys: any[]) => {
|
||||||
|
selectedResourceIds.value = keys
|
||||||
|
}
|
||||||
|
|
||||||
|
// 全选/取消全选
|
||||||
|
const toggleSelectAll = (checked: boolean) => {
|
||||||
|
if (checked) {
|
||||||
|
selectedResourceIds.value = resources.value.map(r => r.id)
|
||||||
|
} else {
|
||||||
|
selectedResourceIds.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 单个转存
|
||||||
|
const singleTransfer = async (resource: any) => {
|
||||||
|
try {
|
||||||
|
const taskData = {
|
||||||
|
title: `转存资源: ${resource.title}`,
|
||||||
|
description: `转存单个资源: ${resource.title}`,
|
||||||
|
resources: [{
|
||||||
|
title: resource.title,
|
||||||
|
url: resource.url,
|
||||||
|
category_id: resource.category_id || 0
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await taskApi.createBatchTransferTask(taskData)
|
||||||
|
notification.success({
|
||||||
|
content: '转存任务已创建',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
|
||||||
|
// 跳转到任务详情页
|
||||||
|
navigateTo(`/admin/tasks/${response.id}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('创建转存任务失败:', error)
|
||||||
|
notification.error({
|
||||||
|
content: '创建转存任务失败',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量转存
|
||||||
|
const batchTransfer = () => {
|
||||||
|
if (selectedResources.value.length === 0) {
|
||||||
|
notification.warning({
|
||||||
|
content: '请先选择要转存的资源',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
showBatchTransferModal.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确认批量转存
|
||||||
|
const confirmBatchTransfer = async () => {
|
||||||
|
transferring.value = true
|
||||||
|
try {
|
||||||
|
const taskData = {
|
||||||
|
title: `批量转存 ${selectedResources.value.length} 个资源`,
|
||||||
|
description: `批量转存 ${selectedResources.value.length} 个夸克网盘资源`,
|
||||||
|
resources: selectedResources.value.map(r => ({
|
||||||
|
title: r.title,
|
||||||
|
url: r.url,
|
||||||
|
category_id: r.category_id || 0
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await taskApi.createBatchTransferTask(taskData)
|
||||||
|
notification.success({
|
||||||
|
content: `批量转存任务已创建,共 ${selectedResources.value.length} 个资源`,
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
|
||||||
|
// 跳转到任务详情页
|
||||||
|
navigateTo(`/admin/tasks/${response.id}`)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('创建批量转存任务失败:', error)
|
||||||
|
notification.error({
|
||||||
|
content: '创建批量转存任务失败',
|
||||||
|
duration: 3000
|
||||||
|
})
|
||||||
|
} finally {
|
||||||
|
transferring.value = false
|
||||||
|
showBatchTransferModal.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查看资源详情
|
||||||
|
const viewResource = (id: number) => {
|
||||||
|
navigateTo(`/admin/resources/${id}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 页面加载
|
||||||
|
onMounted(async () => {
|
||||||
|
await Promise.all([
|
||||||
|
fetchCategories(),
|
||||||
|
fetchResources()
|
||||||
|
])
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* 自定义样式 */
|
||||||
|
</style>
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
<div class="flex justify-between mt-3 text-sm text-gray-600 dark:text-gray-300 px-2">
|
<div class="flex justify-between mt-3 text-sm text-gray-600 dark:text-gray-300 px-2">
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<i class="fas fa-calendar-day text-pink-600 mr-1"></i>
|
<i class="fas fa-calendar-day text-pink-600 mr-1"></i>
|
||||||
今日更新: <span class="font-medium text-pink-600 ml-1 count-up" :data-target="safeStats?.today_updates || 0">0</span>
|
今日资源: <span class="font-medium text-pink-600 ml-1 count-up" :data-target="safeStats?.today_resources || 0">0</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center">
|
<div class="flex items-center">
|
||||||
<i class="fas fa-database text-blue-600 mr-1"></i>
|
<i class="fas fa-database text-blue-600 mr-1"></i>
|
||||||
@@ -303,7 +303,7 @@ watch(systemConfigError, (error) => {
|
|||||||
|
|
||||||
// 从 SSR 数据中获取值
|
// 从 SSR 数据中获取值
|
||||||
const safeResources = computed(() => (resourcesData.value as any)?.data || [])
|
const safeResources = computed(() => (resourcesData.value as any)?.data || [])
|
||||||
const safeStats = computed(() => (statsData.value as any) || { total_resources: 0, total_categories: 0, total_tags: 0, total_views: 0, today_updates: 0 })
|
const safeStats = computed(() => (statsData.value as any) || { total_resources: 0, total_categories: 0, total_tags: 0, total_views: 0, today_resources: 0 })
|
||||||
const platforms = computed(() => (platformsData.value as any) || [])
|
const platforms = computed(() => (platformsData.value as any) || [])
|
||||||
const systemConfig = computed(() => (systemConfigData.value as any).data || { site_title: '老九网盘资源数据库' })
|
const systemConfig = computed(() => (systemConfigData.value as any).data || { site_title: '老九网盘资源数据库' })
|
||||||
const safeLoading = computed(() => pending.value)
|
const safeLoading = computed(() => pending.value)
|
||||||
|
|||||||
@@ -7,11 +7,12 @@ export const useSystemConfigStore = defineStore('systemConfig', {
|
|||||||
initialized: false
|
initialized: false
|
||||||
}),
|
}),
|
||||||
actions: {
|
actions: {
|
||||||
async initConfig(force = false) {
|
async initConfig(force = false, useAdminApi = false) {
|
||||||
if (this.initialized && !force) return
|
if (this.initialized && !force) return
|
||||||
try {
|
try {
|
||||||
// 使用公开的系统配置API,不需要管理员权限
|
// 根据上下文选择API:管理员页面使用管理员API,其他页面使用公开API
|
||||||
const response = await useApiFetch('/public/system-config')
|
const apiUrl = useAdminApi ? '/system/config' : '/public/system-config'
|
||||||
|
const response = await useApiFetch(apiUrl)
|
||||||
console.log('Store API响应:', response) // 调试信息
|
console.log('Store API响应:', response) // 调试信息
|
||||||
|
|
||||||
// 正确处理API响应结构
|
// 正确处理API响应结构
|
||||||
|
|||||||
Reference in New Issue
Block a user