diff --git a/Controllers/VideoController.cs b/Controllers/VideoController.cs
index 9df3ede..dbc9db5 100644
--- a/Controllers/VideoController.cs
+++ b/Controllers/VideoController.cs
@@ -238,6 +238,75 @@ namespace dy.net.Controllers
return new EmptyResult();
}
+ ///
+ /// 获取视频封面图片
+ ///
+ /// 视频ID
+ [Authorize]
+ [HttpGet("cover/{id}")]
+ public async Task GetVideoCover([FromRoute] string id)
+ {
+ try
+ {
+ var video = await douyinVideoService.GetById(id);
+ if (video == null)
+ {
+ return GetDefaultCoverImage();
+ }
+
+ // 优先使用本地保存的封面
+ if (!string.IsNullOrWhiteSpace(video.VideoCoverSavePath) && System.IO.File.Exists(video.VideoCoverSavePath))
+ {
+ var contentType = GetImageContentType(video.VideoCoverSavePath);
+ var fileStream = System.IO.File.OpenRead(video.VideoCoverSavePath);
+ return File(fileStream, contentType);
+ }
+
+ // 本地封面缺失时回落到默认占位图
+ return GetDefaultCoverImage();
+ }
+ catch (Exception ex)
+ {
+ Serilog.Log.Error(ex, $"获取视频封面失败: {id}");
+ return GetDefaultCoverImage();
+ }
+ }
+
+ ///
+ /// 默认封面占位图(SVG)
+ ///
+ private IActionResult GetDefaultCoverImage()
+ {
+ var svgContent = @"";
+ var svgBytes = System.Text.Encoding.UTF8.GetBytes(svgContent);
+ return File(svgBytes, "image/svg+xml");
+ }
+
+ ///
+ /// 根据文件扩展名获取图片 Content-Type
+ ///
+ private string GetImageContentType(string fileName)
+ {
+ if (string.IsNullOrWhiteSpace(fileName))
+ {
+ return "image/jpeg";
+ }
+ var extension = Path.GetExtension(fileName).ToLowerInvariant();
+ return extension switch
+ {
+ ".jpg" or ".jpeg" => "image/jpeg",
+ ".png" => "image/png",
+ ".gif" => "image/gif",
+ ".webp" => "image/webp",
+ ".svg" => "image/svg+xml",
+ ".bmp" => "image/bmp",
+ _ => "image/jpeg"
+ };
+ }
+
///
/// 播放视频
///
diff --git a/app/src/App.vue b/app/src/App.vue
index 200fbf1..7b80e14 100644
--- a/app/src/App.vue
+++ b/app/src/App.vue
@@ -179,15 +179,29 @@ body {
}
}
-// 抖音授权 Cookie 编辑用 full-modal(顶部对齐的宽 modal)
+// 抖音授权 Cookie 编辑用 full-modal(全屏抽屉式,高度 100vh、footer 常驻底部)
.full-modal .ant-modal {
top: 0 !important;
padding-bottom: 0 !important;
- margin-bottom: 0;
+ margin: 0 !important;
+ max-width: 100%;
+ height: 100vh;
+}
+.full-modal .ant-modal-content {
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ border-radius: 0;
+}
+.full-modal .ant-modal-header,
+.full-modal .ant-modal-footer {
+ flex-shrink: 0;
}
-// full-modal 没有 top 留白,body max-height 减去的常量更小
.full-modal .ant-modal-body {
- max-height: calc(100vh - 110px) !important;
+ flex: 1 1 auto;
+ min-height: 0;
+ max-height: none !important;
+ overflow-y: auto !important;
}
// 登录页整个界面本身就是一个 Modal(LoginModal.vue),必须恢复 antd 默认布局,不能被上面的全局规则改
diff --git a/app/src/pages/cok/CookieTable.vue b/app/src/pages/cok/CookieTable.vue
index 722ecde..dea74ad 100644
--- a/app/src/pages/cok/CookieTable.vue
+++ b/app/src/pages/cok/CookieTable.vue
@@ -713,26 +713,8 @@ const switchdownCollect = (e: any) => {