diff --git a/db/repo/category_repository.go b/db/repo/category_repository.go index fcd4f04..5b4ac61 100644 --- a/db/repo/category_repository.go +++ b/db/repo/category_repository.go @@ -10,6 +10,7 @@ import ( type CategoryRepository interface { BaseRepository[entity.Category] FindByName(name string) (*entity.Category, error) + FindByNameIncludingDeleted(name string) (*entity.Category, error) FindWithResources() ([]entity.Category, error) FindWithTags() ([]entity.Category, error) GetResourceCount(categoryID uint) (int64, error) @@ -17,6 +18,7 @@ type CategoryRepository interface { GetTagNames(categoryID uint) ([]string, error) FindWithPagination(page, pageSize int) ([]entity.Category, int64, error) Search(query string, page, pageSize int) ([]entity.Category, int64, error) + RestoreDeletedCategory(id uint) error } // CategoryRepositoryImpl Category的Repository实现 @@ -41,6 +43,21 @@ func (r *CategoryRepositoryImpl) FindByName(name string) (*entity.Category, erro return &category, nil } +// FindByNameIncludingDeleted 根据名称查找(包括已删除的记录) +func (r *CategoryRepositoryImpl) FindByNameIncludingDeleted(name string) (*entity.Category, error) { + var category entity.Category + err := r.db.Unscoped().Where("name = ?", name).First(&category).Error + if err != nil { + return nil, err + } + return &category, nil +} + +// RestoreDeletedCategory 恢复已删除的分类 +func (r *CategoryRepositoryImpl) RestoreDeletedCategory(id uint) error { + return r.db.Unscoped().Model(&entity.Category{}).Where("id = ?", id).Update("deleted_at", nil).Error +} + // FindWithResources 查找包含资源的分类 func (r *CategoryRepositoryImpl) FindWithResources() ([]entity.Category, error) { var categories []entity.Category diff --git a/db/repo/tag_repository.go b/db/repo/tag_repository.go index 3d31356..a2e876d 100644 --- a/db/repo/tag_repository.go +++ b/db/repo/tag_repository.go @@ -10,6 +10,7 @@ import ( type TagRepository interface { BaseRepository[entity.Tag] FindByName(name string) (*entity.Tag, error) + FindByNameIncludingDeleted(name string) (*entity.Tag, error) FindWithResources() ([]entity.Tag, error) FindByCategoryID(categoryID uint) ([]entity.Tag, error) FindByCategoryIDPaginated(categoryID uint, page, pageSize int) ([]entity.Tag, int64, error) @@ -19,6 +20,7 @@ type TagRepository interface { Search(query string, page, pageSize int) ([]entity.Tag, int64, error) UpdateWithNulls(tag *entity.Tag) error GetByID(id uint) (*entity.Tag, error) + RestoreDeletedTag(id uint) error } // TagRepositoryImpl Tag的Repository实现 @@ -43,6 +45,16 @@ func (r *TagRepositoryImpl) FindByName(name string) (*entity.Tag, error) { return &tag, nil } +// FindByNameIncludingDeleted 根据名称查找(包括已删除的记录) +func (r *TagRepositoryImpl) FindByNameIncludingDeleted(name string) (*entity.Tag, error) { + var tag entity.Tag + err := r.db.Unscoped().Where("name = ?", name).First(&tag).Error + if err != nil { + return nil, err + } + return &tag, nil +} + // FindWithResources 查找包含资源的标签 func (r *TagRepositoryImpl) FindWithResources() ([]entity.Tag, error) { var tags []entity.Tag @@ -155,3 +167,8 @@ func (r *TagRepositoryImpl) GetByID(id uint) (*entity.Tag, error) { } return &tag, nil } + +// RestoreDeletedTag 恢复已删除的标签 +func (r *TagRepositoryImpl) RestoreDeletedTag(id uint) error { + return r.db.Unscoped().Model(&entity.Tag{}).Where("id = ?", id).Update("deleted_at", nil).Error +} diff --git a/handlers/category_handler.go b/handlers/category_handler.go index 2a79829..b6c2efc 100644 --- a/handlers/category_handler.go +++ b/handlers/category_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" ) @@ -18,6 +19,8 @@ func GetCategories(c *gin.Context) { pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20")) search := c.Query("search") + utils.Debug("获取分类列表 - 分页参数: page=%d, pageSize=%d, search=%s", page, pageSize, search) + var categories []entity.Category var total int64 var err error @@ -35,6 +38,8 @@ func GetCategories(c *gin.Context) { return } + utils.Debug("查询到分类数量: %d, 总数: %d", len(categories), total) + // 获取每个分类的资源数量和标签名称 resourceCounts := make(map[uint]int64) tagNamesMap := make(map[uint][]string) @@ -73,12 +78,50 @@ func CreateCategory(c *gin.Context) { return } + // 首先检查是否存在已删除的同名分类 + deletedCategory, err := repoManager.CategoryRepository.FindByNameIncludingDeleted(req.Name) + if err == nil && deletedCategory.DeletedAt.Valid { + utils.Debug("找到已删除的分类: ID=%d, Name=%s", deletedCategory.ID, deletedCategory.Name) + + // 如果存在已删除的同名分类,则恢复它 + err = repoManager.CategoryRepository.RestoreDeletedCategory(deletedCategory.ID) + if err != nil { + ErrorResponse(c, "恢复已删除分类失败: "+err.Error(), http.StatusInternalServerError) + return + } + utils.Debug("分类恢复成功: ID=%d", deletedCategory.ID) + + // 重新获取恢复后的分类 + restoredCategory, err := repoManager.CategoryRepository.FindByID(deletedCategory.ID) + if err != nil { + ErrorResponse(c, "获取恢复的分类失败: "+err.Error(), http.StatusInternalServerError) + return + } + utils.Debug("重新获取到恢复的分类: ID=%d, Name=%s", restoredCategory.ID, restoredCategory.Name) + + // 更新分类信息 + restoredCategory.Description = req.Description + err = repoManager.CategoryRepository.Update(restoredCategory) + if err != nil { + ErrorResponse(c, "更新恢复的分类失败: "+err.Error(), http.StatusInternalServerError) + return + } + utils.Debug("分类信息更新成功: ID=%d, Description=%s", restoredCategory.ID, restoredCategory.Description) + + SuccessResponse(c, gin.H{ + "message": "分类恢复成功", + "category": converter.ToCategoryResponse(restoredCategory, 0, []string{}), + }) + return + } + + // 如果不存在已删除的同名分类,则创建新分类 category := &entity.Category{ Name: req.Name, Description: req.Description, } - err := repoManager.CategoryRepository.Create(category) + err = repoManager.CategoryRepository.Create(category) if err != nil { ErrorResponse(c, err.Error(), http.StatusInternalServerError) return diff --git a/handlers/tag_handler.go b/handlers/tag_handler.go index 3c437a2..1d10fde 100644 --- a/handlers/tag_handler.go +++ b/handlers/tag_handler.go @@ -65,13 +65,47 @@ func CreateTag(c *gin.Context) { return } + // 首先检查是否存在已删除的同名标签 + deletedTag, err := repoManager.TagRepository.FindByNameIncludingDeleted(req.Name) + if err == nil && deletedTag.DeletedAt.Valid { + // 如果存在已删除的同名标签,则恢复它 + err = repoManager.TagRepository.RestoreDeletedTag(deletedTag.ID) + if err != nil { + ErrorResponse(c, "恢复已删除标签失败: "+err.Error(), http.StatusInternalServerError) + return + } + + // 重新获取恢复后的标签 + restoredTag, err := repoManager.TagRepository.FindByID(deletedTag.ID) + if err != nil { + ErrorResponse(c, "获取恢复的标签失败: "+err.Error(), http.StatusInternalServerError) + return + } + + // 更新标签信息 + restoredTag.Description = req.Description + restoredTag.CategoryID = req.CategoryID + err = repoManager.TagRepository.UpdateWithNulls(restoredTag) + if err != nil { + ErrorResponse(c, "更新恢复的标签失败: "+err.Error(), http.StatusInternalServerError) + return + } + + SuccessResponse(c, gin.H{ + "message": "标签恢复成功", + "tag": converter.ToTagResponse(restoredTag, 0), + }) + return + } + + // 如果不存在已删除的同名标签,则创建新标签 tag := &entity.Tag{ Name: req.Name, Description: req.Description, CategoryID: req.CategoryID, } - err := repoManager.TagRepository.Create(tag) + err = repoManager.TagRepository.Create(tag) if err != nil { ErrorResponse(c, err.Error(), http.StatusInternalServerError) return diff --git a/utils/scheduler.go b/utils/scheduler.go index 2edc1f3..ba5bbc2 100644 --- a/utils/scheduler.go +++ b/utils/scheduler.go @@ -1038,15 +1038,30 @@ func (s *Scheduler) handleTags(tagStr string) ([]uint, error) { Debug("查找标签: %s", name) tag, err := s.tagRepo.FindByName(name) if err != nil { - Debug("标签 %s 不存在,创建新标签", name) - // 不存在则新建 - tag = &entity.Tag{Name: name} - err = s.tagRepo.Create(tag) - if err != nil { - Error("创建标签 %s 失败: %v", name, err) - return nil, fmt.Errorf("创建标签 %s 失败: %v", name, err) + // 检查是否存在已删除的同名标签 + Debug("标签 %s 不存在,检查是否有已删除的同名标签", name) + deletedTag, err2 := s.tagRepo.FindByNameIncludingDeleted(name) + if err2 == nil && deletedTag.DeletedAt.Valid { + // 如果存在已删除的同名标签,则恢复它 + Debug("找到已删除的同名标签 %s,正在恢复", name) + err2 = s.tagRepo.RestoreDeletedTag(deletedTag.ID) + if err2 != nil { + Error("恢复已删除标签 %s 失败: %v", name, err2) + return nil, fmt.Errorf("恢复已删除标签 %s 失败: %v", name, err2) + } + tag = deletedTag + Debug("成功恢复标签: %s (ID: %d)", name, tag.ID) + } else { + // 如果不存在已删除的同名标签,则创建新标签 + Debug("标签 %s 不存在,创建新标签", name) + tag = &entity.Tag{Name: name} + err2 = s.tagRepo.Create(tag) + if err2 != nil { + Error("创建标签 %s 失败: %v", name, err2) + return nil, fmt.Errorf("创建标签 %s 失败: %v", name, err2) + } + Debug("成功创建标签: %s (ID: %d)", name, tag.ID) } - Debug("成功创建标签: %s (ID: %d)", name, tag.ID) } else { Debug("找到已存在的标签: %s (ID: %d)", name, tag.ID) } @@ -1065,8 +1080,24 @@ func (s *Scheduler) resolveCategory(categoryName string, tagIDs []uint) (*uint, Debug("查找分类: %s", categoryName) cat, err := s.categoryRepo.FindByName(categoryName) if err != nil { - Debug("分类 %s 不存在: %v", categoryName, err) - } else if cat != nil { + // 检查是否存在已删除的同名分类 + Debug("分类 %s 不存在,检查是否有已删除的同名分类", categoryName) + deletedCat, err2 := s.categoryRepo.FindByNameIncludingDeleted(categoryName) + if err2 == nil && deletedCat.DeletedAt.Valid { + // 如果存在已删除的同名分类,则恢复它 + Debug("找到已删除的同名分类 %s,正在恢复", categoryName) + err2 = s.categoryRepo.RestoreDeletedCategory(deletedCat.ID) + if err2 != nil { + Error("恢复已删除分类 %s 失败: %v", categoryName, err2) + return nil, fmt.Errorf("恢复已删除分类 %s 失败: %v", categoryName, err2) + } + cat = deletedCat + Debug("成功恢复分类: %s (ID: %d)", categoryName, cat.ID) + } else { + Debug("分类 %s 不存在: %v", categoryName, err) + } + } + if cat != nil { Debug("找到分类: %s (ID: %d)", categoryName, cat.ID) return &cat.ID, nil } diff --git a/web/components.d.ts b/web/components.d.ts index d4f3195..afa3022 100644 --- a/web/components.d.ts +++ b/web/components.d.ts @@ -11,6 +11,8 @@ declare module 'vue' { NA: typeof import('naive-ui')['NA'] NAlert: typeof import('naive-ui')['NAlert'] NButton: typeof import('naive-ui')['NButton'] + NCheckbox: typeof import('naive-ui')['NCheckbox'] + NDialogProvider: typeof import('naive-ui')['NDialogProvider'] NInput: typeof import('naive-ui')['NInput'] NNotificationProvider: typeof import('naive-ui')['NNotificationProvider'] NSelect: typeof import('naive-ui')['NSelect'] diff --git a/web/components/BatchAddResource.vue b/web/components/BatchAddResource.vue index ba59b71..68cb63e 100644 --- a/web/components/BatchAddResource.vue +++ b/web/components/BatchAddResource.vue @@ -7,8 +7,8 @@

格式要求:标题和URL为一组,标题必填, 同一标题URL支持多行

 电影1
-https://pan.baidu.com/s/123456  # 百度网盘 电影1 
-https://pan.quark.com/s/123456  # 夸克网盘 电影1 
+https://pan.baidu.com/s/123456
+https://pan.quark.com/s/123456
 电影标题2
 https://pan.baidu.com/s/789012
 电视剧标题3
@@ -21,9 +21,9 @@ https://pan.quark.cn/s/345678
- +
diff --git a/web/components/ResourceModal.vue b/web/components/ResourceModal.vue deleted file mode 100644 index 077519e..0000000 --- a/web/components/ResourceModal.vue +++ /dev/null @@ -1,272 +0,0 @@ - - - - - \ No newline at end of file diff --git a/web/components/SingleAddResource.vue b/web/components/SingleAddResource.vue index 4dec13f..584edb8 100644 --- a/web/components/SingleAddResource.vue +++ b/web/components/SingleAddResource.vue @@ -5,9 +5,8 @@ - @@ -18,12 +17,11 @@ - + /> @@ -31,13 +29,12 @@ - + />

支持百度网盘、阿里云盘、夸克网盘等链接,每行一个链接

@@ -48,9 +45,8 @@ - @@ -76,10 +72,9 @@ - @@ -89,9 +84,8 @@ - @@ -101,9 +95,8 @@ - @@ -113,12 +106,11 @@ - + /> diff --git a/web/layouts/admin.vue b/web/layouts/admin.vue index 9b24cb0..8a7985c 100644 --- a/web/layouts/admin.vue +++ b/web/layouts/admin.vue @@ -23,8 +23,10 @@
- - + + + +
diff --git a/web/pages/admin/categories.vue b/web/pages/admin/categories.vue index 0ab2757..4535591 100644 --- a/web/pages/admin/categories.vue +++ b/web/pages/admin/categories.vue @@ -3,24 +3,21 @@
- + + 添加分类 +
-
- +
@@ -54,10 +51,9 @@
暂无分类
你可以点击上方"添加分类"按钮创建新分类
- + @@ -85,16 +81,12 @@
- - +
@@ -152,36 +144,31 @@

{{ editingCategory ? '编辑分类' : '添加分类' }}

- +
-
- +
- - +
@@ -221,6 +208,7 @@ let searchTimeout: NodeJS.Timeout | null = null const showAddModal = ref(false) const submitting = ref(false) const editingCategory = ref(null) +const dialog = useDialog() // 表单数据 const formData = ref({ @@ -263,6 +251,8 @@ const fetchCategories = async () => { console.log('获取分类列表参数:', params) const response = await categoryApi.getCategories(params) console.log('分类接口响应:', response) + console.log('响应类型:', typeof response) + console.log('响应是否为数组:', Array.isArray(response)) // 适配后端API响应格式 if (response && response.items) { @@ -283,6 +273,7 @@ const fetchCategories = async () => { totalPages.value = 1 } console.log('最终分类数据:', categories.value) + console.log('分类数据长度:', categories.value.length) } catch (error) { console.error('获取分类列表失败:', error) categories.value = [] @@ -317,36 +308,61 @@ const goToPage = (page: number) => { // 编辑分类 const editCategory = (category: any) => { + console.log('编辑分类:', category) editingCategory.value = category formData.value = { name: category.name, description: category.description || '' } + console.log('设置表单数据:', formData.value) showAddModal.value = true } // 删除分类 const deleteCategory = async (categoryId: number) => { - if (!confirm(`确定要删除分类吗?`)) { - return - } - try { - await categoryApi.deleteCategory(categoryId) - await fetchCategories() - } catch (error) { - console.error('删除分类失败:', error) - } + dialog.warning({ + title: '警告', + content: '确定要删除分类吗?', + positiveText: '确定', + negativeText: '取消', + draggable: true, + onPositiveClick: async () => { + try { + await categoryApi.deleteCategory(categoryId) + await fetchCategories() + } catch (error) { + console.error('删除分类失败:', error) + } + } + }) } // 提交表单 const handleSubmit = async () => { try { submitting.value = true + let response: any if (editingCategory.value) { - await categoryApi.updateCategory(editingCategory.value.id, formData.value) + response = await categoryApi.updateCategory(editingCategory.value.id, formData.value) } else { - await categoryApi.createCategory(formData.value) + response = await categoryApi.createCategory(formData.value) } + console.log('分类操作响应:', response) + + // 检查是否是恢复操作 + if (response && response.message && response.message.includes('恢复成功')) { + console.log('检测到分类恢复操作,延迟刷新数据') + console.log('恢复的分类信息:', response.category) + closeModal() + // 延迟一点时间再刷新,确保数据库状态已更新 + setTimeout(async () => { + console.log('开始刷新分类数据...') + await fetchCategories() + console.log('分类数据刷新完成') + }, 500) + return + } + closeModal() await fetchCategories() } catch (error) { diff --git a/web/pages/admin/cks.vue b/web/pages/admin/cks.vue index a98b97b..989a428 100644 --- a/web/pages/admin/cks.vue +++ b/web/pages/admin/cks.vue @@ -19,23 +19,23 @@
- +
- +
- +
@@ -71,12 +71,12 @@
暂无账号
你可以点击上方"添加账号"按钮创建新账号
- + @@ -228,8 +228,8 @@ class="mt-1 block w-full border border-gray-300 dark:border-gray-600 rounded-md px-3 py-2 focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 dark:bg-gray-700 dark:text-gray-100" > -

编辑时不允许修改平台类型

@@ -242,49 +242,44 @@
- + />
-
- - +
@@ -323,6 +318,7 @@ const loading = ref(true) const pageLoading = ref(true) const submitting = ref(false) const platform = ref(null) +const dialog = useDialog() import { useCksApi, usePanApi } from '~/composables/useApi' const cksApi = useCksApi() @@ -334,10 +330,18 @@ const pans = computed(() => { return Array.isArray(pansData.value) ? pansData.value : (pansData.value?.list || []) }) const platformOptions = computed(() => { - return pans.value.map(pan => ({ - label: pan.remark, - value: pan.id - })) + const options = [ + { label: '全部平台', value: null } + ] + + pans.value.forEach(pan => { + options.push({ + label: pan.remark || pan.name || `平台${pan.id}`, + value: pan.id + }) + }) + + return options }) // 检查认证 @@ -383,8 +387,11 @@ const createCks = async () => { await fetchCks() closeModal() } catch (error) { - console.error('创建账号失败:', error) - alert('创建账号失败: ' + (error.message || '未知错误')) + dialog.error({ + title: '错误', + content: '创建账号失败: ' + (error.message || '未知错误'), + positiveText: '确定' + }) } finally { submitting.value = false } @@ -407,47 +414,68 @@ const updateCks = async () => { // 删除账号 const deleteCks = async (id) => { - if (!confirm('确定要删除这个账号吗?')) return - - try { - await cksApi.deleteCks(id) - await fetchCks() - } catch (error) { - console.error('删除账号失败:', error) - alert('删除账号失败: ' + (error.message || '未知错误')) - } + dialog.warning({ + title: '警告', + content: '确定要删除这个账号吗?', + positiveText: '确定', + negativeText: '取消', + draggable: true, + onPositiveClick: async () => { + try { + await cksApi.deleteCks(id) + await fetchCks() + } catch (error) { + console.error('删除账号失败:', error) + alert('删除账号失败: ' + (error.message || '未知错误')) + } + } + }) } // 刷新容量 const refreshCapacity = async (id) => { - if (!confirm('确定要刷新此账号的容量信息吗?')) return - - try { - await cksApi.refreshCapacity(id) - await fetchCks() - alert('容量信息已刷新!') - } catch (error) { - console.error('刷新容量失败:', error) - alert('刷新容量失败: ' + (error.message || '未知错误')) - } + dialog.warning({ + title: '警告', + content: '确定要刷新此账号的容量信息吗?', + positiveText: '确定', + negativeText: '取消', + draggable: true, + onPositiveClick: async () => { + try { + await cksApi.refreshCapacity(id) + await fetchCks() + alert('容量信息已刷新!') + } catch (error) { + console.error('刷新容量失败:', error) + alert('刷新容量失败: ' + (error.message || '未知错误')) + } + } + }) } // 切换账号状态 const toggleStatus = async (cks) => { const newStatus = !cks.is_valid - if (!confirm(`确定要${cks.is_valid ? '禁用' : '启用'}此账号吗?`)) return - - try { - console.log('切换状态 - 账号ID:', cks.id, '当前状态:', cks.is_valid, '新状态:', newStatus) - await cksApi.updateCks(cks.id, { is_valid: newStatus }) - console.log('状态更新成功,正在刷新数据...') - await fetchCks() - console.log('数据刷新完成') - alert(`账号已${newStatus ? '启用' : '禁用'}!`) - } catch (error) { - console.error('切换账号状态失败:', error) - alert(`切换账号状态失败: ${error.message || '未知错误'}`) - } + 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('数据刷新完成') + alert(`账号已${newStatus ? '启用' : '禁用'}!`) + } catch (error) { + console.error('切换账号状态失败:', error) + alert(`切换账号状态失败: ${error.message || '未知错误'}`) + } + } + }) } // 编辑账号 @@ -532,13 +560,24 @@ const formatFileSize = (bytes) => { // 过滤和分页计算 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 @@ -556,9 +595,17 @@ const debounceSearch = () => { }, 500) } +// 平台变化处理 +const onPlatformChange = () => { + currentPage.value = 1 + console.log('平台过滤条件变化:', platform.value) + console.log('当前过滤后的账号数量:', filteredCksList.value.length) +} + // 刷新数据 const refreshData = () => { currentPage.value = 1 + // 保持当前的过滤条件,只刷新数据 fetchCks() fetchPlatforms() } diff --git a/web/pages/admin/failed-resources.vue b/web/pages/admin/failed-resources.vue index ff5bb9c..bb0c3f7 100644 --- a/web/pages/admin/failed-resources.vue +++ b/web/pages/admin/failed-resources.vue @@ -251,6 +251,7 @@ const totalPages = ref(0) // 错误统计 const errorStats = ref>({}) +const dialog = useDialog() // 获取失败资源API import { useReadyResourceApi } from '~/composables/useApi' @@ -338,83 +339,108 @@ const refreshData = () => { // 重试单个资源 const retryResource = async (id: number) => { - if (!confirm('确定要重试这个资源吗?')) { - return - } - - try { - await readyResourceApi.clearErrorMsg(id) - alert('错误信息已清除,资源将在下次调度时重新处理') - fetchData() - } catch (error) { - console.error('重试失败:', error) - alert('重试失败') - } + dialog.warning({ + title: '警告', + content: '确定要重试这个资源吗?', + positiveText: '确定', + negativeText: '取消', + draggable: true, + onPositiveClick: async () => { + try { + await readyResourceApi.clearErrorMsg(id) + alert('错误信息已清除,资源将在下次调度时重新处理') + fetchData() + } catch (error) { + console.error('重试失败:', error) + alert('重试失败') + } + } + }) } // 清除单个资源错误 const clearError = async (id: number) => { - if (!confirm('确定要清除这个资源的错误信息吗?')) { - return - } - - try { - await readyResourceApi.clearErrorMsg(id) - alert('错误信息已清除') - fetchData() - } catch (error) { - console.error('清除错误失败:', error) - alert('清除错误失败') - } + dialog.warning({ + title: '警告', + content: '确定要清除这个资源的错误信息吗?', + positiveText: '确定', + negativeText: '取消', + draggable: true, + onPositiveClick: async () => { + try { + await readyResourceApi.clearErrorMsg(id) + alert('错误信息已清除') + fetchData() + } catch (error) { + console.error('清除错误失败:', error) + alert('清除错误失败') + } + } + }) } // 删除资源 const deleteResource = async (id: number) => { - if (!confirm('确定要删除这个失败资源吗?')) { - return - } - - try { - await readyResourceApi.deleteReadyResource(id) - if (failedResources.value.length === 1 && currentPage.value > 1) { - currentPage.value-- + dialog.warning({ + title: '警告', + content: '确定要删除这个失败资源吗?', + positiveText: '确定', + negativeText: '取消', + draggable: true, + onPositiveClick: async () => { + try { + await readyResourceApi.deleteReadyResource(id) + if (failedResources.value.length === 1 && currentPage.value > 1) { + currentPage.value-- + } + fetchData() + } catch (error) { + console.error('删除失败:', error) + alert('删除失败') + } } - fetchData() - } catch (error) { - console.error('删除失败:', error) - alert('删除失败') - } + }) } // 重试所有失败资源 const retryAllFailed = async () => { - if (!confirm('确定要重试所有可重试的失败资源吗?')) { - return - } - - try { - const response = await readyResourceApi.retryFailedResources() as any - alert(`重试操作完成:\n总数量:${response.total_count}\n已清除:${response.cleared_count}\n跳过:${response.skipped_count}`) - fetchData() - } catch (error) { - console.error('重试所有失败资源失败:', error) - alert('重试失败') - } + dialog.warning({ + title: '警告', + content: '确定要重试所有可重试的失败资源吗?', + positiveText: '确定', + negativeText: '取消', + draggable: true, + onPositiveClick: async () => { + try { + const response = await readyResourceApi.retryFailedResources() as any + alert(`重试操作完成:\n总数量:${response.total_count}\n已清除:${response.cleared_count}\n跳过:${response.skipped_count}`) + fetchData() + } catch (error) { + console.error('重试所有失败资源失败:', error) + alert('重试失败') + } + } + }) } // 清除所有错误 const clearAllErrors = async () => { - if (!confirm('确定要清除所有失败资源的错误信息吗?此操作不可恢复!')) { - return - } - - try { - // 这里需要实现批量清除错误的API - alert('批量清除错误功能待实现') - } catch (error) { - console.error('清除所有错误失败:', error) - alert('清除失败') - } + dialog.warning({ + title: '警告', + content: '确定要清除所有失败资源的错误信息吗?此操作不可恢复!', + positiveText: '确定', + negativeText: '取消', + draggable: true, + onPositiveClick: async () => { + try { + // 这里需要实现批量清除错误的API + alert('批量清除错误功能待实现') + } catch (error) { + console.error('清除所有错误失败:', error) + alert('清除失败') + } + } + }) } // 格式化时间 diff --git a/web/pages/admin/ready-resources.vue b/web/pages/admin/ready-resources.vue index 954bfc4..9a6b555 100644 --- a/web/pages/admin/ready-resources.vue +++ b/web/pages/admin/ready-resources.vue @@ -78,83 +78,31 @@ - -
-
-
-

批量添加待处理资源

- -
- -
- -
-

格式1:标题和URL两行一组

-
-电影标题1
-https://pan.baidu.com/s/123456
-电影标题2
-https://pan.baidu.com/s/789012
-

格式2:只有URL,系统自动判断

-
-https://pan.baidu.com/s/123456
-https://pan.baidu.com/s/789012
-https://pan.baidu.com/s/345678
-
-
- -
- - -
- -
- - -
-
-
+
添加资源 -
- - +
@@ -189,17 +137,11 @@ https://pan.baidu.com/s/345678
你可以点击上方"添加资源"按钮快速导入资源
添加资源 -
@@ -324,8 +266,6 @@ interface ReadyResource { const readyResources = ref([]) const loading = ref(false) -const showAddModal = ref(false) -const resourceText = ref('') const pageLoading = ref(true) // 添加页面加载状态 // 分页相关状态 @@ -344,6 +284,7 @@ const systemConfigStore = useSystemConfigStore() // 获取系统配置 const systemConfig = ref(null) const updatingConfig = ref(false) // 添加配置更新状态 +const dialog = useDialog() const fetchSystemConfig = async () => { try { const response = await systemConfigApi.getSystemConfig() @@ -449,66 +390,55 @@ const refreshConfig = () => { fetchSystemConfig() } -// 关闭模态框 -const closeModal = () => { - showAddModal.value = false - resourceText.value = '' -} -// 批量添加 -const handleBatchAdd = async () => { - if (!resourceText.value.trim()) { - alert('请输入资源内容') - return - } - try { - const response = await readyResourceApi.createReadyResourcesFromText(resourceText.value) as any - console.log('批量添加成功:', response) - closeModal() - fetchData() - alert(`成功添加 ${response.data.count} 个资源`) - } catch (error) { - console.error('批量添加失败:', error) - alert('批量添加失败,请检查输入格式') - } -} + // 删除资源 const deleteResource = async (id: number) => { - if (!confirm('确定要删除这个待处理资源吗?')) { - return - } - - try { - await readyResourceApi.deleteReadyResource(id) - // 如果当前页没有数据了,回到上一页 - if (readyResources.value.length === 1 && currentPage.value > 1) { - currentPage.value-- + dialog.warning({ + title: '警告', + content: '确定要删除这个待处理资源吗?', + positiveText: '确定', + negativeText: '取消', + draggable: true, + onPositiveClick: async () => { + try { + await readyResourceApi.deleteReadyResource(id) + // 如果当前页没有数据了,回到上一页 + if (readyResources.value.length === 1 && currentPage.value > 1) { + currentPage.value-- + } + fetchData() + } catch (error) { + console.error('删除失败:', error) + alert('删除失败') + } } - fetchData() - } catch (error) { - console.error('删除失败:', error) - alert('删除失败') - } + }) } // 清空全部 const clearAll = async () => { - if (!confirm('确定要清空所有待处理资源吗?此操作不可恢复!')) { - return - } - - try { - const response = await readyResourceApi.clearReadyResources() as any - console.log('清空成功:', response) - currentPage.value = 1 // 清空后回到第一页 - fetchData() - alert(`成功清空 ${response.data.deleted_count} 个资源`) - } catch (error) { - console.error('清空失败:', error) - alert('清空失败') - } + dialog.warning({ + title: '警告', + content: '确定要清空所有待处理资源吗?此操作不可恢复!', + positiveText: '确定', + negativeText: '取消', + draggable: true, + onPositiveClick: async () => { + try { + const response = await readyResourceApi.clearReadyResources() as any + console.log('清空成功:', response) + currentPage.value = 1 // 清空后回到第一页 + fetchData() + alert(`成功清空 ${response.data.deleted_count} 个资源`) + } catch (error) { + console.error('清空失败:', error) + alert('清空失败') + } + } + }) } // 格式化时间 diff --git a/web/pages/admin/resources.vue b/web/pages/admin/resources.vue index d301076..508521f 100644 --- a/web/pages/admin/resources.vue +++ b/web/pages/admin/resources.vue @@ -22,11 +22,10 @@
-
@@ -69,18 +68,18 @@
- - +
共找到 {{ totalCount }} 个资源 @@ -91,26 +90,26 @@
- +
- - +
@@ -119,9 +118,9 @@

批量操作

- +
@@ -155,11 +154,19 @@
- {{ tag.name }}
@@ -168,12 +175,12 @@
- - +
@@ -185,11 +192,9 @@ - ID @@ -219,17 +224,11 @@
你可以点击上方"添加资源"按钮快速导入资源
添加资源 -
@@ -240,11 +239,19 @@ class="hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors" > - {{ resource.id }} @@ -419,6 +426,7 @@ const batchCategory = ref('') const batchTags = ref([]) const selectedResources = ref([]) const selectAll = ref(false) +const dialog = useDialog() // API import { useResourceApi, usePanApi, useCategoryApi, useTagApi } from '~/composables/useApi' @@ -577,8 +585,8 @@ const visiblePages = computed(() => { }) // 全选/取消全选 -const toggleSelectAll = () => { - if (selectAll.value) { +const toggleSelectAll = (checked: boolean) => { + if (checked) { selectedResources.value = resources.value.map(r => r.id) } else { selectedResources.value = [] @@ -600,10 +608,18 @@ const handleBatchAction = async () => { try { switch (batchAction.value) { case 'delete': - if (confirm(`确定要删除选中的 ${selectedResources.value.length} 个资源吗?`)) { - await resourceApi.batchDeleteResources(selectedResources.value) - alert('批量删除成功') - } + dialog.warning({ + title: '警告', + content: `确定要删除选中的 ${selectedResources.value.length} 个资源吗?`, + positiveText: '确定', + negativeText: '取消', + draggable: true, + onPositiveClick: async () => { + await resourceApi.batchDeleteResources(selectedResources.value) + alert('批量删除成功') + } + }) + return break case 'update_category': if (!batchCategory.value) { @@ -649,16 +665,23 @@ const editResource = (resource: Resource) => { // 删除资源 const deleteResource = async (id: number) => { - if (confirm('确定要删除这个资源吗?')) { - try { - await resourceApi.deleteResource(id) - alert('删除成功') - fetchData() - } catch (error) { - console.error('删除失败:', error) - alert('删除失败') + dialog.warning({ + title: '警告', + content: '确定要删除这个资源吗?', + positiveText: '确定', + negativeText: '取消', + draggable: true, + onPositiveClick: async () => { + try { + await resourceApi.deleteResource(id) + alert('删除成功') + fetchData() + } catch (error) { + console.error('删除失败:', error) + alert('删除失败') + } } - } + }) } // 工具函数 diff --git a/web/pages/admin/system-config.vue b/web/pages/admin/system-config.vue index c391ffa..94aa717 100644 --- a/web/pages/admin/system-config.vue +++ b/web/pages/admin/system-config.vue @@ -278,6 +278,7 @@ {{ saving ? '保存中...' : '保存配置' }} diff --git a/web/pages/admin/tags.vue b/web/pages/admin/tags.vue index 20890aa..b9953d9 100644 --- a/web/pages/admin/tags.vue +++ b/web/pages/admin/tags.vue @@ -29,11 +29,10 @@
-
@@ -203,19 +202,18 @@

{{ editingTag ? '编辑标签' : '添加标签' }}

- +
-
@@ -235,29 +233,27 @@
- + />
- - +
@@ -308,6 +304,7 @@ let searchTimeout: NodeJS.Timeout | null = null const showAddModal = ref(false) const submitting = ref(false) const editingTag = ref(null) +const dialog = useDialog() // 表单数据 const formData = ref({ @@ -374,6 +371,8 @@ const fetchTags = async () => { search: searchQuery.value } console.log('获取标签列表参数:', params) + console.log('搜索查询值:', searchQuery.value) + console.log('搜索查询类型:', typeof searchQuery.value) let response: any if (selectedCategory.value) { @@ -419,10 +418,12 @@ const onCategoryChange = () => { // 搜索防抖 const debounceSearch = () => { + console.log('搜索防抖触发,当前搜索值:', searchQuery.value) if (searchTimeout) { clearTimeout(searchTimeout) } searchTimeout = setTimeout(() => { + console.log('执行搜索,搜索值:', searchQuery.value) currentPage.value = 1 fetchTags() }, 300) @@ -452,16 +453,21 @@ const editTag = (tag: any) => { // 删除标签 const deleteTag = async (tagId: number) => { - if (!confirm(`确定要删除标签吗?`)) { - return - } - - try { - await tagApi.deleteTag(tagId) - await fetchTags() - } catch (error) { - console.error('删除标签失败:', error) - } + dialog.warning({ + title: '警告', + content: '确定要删除标签吗?', + positiveText: '确定', + negativeText: '取消', + draggable: true, + onPositiveClick: async () => { + try { + await tagApi.deleteTag(tagId) + await fetchTags() + } catch (error) { + console.error('删除标签失败:', error) + } + } + }) } // 提交表单 @@ -481,10 +487,24 @@ const handleSubmit = async () => { category_id: categoryId } + let response: any if (editingTag.value) { - await tagApi.updateTag(editingTag.value.id, submitData) + response = await tagApi.updateTag(editingTag.value.id, submitData) } else { - await tagApi.createTag(submitData) + response = await tagApi.createTag(submitData) + } + + console.log('标签操作响应:', response) + + // 检查是否是恢复操作 + if (response && response.message && response.message.includes('恢复成功')) { + console.log('检测到标签恢复操作,延迟刷新数据') + closeModal() + // 延迟一点时间再刷新,确保数据库状态已更新 + setTimeout(async () => { + await fetchTags() + }, 500) + return } closeModal() diff --git a/web/pages/admin/users.vue b/web/pages/admin/users.vue index 4261eed..bc169d2 100644 --- a/web/pages/admin/users.vue +++ b/web/pages/admin/users.vue @@ -4,12 +4,12 @@

用户管理

- +
@@ -52,9 +52,11 @@ {{ user.last_login ? formatDate(user.last_login) : '从未登录' }} - - - + + 编辑{{ user.username === 'admin' ? ' (只读)' : '' }} + + 修改密码 + 删除 @@ -69,33 +71,44 @@

{{ showEditModal ? '编辑用户' : '创建用户' }}

+
+

+ + 管理员用户信息不可修改,只能通过修改密码功能来更新密码。 +

+
+
+

+ + 编辑模式:用户名和邮箱不可修改,只能修改角色和激活状态。 +

+
-
-
-
@@ -103,20 +116,19 @@
- + +
@@ -129,7 +141,8 @@ @@ -153,22 +166,20 @@
-
-
@@ -210,6 +221,7 @@ const showEditModal = ref(false) const showPasswordModal = ref(false) const editingUser = ref(null) const changingPasswordUser = ref(null) +const dialog = useDialog() const form = ref({ username: '', email: '', @@ -271,16 +283,23 @@ const updateUser = async () => { // 删除用户 const deleteUser = async (id) => { - if (!confirm('确定要删除这个用户吗?')) return - - try { - const { useUserApi } = await import('~/composables/useApi') - const userApi = useUserApi() - await userApi.deleteUser(id) - await fetchUsers() - } catch (error) { - console.error('删除用户失败:', error) - } + dialog.warning({ + title: '警告', + content: '确定要删除这个用户吗?', + positiveText: '确定', + negativeText: '取消', + draggable: true, + onPositiveClick: async () => { + try { + const { useUserApi } = await import('~/composables/useApi') + const userApi = useUserApi() + await userApi.deleteUser(id) + await fetchUsers() + } catch (error) { + console.error('删除用户失败:', error) + } + } + }) } // 显示修改密码模态框 diff --git a/web/pages/index.vue b/web/pages/index.vue index e7bc155..d7b8b81 100644 --- a/web/pages/index.vue +++ b/web/pages/index.vue @@ -57,7 +57,7 @@
- + diff --git a/web/pages/login.vue b/web/pages/login.vue index 29e4b64..990f659 100644 --- a/web/pages/login.vue +++ b/web/pages/login.vue @@ -12,27 +12,25 @@
- + />

{{ errors.username }}

- + />

{{ errors.password }}

diff --git a/web/pages/monitor.vue b/web/pages/monitor.vue index 69a4bb6..9df6633 100644 --- a/web/pages/monitor.vue +++ b/web/pages/monitor.vue @@ -47,10 +47,8 @@
- {{ autoRefreshInterval }}秒
diff --git a/web/pages/register.vue b/web/pages/register.vue index b43ef86..d6862ea 100644 --- a/web/pages/register.vue +++ b/web/pages/register.vue @@ -10,53 +10,49 @@
- + />

{{ errors.username }}

- + />

{{ errors.email }}

- + />

{{ errors.password }}

- + />

{{ errors.confirmPassword }}