1、nfo刮削文件优化,修复演员信息(视频作者),建议emby媒体库选影视,或者混合类型-右上角有一个兼容旧数据的按钮一键重置所有刮削文件

2、图文视频 以及动态视频,遇到因版权原因无法下载音频时,需要额外的音频文件,现在放到mp3目录了,docker-compose增加一个映射路径即可 例如: - /vol2/1000/media/dysync/mp3:/app/mp3
3、增加动态视频合成配置(不建议保留原视频,没有额外刮削的,因为有些视频是几十个很短的动图一样的视频拼接成的,保留原视频emby会出现一堆没有封面的视频)
4、容器重启后,手机端会连续跳转登录页很多次的bug修复
5、关注博主保存文件夹路径长度增加,允许_
6、批量永久删除选中的视频(不再下载,慎重)
7、看板页面 隐藏一个功能:双击作者头像,可以删除作者所有视频(永久删除,不再下载,慎重)
8、手机端 视频播放优化
This commit is contained in:
jianzhichu
2026-01-15 00:41:47 +08:00
parent 8da82889d1
commit ff80250012
22 changed files with 795 additions and 543 deletions
+52 -109
View File
@@ -148,7 +148,7 @@ namespace dy.net.Controllers
[AllowAnonymous] [AllowAnonymous]
public async Task<IActionResult> IsInit() public async Task<IActionResult> IsInit()
{ {
var init= await dyCookieService.IsInit(); var init = await dyCookieService.IsInit();
return ApiResult.Success(init); return ApiResult.Success(init);
} }
@@ -179,7 +179,7 @@ namespace dy.net.Controllers
{ {
dyUserCookies.Id = IdGener.GetLong().ToString(); dyUserCookies.Id = IdGener.GetLong().ToString();
if(string.IsNullOrWhiteSpace(dyUserCookies.SavePath)) if (string.IsNullOrWhiteSpace(dyUserCookies.SavePath))
{ {
return ApiResult.Fail("收藏存储路径不能为空"); return ApiResult.Fail("收藏存储路径不能为空");
} }
@@ -221,10 +221,10 @@ namespace dy.net.Controllers
public async Task<IActionResult> GetAppPort() public async Task<IActionResult> GetAppPort()
{ {
//return ApiResult.Success(10105); //return ApiResult.Success(10105);
return ApiResult.Success(string.IsNullOrWhiteSpace(Appsettings.Get("appPort"))?10101: Convert.ToInt32(Appsettings.Get("appPort"))); return ApiResult.Success(string.IsNullOrWhiteSpace(Appsettings.Get("appPort")) ? 10101 : Convert.ToInt32(Appsettings.Get("appPort")));
} }
/// <summary> /// <summary>
@@ -292,7 +292,7 @@ namespace dy.net.Controllers
var data = commonService.GetConfig(); var data = commonService.GetConfig();
return ApiResult.Success(data); return ApiResult.Success(data);
} }
[HttpPost("UpdateConfig")] [HttpPost("UpdateConfig")]
public async Task<IActionResult> UpdateConfig(AppConfig config) public async Task<IActionResult> UpdateConfig(AppConfig config)
@@ -350,15 +350,15 @@ namespace dy.net.Controllers
{ {
var deploy = Appsettings.Get("deploy"); var deploy = Appsettings.Get("deploy");
if(string.IsNullOrWhiteSpace(deploy)) if (string.IsNullOrWhiteSpace(deploy))
{ {
return await GetDockerTagVersions(); return await GetDockerTagVersions();
} }
else else
{ {
if(deploy== "fn")//飞牛 if (deploy == "fn")//飞牛
{ {
return ApiResult.Success(new List<string> { "beta_"+Appsettings.Get("fnVersion") }); return ApiResult.Success(new List<string> { "beta_" + Appsettings.Get("fnVersion") });
} }
else else
{ {
@@ -366,7 +366,7 @@ namespace dy.net.Controllers
} }
} }
} }
private static async Task<IActionResult> GetDockerTagVersions() private static async Task<IActionResult> GetDockerTagVersions()
@@ -387,107 +387,50 @@ namespace dy.net.Controllers
} }
} }
/// <summary>
/// 查询mp3目录下有没有音频文件
/// </summary>
/// <returns></returns>
[HttpGet("mp3List")]
public async Task<IActionResult> GetExistMps()
{
var path = Path.Combine(AppContext.BaseDirectory, "mp3");
if (Directory.Exists(path))
{
var audioFiles = Directory.GetFiles(path);
///// <summary> var fileNames = audioFiles.Select(f => new {filename= Path.GetFileName(f) }).Where(x=>x.filename!= "silent_10.mp3").ToList();
///// 上传音频文件接口 return ApiResult.Success(fileNames);
///// </summary> }
///// <param name="file">要上传的音频文件</param> else
///// <returns>上传结果</returns> {
//[HttpPost("UploadAudio")] return ApiResult.Fail("没有找到默认音频文件");
//public IActionResult UploadAudio(IFormFile file) }
//{ }
// // 1. 验证文件是否为空
// if (file == null || file.Length == 0)
// {
// return BadRequest(new { success = false, message = "请选择要上传的音频文件" });
// }
// try
// {
// // 2. 验证文件大小
// if (file.Length > _maxFileSize)
// {
// return BadRequest(new { success = false, message = $"文件大小超过限制(最大允许 {_maxFileSize / 1024 / 1024}MB" });
// }
// // 3. 获取文件扩展名并验证
// var fileExtension = Path.GetExtension(file.FileName).ToLower();
// if (!_allowedAudioExtensions.Contains(fileExtension))
// {
// return BadRequest(new
// {
// success = false,
// message = $"不支持的音频格式,仅允许:{string.Join(", ", _allowedAudioExtensions)}"
// });
// }
// // 4. 验证 MIME 类型(可选但推荐,防止扩展名伪造)
// var contentType = file.ContentType.ToLower();
// if (!_allowedAudioMimeTypes.Contains(contentType))
// {
// return BadRequest(new
// {
// success = false,
// message = "文件类型验证失败,请上传合法的音频文件"
// });
// }
// //先删除
// var uploadMp3 = Directory.GetFiles(Path.Combine(AppContext.BaseDirectory, "mp3"))
// .Where(filePath => Path.GetFileNameWithoutExtension(filePath) != "silent_10")
// .FirstOrDefault();
// if (!string.IsNullOrWhiteSpace(uploadMp3) && System.IO.File.Exists(uploadMp3))
// {
// System.IO.File.Delete(uploadMp3);
// }
// // 5. 生成唯一文件名(避免重复)
// var uniqueFileName = $"dysync_default{fileExtension}";
// // 6. 定义文件保存路径(建议配置在 appsettings.json 中,这里简化处理)
// var uploadPath = Path.Combine(AppContext.BaseDirectory, "mp3");
// // 确保目录存在
// if (!Directory.Exists(uploadPath))
// {
// Directory.CreateDirectory(uploadPath);
// }
// // 7. 保存文件
// var filePath = Path.Combine(uploadPath, uniqueFileName);
// if (System.IO.File.Exists(filePath))
// {
// System.IO.File.Delete(filePath);
// }
// using (var stream = new FileStream(filePath, FileMode.Create))
// {
// file.CopyTo(stream);
// }
// // 8. 返回成功结果(可根据需求返回文件路径/URL 等)
// return ApiResult.Success(new { fileName = uniqueFileName, filePath });
// }
// catch (Exception ex)
// {
// // 捕获异常并返回错误信息
// return ApiResult.Fail(ex.Message);
// }
//}
//[AllowAnonymous]
//[HttpGet("defaudio")]
//public async Task<IActionResult> GetDefaultAudioUrl()
//{
// var uploadMp3 = Directory.GetFiles(Path.Combine(AppContext.BaseDirectory, "mp3"))
// .Where(filePath => Path.GetFileNameWithoutExtension(filePath) != "silent_10")
// .FirstOrDefault();
// if (!string.IsNullOrWhiteSpace(uploadMp3) && System.IO.File.Exists(uploadMp3))
// {
// return File(System.IO.File.ReadAllBytes(uploadMp3), "application/octet-stream", Path.GetFileName(uploadMp3));
// }
// return ApiResult.Fail("未上传音频");
//}
/// <summary>
/// 播放音频流
/// </summary>
/// <param name="name"></param>
/// <returns></returns>
[AllowAnonymous]
[HttpGet("getmp3")]
public async Task<IActionResult> GetMp3([FromQuery] string name)
{
var path = Path.Combine(AppContext.BaseDirectory, "mp3", name);
if (System.IO.File.Exists(path))
{
//返回mp3文件流
var fileStream = new FileStream(path, FileMode.Open, FileAccess.Read);
return new FileStreamResult(fileStream, "audio/mpeg")
{
FileDownloadName = name
};
}
else
{
return ApiResult.Fail("文件不存在");
}
}
} }
} }
+55 -24
View File
@@ -5,6 +5,7 @@ using dy.net.utils;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System; using System;
using System.Security.Cryptography;
namespace dy.net.Controllers namespace dy.net.Controllers
{ {
@@ -174,7 +175,6 @@ namespace dy.net.Controllers
/// </summary> /// </summary>
/// <param name="dto"></param> /// <param name="dto"></param>
/// <returns></returns> /// <returns></returns>
[HttpPost("redown")] [HttpPost("redown")]
public async Task<IActionResult> ReDownload(ReDownViedoDto dto) public async Task<IActionResult> ReDownload(ReDownViedoDto dto)
{ {
@@ -195,6 +195,27 @@ namespace dy.net.Controllers
} }
} }
} }
/// <summary>
/// 批量删除
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPost("vdelete/batch")]
public async Task<IActionResult> BathRealDelete(ReDownViedoDto dto)
{
var result = await douyinVideoService.RealDeleteVideos(dto.Ids);
if (result)
{
return ApiResult.Success(true);
}
else
{
return ApiResult.Fail("错误");
}
}
/// <summary> /// <summary>
/// 删除视频-不再下载 /// 删除视频-不再下载
/// </summary> /// </summary>
@@ -209,34 +230,15 @@ namespace dy.net.Controllers
} }
else else
{ {
var video = await douyinVideoService.GetById(vid); var res = await douyinVideoService.RealDeleteVideos(new List<string> { vid });
if (video == null) if (res)
{ {
return ApiResult.Fail("请求失败"); return ApiResult.Success("删除成功");
} }
else else
{ {
var result = await douyinVideoService.ReDownloadViedoAsync(new ReDownViedoDto { Ids = new List<string> { vid } }); return ApiResult.Fail("删除失败");
if (result)
{
//加入删除逻辑
await douyinCommonService.AddDeleteVideo(new DouyinVideoDelete
{
ViedoId = video.AwemeId,
VideoTitle = video.VideoTitle,
VideoSavePath = video.VideoSavePath
});
Serilog.Log.Debug($"前面的日志,你错了,这条视频是永久删除..哈哈--{video.VideoTitle}");
return ApiResult.Success();
}
else
{
return ApiResult.Fail();
}
} }
} }
} }
@@ -249,6 +251,35 @@ namespace dy.net.Controllers
{ {
return ApiResult.Success(await douyinCommonService.GetDouyinDeleteVideos()); return ApiResult.Success(await douyinCommonService.GetDouyinDeleteVideos());
} }
/// <summary>
/// 根据博主id删除博主所有视频
/// </summary>
/// <param name="uperUid"></param>
/// <returns></returns>
[HttpGet("vdelete/byauthor/{uperUid}")]
public async Task<IActionResult> DeleteByAuthor([FromRoute] string uperUid)
{
var videos = await douyinVideoService.GetByAuthorId(uperUid);
if (videos != null && videos.Any())
{
var res = await douyinVideoService.RealDeleteVideos(videos.Select(x => x.Id).ToList());
if (res)
{
return ApiResult.Success("删除成功");
}
else
{
return ApiResult.Fail("删除失败");
}
}
return ApiResult.Fail("未找到该博主视频");
}
//private async Task<(bool flowControl, IActionResult value)> BatchDeleteVideos(List<DouyinVideo> videos)
//{
// return (flowControl: true, value: null);
//}
/// <summary> /// <summary>
/// 查询最新N条数据 /// 查询最新N条数据
@@ -3,7 +3,7 @@
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\</_PublishTargetUrl> <_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\</_PublishTargetUrl>
<History>True|2026-01-13T14:34:15.2372411Z||;True|2026-01-13T22:26:46.4522947+08:00||;True|2026-01-13T22:15:56.9735179+08:00||;True|2026-01-13T22:15:51.8250677+08:00||;False|2026-01-13T22:15:46.6608690+08:00||;True|2026-01-13T22:03:06.4683237+08:00||;False|2026-01-13T22:03:01.2036027+08:00||;True|2026-01-13T21:36:05.8776209+08:00||;True|2026-01-13T21:33:02.0766289+08:00||;False|2026-01-13T21:32:57.1976141+08:00||;True|2026-01-13T19:14:35.2519439+08:00||;True|2026-01-13T15:07:50.0836745+08:00||;False|2026-01-13T15:06:35.5929685+08:00||;True|2026-01-13T10:21:41.6012776+08:00||;True|2026-01-13T09:57:25.4788847+08:00||;True|2026-01-13T09:53:14.4900360+08:00||;True|2026-01-13T09:23:53.6465295+08:00||;True|2026-01-12T22:04:14.5886955+08:00||;False|2026-01-12T22:04:08.2008101+08:00||;True|2026-01-12T21:53:46.9947176+08:00||;True|2026-01-12T21:11:15.8358024+08:00||;True|2026-01-12T21:09:51.8663228+08:00||;True|2026-01-11T21:25:07.5052845+08:00||;False|2026-01-11T21:24:27.5744557+08:00||;True|2026-01-11T19:59:15.4734611+08:00||;False|2026-01-11T19:59:04.9543339+08:00||;True|2026-01-11T18:51:30.6649269+08:00||;True|2026-01-08T21:07:57.5094466+08:00||;True|2026-01-08T14:51:18.2266231+08:00||;True|2026-01-08T14:31:55.1137120+08:00||;True|2026-01-08T00:16:24.4451490+08:00||;True|2026-01-08T00:14:20.7430793+08:00||;True|2026-01-08T00:07:46.7228194+08:00||;True|2026-01-07T23:52:13.3290082+08:00||;True|2026-01-07T23:49:28.4838650+08:00||;True|2026-01-07T23:47:45.1936189+08:00||;True|2026-01-07T23:35:09.7818611+08:00||;True|2026-01-07T23:20:43.7462863+08:00||;True|2026-01-07T23:04:39.5140429+08:00||;False|2026-01-07T23:04:36.5367611+08:00||;True|2026-01-07T22:53:14.6013449+08:00||;True|2026-01-07T22:49:59.9549006+08:00||;True|2026-01-07T22:36:40.0254856+08:00||;False|2026-01-07T22:36:28.8275903+08:00||;True|2026-01-07T21:59:51.4355171+08:00||;True|2026-01-03T21:38:51.4307034+08:00||;True|2026-01-02T19:09:16.9668906+08:00||;False|2026-01-02T19:09:11.7496369+08:00||;True|2026-01-02T15:42:08.2215697+08:00||;True|2026-01-02T09:46:56.1654861+08:00||;True|2026-01-02T09:35:28.0211225+08:00||;True|2026-01-01T23:07:01.9022045+08:00||;False|2026-01-01T23:06:56.0537216+08:00||;True|2026-01-01T22:16:10.2974067+08:00||;True|2026-01-01T22:16:06.2123787+08:00||;False|2026-01-01T22:15:33.3626979+08:00||;True|2026-01-01T22:01:30.7161900+08:00||;True|2026-01-01T21:10:19.3664263+08:00||;True|2026-01-01T21:09:38.6071080+08:00||;True|2025-12-30T11:45:54.5543034+08:00||;True|2025-12-30T09:19:08.3178124+08:00||;True|2025-12-29T18:57:29.7032246+08:00||;True|2025-12-27T14:52:24.4780776+08:00||;False|2025-12-27T14:52:19.5635794+08:00||;True|2025-12-27T14:48:01.6252748+08:00||;False|2025-12-27T14:47:55.7976192+08:00||;True|2025-12-27T14:38:23.9723838+08:00||;True|2025-12-27T13:00:29.8583858+08:00||;True|2025-12-26T22:18:42.4015637+08:00||;True|2025-12-26T22:10:58.5274572+08:00||;True|2025-12-26T22:06:26.3129600+08:00||;True|2025-12-26T22:03:55.6718618+08:00||;False|2025-12-26T22:03:48.3809954+08:00||;True|2025-12-26T22:02:33.1840390+08:00||;True|2025-12-26T22:01:16.1660100+08:00||;True|2025-12-25T00:50:26.1116465+08:00||;True|2025-12-25T00:48:27.3087708+08:00||;True|2025-12-25T00:47:38.4835720+08:00||;</History> <History>True|2026-01-14T16:10:42.7288439Z||;True|2026-01-15T00:06:28.4241682+08:00||;True|2026-01-13T22:34:15.2372411+08:00||;True|2026-01-13T22:26:46.4522947+08:00||;True|2026-01-13T22:15:56.9735179+08:00||;True|2026-01-13T22:15:51.8250677+08:00||;False|2026-01-13T22:15:46.6608690+08:00||;True|2026-01-13T22:03:06.4683237+08:00||;False|2026-01-13T22:03:01.2036027+08:00||;True|2026-01-13T21:36:05.8776209+08:00||;True|2026-01-13T21:33:02.0766289+08:00||;False|2026-01-13T21:32:57.1976141+08:00||;True|2026-01-13T19:14:35.2519439+08:00||;True|2026-01-13T15:07:50.0836745+08:00||;False|2026-01-13T15:06:35.5929685+08:00||;True|2026-01-13T10:21:41.6012776+08:00||;True|2026-01-13T09:57:25.4788847+08:00||;True|2026-01-13T09:53:14.4900360+08:00||;True|2026-01-13T09:23:53.6465295+08:00||;True|2026-01-12T22:04:14.5886955+08:00||;False|2026-01-12T22:04:08.2008101+08:00||;True|2026-01-12T21:53:46.9947176+08:00||;True|2026-01-12T21:11:15.8358024+08:00||;True|2026-01-12T21:09:51.8663228+08:00||;True|2026-01-11T21:25:07.5052845+08:00||;False|2026-01-11T21:24:27.5744557+08:00||;True|2026-01-11T19:59:15.4734611+08:00||;False|2026-01-11T19:59:04.9543339+08:00||;True|2026-01-11T18:51:30.6649269+08:00||;True|2026-01-08T21:07:57.5094466+08:00||;True|2026-01-08T14:51:18.2266231+08:00||;True|2026-01-08T14:31:55.1137120+08:00||;True|2026-01-08T00:16:24.4451490+08:00||;True|2026-01-08T00:14:20.7430793+08:00||;True|2026-01-08T00:07:46.7228194+08:00||;True|2026-01-07T23:52:13.3290082+08:00||;True|2026-01-07T23:49:28.4838650+08:00||;True|2026-01-07T23:47:45.1936189+08:00||;True|2026-01-07T23:35:09.7818611+08:00||;True|2026-01-07T23:20:43.7462863+08:00||;True|2026-01-07T23:04:39.5140429+08:00||;False|2026-01-07T23:04:36.5367611+08:00||;True|2026-01-07T22:53:14.6013449+08:00||;True|2026-01-07T22:49:59.9549006+08:00||;True|2026-01-07T22:36:40.0254856+08:00||;False|2026-01-07T22:36:28.8275903+08:00||;True|2026-01-07T21:59:51.4355171+08:00||;True|2026-01-03T21:38:51.4307034+08:00||;True|2026-01-02T19:09:16.9668906+08:00||;False|2026-01-02T19:09:11.7496369+08:00||;True|2026-01-02T15:42:08.2215697+08:00||;True|2026-01-02T09:46:56.1654861+08:00||;True|2026-01-02T09:35:28.0211225+08:00||;True|2026-01-01T23:07:01.9022045+08:00||;False|2026-01-01T23:06:56.0537216+08:00||;True|2026-01-01T22:16:10.2974067+08:00||;True|2026-01-01T22:16:06.2123787+08:00||;False|2026-01-01T22:15:33.3626979+08:00||;True|2026-01-01T22:01:30.7161900+08:00||;True|2026-01-01T21:10:19.3664263+08:00||;True|2026-01-01T21:09:38.6071080+08:00||;True|2025-12-30T11:45:54.5543034+08:00||;True|2025-12-30T09:19:08.3178124+08:00||;True|2025-12-29T18:57:29.7032246+08:00||;True|2025-12-27T14:52:24.4780776+08:00||;False|2025-12-27T14:52:19.5635794+08:00||;True|2025-12-27T14:48:01.6252748+08:00||;False|2025-12-27T14:47:55.7976192+08:00||;True|2025-12-27T14:38:23.9723838+08:00||;True|2025-12-27T13:00:29.8583858+08:00||;True|2025-12-26T22:18:42.4015637+08:00||;True|2025-12-26T22:10:58.5274572+08:00||;True|2025-12-26T22:06:26.3129600+08:00||;True|2025-12-26T22:03:55.6718618+08:00||;False|2025-12-26T22:03:48.3809954+08:00||;True|2025-12-26T22:02:33.1840390+08:00||;True|2025-12-26T22:01:16.1660100+08:00||;True|2025-12-25T00:50:26.1116465+08:00||;True|2025-12-25T00:48:27.3087708+08:00||;True|2025-12-25T00:47:38.4835720+08:00||;</History>
<LastFailureDetails /> <LastFailureDetails />
</PropertyGroup> </PropertyGroup>
</Project> </Project>
+1 -1
View File
@@ -5,7 +5,7 @@ https://go.microsoft.com/fwlink/?LinkID=208121.
<Project> <Project>
<PropertyGroup> <PropertyGroup>
<_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\</_PublishTargetUrl> <_PublishTargetUrl>E:\code\dysync\bin\Release\net6.0\publish\</_PublishTargetUrl>
<History>True|2026-01-13T14:27:04.3807307Z||;True|2026-01-08T14:31:13.4473659+08:00||;True|2026-01-08T00:19:20.8948701+08:00||;True|2026-01-07T21:59:04.8702022+08:00||;True|2026-01-03T21:42:41.3864639+08:00||;False|2026-01-03T21:42:26.7041325+08:00||;True|2026-01-02T09:32:11.1396877+08:00||;True|2026-01-01T23:30:38.0205783+08:00||;True|2026-01-01T23:29:35.2877898+08:00||;False|2026-01-01T23:27:39.5930556+08:00||;False|2026-01-01T23:24:52.0996222+08:00||;False|2026-01-01T23:24:09.0444840+08:00||;False|2026-01-01T23:24:06.1610789+08:00||;False|2026-01-01T23:24:03.3178410+08:00||;False|2026-01-01T23:22:56.2224992+08:00||;False|2026-01-01T23:22:47.8246988+08:00||;False|2026-01-01T23:22:43.2890820+08:00||;True|2026-01-01T23:21:03.6027351+08:00||;True|2026-01-01T23:07:57.5938094+08:00||;True|2025-12-29T18:55:45.8262096+08:00||;True|2025-12-28T00:21:04.1648410+08:00||;True|2025-12-28T00:18:49.2137559+08:00||;True|2025-12-28T00:14:18.1456892+08:00||;True|2025-12-28T00:11:06.6645248+08:00||;False|2025-12-28T00:10:49.4906020+08:00||;True|2025-12-27T23:51:57.6426264+08:00||;True|2025-12-27T23:40:08.4783210+08:00||;True|2025-12-27T14:59:32.0605406+08:00||;False|2025-12-27T14:59:21.2076690+08:00||;True|2025-12-27T12:58:06.9934830+08:00||;True|2025-12-27T12:51:45.3402320+08:00||;True|2025-12-27T12:45:49.9795040+08:00||;True|2025-12-27T12:42:13.9395619+08:00||;True|2025-12-27T11:32:35.8851241+08:00||;True|2025-12-27T11:32:11.1199261+08:00||;True|2025-12-27T11:31:34.3939619+08:00||;True|2025-12-26T22:39:54.4903900+08:00||;False|2025-12-26T22:39:38.9956198+08:00||;True|2025-12-26T13:29:16.3817938+08:00||;True|2025-12-26T13:13:27.1756564+08:00||;True|2025-12-26T13:12:27.5681845+08:00||;True|2025-12-25T21:53:39.9851108+08:00||;True|2025-12-25T21:14:06.5238213+08:00||;True|2025-12-25T14:05:40.5153425+08:00||;True|2025-12-25T00:52:42.0632072+08:00||;False|2025-12-25T00:52:36.5033522+08:00||;True|2025-12-24T20:47:03.4244585+08:00||;True|2025-12-24T20:38:49.1905853+08:00||;True|2025-12-24T20:38:18.7336590+08:00||;True|2025-12-24T20:33:27.1126773+08:00||;True|2025-12-24T20:32:29.2799273+08:00||;True|2025-12-23T20:55:43.6793723+08:00||;True|2025-12-23T20:54:55.9960612+08:00||;True|2025-12-21T13:23:11.0272294+08:00||;True|2025-12-21T13:22:56.8339811+08:00||;False|2025-12-21T13:22:50.2395530+08:00||;True|2025-12-21T13:11:36.6129339+08:00||;True|2025-12-21T12:15:18.4086793+08:00||;True|2025-12-21T12:15:11.4448190+08:00||;True|2025-12-20T14:05:36.6224332+08:00||;False|2025-12-20T14:05:28.6359828+08:00||;True|2025-12-20T13:58:30.1641630+08:00||;True|2025-12-20T13:55:16.1759645+08:00||;True|2025-12-20T12:16:22.5224516+08:00||;True|2025-12-19T09:50:16.9517676+08:00||;True|2025-12-19T09:49:47.0871734+08:00||;False|2025-12-19T09:49:36.7638757+08:00||;True|2025-12-19T09:35:13.2489248+08:00||;True|2025-12-14T19:09:59.9031104+08:00||;True|2025-12-13T21:21:10.0022707+08:00||;True|2025-12-11T22:07:16.6229994+08:00||;True|2025-12-11T22:05:56.7363206+08:00||;True|2025-12-11T22:01:07.5862406+08:00||;True|2025-12-11T22:00:43.5796594+08:00||;True|2025-12-08T21:20:37.8369271+08:00||;False|2025-12-08T21:20:21.1646252+08:00||;True|2025-12-08T20:51:50.6845619+08:00||;True|2025-12-08T16:43:45.2425324+08:00||;False|2025-12-08T16:42:23.0308163+08:00||;True|2025-12-08T16:41:52.4103868+08:00||;True|2025-12-08T16:41:49.8972006+08:00||;False|2025-12-08T16:41:39.5343442+08:00||;True|2025-12-08T12:40:19.2430740+08:00||;True|2025-12-08T12:02:30.6005542+08:00||;True|2025-12-08T11:34:29.6673185+08:00||;False|2025-12-08T11:30:56.2857042+08:00||;True|2025-12-08T10:52:43.7427594+08:00||;True|2025-12-08T07:05:03.2845531+08:00||;True|2025-12-07T23:02:10.0037815+08:00||;True|2025-12-07T22:48:25.7859648+08:00||;True|2025-12-07T20:49:13.6683281+08:00||;True|2025-12-07T08:47:30.1236366+08:00||;True|2025-12-06T13:14:32.4410202+08:00||;True|2025-12-06T13:07:29.7182102+08:00||;True|2025-12-06T13:05:46.2855366+08:00||;False|2025-12-06T13:05:40.3550904+08:00||;True|2025-12-06T13:03:47.8773880+08:00||;True|2025-12-06T13:03:28.9521030+08:00||;True|2025-12-05T23:11:51.5026151+08:00||;True|2025-12-05T12:46:26.1415079+08:00||;</History> <History>True|2026-01-14T16:36:39.6208221Z||;True|2026-01-15T00:36:29.1250549+08:00||;False|2026-01-15T00:36:18.2658521+08:00||;True|2026-01-13T22:27:04.3807307+08:00||;True|2026-01-08T14:31:13.4473659+08:00||;True|2026-01-08T00:19:20.8948701+08:00||;True|2026-01-07T21:59:04.8702022+08:00||;True|2026-01-03T21:42:41.3864639+08:00||;False|2026-01-03T21:42:26.7041325+08:00||;True|2026-01-02T09:32:11.1396877+08:00||;True|2026-01-01T23:30:38.0205783+08:00||;True|2026-01-01T23:29:35.2877898+08:00||;False|2026-01-01T23:27:39.5930556+08:00||;False|2026-01-01T23:24:52.0996222+08:00||;False|2026-01-01T23:24:09.0444840+08:00||;False|2026-01-01T23:24:06.1610789+08:00||;False|2026-01-01T23:24:03.3178410+08:00||;False|2026-01-01T23:22:56.2224992+08:00||;False|2026-01-01T23:22:47.8246988+08:00||;False|2026-01-01T23:22:43.2890820+08:00||;True|2026-01-01T23:21:03.6027351+08:00||;True|2026-01-01T23:07:57.5938094+08:00||;True|2025-12-29T18:55:45.8262096+08:00||;True|2025-12-28T00:21:04.1648410+08:00||;True|2025-12-28T00:18:49.2137559+08:00||;True|2025-12-28T00:14:18.1456892+08:00||;True|2025-12-28T00:11:06.6645248+08:00||;False|2025-12-28T00:10:49.4906020+08:00||;True|2025-12-27T23:51:57.6426264+08:00||;True|2025-12-27T23:40:08.4783210+08:00||;True|2025-12-27T14:59:32.0605406+08:00||;False|2025-12-27T14:59:21.2076690+08:00||;True|2025-12-27T12:58:06.9934830+08:00||;True|2025-12-27T12:51:45.3402320+08:00||;True|2025-12-27T12:45:49.9795040+08:00||;True|2025-12-27T12:42:13.9395619+08:00||;True|2025-12-27T11:32:35.8851241+08:00||;True|2025-12-27T11:32:11.1199261+08:00||;True|2025-12-27T11:31:34.3939619+08:00||;True|2025-12-26T22:39:54.4903900+08:00||;False|2025-12-26T22:39:38.9956198+08:00||;True|2025-12-26T13:29:16.3817938+08:00||;True|2025-12-26T13:13:27.1756564+08:00||;True|2025-12-26T13:12:27.5681845+08:00||;True|2025-12-25T21:53:39.9851108+08:00||;True|2025-12-25T21:14:06.5238213+08:00||;True|2025-12-25T14:05:40.5153425+08:00||;True|2025-12-25T00:52:42.0632072+08:00||;False|2025-12-25T00:52:36.5033522+08:00||;True|2025-12-24T20:47:03.4244585+08:00||;True|2025-12-24T20:38:49.1905853+08:00||;True|2025-12-24T20:38:18.7336590+08:00||;True|2025-12-24T20:33:27.1126773+08:00||;True|2025-12-24T20:32:29.2799273+08:00||;True|2025-12-23T20:55:43.6793723+08:00||;True|2025-12-23T20:54:55.9960612+08:00||;True|2025-12-21T13:23:11.0272294+08:00||;True|2025-12-21T13:22:56.8339811+08:00||;False|2025-12-21T13:22:50.2395530+08:00||;True|2025-12-21T13:11:36.6129339+08:00||;True|2025-12-21T12:15:18.4086793+08:00||;True|2025-12-21T12:15:11.4448190+08:00||;True|2025-12-20T14:05:36.6224332+08:00||;False|2025-12-20T14:05:28.6359828+08:00||;True|2025-12-20T13:58:30.1641630+08:00||;True|2025-12-20T13:55:16.1759645+08:00||;True|2025-12-20T12:16:22.5224516+08:00||;True|2025-12-19T09:50:16.9517676+08:00||;True|2025-12-19T09:49:47.0871734+08:00||;False|2025-12-19T09:49:36.7638757+08:00||;True|2025-12-19T09:35:13.2489248+08:00||;True|2025-12-14T19:09:59.9031104+08:00||;True|2025-12-13T21:21:10.0022707+08:00||;True|2025-12-11T22:07:16.6229994+08:00||;True|2025-12-11T22:05:56.7363206+08:00||;True|2025-12-11T22:01:07.5862406+08:00||;True|2025-12-11T22:00:43.5796594+08:00||;True|2025-12-08T21:20:37.8369271+08:00||;False|2025-12-08T21:20:21.1646252+08:00||;True|2025-12-08T20:51:50.6845619+08:00||;True|2025-12-08T16:43:45.2425324+08:00||;False|2025-12-08T16:42:23.0308163+08:00||;True|2025-12-08T16:41:52.4103868+08:00||;True|2025-12-08T16:41:49.8972006+08:00||;False|2025-12-08T16:41:39.5343442+08:00||;True|2025-12-08T12:40:19.2430740+08:00||;True|2025-12-08T12:02:30.6005542+08:00||;True|2025-12-08T11:34:29.6673185+08:00||;False|2025-12-08T11:30:56.2857042+08:00||;True|2025-12-08T10:52:43.7427594+08:00||;True|2025-12-08T07:05:03.2845531+08:00||;True|2025-12-07T23:02:10.0037815+08:00||;True|2025-12-07T22:48:25.7859648+08:00||;True|2025-12-07T20:49:13.6683281+08:00||;True|2025-12-07T08:47:30.1236366+08:00||;True|2025-12-06T13:14:32.4410202+08:00||;True|2025-12-06T13:07:29.7182102+08:00||;True|2025-12-06T13:05:46.2855366+08:00||;False|2025-12-06T13:05:40.3550904+08:00||;True|2025-12-06T13:03:47.8773880+08:00||;</History>
<LastFailureDetails /> <LastFailureDetails />
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
+2
View File
@@ -137,6 +137,8 @@ services:
volumes: volumes:
# 基础路径映射 # 基础路径映射
- /vol2/1000/media/dysync/db:/app/db # 数据库目录(持久化配置和同步记录) - /vol2/1000/media/dysync/db:/app/db # 数据库目录(持久化配置和同步记录)
# 默认音频目录(用于图文和动态视频合成时遇到因版权无法下载的音频时用作合成视频所需要的音频)
- /vol2/1000/media/dysync/mp3:/app/mp3
- /vol2/1000/media/dysync/dy1/coll:/app/collect # 个人收藏视频目录 - /vol2/1000/media/dysync/dy1/coll:/app/collect # 个人收藏视频目录
- /vol2/1000/media/dysync/dy1/fav:/app/favorite # 个人喜欢视频目录 - /vol2/1000/media/dysync/dy1/fav:/app/favorite # 个人喜欢视频目录
- /vol2/1000/media/dysync/dy1/imgv:/app/images # 图文视频目录 - /vol2/1000/media/dysync/dy1/imgv:/app/images # 图文视频目录
+126 -107
View File
@@ -4,114 +4,140 @@
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<link rel="icon" type="image/png" href="/logo.png" /> <link rel="icon" type="image/png" href="/logo.png" />
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0" /> -->
<meta name="viewport" <meta name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"> content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>抖小云</title> <title>抖小云</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html,
body {
background-color: #ffffff;
height: 100vh;
overflow: hidden;
font-family: "Microsoft YaHei", sans-serif;
}
/* ######### 核心修改:区分 #stepin-app 的两个状态 ######### */
/* 1. 加载阶段:#stepin-app 仅包含加载动画(或为空),应用 flex 居中,保留原有样式 */
#stepin-app {
/* 加载阶段默认样式:flex 居中,保留呼吸圆环效果 */
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
gap: 35px;
}
/* 2. Vue 挂载后:#stepin-app 非空(包含 Vue 组件),覆盖为普通块级元素,解除约束 */
#stepin-app:not(:has(.breath-ring)) {
/* 核心:取消 flex 布局,改为普通块级元素 */
display: block !important;
/* 覆盖原有 flex 相关属性,解除居中约束 */
align-items: unset !important;
justify-content: unset !important;
/* 确保页面撑满横向宽度 */
width: 100% !important;
/* 保留高度,确保 Vue 页面正常占满视口 */
height: 100% !important;
/* 移除 gap,避免影响 Vue 页面布局 */
gap: 0 !important;
/* 恢复页面滚动功能,解除 overflow: hidden 的限制 */
overflow: auto !important;
}
/* 核心呼吸圆环容器(原有样式保留,无修改) */
.breath-ring {
width: 60px;
height: 60px;
position: relative;
}
/* 圆环基础样式(原有样式保留,无修改) */
.breath-ring::before,
.breath-ring::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
border-radius: 50%;
opacity: 0.6;
}
/* 内层圆环:细圆环 + 渐变(原有样式保留,无修改) */
.breath-ring::before {
border: 3px solid transparent;
border-top-color: #1890ff;
border-right-color: #722ed1;
animation: ringRotate 1.5s linear infinite;
}
/* 外层圆环:呼吸扩散动画(原有样式保留,无修改) */
.breath-ring::after {
border: 2px solid #1890ff;
animation: ringBreath 2s ease-in-out infinite;
}
/* 加载文字:极简高级(原有样式保留,无修改) */
.loading-text {
color: #222222;
font-size: 17px;
font-weight: 400;
letter-spacing: 0.5px;
}
.loading-tip {
color: #aaaaaa;
font-size: 13px;
margin-top: -10px;
}
/* 圆环旋转动画(原有样式保留,无修改) */
@keyframes ringRotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
/* 圆环呼吸扩散动画(原有样式保留,无修改) */
@keyframes ringBreath {
0% {
transform: scale(0.8);
opacity: 0.8;
}
50% {
transform: scale(1.2);
opacity: 0.2;
}
100% {
transform: scale(0.8);
opacity: 0.8;
}
}
</style>
</head> </head>
<body> <body>
<div id="stepin-app"> <div id="stepin-app">
<style> <!-- <div class="loading-text">正在加载资源,请稍候...</div> -->
html { <!-- 呼吸圆环加载组件 -->
overflow: hidden; <div class="breath-ring"></div>
} <!-- <div class="loading-tip">首次加载较慢,请耐心等待...</div> -->
body {
height: 100vh;
text-align: center;
align-items: center;
background-color: #011627;
display: flex;
justify-content: center;
overflow: hidden;
}
.loader {
color: rgb(201, 195, 195);
font-family: "Poppins", sans-serif;
font-weight: 500;
font-size: 20px;
-webkit-box-sizing: content-box;
box-sizing: content-box;
height: 40px;
padding: 10px 10px;
display: -webkit-box;
display: -ms-flexbox;
display: flex;
text-align: center;
justify-content: center;
border-radius: 8px;
}
.words {
overflow: hidden;
}
.word {
display: block;
height: 100%;
padding-left: 6px;
color: #ffca29;
animation: spin_4991 4s infinite;
}
@keyframes spin_4991 {
10% {
-webkit-transform: translateY(-105%);
transform: translateY(-105%);
}
25% {
-webkit-transform: translateY(-100%);
transform: translateY(-100%);
}
35% {
-webkit-transform: translateY(-205%);
transform: translateY(-205%);
}
50% {
-webkit-transform: translateY(-200%);
transform: translateY(-200%);
}
60% {
-webkit-transform: translateY(-305%);
transform: translateY(-305%);
}
75% {
-webkit-transform: translateY(-300%);
transform: translateY(-300%);
}
85% {
-webkit-transform: translateY(-405%);
transform: translateY(-405%);
}
100% {
-webkit-transform: translateY(-400%);
transform: translateY(-400%);
}
}
</style>
<!-- <img style="margin-bottom: 8px;" width="64px" src="/vite.svg" /> -->
<div class="loader">
<!-- <img src="/vite.svg"> -->
<div> loading ...</div>
<!-- <div class="words">
<span class="word">buttons</span>
<span class="word">forms</span>
<span class="word">switches</span>
<span class="word">cards</span>
<span class="word">buttons</span>
</div> -->
</div>
<div class="" style="font-size: 15px; color: rgba(249, 244, 244, 0.55)">首次加载可能较慢,请耐心等待...</div>
</div> </div>
<script type="module" src="/src/main.ts"></script> <script type="module" src="/src/main.ts"></script>
<script> <script>
if (!global) { if (!global) {
@@ -120,11 +146,4 @@
</script> </script>
</body> </body>
<!-- <link rel="stylesheet" href="https://cdn.bootcdn.net/ajax/libs/font-awesome/5.15.4/css/all.min.css"> -->
<style>
html {
overflow: hidden;
}
</style>
</html> </html>
+4 -4
View File
@@ -54,9 +54,9 @@
{{ item.uperName }} {{ item.uperName }}
<!-- 非关注小标记 --> <!-- 非关注小标记 -->
<span v-if="item.isNoFollowed" class="no-followed-badge">非关注</span> <span v-if="item.isNoFollowed" class="no-followed-badge">非关注</span>
<!-- 删除按钮仅非关注项显示放在名字+非关注后面 --> <!-- 删除按钮仅非关注项显示放在名字+非关注后面v-if="item.isNoFollowed" -->
<a-button v-if="item.isNoFollowed" type="text" class="delete-btn" @click="(e) => { e.stopPropagation(); handleDeleteItem(item); }" :disabled="item.isSaving" title="删除该非关注博主"> <a-button type="text" class="delete-btn" @click="(e) => { e.stopPropagation(); handleDeleteItem(item); }" :disabled="item.isSaving" title="删除该非关注博主">
<DeleteOutlined /> <close-outlined />
</a-button> </a-button>
</div> </div>
<!-- 签名多行显示高度控制 + 溢出截断 + Tooltip气泡 --> <!-- 签名多行显示高度控制 + 溢出截断 + Tooltip气泡 -->
@@ -842,7 +842,7 @@ const goDouyinUp = (item) => {
height: 24px !important; height: 24px !important;
width: 24px !important; width: 24px !important;
color: #ef4444 !important; color: #ef4444 !important;
border-radius: 4px !important; border-radius: 50% !important;
transition: all 0.2s ease; transition: all 0.2s ease;
vertical-align: middle; vertical-align: middle;
} }
+123 -53
View File
@@ -185,23 +185,24 @@
</div> </div>
</div> </div>
<!-- 新增视频播放弹窗移动端适配 --> <!-- 新增视频播放弹窗全屏适配+暂停显示删除按钮 -->
<div v-if="showVideoPlayer" class="video-modal-mask" @click="closeVideoPlayer"> <div v-if="showVideoPlayer" class="video-modal-mask" @click="closeVideoPlayer">
<div class="video-modal-content" @click.stop> <div class="video-modal-content" @click.stop>
<div class="video-modal-header"> <!-- 移除原有标题栏将关闭按钮直接放在内容容器内实现悬浮 -->
<h3 class="video-title-text"></h3> <button class="video-close-btn floating-close-btn" @click="closeVideoPlayer">
<button class="video-close-btn" @click="closeVideoPlayer"> <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="18" y1="6" x2="6" y2="18"></line> <line x1="6" y1="6" x2="18" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line> </svg>
</svg> </button>
</button>
</div>
<div class="video-modal-body"> <div class="video-modal-body">
<!-- 加载中状态 -->
<div v-if="videoLoading" class="video-loading"> <div v-if="videoLoading" class="video-loading">
<div class="loading-spinner"></div> <div class="loading-spinner"></div>
<p>加载视频中...</p> <p>加载视频中...</p>
</div> </div>
<!-- 错误状态 -->
<div v-else-if="videoError" class="video-error"> <div v-else-if="videoError" class="video-error">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#f44336" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="#f44336" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<circle cx="12" cy="12" r="10"></circle> <circle cx="12" cy="12" r="10"></circle>
@@ -211,15 +212,24 @@
<p>视频加载失败</p> <p>视频加载失败</p>
<button class="retry-btn" @click="loadVideo(currentVideo)">重试</button> <button class="retry-btn" @click="loadVideo(currentVideo)">重试</button>
</div> </div>
<video v-else class="video-player" controls autoplay playsinline :src="videoPlayUrl" @error="handleVideoError"> <!-- 视频播放区域添加ref用于获取视频实例绑定暂停/播放事件 -->
您的浏览器不支持HTML5视频播放 <div v-else class="video-player-wrapper">
</video> <video ref="videoPlayerRef" class="video-player" controls autoplay playsinline :src="videoPlayUrl" @error="handleVideoError" @pause="onVideoPause" @play="onVideoPlay">
您的浏览器不支持HTML5视频播放
</video>
<!-- 悬浮透明删除X按钮仅暂停时显示 -->
<button class="video-delete-btn" v-show="isVideoPaused" @click.stop="deleteCurrentVideo" title="删除该视频">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#ffffff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted, computed } from 'vue'; import { ref, onMounted, computed } from 'vue';
import { useApiStore } from '@/store'; import { useApiStore } from '@/store';
@@ -286,6 +296,9 @@ const currentVideo = ref<TopVideoItem | null>(null);
const videoPlayUrl = ref<string>(''); const videoPlayUrl = ref<string>('');
const videoLoading = ref<boolean>(false); const videoLoading = ref<boolean>(false);
const videoError = ref<boolean>(false); const videoError = ref<boolean>(false);
// 新增:视频实例引用和暂停状态
const videoPlayerRef = ref<HTMLVideoElement | null>(null);
const isVideoPaused = ref<boolean>(false); // 控制删除按钮显示
// 过滤后的日志列表 // 过滤后的日志列表
const filteredLogs = computed(() => { const filteredLogs = computed(() => {
@@ -453,7 +466,12 @@ const openVideoPlayer = (video: TopVideoItem) => {
currentVideo.value = video; currentVideo.value = video;
showVideoPlayer.value = true; showVideoPlayer.value = true;
loadVideo(video); loadVideo(video).then(() => {
// 视频加载完成后触发播放,避免浏览器拦截
if (videoPlayerRef.value) {
videoPlayerRef.value.play().catch((err) => console.log('自动播放被拦截:', err));
}
});
}; };
// 新增:加载视频播放地址 // 新增:加载视频播放地址
@@ -479,6 +497,7 @@ const closeVideoPlayer = () => {
videoPlayUrl.value = ''; videoPlayUrl.value = '';
videoLoading.value = false; videoLoading.value = false;
videoError.value = false; videoError.value = false;
isVideoPaused.value = false; // 重置暂停状态
}; };
// 新增:处理视频播放错误 // 新增:处理视频播放错误
@@ -486,6 +505,23 @@ const handleVideoError = () => {
videoError.value = true; videoError.value = true;
console.error('视频播放出错'); console.error('视频播放出错');
}; };
// 新增:视频暂停回调
const onVideoPause = () => {
isVideoPaused.value = true;
};
// 新增:视频播放回调
const onVideoPlay = () => {
isVideoPaused.value = false;
};
const deleteCurrentVideo = async () => {
// 关闭播放弹窗,刷新视频列表
closeVideoPlayer();
TopVideo(); // 重新加载最新视频列表
loadDashboardData(); // 刷新仪表盘统计数据
};
</script> </script>
<style scoped> <style scoped>
@@ -1003,68 +1039,58 @@ const handleVideoError = () => {
.copy-btn:hover:not(:disabled) { .copy-btn:hover:not(:disabled) {
background-color: #43a047; background-color: #43a047;
} }
/* 视频播放弹窗样式(纯全屏+悬浮右上角关闭按钮) */
/* 新增:视频播放弹窗样式(移动端适配) */
.video-modal-mask { .video-modal-mask {
position: fixed; position: fixed;
top: 0; top: 0;
left: 0; left: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background-color: rgba(0, 0, 0, 0.8); background-color: #000; /* 纯黑背景更贴合全屏播放 */
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
z-index: 2000; z-index: 2000;
padding: 0px; padding: 0;
box-sizing: border-box; box-sizing: border-box;
-webkit-backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px);
backdrop-filter: blur(4px); backdrop-filter: blur(4px);
} }
.video-modal-content { .video-modal-content {
width: 100%; width: 100vw; /* 视口宽度100% */
max-width: 100%; height: 100vh; /* 视口高度100% */
max-width: none; /* 移除原有最大宽度限制 */
max-height: none; /* 移除原有最大高度限制 */
background-color: #111; background-color: #111;
border-radius: 12px; border-radius: 0; /* 全屏移除圆角 */
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); box-shadow: none; /* 全屏无需阴影 */
max-height: 80vh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
position: relative; /* 为悬浮关闭按钮提供定位上下文 */
} }
.video-modal-header { /* 悬浮右上角关闭按钮样式 */
padding: 5px; .floating-close-btn {
display: flex; position: absolute;
justify-content: space-between; top: 20px; /* 右上角间距,可调整 */
align-items: center; right: 20px; /* 右上角间距,可调整 */
background-color: #222; width: 40px;
} height: 40px;
.video-title-text { border-radius: 50%;
font-size: 14px; background-color: rgba(0, 0, 0, 0.5);
color: #fff;
font-weight: 500;
margin: 0;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
max-width: 80%;
}
.video-close-btn {
background: transparent;
border: none; border: none;
cursor: pointer; cursor: pointer;
color: #fff; color: #fff;
padding: 4px;
border-radius: 4px;
transition: all 0.2s ease;
width: 32px;
height: 32px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: all 0.2s ease;
z-index: 2010; /* 确保在视频上方 */
backdrop-filter: blur(2px);
} }
.video-close-btn:hover { .floating-close-btn:hover {
background-color: rgba(255, 255, 255, 0.1); background-color: rgba(255, 255, 255, 0.2);
transform: scale(1.1);
} }
.video-modal-body { .video-modal-body {
padding: 0; padding: 0;
@@ -1074,13 +1100,44 @@ const handleVideoError = () => {
justify-content: center; justify-content: center;
background-color: #000; background-color: #000;
position: relative; position: relative;
width: 100%;
height: 100%; /* 完全填充父容器,无高度损耗 */
}
/* 新增:视频播放器容器(用于定位删除按钮) */
.video-player-wrapper {
position: relative;
width: 100vw;
height: 100vh;
} }
.video-player { .video-player {
width: 100%; width: 100%;
height: 100%; height: 100%;
min-height: 200px; object-fit: contain; /* 保持视频比例,全屏填充无黑边(也可改为 cover 强制填充,可能裁剪视频) */
max-height: 60vh; }
object-fit: contain; /* 悬浮透明删除X按钮样式 */
.video-delete-btn {
position: fixed;
top: 50%;
right: 20px;
transform: translateY(-50%);
width: 48px;
height: 48px;
border-radius: 50%;
background-color: rgba(0, 0, 0, 0.3);
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: #ffffff;
transition: all 0.2s ease;
z-index: 2010;
backdrop-filter: blur(2px);
}
.video-delete-btn:hover,
.video-delete-btn:active {
background-color: rgba(244, 67, 54, 0.5);
transform: translateY(-50%) scale(1.1);
} }
/* 视频加载状态 */ /* 视频加载状态 */
.video-loading { .video-loading {
@@ -1090,6 +1147,8 @@ const handleVideoError = () => {
justify-content: center; justify-content: center;
color: #fff; color: #fff;
padding: 40px 20px; padding: 40px 20px;
width: 100%;
height: 100%;
} }
.loading-spinner { .loading-spinner {
width: 40px; width: 40px;
@@ -1114,6 +1173,8 @@ const handleVideoError = () => {
color: #fff; color: #fff;
padding: 40px 20px; padding: 40px 20px;
text-align: center; text-align: center;
width: 100%;
height: 100%;
} }
.video-error p { .video-error p {
margin: 16px 0 24px; margin: 16px 0 24px;
@@ -1277,6 +1338,15 @@ html.dark-mode .close-btn:hover {
color: #ffffff; color: #ffffff;
} }
/* 夜间模式 - 视频弹窗样式适配 */
html.dark-mode .video-delete-btn {
background-color: rgba(0, 0, 0, 0.4);
}
html.dark-mode .video-delete-btn:hover,
html.dark-mode .video-delete-btn:active {
background-color: rgba(244, 67, 54, 0.6);
}
.ant-message { .ant-message {
z-index: 10000 !important; z-index: 10000 !important;
} }
+128 -162
View File
@@ -25,7 +25,7 @@
<a-switch v-model:checked="formState.OnlySyncNew" /> <a-switch v-model:checked="formState.OnlySyncNew" />
<div class="flex items-start mt-1 text-sm text-gray-500"> <div class="flex items-start mt-1 text-sm text-gray-500">
<InfoCircleOutlined class="text-blue-400 mr-1 mt-0.5" /> <InfoCircleOutlined class="text-blue-400 mr-1 mt-0.5" />
<span>开启后仅同步最近收藏的20条以及未来新收藏的不会去同步之前的视频默认开启 避免突然大量下载导致风控</span> <span>开启后仅同步最近的20条以及未来新加入收藏喜欢关注的视频不会去同步之前的视频默认开启 避免突然大量下载导致风控</span>
</div> </div>
</a-form-item> </a-form-item>
</div> </div>
@@ -113,32 +113,44 @@
</div> </div>
</a-form-item> </a-form-item>
<a-form-item v-if="formState.DownImageVideo" has-feedback label="默认音频" name="AudioFile" :wrapper-col="{ span: 20 }"> <a-form-item has-feedback label="默认音频" name="AudioFile" :wrapper-col="{ span: 20 }">
<!-- 音频上传与播放器容器 --> <!-- 仅保留检测按钮 -->
<div class="audio-upload-player-wrapper" style="display: flex; align-items: center; gap: 16px;"> <div class="audio-check-wrapper" style="display: flex; align-items: center; gap: 16px;">
<a-upload :before-upload="beforeUpload" :custom-request="customUpload" :show-upload-list="false" accept=".mp3,.wav"> <a-button type="primary" @click="checkAudioList">
<a-button type="default"> <play-circle-outlined /> 音频列表
<UploadOutlined /> 选择文件 </a-button>
</a-button>
</a-upload>
<!-- 新增启用原生完整控件controls属性自带可拖拽进度条 -->
<div class="audio-player" v-if="audioUrl" style="flex: 1; max-width: 500px;">
<audio ref="audioInstance" :src="audioUrl" controls controlsList="nodownload" @ended="() => isPlaying = false" @pause="() => isPlaying = false" @play="() => isPlaying = true" class="native-audio-player">
您的浏览器不支持HTML5音频播放请升级至现代浏览器
</audio>
</div>
</div> </div>
<!-- 提示文本调整 -->
<div class="flex items-start mt-1 text-sm text-gray-500" style="color:red"> <div class="flex items-start mt-1 text-sm text-gray-500" style="color:red">
<InfoCircleOutlined class="text-blue-400 mr-1 mt-0.5" /> <InfoCircleOutlined class="text-blue-400 mr-1 mt-0.5" />
<span>当下载图文视频时音频因版权原因无法下载时将用该音频文件作为合成视频的音频</span> <span>当下载图文视频时音频因版权原因无法下载时将用该音频文件作为合成视频的音频</span>
</div> </div>
<div class="flex items-start mt-1 text-sm text-gray-500">
<InfoCircleOutlined class="text-blue-400 mr-1 mt-0.5" />
<span>支持格式MP3WAVAACFLACOGGM4AWMA单个文件最大20MB</span>
</div>
</a-form-item> </a-form-item>
<a-modal v-model:visible="audioListModalVisible" title="发现音频" width="666px" destroyOnClose @ok="closeAudioListModal" @cancel="closeAudioListModal">
<div class="audio-list-container" style="max-height: 500px; overflow-y: auto; padding: 10px 0;">
<!-- 空列表提示 -->
<div v-if="audioList.length === 0" style="text-align: center; padding: 40px; color: #999;">
暂无可用音频文件
</div>
<!-- 音频列表嵌入原生audio播放器 -->
<a-list v-else bordered :data-source="audioList" item-layout="horizontal">
<template #renderItem="{item}">
<a-list-item>
<a-list-item-meta :title="item.filename " />
<template #actions>
<!-- 替换原播放按钮嵌入原生HTML音频播放器 -->
<audio class="native-audio-player" controls :src="'/api/config/getmp3?name='+item.filename" preload="none" @play="handleAudioPlay($event.target)">
您的浏览器不支持HTML5音频播放器请升级浏览器后重试
</audio>
</template>
</a-list-item>
</template>
</a-list>
</div>
</a-modal>
</div> </div>
<div class="form-section"> <div class="form-section">
@@ -226,7 +238,7 @@
<input ref="importFileInput" type="file" accept=".json" class="import-file-input" @change="handleImportFile"> <input ref="importFileInput" type="file" accept=".json" class="import-file-input" @change="handleImportFile">
</div> </div>
<div class="top-right-float-btn-container"> <div class="top-right-float-btn-container">
<a-tooltip title="重新对同步好的视频文件进行刮削" placement="bottom"> <a-tooltip title="重置所有刮削" placement="bottom">
<a-button class="top-right-float-btn" type="primary" shape="circle" @click="renfo"> <a-button class="top-right-float-btn" type="primary" shape="circle" @click="renfo">
<bulb-outlined /> <bulb-outlined />
</a-button> </a-button>
@@ -276,9 +288,6 @@ const downImgVideo = ref(true);
const floatMenuVisible = ref(false); const floatMenuVisible = ref(false);
const importFileInput = ref<HTMLInputElement | null>(null); const importFileInput = ref<HTMLInputElement | null>(null);
// 新增:文件上传相关状态(已删除 uploadFileList,仅保留 isUploading 可选)
const isUploading = ref(false);
//开启或关闭合成视频 //开启或关闭合成视频
const downImageVideoHandler = () => { const downImageVideoHandler = () => {
if (formState.DownImageVideo) { if (formState.DownImageVideo) {
@@ -405,7 +414,11 @@ const getConfig = () => {
tagData.value = JSON.parse(res.data.priorityLevel || '[]'); tagData.value = JSON.parse(res.data.priorityLevel || '[]');
} else { } else {
message.error(res.message || '获取配置失败', 8); if (res.message.indexOf('401') == -1) {
message.error(res.message || '获取配置失败', 8);
} else {
console.error('获取配置失败:', res);
}
} }
}) })
.catch((error) => { .catch((error) => {
@@ -443,135 +456,65 @@ const updateTagSort = () => {
}); });
}; };
// ========== 文件上传相关方法(已修改:移除进度条逻辑) ========== // 音频列表相关状态
const audioListModalVisible = ref(false); // 弹窗显隐
const audioList = ref<any[]>([]); // 存储接口返回的音频列表
const currentPlayingAudio = ref<HTMLAudioElement | null>(null); // 核心:存储当前正在播放的音频
/** /**
* 上传前校验 * 音频播放互斥处理(核心:同一时间只播放一个音频)
* @param target 事件目标元素(EventTarget 类型)
*/ */
const beforeUpload: UploadProps['beforeUpload'] = (file) => { const handleAudioPlay = (target: EventTarget | null) => {
// 1. 校验文件大小(10MB // 1. 校验元素是否存在,且是 HTMLAudioElement 类型
const isLt20M = file.size / 1024 / 1024 < 20; if (!target || !(target instanceof HTMLAudioElement)) {
if (!isLt20M) { return; // 非音频元素,直接返回,避免报错
message.error('文件大小不能超过20MB!');
return false;
} }
// 2. 校验文件类型 // 2. 此时 target 已被确认是 HTMLAudioElement 类型,可安全操作
const acceptTypes = ['.mp3', '.wav', '.aac', '.flac', '.ogg', '.m4a', '.wma']; const audioEl = target;
const fileExt = '.' + file.name.split('.').pop()?.toLowerCase(); // 3. 原有互斥逻辑
if (!acceptTypes.includes(fileExt || '')) { if (currentPlayingAudio.value && currentPlayingAudio.value !== audioEl) {
message.error('仅支持MP3、WAV、AAC、FLAC、OGG、M4A、WMA格式的音频文件!'); currentPlayingAudio.value.pause();
return false;
} }
currentPlayingAudio.value = audioEl;
return true;
};
// 1. 新增音频播放相关状态(放在现有状态定义区域,如 isUploading 下方)
const audioUrl = ref('/api/config/defaudio'); // 上传成功后的音频文件URL
const isPlaying = ref(false); // 音频是否正在播放
const audioInstance = ref<HTMLAudioElement | null>(null); // 音频播放器实例
// 2. 改造原有 customUpload 方法,保存上传成功后的音频URL
const customUpload: UploadProps['customRequest'] = (options) => {
const { file, onSuccess, onError } = options;
isUploading.value = true;
// 构造FormData
const formData = new FormData();
formData.append('file', file);
useApiStore()
.apiUploadAudio(formData)
.then((res) => {
console.log(res);
if (res.code === 0) {
// message.success('音频文件上传成功!');
audioUrl.value = `/api/config/defaudio?t=${Date.now()}`;
onSuccess(res);
} else {
message.error(res.message || '文件上传失败!');
onError(new Error(res.message || '上传失败'), file);
}
})
.catch((error) => {
console.error('文件上传失败:', error);
message.error('文件上传失败,请稍后重试!');
onError(error, file);
})
.finally(() => {
isUploading.value = false;
});
}; };
// 新增:封装 load() 为 Promise (可复用) /**
const audioLoadPromise = (audio: HTMLAudioElement): Promise<void> => { * 检测音频列表:调用mp3List接口并展示弹窗
return new Promise((resolve, reject) => { */
// 加载成功回调 const checkAudioList = async () => {
const onLoadSuccess = () => {
audio.removeEventListener('canplaythrough', onLoadSuccess);
audio.removeEventListener('error', onLoadError);
resolve();
};
// 加载失败回调
const onLoadError = () => {
audio.removeEventListener('canplaythrough', onLoadSuccess);
audio.removeEventListener('error', onLoadError);
reject(new Error('音频加载失败'));
};
audio.addEventListener('canplaythrough', onLoadSuccess);
audio.addEventListener('error', onLoadError);
audio.load();
});
};
// 修改 refreshAudioPlayer 为 async 方法
const refreshAudioPlayer = async () => {
if (!audioInstance.value) return;
try { try {
// 1. 暂停播放、重置状态 // 步骤1:打开弹窗前,先暂停上一次可能残留的播放音频
audioInstance.value.pause(); if (currentPlayingAudio.value) {
audioInstance.value.currentTime = 0; currentPlayingAudio.value.pause();
isPlaying.value = false; currentPlayingAudio.value = null;
// 2. 等待加载完成(核心:解决时序冲突)
await audioLoadPromise(audioInstance.value);
// 3. 加载完成后,安全调用 play()
await audioInstance.value.play();
isPlaying.value = true;
// message.success('音频已自动播放');
} catch (err) {
if ((err as Error).message !== '音频加载失败') {
// 区分加载失败和自动播放失败
message.warning('自动播放失败,请手动点击播放按钮(浏览器限制)');
} else {
message.error('音频加载失败,无法自动播放');
} }
console.log('错误详情:', err);
isPlaying.value = false; // 步骤2:调用接口获取音频列表
const res = await useApiStore().mp3List();
if (res.code === 0) {
audioList.value = res.data || [];
audioListModalVisible.value = true;
} else {
message.error(res.message || '获取音频列表失败');
}
} catch (error) {
console.error('检测音频列表失败:', error);
message.error('获取音频列表失败,请稍后重试');
}
};
/**
* 关闭音频列表弹窗
*/
const closeAudioListModal = () => {
audioListModalVisible.value = false;
// 清理当前播放的音频,避免弹窗关闭后仍在播放或残留实例
if (currentPlayingAudio.value) {
currentPlayingAudio.value.pause();
currentPlayingAudio.value = null;
} }
}; };
// 监听 audioUrl 变化,重置播放状态(可选,优化用户体验)
watch(
audioUrl,
(newVal, oldVal) => {
// 排除初始值(仅当 url 发生有效变更时刷新)
if (newVal && newVal !== oldVal) {
isPlaying.value = false;
// 核心:调用刷新方法,强制加载新音频
refreshAudioPlayer();
} else if (!newVal) {
// 若 url 为空,仅重置状态
isPlaying.value = false;
if (audioInstance.value) {
audioInstance.value.currentTime = 0;
}
}
},
{ immediate: false }
); // 关闭 immediate,避免初始加载时触发
// 组件挂载时获取配置 // 组件挂载时获取配置
onMounted(async () => { onMounted(async () => {
getConfig(); getConfig();
@@ -938,29 +881,24 @@ const renfo = () => {
:deep(.ant-upload.ant-upload-select) { :deep(.ant-upload.ant-upload-select) {
display: inline-block; display: inline-block;
} }
// 音频列表弹窗样式优化
:deep(.audio-list-container .ant-list) {
.ant-list-item {
padding: 12px 16px;
&:hover {
background-color: #fafafa;
}
}
// 音频上传与播放器容器样式 .ant-list-item-meta-title {
.audio-upload-player-wrapper { font-weight: 500;
flex-wrap: wrap; color: #333;
@media (max-width: 768px) {
flex-direction: column;
align-items: flex-start;
} }
} }
// 音频播放器样式优化 // 检测按钮样式
:deep(.audio-player audio) { .audio-check-wrapper {
// border: 1px solid #d9d9d9; padding: 8px 0;
border-radius: 4px;
padding: 2px;
&:hover {
border-color: #1890ff;
}
}
// 播放/清空按钮 hover 效果
:deep(.audio-player .ant-btn-text:hover) {
background-color: #f5f5f5;
} }
// 右上角悬浮按钮容器 // 右上角悬浮按钮容器
@@ -987,4 +925,32 @@ const renfo = () => {
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15); // 悬浮时阴影加深(可选,可删除) box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15); // 悬浮时阴影加深(可选,可删除)
} }
} }
// 原生音频播放器样式优化
.native-audio-player {
width: 200px; // 固定宽度,适配列表布局
height: 32px; // 统一高度,与列表项对齐
outline: none; // 移除聚焦轮廓
border: 1px solid #d9d9d9; // 添边框,与AntD风格统一
border-radius: 4px; // 圆角,与AntD风格统一
background-color: #fafafa; // 浅背景色,提升质感
// 优化播放器内部控件样式(部分浏览器支持)
&::-webkit-media-controls {
background-color: #fafafa;
}
// 悬浮效果
&:hover {
border-color: #1890ff; // 悬浮时边框变主色,提升交互感
box-shadow: 0 0 0 2px rgba(24, 144, 255, 0.1); // 轻微发光效果
}
}
// 音频列表项适配,保证播放器与内容对齐
:deep(.ant-list-item-actions) {
display: flex;
align-items: center;
padding-right: 8px;
}
</style> </style>
+62 -9
View File
@@ -44,18 +44,24 @@
<a-space size="middle" class="button-group"> <a-space size="middle" class="button-group">
<a-button success @click="handleBatchShare" class="delete-button" v-if="isBatchMode" :disabled="selectedRowKeys.length === 0 || isSyncing"> <a-button success @click="handleBatchShare" class="delete-button" v-if="isBatchMode" :disabled="selectedRowKeys.length === 0 || isSyncing">
<ShareAltOutlined /> <ShareAltOutlined />
分享选中 批量分享
</a-button> </a-button>
<a-button type="danger" @click="handleBatchDelete" class="delete-button" v-if="isBatchMode" :disabled="selectedRowKeys.length === 0 || isSyncing"> <a-button danger @click="handleBatchSync" class="delete-button" v-if="isBatchMode" :disabled="selectedRowKeys.length === 0 || isSyncing">
<SyncOutlined /> <SyncOutlined />
重新同步 重新下载
</a-button>
<a-button danger @click="handleBatchDelete" class="delete-button" v-if="isBatchMode" :disabled="selectedRowKeys.length === 0 || isSyncing">
<close-outlined />
批量删除
</a-button> </a-button>
</a-space> </a-space>
</a-form-item> </a-form-item>
<!-- 按钮代码 --> <!-- 按钮代码 -->
<a-form-item class="form-item delete-btn-2-wrapper"> <a-form-item class="form-item delete-btn-2-wrapper">
<a-button danger @click="handShowDeleteVideos" class="delete-button-2"> <a-button type="primary" danger @click="handShowDeleteVideos" class="delete-button-2">
<ClearOutlined /> <!-- 注意首字母大写Antd图标命名规范 --> <!-- <ClearOutlined /> -->
<!-- 注意首字母大写Antd图标命名规范 -->
<delete-outlined />
已删除 已删除
</a-button> </a-button>
</a-form-item> </a-form-item>
@@ -79,9 +85,9 @@
</span> </span>
</div> </div>
<a-button type="text" size="small" class="copy-delete-video-btn" @click="(e) => copyVideoPath(item.videoSavePath)"> <!-- <a-button type="text" size="small" class="copy-delete-video-btn" @click="(e) => copyVideoPath(item.videoSavePath)">
<CopyOutlined /> 复制 <CopyOutlined /> 复制
</a-button> </a-button> -->
</a-list-item> </a-list-item>
</template> </template>
</a-list> </a-list>
@@ -639,9 +645,9 @@ watch(
// -------------------------- 批量操作和操作列事件 -------------------------- // -------------------------- 批量操作和操作列事件 --------------------------
/** 批量删除事件 */ /** 批量删除事件 */
const handleBatchDelete = () => { const handleBatchSync = () => {
if (selectedRowKeys.value.length === 0) { if (selectedRowKeys.value.length === 0) {
message.warning('请先选择要删除的视频'); message.warning('请先选择要重新下载的视频');
return; return;
} }
@@ -657,6 +663,24 @@ const handleBatchDelete = () => {
}); });
}; };
const handleBatchDelete = () => {
if (selectedRowKeys.value.length === 0) {
message.warning('请先选择要彻底删除的视频');
return;
}
Modal.confirm({
title: '确认删除这些下载的视频吗',
content: `您确定要彻底下删除选中的 ${selectedRowKeys.value.length} 条视频数据吗?`,
okText: '确认彻底删除',
cancelText: '取消',
okType: 'danger',
onOk: async () => {
deleteBatch({ ids: selectedRowKeys.value });
},
});
};
const deleteVideoShow = ref(false); const deleteVideoShow = ref(false);
const handShowDeleteVideos = () => { const handShowDeleteVideos = () => {
deleteVideoShow.value = true; deleteVideoShow.value = true;
@@ -704,6 +728,35 @@ const reDownload = (param: object) => {
} }
}; };
const deleteBatch = (param: object) => {
try {
loading.value = true;
console.log('执行批量删除,选中ID', selectedRowKeys.value);
useApiStore()
.BathRealDelete(param)
.then((res) => {
loading.value = false;
if (res.code === 0) {
message.success('删除成功,以后都不会下载了哦,你自己选的');
// 刷新数据并清空选中状态
GetRecords();
selectedRowKeys.value = [];
} else {
message.warning(res.message || '获取数据失败');
}
})
.catch((error) => {
loading.value = false;
});
} catch (error) {
console.error('批量删除失败:', error);
message.error('删除失败,请稍后重试');
} finally {
loading.value = false;
}
};
/** 重新下载事件 */ /** 重新下载事件 */
const handleReDownload = (record: DataItem) => { const handleReDownload = (record: DataItem) => {
if (!record.id) { if (!record.id) {
+33 -2
View File
@@ -156,7 +156,7 @@
<!-- 作者统计 --> <!-- 作者统计 -->
<div v-if="currentTab === 'author'" key="author-view" class="stats-content"> <div v-if="currentTab === 'author'" key="author-view" class="stats-content">
<div class="authors-grid"> <div class="authors-grid">
<div class="author-card" v-for="(author, index) in authors" :key="index"> <div class="author-card" v-for="(author, index) in authors" :key="index" @dblclick="handleDeleteItem(author)">
<!-- 新增横向容器包裹头像和作者信息 --> <!-- 新增横向容器包裹头像和作者信息 -->
<div class="author-info-row"> <div class="author-info-row">
<div class="author-avatar"> <div class="author-avatar">
@@ -209,7 +209,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref, onMounted } from 'vue'; import { ref, onMounted } from 'vue';
import { useApiStore } from '@/store'; import { useApiStore } from '@/store';
import { message, Spin, Empty, Tooltip, Modal, Form, FormInstance, Popconfirm } from 'ant-design-vue';
// 类型接口 // 类型接口
interface Author { interface Author {
name: string; name: string;
@@ -313,6 +313,37 @@ const getRandomElements = (arr: any[], n: number) => {
if (n >= arr.length) return [...arr]; if (n >= arr.length) return [...arr];
return [...arr].sort(() => Math.random() - 0.5).slice(0, n); return [...arr].sort(() => Math.random() - 0.5).slice(0, n);
}; };
const handleDeleteItem = (item: any) => {
Modal.confirm({
title: '确认删除',
content: `确定要删除博主「${item.name}」所有视频吗?删除后将无法恢复。`,
okText: '确认删除',
cancelText: '取消',
okType: 'danger',
maskClosable: false,
onOk: () => {
return new Promise((resolve, reject) => {
useApiStore()
.DeleteByAuthor(item.uperId)
.then((res) => {
if (res.code === 0) {
message.success('根据视频数量,需要时常不确定,可以稍后去日志查看...');
resolve(true);
} else {
message.error('删除博主视频失败' + (res.message || '未知错误'));
reject(false);
}
})
.catch((err) => {
console.error('删除博主视频异常', err);
message.error('删除博主视频异常' + err);
reject(false);
});
});
},
});
};
</script> </script>
<style scoped> <style scoped>
/* 基础样式 */ /* 基础样式 */
+46 -10
View File
@@ -31,18 +31,25 @@ import { Response } from '@/types';
export const useApiStore = defineStore('coreapi', () => { export const useApiStore = defineStore('coreapi', () => {
//获取配置
async function apiGetConfig() { async function apiGetConfig() {
return http return http.request<any, Response<any>>('/api/config/GetConfig', 'get').then(r => {
.request<any, Response<any>>('/api/config/GetConfig', 'GET') return r;
.then((res) => { }).finally(() => {
return res;
})
.finally(() => {
}); });
} }
// //获取配置
// async function apiGetConfig() {
// return http
// .request<any, Response<any>>('/api/config/GetConfig', 'GET')
// .then((res) => {
// return res;
// })
// .finally(() => {
// });
// }
//修改配置 //修改配置
async function apiUpdateConfig(request: object) { async function apiUpdateConfig(request: object) {
return http return http
@@ -201,6 +208,14 @@ export const useApiStore = defineStore('coreapi', () => {
}); });
} }
//批量删除
async function BathRealDelete(param: object) {
return http.request<any, Response<any>>('/api/video/vdelete/batch', 'post_json', param).then(r => {
return r;
}).finally(() => {
});
}
//删除 //删除
async function DeleteVideo(param: string) { async function DeleteVideo(param: string) {
return http.request<any, Response<any>>('/api/video/vdelete/' + param, 'get').then(r => { return http.request<any, Response<any>>('/api/video/vdelete/' + param, 'get').then(r => {
@@ -209,7 +224,7 @@ export const useApiStore = defineStore('coreapi', () => {
}); });
} }
//删除 //查询已删除
async function GetDeleteViedos() { async function GetDeleteViedos() {
return http.request<any, Response<any>>('/api/video/vdelete/get', 'get').then(r => { return http.request<any, Response<any>>('/api/video/vdelete/get', 'get').then(r => {
return r; return r;
@@ -217,6 +232,14 @@ export const useApiStore = defineStore('coreapi', () => {
}); });
} }
//删除博主全部视频
async function DeleteByAuthor(param: string) {
return http.request<any, Response<any>>('/api/video/vdelete/byauthor/' + param, 'get').then(r => {
return r;
}).finally(() => {
});
}
//检查版本 //检查版本
@@ -227,6 +250,16 @@ export const useApiStore = defineStore('coreapi', () => {
}); });
} }
async function mp3List() {
return http.request<any, Response<any>>('/api/config/mp3List', 'get').then(r => {
return r;
}).finally(() => {
});
}
//快速停止或启动cookie配置 //快速停止或启动cookie配置
async function SwitchCookieStatus(param: object) { async function SwitchCookieStatus(param: object) {
return http.request<any, Response<any>>('/api/config/switch', 'post_json', param).then(r => { return http.request<any, Response<any>>('/api/config/switch', 'post_json', param).then(r => {
@@ -331,6 +364,9 @@ export const useApiStore = defineStore('coreapi', () => {
} }
return { return {
mp3List,
BathRealDelete,
DeleteByAuthor,
Renfo, Renfo,
apiUploadAudio, apiUploadAudio,
GetAppPort, GetAppPort,
+14 -16
View File
@@ -48,23 +48,21 @@ http.interceptors.response.use(
accountStore.setLogged(false); accountStore.setLogged(false);
message.warning('登录状态已过期,请重新登录'); message.warning('登录状态已过期,请重新登录');
setTimeout(() => { const redirectPath = router.currentRoute.value.fullPath;
const redirectPath = router.currentRoute.value.fullPath; if (redirectPath !== '/login') {
if (redirectPath !== '/login') { router.push({
router.push({ path: '/login',
path: '/login', query: { redirect: redirectPath }
query: { redirect: redirectPath } }).then(() => {
}).then(() => { console.log('跳转登录页成功');
console.log('跳转登录页成功'); }).catch((err) => {
}).catch((err) => { console.error('跳转登录页失败:', err);
console.error('跳转登录页失败:', err); }).finally(() => {
}).finally(() => {
isRedirecting = false;
});
} else {
isRedirecting = false; isRedirecting = false;
} });
}, 100); } else {
isRedirecting = false;
}
} }
// 新增结束 // 新增结束
} else { } else {
+7 -23
View File
@@ -1,4 +1,4 @@
import axios, { AxiosInstance, AxiosRequestConfig, Method as _Method, AxiosResponse } from 'axios'; // 移除 AxiosProgressEvent import axios, { AxiosInstance, AxiosRequestConfig, Method as _Method, AxiosResponse } from 'axios';
import qs from 'qs'; import qs from 'qs';
import Cookie from 'js-cookie'; import Cookie from 'js-cookie';
@@ -13,8 +13,8 @@ declare interface _AxiosExtend {
request<T = any, R = AxiosResponse<T>>( request<T = any, R = AxiosResponse<T>>(
url: string, url: string,
method: Method, method: Method,
params?: Record<string | number, any> | FormData, // 支持 FormData 类型 params?: Record<string | number, any> | FormData,
config?: AxiosRequestConfig & { onUploadProgress?: (progressEvent: ProgressEvent) => void } // 改用原生 ProgressEvent config?: AxiosRequestConfig & { onUploadProgress?: (progressEvent: ProgressEvent) => void }
): Promise<R>; ): Promise<R>;
/** /**
* 设置token * 设置token
@@ -40,7 +40,6 @@ declare interface _AxiosExtend {
export interface AxiosHttp extends Omit<AxiosInstance, 'request'>, _AxiosExtend { } export interface AxiosHttp extends Omit<AxiosInstance, 'request'>, _AxiosExtend { }
// 新增 post_form / POST_FORM 类型
export type Method = _Method | 'POST_JSON' | 'post_json' | 'PUT_JSON' | 'put_json' | 'POST_FORM' | 'post_form'; export type Method = _Method | 'POST_JSON' | 'post_json' | 'PUT_JSON' | 'put_json' | 'POST_FORM' | 'post_form';
/** /**
@@ -83,22 +82,10 @@ function toUrlencoded(params?: Record<string | number, any>) {
function createAxiosHttp(config: AxiosRequestConfig): AxiosHttp { function createAxiosHttp(config: AxiosRequestConfig): AxiosHttp {
const _axios = axios.create(config); const _axios = axios.create(config);
// 添加响应拦截器处理401状态 // 移除 401 处理,仅保留基础的响应拦截器(可选,也可直接删除)
_axios.interceptors.response.use( _axios.interceptors.response.use(
// 成功响应直接返回
response => response, response => response,
// 错误响应处理 error => Promise.reject(error)
error => {
// 检查是否是401未授权错误
if (error.response && error.response.status === 401) {
// 调用removeAuthorization方法清除token
http.removeAuthorization();
// 这里可以添加额外的处理,比如跳转到登录页
// 示例: window.location.href = '/login';
}
return Promise.reject(error);
}
); );
const http: AxiosHttp = { const http: AxiosHttp = {
@@ -107,13 +94,12 @@ function createAxiosHttp(config: AxiosRequestConfig): AxiosHttp {
url: string, url: string,
method: Method, method: Method,
params?: Record<string | number, any> | FormData, params?: Record<string | number, any> | FormData,
config?: AxiosRequestConfig & { onUploadProgress?: (progressEvent: ProgressEvent) => void } // 改用原生 ProgressEvent config?: AxiosRequestConfig & { onUploadProgress?: (progressEvent: ProgressEvent) => void }
): Promise<R> { ): Promise<R> {
const _method = method.toUpperCase(); const _method = method.toUpperCase();
// 处理上传进度配置
const requestConfig: AxiosRequestConfig = { const requestConfig: AxiosRequestConfig = {
...config, ...config,
onUploadProgress: config?.onUploadProgress, // 透传上传进度回调 onUploadProgress: config?.onUploadProgress,
}; };
switch (_method) { switch (_method) {
@@ -132,11 +118,9 @@ function createAxiosHttp(config: AxiosRequestConfig): AxiosHttp {
...requestConfig, ...requestConfig,
headers: { 'Content-Type': 'application/json', ...requestConfig.headers }, headers: { 'Content-Type': 'application/json', ...requestConfig.headers },
}); });
// 新增:POST_FORM 类型(适配文件上传的 FormData)
case 'POST_FORM': case 'POST_FORM':
return _axios.post(url, params, { return _axios.post(url, params, {
...requestConfig, ...requestConfig,
// FormData 不需要手动设置 Content-Typeaxios 会自动处理为 multipart/form-data
headers: { ...requestConfig.headers }, headers: { ...requestConfig.headers },
}); });
case 'PUT': case 'PUT':
+2
View File
@@ -10,6 +10,8 @@
public string Icon { get; set; } public string Icon { get; set; }
public string UperId { get; set; }
} }
+1 -1
View File
@@ -2,7 +2,7 @@
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> <Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup> <PropertyGroup>
<ActiveDebugProfile>tiny.ddns</ActiveDebugProfile> <ActiveDebugProfile>tiny.ddns</ActiveDebugProfile>
<NameOfLastUsedPublishProfile>E:\code\dysync\Properties\PublishProfiles\docker.pubxml</NameOfLastUsedPublishProfile> <NameOfLastUsedPublishProfile>E:\code\dysync\Properties\PublishProfiles\fn.pubxml</NameOfLastUsedPublishProfile>
<Controller_SelectedScaffolderID>ApiControllerEmptyScaffolder</Controller_SelectedScaffolderID> <Controller_SelectedScaffolderID>ApiControllerEmptyScaffolder</Controller_SelectedScaffolderID>
<Controller_SelectedScaffolderCategoryPath>root/Common/Api</Controller_SelectedScaffolderCategoryPath> <Controller_SelectedScaffolderCategoryPath>root/Common/Api</Controller_SelectedScaffolderCategoryPath>
</PropertyGroup> </PropertyGroup>
+6 -1
View File
@@ -33,7 +33,7 @@ namespace dy.net.extension
public string Title { get; set; } public string Title { get; set; }
} }
public static string FnDataFolder = string.Empty;
private static DbType GetDBType(IConfiguration configuration) private static DbType GetDBType(IConfiguration configuration)
{ {
DbType dbType = DbType.Sqlite; DbType dbType = DbType.Sqlite;
@@ -68,6 +68,11 @@ namespace dy.net.extension
if (!string.IsNullOrEmpty(dbPath)) if (!string.IsNullOrEmpty(dbPath))
{ {
fileFloder= Path.Combine(dbPath, "db"); fileFloder= Path.Combine(dbPath, "db");
FnDataFolder = Path.Combine(dbPath, "mp3");
if ((!Directory.Exists(FnDataFolder)))
{
Directory.CreateDirectory(FnDataFolder);
}
} }
else else
{ {
+10 -3
View File
@@ -140,7 +140,10 @@ namespace dy.net.job
Log.Debug($"{VideoType}-未获取到系统配置,任务终止!!!"); Log.Debug($"{VideoType}-未获取到系统配置,任务终止!!!");
return; return;
} }
//if(VideoType!= VideoTypeEnum.dy_follows)
//{
// return;
//}
// 2. 从配置中获取每页请求数量--固定18 // 2. 从配置中获取每页请求数量--固定18
//if (config.BatchCount > 0) //if (config.BatchCount > 0)
@@ -153,10 +156,10 @@ namespace dy.net.job
var cookies = await GetValidCookies(); var cookies = await GetValidCookies();
if (cookies == null || !cookies.Any()) if (cookies == null || !cookies.Any())
{ {
Log.Debug($"{VideoType}-未配置cookie或开启同步,任务终止!!!"); Log.Debug($"{VideoType}-无有效cookie或cookie未开启同步,任务终止!!!");
return; return;
} }
Log.Debug($"{VideoType}-共发现{cookies.Count}个Cookie,同步任务即将开始..."); Log.Debug($"{VideoType}-共发现{cookies.Count}个有效的cookie,同步任务即将开始...");
// 6. 遍历每个有效的Cookie,执行同步操作 // 6. 遍历每个有效的Cookie,执行同步操作
foreach (var cookie in cookies) foreach (var cookie in cookies)
@@ -449,6 +452,10 @@ namespace dy.net.job
//{ //{
// continue; // continue;
//} //}
//if (!item.Desc.Contains("小房车正式启程"))
//{
// continue;
//}
//判断视频是否是强制删除且不再下载的视频 //判断视频是否是强制删除且不再下载的视频
var deleteVideo = await douyinCommonService.ExistDeleteVideo(item.AwemeId); var deleteVideo = await douyinCommonService.ExistDeleteVideo(item.AwemeId);
+9 -6
View File
@@ -55,12 +55,15 @@ namespace dy.net.job
protected override string CreateSaveFolder(DouyinCookie cookie, Aweme item, AppConfig config, DouyinFollowed followed) protected override string CreateSaveFolder(DouyinCookie cookie, Aweme item, AppConfig config, DouyinFollowed followed)
{ {
#region 使UP主名称作为文件夹名称使 #region 使UP主名称作为文件夹名称使
var authorName = string.IsNullOrWhiteSpace(item.Author?.Nickname) ? "UnknownAuthor" : DouyinFileNameHelper.SanitizeLinuxFileName(item.Author.Nickname,"",true); // 1. 优先获取有效的作者名称(遵循原有优先级:followed.UperName > item.Author.Nickname > 默认值)
var folder = Path.Combine(cookie.UpSavePath, authorName); var rawAuthorName = followed?.UperName ?? item?.Author?.Nickname;
if (followed != null && !string.IsNullOrWhiteSpace(followed.SavePath)) var authorName = string.IsNullOrWhiteSpace(rawAuthorName)
{ ? "UnknownAuthor"
folder = Path.Combine(cookie.UpSavePath, followed.SavePath); : DouyinFileNameHelper.SanitizeLinuxFileName(rawAuthorName, "", true);
} // 2. 确定最终文件夹路径(遵循原有优先级:followed.SavePath > authorName > 基础路径)
var targetFolderName = !string.IsNullOrWhiteSpace(followed?.SavePath) ? followed.SavePath : authorName;
var folder = Path.Combine(cookie.UpSavePath, targetFolderName);
if (!Directory.Exists(folder)) Directory.CreateDirectory(folder); if (!Directory.Exists(folder)) Directory.CreateDirectory(folder);
#endregion #endregion
+2
View File
@@ -227,6 +227,8 @@ namespace dy.net.service
.ToListAsync(); .ToListAsync();
} }
#region #region
+8 -1
View File
@@ -1,5 +1,6 @@
using ClockSnowFlake; using ClockSnowFlake;
using dy.net.dto; using dy.net.dto;
using dy.net.extension;
using dy.net.utils; using dy.net.utils;
using Serilog; using Serilog;
using System.Collections.Generic; using System.Collections.Generic;
@@ -66,7 +67,13 @@ namespace dy.net.service
{ {
var allowedExtensions = new HashSet<string> { ".mp3", ".wav" }; var allowedExtensions = new HashSet<string> { ".mp3", ".wav" };
string mergMusic = Path.Combine(AppContext.BaseDirectory, "mp3", "silent_10.mp3");//默认音频 string mergMusic = Path.Combine(AppContext.BaseDirectory, "mp3", "silent_10.mp3");//默认音频
var customMusics = Directory.GetFiles(Path.Combine(AppContext.BaseDirectory, "mp3"))
var mp3folder = Path.Combine(AppContext.BaseDirectory, "mp3");
if (!string.IsNullOrWhiteSpace(ServiceExtension.FnDataFolder))
{
mp3folder = ServiceExtension.FnDataFolder;//飞牛数据目录
}
var customMusics = Directory.GetFiles(mp3folder)
.Where(filePath => .Where(filePath =>
allowedExtensions.Contains(Path.GetExtension(filePath).ToLowerInvariant()) && allowedExtensions.Contains(Path.GetExtension(filePath).ToLowerInvariant()) &&
Path.GetFileNameWithoutExtension(filePath) != "silent_10") Path.GetFileNameWithoutExtension(filePath) != "silent_10")
+103 -10
View File
@@ -4,6 +4,7 @@ using dy.net.model;
using dy.net.repository; using dy.net.repository;
using dy.net.utils; using dy.net.utils;
using Newtonsoft.Json; using Newtonsoft.Json;
using SqlSugar;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel; using System.ComponentModel;
using System.Threading.Tasks; using System.Threading.Tasks;
@@ -13,13 +14,16 @@ namespace dy.net.service
public class DouyinVideoService public class DouyinVideoService
{ {
private readonly ISqlSugarClient sqlSugarClient;
private readonly DouyinVideoRepository _dyCollectVideoRepository; private readonly DouyinVideoRepository _dyCollectVideoRepository;
private readonly DouyinCookieRepository douyinCookieRepository; private readonly DouyinCookieRepository douyinCookieRepository;
public DouyinVideoService(DouyinVideoRepository dyCollectVideoRepository, DouyinCookieRepository douyinCookieRepository) public DouyinVideoService(DouyinVideoRepository dyCollectVideoRepository, DouyinCookieRepository douyinCookieRepository, ISqlSugarClient sqlSugarClient)
{ {
_dyCollectVideoRepository = dyCollectVideoRepository; _dyCollectVideoRepository = dyCollectVideoRepository;
this.douyinCookieRepository = douyinCookieRepository; this.douyinCookieRepository = douyinCookieRepository;
this.sqlSugarClient = sqlSugarClient;
} }
@@ -125,7 +129,13 @@ namespace dy.net.service
data.GraphicVideoSize = "<0.01";//避免显示0.00误导用户 data.GraphicVideoSize = "<0.01";//避免显示0.00误导用户
} }
} }
data.Authors = list.GroupBy(x => x.Author).Select(x => new VideoStaticsItemDto { Name = x.Key, Count = x.LongCount(), Icon = x.LastOrDefault().AuthorAvatarUrl }).OrderByDescending(d => d.Count).ToList(); data.Authors = list.GroupBy(x => x.Author).Select(x => new VideoStaticsItemDto
{
Name = x.Key,
Count = x.LongCount(),
Icon = x.LastOrDefault()?.AuthorAvatarUrl,
UperId = x.LastOrDefault()?.AuthorId ?? x.LastOrDefault()?.DyUserId
}).OrderByDescending(d => d.Count).ToList();
return data; return data;
} }
@@ -183,7 +193,7 @@ namespace dy.net.service
/// <returns>是否执行成功(true=流程执行完成,false=无有效数据或执行失败)</returns> /// <returns>是否执行成功(true=流程执行完成,false=无有效数据或执行失败)</returns>
/// <exception cref="ArgumentNullException">DTO或ID列表为空时抛出</exception> /// <exception cref="ArgumentNullException">DTO或ID列表为空时抛出</exception>
/// <exception cref="IOException">文件操作失败时抛出(可根据业务调整处理方式)</exception> /// <exception cref="IOException">文件操作失败时抛出(可根据业务调整处理方式)</exception>
public async Task<bool> ReDownloadViedoAsync(ReDownViedoDto dto) public async Task<bool> ReDownloadViedoAsync(ReDownViedoDto dto, bool forever = false)
{ {
// 1. 严格参数校验(避免无效流程) // 1. 严格参数校验(避免无效流程)
if (dto == null) if (dto == null)
@@ -205,7 +215,7 @@ namespace dy.net.service
// 3. 构建重新下载记录(提前准备数据,避免事务内耗时操作) // 3. 构建重新下载记录(提前准备数据,避免事务内耗时操作)
var reDownList = new List<DouyinReDownload>(); var reDownList = new List<DouyinReDownload>();
var filePathsToDelete = new List<(string path,bool onlyImgOrMp3)>(); // 收集待删除文件路径,统一处理 var filePathsToDelete = new List<(string path, bool onlyImgOrMp3)>(); // 收集待删除文件路径,统一处理
foreach (var video in videos) foreach (var video in videos)
{ {
@@ -227,7 +237,7 @@ namespace dy.net.service
CookieId = video.CookieId CookieId = video.CookieId
}); });
filePathsToDelete.Add((video.VideoSavePath,video.OnlyImgOrOnlyMp3)); filePathsToDelete.Add((video.VideoSavePath, video.OnlyImgOrOnlyMp3));
} }
// 无有效重新下载记录时直接返回 // 无有效重新下载记录时直接返回
@@ -319,7 +329,8 @@ namespace dy.net.service
await douyinCookieRepository.UpdateAsync(cookie); await douyinCookieRepository.UpdateAsync(cookie);
} }
Serilog.Log.Debug("重新下载视频流程执行完成:成功创建{0}条重新下载记录,删除{1}个文件,等待重新下载...", reDownList.Count, filePathsToDelete.Count); if (!forever)
Serilog.Log.Debug("重新下载视频流程执行完成:成功创建{0}条重新下载记录,删除{1}个文件,等待重新下载...", reDownList.Count, filePathsToDelete.Count);
return true; return true;
} }
catch (Exception ex) catch (Exception ex)
@@ -331,7 +342,7 @@ namespace dy.net.service
public async Task<List<DouyinVideoTopDto>> GetLastSyncTop(int top=5) public async Task<List<DouyinVideoTopDto>> GetLastSyncTop(int top = 5)
{ {
return await _dyCollectVideoRepository.GetTopsOrderBySyncTime(top); return await _dyCollectVideoRepository.GetTopsOrderBySyncTime(top);
} }
@@ -343,10 +354,10 @@ namespace dy.net.service
/// <returns></returns> /// <returns></returns>
public async Task<List<DeleteInvalidVideoDto>> DeleteInvalidVideo() public async Task<List<DeleteInvalidVideoDto>> DeleteInvalidVideo()
{ {
var videos=await _dyCollectVideoRepository.GetAllAsync(); var videos = await _dyCollectVideoRepository.GetAllAsync();
List<DeleteInvalidVideoDto> vList = new List<DeleteInvalidVideoDto>(); List<DeleteInvalidVideoDto> vList = new List<DeleteInvalidVideoDto>();
List<string> douyinVideoIds=new List<string>(); List<string> douyinVideoIds = new List<string>();
foreach (var v in videos) foreach (var v in videos)
{ {
if (!File.Exists(v.VideoSavePath)) if (!File.Exists(v.VideoSavePath))
@@ -358,10 +369,92 @@ namespace dy.net.service
if (douyinVideoIds.Any()) if (douyinVideoIds.Any())
{ {
await _dyCollectVideoRepository.DeleteByIdsAsync(douyinVideoIds); await _dyCollectVideoRepository.DeleteByIdsAsync(douyinVideoIds);
} }
return vList; return vList;
} }
/// <summary>
/// 根据博主ID获取视频列表
/// </summary>
/// <param name="uperUid"></param>
/// <returns></returns>
internal async Task<List<DouyinVideo>> GetByAuthorId(string uperUid)
{
return await _dyCollectVideoRepository.GetListAsync(x => x.DyUserId == uperUid);
}
internal async Task<int> AddDeleteVideo(List<DouyinVideo> videos)
{
var deletes = videos.Select(video => new DouyinVideoDelete
{
ViedoId = video.AwemeId,
VideoTitle = video.VideoTitle,
VideoSavePath = video.VideoSavePath,
Id = IdGener.GetLong().ToString(),
DeleteTime = DateTime.Now
})?.ToList();
return await sqlSugarClient.Insertable<DouyinVideoDelete>(deletes).ExecuteCommandAsync();
}
/// <summary>
/// 彻底删除视频
/// </summary>
/// <param name="Ids"></param>
/// <returns></returns>
public async Task<bool> RealDeleteVideos(List<string> Ids)
{
if (Ids == null || !Ids.Any())
return false;
var videos = await _dyCollectVideoRepository.GetListAsync(x => Ids.Contains(x.Id));
if (videos != null && videos.Count > 0)
{
if (videos.Count <= 30)
{
var result = await ReDownloadViedoAsync(new ReDownViedoDto { Ids = videos.Select(x => x.Id)?.ToList() }, true);
if (result)
{
//加入删除逻辑
var deletes = await AddDeleteVideo(videos);
Serilog.Log.Debug($"批量永久删除博主{videos.FirstOrDefault()?.Author},共{deletes}条记录");
return true;
}
else
{
Serilog.Log.Error($"批量删除{videos.FirstOrDefault()?.Author}视频失败");
return false;
}
}
else
{
Task.Run(async () =>
{
var result = await ReDownloadViedoAsync(new ReDownViedoDto { Ids = videos.Select(x => x.Id)?.ToList() }, true);
if (result)
{
//加入删除逻辑
var deletes = await AddDeleteVideo(videos);
Serilog.Log.Debug($"批量永久删除博主{videos.FirstOrDefault()?.Author}{deletes}条记录");
}
else
{
Serilog.Log.Error($"批量删除{videos.FirstOrDefault()?.Author}视频失败");
}
});
return true;
}
}
else
{
Serilog.Log.Error($"没有查询到可删除的视频");
return false;
}
}
} }
} }