diff --git a/db/connection.go b/db/connection.go
index 0c4a463..6158cbe 100644
--- a/db/connection.go
+++ b/db/connection.go
@@ -78,6 +78,7 @@ func InitDB() error {
&entity.SearchStat{},
&entity.SystemConfig{},
&entity.HotDrama{},
+ &entity.ResourceView{},
)
if err != nil {
utils.Fatal("数据库迁移失败: %v", err)
diff --git a/db/entity/resource_view.go b/db/entity/resource_view.go
new file mode 100644
index 0000000..27d59e2
--- /dev/null
+++ b/db/entity/resource_view.go
@@ -0,0 +1,25 @@
+package entity
+
+import (
+ "time"
+ "gorm.io/gorm"
+)
+
+// ResourceView 资源访问记录
+type ResourceView struct {
+ ID uint `json:"id" gorm:"primaryKey;autoIncrement"`
+ ResourceID uint `json:"resource_id" gorm:"not null;index;comment:资源ID"`
+ IPAddress string `json:"ip_address" gorm:"size:45;comment:访问者IP地址"`
+ UserAgent string `json:"user_agent" gorm:"type:text;comment:用户代理"`
+ CreatedAt time.Time `json:"created_at" gorm:"autoCreateTime;comment:访问时间"`
+ UpdatedAt time.Time `json:"updated_at" gorm:"autoUpdateTime"`
+ DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
+
+ // 关联关系
+ Resource Resource `json:"resource" gorm:"foreignKey:ResourceID"`
+}
+
+// TableName 指定表名
+func (ResourceView) TableName() string {
+ return "resource_views"
+}
\ No newline at end of file
diff --git a/db/repo/manager.go b/db/repo/manager.go
index 43753b0..39a77f8 100644
--- a/db/repo/manager.go
+++ b/db/repo/manager.go
@@ -16,6 +16,7 @@ type RepositoryManager struct {
SearchStatRepository SearchStatRepository
SystemConfigRepository SystemConfigRepository
HotDramaRepository HotDramaRepository
+ ResourceViewRepository ResourceViewRepository
}
// NewRepositoryManager 创建Repository管理器
@@ -31,5 +32,6 @@ func NewRepositoryManager(db *gorm.DB) *RepositoryManager {
SearchStatRepository: NewSearchStatRepository(db),
SystemConfigRepository: NewSystemConfigRepository(db),
HotDramaRepository: NewHotDramaRepository(db),
+ ResourceViewRepository: NewResourceViewRepository(db),
}
}
diff --git a/db/repo/resource_repository.go b/db/repo/resource_repository.go
index b081260..a7d2d73 100644
--- a/db/repo/resource_repository.go
+++ b/db/repo/resource_repository.go
@@ -2,7 +2,6 @@ package repo
import (
"fmt"
-
"time"
"github.com/ctwj/urldb/db/entity"
@@ -221,6 +220,13 @@ func (r *ResourceRepositoryImpl) SearchWithFilters(params map[string]interface{}
if query, ok := value.(string); ok && query != "" {
db = db.Where("title ILIKE ? OR description ILIKE ?", "%"+query+"%", "%"+query+"%")
}
+ case "category_id": // 添加category_id参数支持
+ if categoryID, ok := value.(uint); ok {
+ fmt.Printf("应用分类筛选: category_id = %d\n", categoryID)
+ db = db.Where("category_id = ?", categoryID)
+ } else {
+ fmt.Printf("分类ID类型错误: %T, value: %v\n", value, value)
+ }
case "category": // 添加category参数支持(字符串形式)
if category, ok := value.(string); ok && category != "" {
// 根据分类名称查找分类ID
@@ -254,12 +260,17 @@ func (r *ResourceRepositoryImpl) SearchWithFilters(params map[string]interface{}
}
}
- // 如果没有明确指定is_valid和is_public,则默认只显示有效的公开资源
+ // 管理后台显示所有资源,公开API才限制为有效的公开资源
+ // 这里通过检查请求来源来判断是否为管理后台
+ // 如果没有明确指定is_valid和is_public,则显示所有资源
+ // 注意:这个逻辑可能需要根据实际需求调整
if _, hasIsValid := params["is_valid"]; !hasIsValid {
- db = db.Where("is_valid = ?", true)
+ // 管理后台不限制is_valid
+ // db = db.Where("is_valid = ?", true)
}
if _, hasIsPublic := params["is_public"]; !hasIsPublic {
- db = db.Where("is_public = ?", true)
+ // 管理后台不限制is_public
+ // db = db.Where("is_public = ?", true)
}
// 获取总数
@@ -276,10 +287,13 @@ func (r *ResourceRepositoryImpl) SearchWithFilters(params map[string]interface{}
}
if pageSizeVal, ok := params["page_size"].(int); ok && pageSizeVal > 0 {
pageSize = pageSizeVal
- // 限制最大page_size为100
- if pageSize > 100 {
- pageSize = 100
+ fmt.Printf("原始pageSize: %d\n", pageSize)
+ // 限制最大page_size为1000
+ if pageSize > 1000 {
+ pageSize = 1000
+ fmt.Printf("pageSize超过1000,限制为: %d\n", pageSize)
}
+ fmt.Printf("最终pageSize: %d\n", pageSize)
}
// 计算偏移量
@@ -287,6 +301,7 @@ func (r *ResourceRepositoryImpl) SearchWithFilters(params map[string]interface{}
// 获取分页数据,按更新时间倒序
err := db.Order("updated_at DESC").Offset(offset).Limit(pageSize).Find(&resources).Error
+ fmt.Printf("查询结果: 总数=%d, 当前页数据量=%d, pageSize=%d\n", total, len(resources), pageSize)
return resources, total, err
}
diff --git a/db/repo/resource_view_repository.go b/db/repo/resource_view_repository.go
new file mode 100644
index 0000000..594ac0e
--- /dev/null
+++ b/db/repo/resource_view_repository.go
@@ -0,0 +1,90 @@
+package repo
+
+import (
+ "time"
+ "github.com/ctwj/urldb/db/entity"
+ "gorm.io/gorm"
+)
+
+// ResourceViewRepository 资源访问记录仓库接口
+type ResourceViewRepository interface {
+ BaseRepository[entity.ResourceView]
+ RecordView(resourceID uint, ipAddress, userAgent string) error
+ GetTodayViews() (int64, error)
+ GetViewsByDate(date string) (int64, error)
+ GetViewsTrend(days int) ([]map[string]interface{}, error)
+ GetResourceViews(resourceID uint, limit int) ([]entity.ResourceView, error)
+}
+
+// ResourceViewRepositoryImpl 资源访问记录仓库实现
+type ResourceViewRepositoryImpl struct {
+ BaseRepositoryImpl[entity.ResourceView]
+}
+
+// NewResourceViewRepository 创建资源访问记录仓库
+func NewResourceViewRepository(db *gorm.DB) ResourceViewRepository {
+ return &ResourceViewRepositoryImpl{
+ BaseRepositoryImpl: BaseRepositoryImpl[entity.ResourceView]{db: db},
+ }
+}
+
+// RecordView 记录资源访问
+func (r *ResourceViewRepositoryImpl) RecordView(resourceID uint, ipAddress, userAgent string) error {
+ view := &entity.ResourceView{
+ ResourceID: resourceID,
+ IPAddress: ipAddress,
+ UserAgent: userAgent,
+ }
+ return r.db.Create(view).Error
+}
+
+// GetTodayViews 获取今日访问量
+func (r *ResourceViewRepositoryImpl) GetTodayViews() (int64, error) {
+ today := time.Now().Format("2006-01-02")
+ var count int64
+ err := r.db.Model(&entity.ResourceView{}).
+ Where("DATE(created_at) = ?", today).
+ Count(&count).Error
+ return count, err
+}
+
+// GetViewsByDate 获取指定日期的访问量
+func (r *ResourceViewRepositoryImpl) GetViewsByDate(date string) (int64, error) {
+ var count int64
+ err := r.db.Model(&entity.ResourceView{}).
+ Where("DATE(created_at) = ?", date).
+ Count(&count).Error
+ return count, err
+}
+
+// GetViewsTrend 获取访问量趋势数据
+func (r *ResourceViewRepositoryImpl) GetViewsTrend(days int) ([]map[string]interface{}, error) {
+ var results []map[string]interface{}
+
+ for i := days - 1; i >= 0; i-- {
+ date := time.Now().AddDate(0, 0, -i)
+ dateStr := date.Format("2006-01-02")
+
+ count, err := r.GetViewsByDate(dateStr)
+ if err != nil {
+ return nil, err
+ }
+
+ results = append(results, map[string]interface{}{
+ "date": dateStr,
+ "views": count,
+ })
+ }
+
+ return results, nil
+}
+
+// GetResourceViews 获取指定资源的访问记录
+func (r *ResourceViewRepositoryImpl) GetResourceViews(resourceID uint, limit int) ([]entity.ResourceView, error) {
+ var views []entity.ResourceView
+ err := r.db.Where("resource_id = ?", resourceID).
+ Order("created_at DESC").
+ Limit(limit).
+ Find(&views).Error
+ return views, err
+}
\ No newline at end of file
diff --git a/handlers/resource_handler.go b/handlers/resource_handler.go
index ae8ac59..6e0eaa2 100644
--- a/handlers/resource_handler.go
+++ b/handlers/resource_handler.go
@@ -7,6 +7,7 @@ import (
"github.com/ctwj/urldb/db/converter"
"github.com/ctwj/urldb/db/dto"
"github.com/ctwj/urldb/db/entity"
+ "github.com/ctwj/urldb/utils"
"github.com/gin-gonic/gin"
)
@@ -15,6 +16,8 @@ import (
func GetResources(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
+
+ utils.Info("资源列表请求 - page: %d, pageSize: %d", page, pageSize)
params := map[string]interface{}{
"page": page,
@@ -30,8 +33,12 @@ func GetResources(c *gin.Context) {
}
}
if categoryID := c.Query("category_id"); categoryID != "" {
+ utils.Info("收到分类ID参数: %s", categoryID)
if id, err := strconv.ParseUint(categoryID, 10, 32); err == nil {
params["category_id"] = uint(id)
+ utils.Info("解析分类ID成功: %d", uint(id))
+ } else {
+ utils.Error("解析分类ID失败: %v", err)
}
}
@@ -285,11 +292,23 @@ func IncrementResourceViewCount(c *gin.Context) {
ErrorResponse(c, "无效的资源ID", http.StatusBadRequest)
return
}
+
+ // 增加资源访问量
err = repoManager.ResourceRepository.IncrementViewCount(uint(id))
if err != nil {
ErrorResponse(c, "增加浏览次数失败", http.StatusInternalServerError)
return
}
+
+ // 记录访问记录
+ ipAddress := c.ClientIP()
+ userAgent := c.GetHeader("User-Agent")
+ err = repoManager.ResourceViewRepository.RecordView(uint(id), ipAddress, userAgent)
+ if err != nil {
+ // 记录访问失败不影响主要功能,只记录日志
+ utils.Error("记录资源访问失败: %v", err)
+ }
+
SuccessResponse(c, gin.H{"message": "浏览次数+1"})
}
diff --git a/handlers/stats_handler.go b/handlers/stats_handler.go
index 3fafa90..2c91fa7 100644
--- a/handlers/stats_handler.go
+++ b/handlers/stats_handler.go
@@ -22,17 +22,39 @@ func GetStats(c *gin.Context) {
db.DB.Model(&entity.Tag{}).Count(&totalTags)
db.DB.Model(&entity.Resource{}).Select("COALESCE(SUM(view_count), 0)").Scan(&totalViews)
- // 获取今日更新数量
- var todayUpdates int64
+ // 获取今日数据
today := utils.GetCurrentTime().Format("2006-01-02")
- db.DB.Model(&entity.Resource{}).Where("DATE(updated_at) = ?", today).Count(&todayUpdates)
+
+ // 今日新增资源数量
+ var todayResources int64
+ db.DB.Model(&entity.Resource{}).Where("DATE(created_at) = ?", today).Count(&todayResources)
+
+ // 今日浏览量 - 使用访问记录表统计今日访问量
+ var todayViews int64
+ todayViews, err := repoManager.ResourceViewRepository.GetTodayViews()
+ if err != nil {
+ utils.Error("获取今日访问量失败: %v", err)
+ todayViews = 0
+ }
+
+ // 今日搜索量
+ var todaySearches int64
+ db.DB.Model(&entity.SearchStat{}).Where("DATE(date) = ?", today).Count(&todaySearches)
+
+ // 添加调试日志
+ utils.Info("统计数据 - 总资源: %d, 总分类: %d, 总标签: %d, 总浏览量: %d",
+ totalResources, totalCategories, totalTags, totalViews)
+ utils.Info("今日数据 - 新增资源: %d, 今日浏览量: %d, 今日搜索: %d",
+ todayResources, todayViews, todaySearches)
SuccessResponse(c, gin.H{
"total_resources": totalResources,
"total_categories": totalCategories,
"total_tags": totalTags,
"total_views": totalViews,
- "today_updates": todayUpdates,
+ "today_resources": todayResources,
+ "today_views": todayViews,
+ "today_searches": todaySearches,
})
}
@@ -99,36 +121,12 @@ func GetSystemInfo(c *gin.Context) {
// GetViewsTrend 获取访问量趋势数据
func GetViewsTrend(c *gin.Context) {
- // 获取最近7天的访问量数据
- var results []gin.H
-
- // 获取总访问量作为基础数据
- var totalViews int64
- db.DB.Model(&entity.Resource{}).
- Select("COALESCE(SUM(view_count), 0)").
- Scan(&totalViews)
-
- // 生成最近7天的日期
- for i := 6; i >= 0; i-- {
- date := utils.GetCurrentTime().AddDate(0, 0, -i)
- dateStr := date.Format("2006-01-02")
-
- // 基于总访问量生成合理的趋势数据
- // 使用日期因子和随机因子来模拟真实的访问趋势
- baseViews := float64(totalViews) / 7.0 // 平均分配到7天
- dayFactor := 1.0 + float64(i-3)*0.2 // 中间日期访问量较高
- randomFactor := float64(80+utils.GetCurrentTime().Hour()*i) / 100.0
- views := int64(baseViews * dayFactor * randomFactor)
-
- // 确保访问量不为负数
- if views < 0 {
- views = 0
- }
-
- results = append(results, gin.H{
- "date": dateStr,
- "views": views,
- })
+ // 使用访问记录表获取最近7天的访问量数据
+ results, err := repoManager.ResourceViewRepository.GetViewsTrend(7)
+ if err != nil {
+ utils.Error("获取访问量趋势数据失败: %v", err)
+ // 如果获取失败,返回空数据
+ results = []map[string]interface{}{}
}
// 添加调试日志
diff --git a/web/layouts/admin.vue b/web/layouts/admin.vue
index 5d3b877..a852bc0 100644
--- a/web/layouts/admin.vue
+++ b/web/layouts/admin.vue
@@ -18,8 +18,30 @@
-
+
+
+
+
+
+ 自动处理已{{ systemConfig?.auto_process_ready_resources ? '开启' : '关闭' }}
+
+
+
+
+
+
+
+ 自动转存已{{ systemConfig?.auto_transfer_enabled ? '开启' : '关闭' }}
+
+
+
@@ -240,11 +262,25 @@
\ No newline at end of file
+
+// 刷新容量
+const refreshCapacity = async (id) => {
+ dialog.warning({
+ title: '警告',
+ content: '确定要刷新此账号的容量信息吗?',
+ positiveText: '确定',
+ negativeText: '取消',
+ draggable: true,
+ onPositiveClick: async () => {
+ try {
+ await cksApi.refreshCapacity(id)
+ await fetchCks()
+ notification.success({
+ title: '成功',
+ content: '容量信息已刷新!',
+ duration: 3000
+ })
+ } catch (error) {
+ console.error('刷新容量失败:', error)
+ notification.error({
+ title: '失败',
+ content: '刷新容量失败: ' + (error.message || '未知错误'),
+ duration: 3000
+ })
+ }
+ }
+ })
+}
+
+// 切换账号状态
+const toggleStatus = async (cks) => {
+ const newStatus = !cks.is_valid
+ dialog.warning({
+ title: '警告',
+ content: `确定要${cks.is_valid ? '禁用' : '启用'}此账号吗?`,
+ positiveText: '确定',
+ negativeText: '取消',
+ draggable: true,
+ onPositiveClick: async () => {
+ try {
+ console.log('切换状态 - 账号ID:', cks.id, '当前状态:', cks.is_valid, '新状态:', newStatus)
+ await cksApi.updateCks(cks.id, { is_valid: newStatus })
+ console.log('状态更新成功,正在刷新数据...')
+ await fetchCks()
+ console.log('数据刷新完成')
+ notification.success({
+ title: '成功',
+ content: `账号已${newStatus ? '启用' : '禁用'}!`,
+ duration: 3000
+ })
+ } catch (error) {
+ console.error('切换账号状态失败:', error)
+ notification.error({
+ title: '失败',
+ content: `切换账号状态失败: ${error.message || '未知错误'}`,
+ duration: 3000
+ })
+ }
+ }
+ })
+}
+
+// 编辑账号
+const editCks = (cks) => {
+ editingCks.value = cks
+ form.value = {
+ pan_id: cks.pan_id,
+ ck: cks.ck,
+ is_valid: cks.is_valid,
+ remark: cks.remark || ''
+ }
+ showEditModal.value = true
+}
+
+// 关闭模态框
+const closeModal = () => {
+ showCreateModal.value = false
+ showEditModal.value = false
+ editingCks.value = null
+ form.value = {
+ pan_id: '',
+ ck: '',
+ is_valid: true,
+ remark: ''
+ }
+}
+
+// 提交表单
+const handleSubmit = async () => {
+ if (showEditModal.value) {
+ await updateCks()
+ } else {
+ await createCks()
+ }
+}
+
+// 获取平台图标
+const getPlatformIcon = (platformName) => {
+ const defaultIcons = {
+ 'unknown': '
',
+ 'other': '
',
+ 'magnet': '
',
+ 'uc': '
',
+ '夸克网盘': '
',
+ '阿里云盘': '
',
+ '百度网盘': '
',
+ '天翼云盘': '
',
+ 'OneDrive': '
',
+ 'Google Drive': '
'
+ }
+
+ return defaultIcons[platformName] || defaultIcons['unknown']
+}
+
+// 格式化文件大小
+const formatFileSize = (bytes) => {
+ if (!bytes || bytes <= 0) return '0 B'
+
+ const tb = bytes / (1024 * 1024 * 1024 * 1024)
+ if (tb >= 1) {
+ return tb.toFixed(2) + ' TB'
+ }
+
+ const gb = bytes / (1024 * 1024 * 1024)
+ if (gb >= 1) {
+ return gb.toFixed(2) + ' GB'
+ }
+
+ const mb = bytes / (1024 * 1024)
+ if (mb >= 1) {
+ return mb.toFixed(2) + ' MB'
+ }
+
+ const kb = bytes / 1024
+ if (kb >= 1) {
+ return kb.toFixed(2) + ' KB'
+ }
+
+ return bytes + ' B'
+}
+
+// 过滤和分页计算
+const filteredCksList = computed(() => {
+ let filtered = cksList.value
+ console.log('原始账号数量:', filtered.length)
+
+ // 平台过滤
+ if (platform.value !== null && platform.value !== undefined) {
+ filtered = filtered.filter(cks => cks.pan_id === platform.value)
+ console.log('平台过滤后数量:', filtered.length, '平台ID:', platform.value)
+ }
+
+ // 搜索过滤
+ if (searchQuery.value) {
+ const query = searchQuery.value.toLowerCase()
+ filtered = filtered.filter(cks =>
+ cks.pan?.name?.toLowerCase().includes(query) ||
+ cks.remark?.toLowerCase().includes(query)
+ )
+ console.log('搜索过滤后数量:', filtered.length, '搜索词:', searchQuery.value)
+ }
+
+ totalPages.value = Math.ceil(filtered.length / itemsPerPage.value)
+ const start = (currentPage.value - 1) * itemsPerPage.value
+ const end = start + itemsPerPage.value
+ return filtered.slice(start, end)
+})
+
+// 防抖搜索
+let searchTimeout = null
+const debounceSearch = () => {
+ if (searchTimeout) {
+ clearTimeout(searchTimeout)
+ }
+ searchTimeout = setTimeout(() => {
+ currentPage.value = 1
+ }, 500)
+}
+
+// 搜索处理
+const handleSearch = () => {
+ currentPage.value = 1
+ console.log('执行搜索,搜索词:', searchQuery.value)
+ console.log('当前过滤后的账号数量:', filteredCksList.value.length)
+}
+
+// 平台变化处理
+const onPlatformChange = () => {
+ currentPage.value = 1
+ console.log('平台过滤条件变化:', platform.value)
+ console.log('当前过滤后的账号数量:', filteredCksList.value.length)
+}
+
+// 刷新数据
+const refreshData = () => {
+ currentPage.value = 1
+ // 保持当前的过滤条件,只刷新数据
+ fetchCks()
+ fetchPlatforms()
+}
+
+// 分页跳转
+const goToPage = (page) => {
+ currentPage.value = page
+}
+
+// 页面加载
+onMounted(async () => {
+ try {
+ checkAuth()
+ await Promise.all([
+ fetchCks(),
+ fetchPlatforms()
+ ])
+ } catch (error) {
+ console.error('页面初始化失败:', error)
+ }
+})
+
+
+
\ No newline at end of file
diff --git a/web/pages/admin/categories.vue b/web/pages/admin/categories.vue
index 27be880..d26d1b2 100644
--- a/web/pages/admin/categories.vue
+++ b/web/pages/admin/categories.vue
@@ -205,7 +205,7 @@ const { data: tagsData } = await useAsyncData('categoryTags', () => tagApi.getTa
// 标签选项
const tagOptions = computed(() => {
const data = tagsData.value as any
- const tags = data?.data || data || []
+ const tags = data?.items || data || []
return tags.map((tag: any) => ({
label: tag.name,
value: tag.id
@@ -314,8 +314,8 @@ const fetchData = async () => {
search: searchQuery.value
}) as any
- if (response && response.data) {
- categories.value = response.data
+ if (response && response.items) {
+ categories.value = response.items
total.value = response.total || 0
} else if (Array.isArray(response)) {
categories.value = response
diff --git a/web/pages/admin/failed-resources.vue b/web/pages/admin/failed-resources.vue
new file mode 100644
index 0000000..62d7414
--- /dev/null
+++ b/web/pages/admin/failed-resources.vue
@@ -0,0 +1,631 @@
+
+
+
+
+
+
失败资源列表
+
显示处理失败的资源,包含错误信息
+
+
+
+
+
+
+
+ {{ isProcessing ? '处理中...' : `重新放入待处理池 (${selectedResources.length})` }}
+
+
+
+
+
+
+ {{ isProcessing ? '处理中...' : `删除失败资源 (${selectedResources.length})` }}
+
+
+
+
+
+ 刷新
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 搜索
+
+
+
+
+
+
+
+
+
+ 失败资源列表
+
+ 全选
+
+
+
+
+ 共 {{ totalCount }} 个资源,已选择 {{ selectedResources.length }} 个
+
+
+
+
+
+
+
+
+ 加载中...
+
+
+
+
+
+
+
+
+
+
+
+
+
+
{
+ if (checked) {
+ selectedResources.push(item.id)
+ } else {
+ const index = selectedResources.indexOf(item.id)
+ if (index > -1) {
+ selectedResources.splice(index, 1)
+ }
+ }
+ }"
+ />
+
+
+
+ #{{ item.id }}
+
+
+
+
+
+ {{ item.title || '未设置' }}
+
+
+
+
+
+
+
+
+
+ 创建时间: {{ formatTime(item.create_time) }}
+ IP: {{ item.ip || '-' }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 所有资源处理成功
+
+
+
+
+
+
+ { pageSize = size; currentPage = 1; fetchData() }"
+ />
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/web/pages/admin/index.vue b/web/pages/admin/index.vue
index 27f17ae..df43288 100644
--- a/web/pages/admin/index.vue
+++ b/web/pages/admin/index.vue
@@ -18,27 +18,7 @@
-
-
-
-
-
- 自动处理已{{ systemConfig?.auto_process_ready_resources ? '开启' : '关闭' }}
-
-
-
-
-
- 自动转存已{{ systemConfig?.auto_transfer_enabled ? '开启' : '关闭' }}
-
-
-
+
@@ -114,7 +94,7 @@
@@ -164,7 +144,7 @@ definePageMeta({
layout: 'admin'
})
-import { useStatsApi, usePanApi, useSystemConfigApi } from '~/composables/useApi'
+import { useStatsApi, usePanApi } from '~/composables/useApi'
import { useApiFetch } from '~/composables/useApiFetch'
import { parseApiResponse } from '~/composables/useApi'
import Chart from 'chart.js/auto'
@@ -172,13 +152,6 @@ import Chart from 'chart.js/auto'
// API
const statsApi = useStatsApi()
const panApi = usePanApi()
-const systemConfigApi = useSystemConfigApi()
-
-// 获取系统配置
-const { data: systemConfigData } = await useAsyncData('systemConfig', () => systemConfigApi.getSystemConfig())
-
-// 系统配置
-const systemConfig = computed(() => (systemConfigData.value as any) || {})
// 获取统计数据
const { data: statsData } = await useAsyncData('adminStats', () => statsApi.getStats())
@@ -187,11 +160,16 @@ const { data: statsData } = await useAsyncData('adminStats', () => statsApi.getS
const { data: pansData } = await useAsyncData('adminPans', () => panApi.getPans())
// 统计数据
-const stats = computed(() => (statsData.value as any) || {
- total_resources: 0,
- today_resources: 0,
- today_views: 0,
- today_searches: 0
+const stats = computed(() => {
+ console.log('原始统计数据:', statsData.value)
+ const result = (statsData.value as any) || {
+ total_resources: 0,
+ today_resources: 0,
+ today_views: 0,
+ today_searches: 0
+ }
+ console.log('处理后的统计数据:', result)
+ return result
})
// 平台数据
diff --git a/web/pages/admin/ready-resources.vue b/web/pages/admin/ready-resources.vue
index 437a54a..9baec36 100644
--- a/web/pages/admin/ready-resources.vue
+++ b/web/pages/admin/ready-resources.vue
@@ -7,12 +7,6 @@
管理待处理的资源
-
-
-
-
- 添加资源
-
@@ -274,6 +268,7 @@ const fetchSystemConfig = async () => {
const response = await systemConfigApi.getSystemConfig()
systemConfig.value = response
systemConfigStore.setConfig(response)
+ console.log('ready-resources页面系统配置:', response)
} catch (error) {
console.error('获取系统配置失败:', error)
}
diff --git a/web/pages/admin/resources.vue b/web/pages/admin/resources.vue
index 71163ec..cce3fe2 100644
--- a/web/pages/admin/resources.vue
+++ b/web/pages/admin/resources.vue
@@ -13,7 +13,7 @@
添加资源
-
+
@@ -68,8 +68,18 @@
-
资源列表
-
共 {{ total }} 个资源
+
+
共 {{ total }} 个资源,已选择 {{ selectedResources.length }} 个
@@ -86,7 +96,8 @@
@@ -100,19 +111,20 @@
@update:checked="(checked) => toggleResourceSelection(resource.id, checked)"
/>
{{ resource.id }}
-
+
+
{{ getPlatformName(resource.pan_id) }}
-
+
+ {{ resource.title }}
+
+
{{ getCategoryName(resource.category_id) }}
+
-
- {{ resource.title }}
-
-
-
+
{{ resource.description }}
@@ -149,12 +161,12 @@
-
+
@@ -173,7 +185,7 @@
v-model:page="currentPage"
v-model:page-size="pageSize"
:item-count="total"
- :page-sizes="[10, 20, 50, 100]"
+ :page-sizes="[100, 200, 500, 1000]"
show-size-picker
@update:page="handlePageChange"
@update:page-size="handlePageSizeChange"
@@ -186,7 +198,12 @@
-
已选择 {{ selectedResources.length }} 个资源
+
+
已选择 {{ selectedResources.length }} 个资源
+
+ {{ isAllSelected ? '已全选当前页面' : isIndeterminate ? '部分选中' : '未选择' }}
+
+
清空选择
@@ -305,7 +322,7 @@ const resources = ref
([])
const loading = ref(false)
const total = ref(0)
const currentPage = ref(1)
-const pageSize = ref(20)
+const pageSize = ref(200)
const searchQuery = ref('')
const selectedCategory = ref(null)
const selectedPlatform = ref(null)
@@ -359,11 +376,15 @@ const { data: platformsData } = await useAsyncData('resourcePlatforms', () => pa
// 分类选项
const categoryOptions = computed(() => {
const data = categoriesData.value as any
- const categories = data?.data || data || []
- return categories.map((cat: any) => ({
+ console.log('分类数据:', data)
+ const categories = data?.items || data || []
+ console.log('处理后的分类:', categories)
+ const options = categories.map((cat: any) => ({
label: cat.name,
value: cat.id
}))
+ console.log('分类选项:', options)
+ return options
})
// 标签选项
@@ -381,7 +402,7 @@ const platformOptions = computed(() => {
const data = platformsData.value as any
const platforms = data?.data || data || []
return platforms.map((platform: any) => ({
- label: platform.name,
+ label: platform.remark || platform.name,
value: platform.id
}))
})
@@ -394,28 +415,50 @@ const getCategoryName = (categoryId: number) => {
// 获取平台名称
const getPlatformName = (platformId: number) => {
- const platform = (platformsData.value as any)?.data?.find((plat: any) => plat.id === platformId)
- return platform?.name || '未知平台'
+ console.log('platformId', platformId, platformsData.value)
+ const platform = (platformsData.value as any)?.find((plat: any) => plat.id === platformId)
+ return platform?.remark || platform?.name || '未知平台'
}
// 获取数据
const fetchData = async () => {
loading.value = true
try {
- const response = await resourceApi.getResources({
+ const params: any = {
page: currentPage.value,
page_size: pageSize.value,
- search: searchQuery.value,
- category_id: selectedCategory.value,
- pan_id: selectedPlatform.value
- }) as any
+ search: searchQuery.value
+ }
+
+ // 添加分类筛选
+ if (selectedCategory.value) {
+ params.category_id = selectedCategory.value
+ console.log('添加分类筛选:', selectedCategory.value)
+ }
+
+ // 添加平台筛选
+ if (selectedPlatform.value) {
+ params.pan_id = selectedPlatform.value
+ console.log('添加平台筛选:', selectedPlatform.value)
+ }
+
+ console.log('请求参数:', params)
+ console.log('pageSize:', pageSize.value)
+ console.log('selectedCategory:', selectedCategory.value)
+ console.log('selectedPlatform:', selectedPlatform.value)
+ const response = await resourceApi.getResources(params) as any
+ console.log('API响应:', response)
+ console.log('返回的资源数量:', response?.data?.length || 0)
if (response && response.data) {
resources.value = response.data
total.value = response.total || 0
+ // 清空选择(因为数据已更新)
+ selectedResources.value = []
} else {
resources.value = []
total.value = 0
+ selectedResources.value = []
}
} catch (error) {
console.error('获取资源失败:', error)
@@ -461,11 +504,45 @@ const toggleResourceSelection = (resourceId: number, checked: boolean) => {
}
}
+// 全选状态计算
+const isAllSelected = computed(() => {
+ return resources.value.length > 0 && selectedResources.value.length === resources.value.length
+})
+
+// 部分选中状态计算
+const isIndeterminate = computed(() => {
+ return selectedResources.value.length > 0 && selectedResources.value.length < resources.value.length
+})
+
+// 切换全选
+const toggleSelectAll = (checked: boolean) => {
+ if (checked) {
+ // 全选:添加所有当前页面的资源ID
+ selectedResources.value = resources.value.map(resource => resource.id)
+ } else {
+ // 取消全选:清空选择
+ selectedResources.value = []
+ }
+}
+
// 清空选择
const clearSelection = () => {
selectedResources.value = []
}
+// 打开批量操作模态框
+const openBatchModal = () => {
+ // 如果没有选择任何资源,自动全选当前页面
+ if (selectedResources.value.length === 0 && resources.value.length > 0) {
+ selectedResources.value = resources.value.map(resource => resource.id)
+ notification.info({
+ content: '已自动全选当前页面资源',
+ duration: 2000
+ })
+ }
+ showBatchModal.value = true
+}
+
// 编辑资源
const editResource = (resource: Resource) => {
editingResource.value = resource
@@ -623,4 +700,17 @@ useHead({
\ No newline at end of file
diff --git a/web/pages/admin/tags.vue b/web/pages/admin/tags.vue
index d629c0a..1280b67 100644
--- a/web/pages/admin/tags.vue
+++ b/web/pages/admin/tags.vue
@@ -206,7 +206,7 @@ const { data: categoriesData } = await useAsyncData('tagCategories', () => categ
const categoryOptions = computed(() => {
const data = categoriesData.value as any
const categories = data?.data || data || []
- return categories.map((cat: any) => ({
+ return categories.items.map((cat: any) => ({
label: cat.name,
value: cat.id
}))
@@ -325,8 +325,8 @@ const fetchData = async () => {
search: searchQuery.value
}) as any
- if (response && response.data) {
- tags.value = response.data
+ if (response && response.items) {
+ tags.value = response.items
total.value = response.total || 0
} else if (Array.isArray(response)) {
tags.value = response
diff --git a/web/stores/systemConfig.ts b/web/stores/systemConfig.ts
index 4b3b356..6bcdf18 100644
--- a/web/stores/systemConfig.ts
+++ b/web/stores/systemConfig.ts
@@ -10,7 +10,7 @@ export const useSystemConfigStore = defineStore('systemConfig', {
async initConfig(force = false) {
if (this.initialized && !force) return
try {
- const data = await useApiFetch('/public/system-config').then((res: any) => res.data || res)
+ const data = await useApiFetch('/system/config').then((res: any) => res.data || res)
this.config = data
this.initialized = true
} catch (e) {