feat(server): 添加删除视频文件的功能

新增 `/movie/delete` 接口用于删除指定视频文件及其缩略图,并从全局电影列表中移除记录。
同时完善了前端删除交互,包括确认对话框、成功提示及错误处理。

- 后端:
  - 新增 `PostDelete` 处理函数,支持通过文件名删除视频
  - 实现 `Movie.DeleteVideo()` 方法,用于删除视频文件和关联缩略图
 - 更新路由注册,增加 `/delete` 接口
- 前端:
  - 新增删除确认弹窗与交互逻辑
  - 添加删除成功的 Snackbar 提示
  - 调整 MovieCard 按钮布局以容纳删除按钮
  - 传递并使用 onDelete 回调函数处理删除操作

该功能增强了系统的文件管理能力,使用户能够安全地移除不需要的视频内容。
This commit is contained in:
eson
2025-11-12 01:21:20 +08:00
parent b17e9dea28
commit 1a058a31c8
5 changed files with 251 additions and 14 deletions

View File

@@ -3,9 +3,9 @@ package main
import (
"log"
"net/http"
"sort"
"strconv"
"strings"
"sort"
"github.com/gin-gonic/gin"
)
@@ -196,4 +196,67 @@ func PostRename(c *gin.Context) {
response["message"] = "Rename successful"
c.JSON(http.StatusOK, response)
}
}
// PostDelete 删除文件
func PostDelete(c *gin.Context) {
var requestData struct {
FileName string `json:"file_name"`
}
response := gin.H{
"code": http.StatusOK,
"message": "Success",
"data": nil,
}
if err := c.ShouldBindJSON(&requestData); err != nil {
response["code"] = http.StatusBadRequest
response["message"] = "Invalid request data: " + err.Error()
c.JSON(http.StatusBadRequest, response)
return
}
if requestData.FileName == "" {
response["code"] = http.StatusBadRequest
response["message"] = "file_name is required"
c.JSON(http.StatusBadRequest, response)
return
}
// 查找要删除的电影
movie, exists := MovieDict[requestData.FileName]
if !exists {
response["code"] = http.StatusNotFound
response["message"] = "Movie not found"
c.JSON(http.StatusNotFound, response)
return
}
// 执行删除
if err := movie.DeleteVideo(); err != nil {
response["code"] = http.StatusInternalServerError
response["message"] = "Failed to delete video: " + err.Error()
c.JSON(http.StatusInternalServerError, response)
return
}
// 从全局Movies切片中移除记录
MovieDictLock.Lock()
defer MovieDictLock.Unlock()
// 从MovieDict中删除
delete(MovieDict, requestData.FileName)
// 从Movies切片中移除记录
for i, m := range Movies {
if m.FileName == requestData.FileName {
// 移除切片中的元素
Movies = append(Movies[:i], Movies[i+1:]...)
break
}
}
response["message"] = "Delete successful"
c.JSON(http.StatusOK, response)
}

View File

@@ -12,7 +12,7 @@ func main() {
// 设置为发布模式
gin.SetMode(gin.ReleaseMode)
eg := gin.Default()
eg.Use(Cors())
eg.Static("/res", "movie/")
@@ -38,6 +38,7 @@ func main() {
movie.GET("/list/category", GetCategoryList)
movie.GET("/list", MovieList)
movie.POST("/rename", PostRename)
movie.POST("/delete", PostDelete)
eg.Run("0.0.0.0:4444")
}
}

View File

@@ -71,6 +71,30 @@ func (m *Movie) RenameVideo(newName string) error {
return nil
}
// DeleteVideo 删除视频文件和对应的缩略图
func (m *Movie) DeleteVideo() error {
m.mu.Lock()
defer m.mu.Unlock()
// 删除视频文件
if err := os.Remove(m.VideoPath); err != nil && !os.IsNotExist(err) {
return fmt.Errorf("删除视频文件失败: %w", err)
}
// 删除缩略图文件
dir := filepath.Dir(m.VideoPath)
imagePath := filepath.Join(dir, m.Image)
if _, err := os.Stat(imagePath); err == nil {
if err := os.Remove(imagePath); err != nil && !os.IsNotExist(err) {
log.Printf("警告:删除缩略图文件失败: %s, 错误: %v", imagePath, err)
// 不返回错误,因为主要文件已删除
}
}
log.Printf("成功删除视频文件: %s", m.FileName)
return nil
}
var Movies []*Movie // 存储所有电影信息的全局切片
var MovieDict = make(map[string]*Movie) // 存储需要处理缩略图的视频字典
var MovieDictLock sync.Mutex // 保护MovieDict的并发访问
@@ -331,4 +355,4 @@ func markRecentMovies() {
for i := 0; i < len(Movies) && i < 20; i++ {
Movies[i].TimeCategory = "最新添加"
}
}
}