Compare commits

..

1 Commits

Author SHA1 Message Date
小肥羊 dd6878210f v1稳定版 2024-11-29 14:27:07 +08:00
356 changed files with 3506 additions and 37159 deletions

7
.gitignore vendored
View File

@ -364,9 +364,4 @@ FodyWeavers.xsd
VideoAnalysis/AICore/_Static/ VideoAnalysis/AICore/_Static/
VideoAnalysisCore/AICore/_Static/ VideoAnalysisCore/AICore/_Static/
VideoAnalysis/WebUI/node_modules/
VideoAnalysis/WebUI/dist/
VideoAnalysis/WebUI/.vscode/
/VideoAnalysis/device_id.config
/Learn.VideoAnalysis.API/device_id.config

View File

@ -1,13 +0,0 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "10.0.6",
"commands": [
"dotnet-ef"
],
"rollForward": false
}
}
}

View File

@ -1,172 +0,0 @@
{
"Env": [
{
"Name": "10楼刀片机",
"ServerList": [],
"LinuxServerList": [
{
"UserName": "heyang",
"Pwd": "9718CB3C9A0760CA326767D677ADEC1C",
"Host": "10.127.127.107",
"NickName": "10楼刀片机",
"IIsFireUrl": null,
"DockerFireUrl": "",
"WindowsServiceFireUrl": null,
"LinuxServiceFireUrl": null
}
],
"IgnoreList": [
"ffmpeg.exe"
],
"WindowsBackUpIgnoreList": []
},
{
"Name": "阿里云_代理",
"ServerList": [],
"LinuxServerList": [
{
"UserName": "heyang",
"Pwd": "AAC53130AF118B652BCED77C39B959F9",
"Host": "10.127.127.77:10022",
"NickName": "",
"IIsFireUrl": null,
"DockerFireUrl": "",
"WindowsServiceFireUrl": null,
"LinuxServiceFireUrl": null
}
],
"IgnoreList": [],
"WindowsBackUpIgnoreList": []
}
],
"IIsConfig": {
"SdkType": "netcore",
"WebSiteName": "",
"LastEnvName": "10楼刀片机",
"EnvPairList": [
{
"EnvName": "10楼刀片机",
"ConfigName": "",
"LinuxEnvParam": null,
"DockerPort": null,
"DockerEnvName": null,
"DockerVolume": null,
"DockerOther": null
},
{
"EnvName": "阿里云_代理",
"ConfigName": "",
"LinuxEnvParam": null,
"DockerPort": null,
"DockerEnvName": null,
"DockerVolume": null,
"DockerOther": null
}
]
},
"WindowsServiveConfig": {
"ServiceName": "",
"SdkType": null,
"LastEnvName": null,
"EnvPairList": [
{
"EnvName": "10楼刀片机",
"ConfigName": "",
"LinuxEnvParam": null,
"DockerPort": null,
"DockerEnvName": null,
"DockerVolume": null,
"DockerOther": null
},
{
"EnvName": "阿里云_代理",
"ConfigName": "",
"LinuxEnvParam": null,
"DockerPort": null,
"DockerEnvName": null,
"DockerVolume": null,
"DockerOther": null
}
]
},
"LinuxServiveConfig": {
"ServiceName": "",
"EnvParam": "",
"LastEnvName": null,
"EnvPairList": [
{
"EnvName": "10楼刀片机",
"ConfigName": "",
"LinuxEnvParam": null,
"DockerPort": null,
"DockerEnvName": null,
"DockerVolume": null,
"DockerOther": null
},
{
"EnvName": "阿里云_代理",
"ConfigName": "",
"LinuxEnvParam": null,
"DockerPort": null,
"DockerEnvName": null,
"DockerVolume": null,
"DockerOther": null
}
]
},
"DockerConfig": {
"Prot": "9040",
"AspNetCoreEnv": "",
"LastEnvName": "阿里云_代理",
"RemoveDaysFromPublished": "10",
"WorkDir": "/home/heyang/",
"Volume": "/home/hy/VideoAnalysis/AICore:/app/AICore/_Static;/home/hy/VideoAnalysis/TaskCachedFile:/app/TaskCachedFile",
"Other": "-e va_args=\"\" --name videoanalysis",
"EnvPairList": [
{
"EnvName": "10楼刀片机",
"ConfigName": null,
"LinuxEnvParam": null,
"DockerPort": "9040",
"DockerEnvName": "",
"DockerVolume": "/home/hy/VideoAnalysis/AICore:/app/AICore/_Static;/home/hy/VideoAnalysis/TaskCachedFile:/app/TaskCachedFile",
"DockerOther": "-e va_args=\"\""
},
{
"EnvName": "阿里云_代理",
"ConfigName": null,
"LinuxEnvParam": null,
"DockerPort": "9040",
"DockerEnvName": "",
"DockerVolume": "/home/hy/VideoAnalysis/AICore:/app/AICore/_Static;/home/hy/VideoAnalysis/TaskCachedFile:/app/TaskCachedFile",
"DockerOther": "-e va_args=\"\" --name videoanalysis"
}
]
},
"DockerImageConfig": {
"BaseHttpProxy": "",
"BaseImage": "",
"BaseImageCredential": {
"UserName": "",
"Password": ""
},
"TargetImage": "",
"TargetHttpProxy": "",
"TargetTags": [
""
],
"TargetImageCredential": {
"UserName": "",
"Password": ""
},
"ImageFormat": "Docker",
"Entrypoint": [
""
],
"Cmd": [
""
],
"IgnoreList": [],
"SkipExistingImages": false
}
}

View File

@ -1,15 +0,0 @@
FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base
WORKDIR /app
EXPOSE 9040
COPY . .
#设置时间为中国上海 环境为开发环境
ENV TZ=Asia/Shanghai
# 给我们要传的参数一个初始值
ENV va_args=
ENV ASPNETCORE_URLS=http://+:9040
ENTRYPOINT dotnet Learn.VideoAnalysis.API.dll $va_args

View File

@ -1,36 +0,0 @@
using Coravel;
using Coravel.Scheduling.Schedule;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VideoAnalysisCore.Job;
namespace Learn.VideoAnalysis.API.Expand
{
public static class CoravelExpand
{
public static void AddCoravel(this IServiceCollection service)
{
Console.WriteLine($"{DateTime.Now}=>初始化 Coravel");
service.AddScheduler();
//service.AddTransient<TaskFileClearJob>();
//service.AddTransient<ClearAllCacheJob>();
service.AddTransient<NodePackageJob>();
}
public static void UseCoravelExpand(this IApplicationBuilder provider)
{
provider.ApplicationServices.UseScheduler(scheduler =>
{
//文件包分析
//scheduler.Schedule<NodePackageJob>().EveryMinute(); //每分钟执行一次
scheduler.Schedule<NodePackageJob>().EveryThirtyMinutes(); //每30分钟执行一次
});
}
}
}

View File

@ -1,288 +0,0 @@
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System;
using System.Linq;
using Microsoft.AspNetCore.Http;
using SqlSugar;
using Microsoft.AspNetCore.Mvc.Infrastructure;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System.Threading.Tasks;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Text.Json;
using System.Collections.Generic;
using System.Data;
using Microsoft.Extensions.Hosting;
using System.IO;
using Microsoft.AspNetCore.Hosting;
using SqlSugar.IOC;
using Microsoft.AspNetCore.Authorization;
using VideoAnalysisCore.Common.Dto;
using VideoAnalysisCore.Common;
using VideoAnalysisCore.Model;
namespace Learn.VideoAnalysis.API.Expand
{
/// <summary>
/// 使用该属性,接口对结果原样输出,不做包装
/// </summary>
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = false)]
public class ResultIgnore : Attribute { }
/// <summary>
/// http接口日志启用
/// </summary>
public class HttpLogEnable : Attribute { }
/// <summary>
/// Http请求过滤器
/// </summary>
public class HttpLogAttribute : ActionFilterAttribute, IAsyncExceptionFilter
{
readonly Stopwatch _stopwatch;//统计程序耗时
public HttpLogAttribute()
{
_stopwatch = Stopwatch.StartNew();
}
/// <summary>
/// 执行接口前文件做缓存处理
/// </summary>
/// <param name="context"></param>
/// <exception cref="CustomException"></exception>
public void ExecutingCached(ActionExecutingContext context)
{
//特殊处理ResultIgnore不进行返回结果包装原样输出
var endpoint = context.HttpContext.GetEndpoint();
var request = context.HttpContext.Request;
// 1. 只有非 GET 请求且不是文件上传时才读取 Body
if (!request.Method.Equals("GET", StringComparison.OrdinalIgnoreCase)
&& !request.HasFormContentType)
{
// 确保从头开始读
request.Body.Position = 0;
// 使用 leaveOpen: true 防止 StreamReader 关闭底层的 Request.Body
using (var reader = new System.IO.StreamReader(request.Body, System.Text.Encoding.UTF8, detectEncodingFromByteOrderMarks: false, bufferSize: 1024, leaveOpen: true))
{
var body = reader.ReadToEnd();
context.HttpContext.Items["RequestBodyRaw"] = body;
request.Body.Position = 0;
}
}
// 直接返回原始结果,不封装
if (endpoint?.Metadata.GetMetadata<HttpLogEnable>() == null) return;
if (context.HttpContext.Request.HasFormContentType &&
context.HttpContext.Request.Form.Files != null &&
context.HttpContext.Request.Form.Files.Count() > 0)
{
context.HttpContext.Items["FileCached"]=
context.HttpContext.Request.Form.Files.Select(s =>
{
var stream = new MemoryStream();
s.CopyTo(stream);
stream.Position = 0;
return (s, stream);
}).ToArray();
}
}
/// <summary>
/// 执行接口前400 处理
/// </summary>
/// <param name="context"></param>
/// <exception cref="CustomException"></exception>
public void Executing400(ActionExecutingContext context)
{
if (!context.ModelState.IsValid)
{
var errMsg = string.Join(',', context.ModelState.Values.SelectMany(s => s.Errors.Select(e => e.ErrorMessage)));
Oh.ModelError(errMsg);
}
}
private bool HasAttribute<T>(ActionExecutedContext context) where T : Attribute
{
if (context.ActionDescriptor is ControllerActionDescriptor descriptor)
{
// 检查方法上是否有 SkipApiResultAttribute
if (descriptor.MethodInfo.GetCustomAttributes(typeof(T), false).Any())
return true;
// 检查控制器上是否有 SkipApiResultAttribute
if (descriptor.ControllerTypeInfo.GetCustomAttributes(typeof(T), false).Any())
return true;
}
return false;
}
/// <summary>
/// 接口结果格式化
/// </summary>
/// <param name="context"></param>
public BaseReturn<object>? ApiResultFormatting(ActionExecutedContext context)
{
//特殊处理ResultIgnore不进行返回结果包装原样输出
if (HasAttribute<ResultIgnore>(context))
{
base.OnActionExecuted(context);
return null;
}
// 返回结果为JsonResult的请求进行Result包装
if (context.Exception != null)
throw context.Exception;
if (context.Result != null)
{
object? resData = null;
if (context.Result is ObjectResult objectResult)
resData = objectResult.Value;
else if (context.Result is ContentResult contentRes)
resData = contentRes.Content;
else if (context.Result is JsonResult resJ)
resData = resJ.Value;
else if (context.Result is FileResult)
return null;
var code = (context?.Result as IStatusCodeActionResult)?.StatusCode ?? 200;
var res = new BaseReturn<object>()
{
Code = code,
Data = resData,
Message = "SUCCESS"
};
//不对返回结果结果做修改
//context.Result = new JsonResult(res);
return res;
}
return null;
}
/// <summary>
/// 添加http日志信息
/// </summary>
/// <param name="context"></param>
/// <param name="result"></param>
/// <param name="e"></param>
/// <returns></returns>
public async Task AddHttpLogAsync(HttpContext context, BaseReturn<object>? result = null, Exception? e = null)
{
string request = null;
var logId = Yitter.IdGenerator.YitIdHelper.NextId();
if (!context.Request.Method.Equals("GET", StringComparison.InvariantCultureIgnoreCase))
{
var fileArr = context.Items.ContainsKey("FileCached") ? context.Items["FileCached"] as (IFormFile file, MemoryStream stream)[] : null;
if (context.Request.HasFormContentType && fileArr != null)
{
string uploadsFolder = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "UploadLogs", logId.ToString());
if (!Directory.Exists(uploadsFolder))
Directory.CreateDirectory(uploadsFolder);
foreach (var fileInfo in fileArr)
{
string uniqueFileName = Guid.NewGuid().ToString().Substring(0, 5) + "_" + Path.GetFileName(fileInfo.file.FileName);
using var stream = new FileStream(Path.Combine(uploadsFolder, uniqueFileName), FileMode.Create, FileAccess.Write);
fileInfo.stream.Position = 0;
await fileInfo.stream.CopyToAsync(stream);
fileInfo.stream.Dispose();
}
request = $"请求体包含{context.Request.Form.Files.Count()}个文件 目录 {uploadsFolder}";
}
}
if (context.Items.ContainsKey("RequestBodyRaw"))
{
request = context.Items["RequestBodyRaw"] as string;
}
//写入队列
await DbScoped.Sugar.CopyNew()
.Insertable<HttpLog>(new HttpLog
{
Id = logId,
Url = context.Request.Path + context.Request.QueryString,
Method = context.Request.Method,
Request = request,
IP = GetClientIp(context),
ResponseCode = result?.Code ?? -1,
Response = (result != null ? JsonSerializer.Serialize(result) : null) ,
Authorization = context.Request.Headers.ContainsKey("Authorization")
? context.Request.Headers["Authorization"].ToString()
: string.Empty,
Exception = e?.ToString(),
ExceptionMessage = e?.Message,
AdminId = 0,
TotalMilliseconds = (double)_stopwatch.Elapsed.TotalMilliseconds
}).ExecuteCommandAsync();
}
private string GetClientIp(HttpContext context)
{
var headers = context.Request.Headers;
var ip = headers["X-Forwarded-For"].FirstOrDefault();
if (!string.IsNullOrWhiteSpace(ip))
{
ip = ip.Split(',', StringSplitOptions.RemoveEmptyEntries).FirstOrDefault()?.Trim();
}
if (string.IsNullOrWhiteSpace(ip))
{
ip = headers["X-Real-IP"].FirstOrDefault();
}
if (string.IsNullOrWhiteSpace(ip) && context.Connection.RemoteIpAddress != null)
{
ip = context.Connection.RemoteIpAddress.ToString();
}
return ip ?? string.Empty;
}
public override async void OnActionExecuting(ActionExecutingContext context)
{
Executing400(context);
ExecutingCached(context);
}
/// <summary>
/// 在Controller的Action执行后执行
/// </summary>
/// <param name="context"></param>
public override async void OnActionExecuted(ActionExecutedContext context)
{
try
{
BaseReturn<object>? res = ApiResultFormatting(context);
await AddHttpLogAsync(context.HttpContext, res);
}
catch (Exception ex)
{
Console.WriteLine($"{DateTime.Now}=>接口出现异常");
Console.WriteLine(ex.Message);
Console.WriteLine(ex.StackTrace);
}
base.OnActionExecuted(context);
}
/// <summary>
/// 执行错误时
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
public async Task OnExceptionAsync(ExceptionContext context)
{
var code = -1;
var msg = context.Exception.Message;
if (context.Exception is OhException exception)
code = exception.Code;
var result = new BaseReturn()
{
Code = code,
Message = context.Exception.Message
};
context.Result = new JsonResult(result);
await AddHttpLogAsync(context.HttpContext, null, context.Exception);
if (code == 401 || code == 403)
context.HttpContext.Response.StatusCode = code;
context.ExceptionHandled = true;
}
}
}

View File

@ -1,41 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
</PropertyGroup>
<ItemGroup>
<None Remove="Dockerfile" />
</ItemGroup>
<ItemGroup>
<Content Include="Dockerfile">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.2-pre01" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\VideoAnalysisCore\VideoAnalysisCore.csproj" />
</ItemGroup>
<ItemGroup>
<Content Update="AntDeploy.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.Production.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>

View File

@ -1,6 +0,0 @@
@Learn.VideoAnalysis.API_HostAddress = http://localhost:5245
GET {{Learn.VideoAnalysis.API_HostAddress}}/weatherforecast/
Accept: application/json
###

View File

@ -1,92 +0,0 @@

using Learn.VideoAnalysis.API.Expand;
using Mapster;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.OpenApi.Models;
using System.Text.Encodings.Web;
using System.Text.Json;
using System.Text.Unicode;
using VideoAnalysisCore.AICore.FFMPGE;
using VideoAnalysisCore.AICore.GPT.DeepSeek;
using VideoAnalysisCore.AICore.SherpaOnnx;
using VideoAnalysisCore.Common;
using VideoAnalysisCore.Common.Expand;
namespace Learn.VideoAnalysis.API
{
public class Program
{
public static void Main(string[] args)
{
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddControllers(options =>
{
// 全局模型赋值默认值 和 统一返回格式处理
options.Filters.Add<HttpLogAttribute>();
}).AddJsonOptions(options =>
{
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);//中文转换时不使用Unicode
//options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;// 默认小驼峰 null 大驼峰
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
{
var file = Path.Combine(AppContext.BaseDirectory, "Learn.VideoAnalysis.API.xml"); // xml文档绝对路径
var file1 = Path.Combine(AppContext.BaseDirectory, "VideoAnalysisCore.xml"); // xml文档绝对路径
c.IncludeXmlComments(file, true); // true : 显示控制器层注释
c.IncludeXmlComments(file1, true); // true : 显示控制器层注释
c.OrderActionsBy(o => o.RelativePath); // 对action的名称进行排序如果有多个就可以看见效果了。
});
builder.Services.AddMapster();
//初始化 插件
builder.AddAppConfig(args);
builder.Services.AddHttpClient();
builder.Services.AddSqlSugarExpand();
builder.Services.AddRedisExpand(false);
builder.Services.AddAlibabaCloudVod();
builder.Services.AddCoravel();
builder.Services.AddCorsExpand();
builder.Services.AddControllersWithViews(options =>
{
options.Filters.Add(typeof(ExceptionFilter));
});
var app = builder.Build();
AppCommon.Services = app.Services;
app.UseMiddleware<BasicAuthMiddleware>("Swagger");
// Configure the HTTP request pipeline.
app.UseSwagger();
app.UseSwaggerUI();
app.UseHttpsRedirection();
app.MapControllers();
//自定义 应用
app.UseCorsExpand();
app.UseSqlSugarExpand();
app.UseCoravelExpand();
// 注册启动后的回调
app.UseServiceSystem(null,false);
app.Run();
}
}
}

View File

@ -1,15 +0,0 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"http:5238": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": true,
"launchUrl": "swagger",
"applicationUrl": "http://localhost:5238",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}

View File

@ -1,44 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Error",
"Microsoft.AspNetCore": "Error"
}
},
"AppConfig": {
"TaskSetting": {
"DownloadSpeed": 8,
"IS_Server": true //?
},
"Subsystem": {
"蓝鲸智库": {
"APIUrl": "https://zyapi.23544.com",
"Token": ""
}
},
"Redis": {
"ConnectionString": "redis-external.23544.com:16379,password=poiuyt)(*&^%,defaultDatabase=5"
},
"FFmpeg": {
" TimeSlice": 600
},
"DB": {
//"ConnectionString": "AllowLoadLocalInfile=true;Server=10.255.255.3;Port=3306;Database=learn.videoanalysis;User ID=marking;Password=qwe123!@#;CharSet=utf8mb4;pooling=true;SslMode=None",
"ConnectionString": "AllowLoadLocalInfile=true;Server=rm-2vc20nd3d11g0oh6g2o.rwlb.cn-chengdu.rds.aliyuncs.com;User ID=marking;Password=poiuytPOIUYT098765)(*&^%;Port=3306;Database=learn.videoanalysis;CharSet=utf8mb4;pooling=true;SslMode=None",
"SqlType": "MySql",
"UpdateTable": false
},
"OtherDBArr": [
{ // 线
"ConfigId": 1001, //ResourceBank
"ConnectionString": "AllowLoadLocalInfile=true;Server=rm-2vc20nd3d11g0oh6g2o.rwlb.cn-chengdu.rds.aliyuncs.com;User ID=marking;Password=poiuytPOIUYT098765)(*&^%;Port=3306;Database=resourcebank;CharSet=utf8mb4;pooling=true;SslMode=None",
"SqlType": "MySql"
},
{
"ConfigId": 1002, //App.public.live
"ConnectionString": "AllowLoadLocalInfile=true;Server=rm-2vc20nd3d11g0oh6g2o.rwlb.cn-chengdu.rds.aliyuncs.com;User ID=marking;Password=poiuytPOIUYT098765)(*&^%;Port=3306;Database=app.public.live;CharSet=utf8mb4;pooling=true;SslMode=None",
"SqlType": "MySql"
}
]
}
}

View File

@ -1,89 +0,0 @@
{
"Logging": {
"LogLevel": {
"Default": "Warning",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"AppConfig": {
"ID": 1, //
"TaskSetting": {
"DownloadSpeed": 2,
"IS_Server": true //
},
"Admin": {
"Account": "admin",
"Password": "q1w2e3!@#"
},
"Subsystem": {
"蓝鲸智库": {
"APIUrl": "http://192.168.2.117:6400",
"Token": ""
}
},
"SimpLetex": {
"Host": "https://server.simpletex.cn/api/",
"AppSecret": "05ZbPfCFZgTmfd4uIqHHc9pHgYR2V8bk",
"AppId": "GH2OXwuxSZEH5W28H61bdSzD"
},
"Redis": {
"ConnectionString": "127.0.0.1:6379,password=Woshiren123,defaultDatabase=10"
},
"Whisper": {
"ModelName": "ggml-small.bin"
},
"FFmpeg": {
" TimeSlice": 600
},
"ChatGpt": {
"KIMI": {
"Host": "https://api.moonshot.cn",
"ApiKey": "sk-8BvvhESZIkgUbiaaJhglPxFa4o2X9H3xEv9lXELrWWwGxHWY"
},
"ChatGpt": {
"Host": "https://api.oaibest.com/",
"ApiKey": "sk-D15tBln31N7dI9Fi7lds7OySFv5tOEK7DMNsG5rY2E6DCr4s"
},
"DeepSeek": {
"Host": "https://api.deepseek.com/chat/completions",
"ApiKey": "sk-88d3d2bc3dae4d50854b2569b281cf76"
},
"aliyun": {
"Host": "https://dashscope.aliyuncs.com/compatible-mode/",
"ApiKey": "sk-1742c2bf7b9846ae835de598dc6c427b"
}
},
"DB": {
"ConnectionString": "AllowLoadLocalInfile=true;Server=192.168.2.9;User ID=root;Password=qwe123!@#;Port=3306;Database=learn.videoanalysis;CharSet=utf8mb4;pooling=true;SslMode=None",
//"ConnectionString": "AllowLoadLocalInfile=true;Server=rm-2vc20nd3d11g0oh6g2o.rwlb.cn-chengdu.rds.aliyuncs.com;User ID=marking;Password=poiuytPOIUYT098765)(*&^%;Port=3306;Database=learn.videoanalysis;CharSet=utf8mb4;pooling=true;SslMode=None",
"SqlType": "MySql",
"UpdateTable": false
},
"AlibabaCloudVod": {
"AccessKeyId": "LTAI5tFLXyC3ixVdxhxLih8K",
"AccessKeySecret": "dlGu3WMoW0XQaoAYxiCPpnxry6qLhB",
"EndPoint": "vod.cn-shanghai.aliyuncs.com" //
},
"AliyunOSS": {
"AccessKeyId": "LTAI5tFLXyC3ixVdxhxLih8K",
"AccessKeySecret": "dlGu3WMoW0XQaoAYxiCPpnxry6qLhB",
"BucketDomain": "https://learn-videoanalysis.oss-cn-chengdu.aliyuncs.com",
"Region": "cn-chengdu",
"BucketName": "learn-videoanalysis",
"EndPoint": "oss-cn-chengdu.aliyuncs.com" //
},
"OtherDBArr": [
{ // 线
"ConfigId": 1001, //ResourceBank
"ConnectionString": "AllowLoadLocalInfile=true;Server=rm-2vc20nd3d11g0oh6g2o.rwlb.cn-chengdu.rds.aliyuncs.com;User ID=marking;Password=poiuytPOIUYT098765)(*&^%;Port=3306;Database=resourcebank;CharSet=utf8mb4;pooling=true;SslMode=None",
"SqlType": "MySql"
},
{
"ConfigId": 1002, //App.public.live
"ConnectionString": "AllowLoadLocalInfile=true;Server=rm-2vc20nd3d11g0oh6g2o.rwlb.cn-chengdu.rds.aliyuncs.com;User ID=marking;Password=poiuytPOIUYT098765)(*&^%;Port=3306;Database=app.public.live;CharSet=utf8mb4;pooling=true;SslMode=None",
"SqlType": "MySql"
}
]
}
}

View File

@ -7,8 +7,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Learn.VideoAnalysis", "Vide
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VideoAnalysisCore", "VideoAnalysisCore\VideoAnalysisCore.csproj", "{69F4243A-B22E-431B-8F0B-ECD8729B8665}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "VideoAnalysisCore", "VideoAnalysisCore\VideoAnalysisCore.csproj", "{69F4243A-B22E-431B-8F0B-ECD8729B8665}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Learn.VideoAnalysis.API", "Learn.VideoAnalysis.API\Learn.VideoAnalysis.API.csproj", "{D31BA4AB-73FC-47B1-A10A-34FD5E921F4A}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -23,10 +21,6 @@ Global
{69F4243A-B22E-431B-8F0B-ECD8729B8665}.Debug|Any CPU.Build.0 = Debug|Any CPU {69F4243A-B22E-431B-8F0B-ECD8729B8665}.Debug|Any CPU.Build.0 = Debug|Any CPU
{69F4243A-B22E-431B-8F0B-ECD8729B8665}.Release|Any CPU.ActiveCfg = Release|Any CPU {69F4243A-B22E-431B-8F0B-ECD8729B8665}.Release|Any CPU.ActiveCfg = Release|Any CPU
{69F4243A-B22E-431B-8F0B-ECD8729B8665}.Release|Any CPU.Build.0 = Release|Any CPU {69F4243A-B22E-431B-8F0B-ECD8729B8665}.Release|Any CPU.Build.0 = Release|Any CPU
{D31BA4AB-73FC-47B1-A10A-34FD5E921F4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D31BA4AB-73FC-47B1-A10A-34FD5E921F4A}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D31BA4AB-73FC-47B1-A10A-34FD5E921F4A}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D31BA4AB-73FC-47B1-A10A-34FD5E921F4A}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
GlobalSection(SolutionProperties) = preSolution GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE HideSolutionNode = FALSE

View File

@ -1,13 +0,0 @@
{
"version": 1,
"isRoot": true,
"tools": {
"dotnet-ef": {
"version": "10.0.3",
"commands": [
"dotnet-ef"
],
"rollForward": false
}
}
}

View File

@ -5,12 +5,12 @@
"ServerList": [], "ServerList": [],
"LinuxServerList": [ "LinuxServerList": [
{ {
"UserName": "heyang", "UserName": "hy",
"Pwd": "9718CB3C9A0760CA326767D677ADEC1C", "Pwd": "E9D1AC136FDD59740A9595ABD0EB953A",
"Host": "10.127.127.107", "Host": "192.168.2.21:10107",
"NickName": "10楼刀片机", "NickName": "",
"IIsFireUrl": null, "IIsFireUrl": null,
"DockerFireUrl": "", "DockerFireUrl": "https://videoanalysis.w.23544.com:8843/",
"WindowsServiceFireUrl": null, "WindowsServiceFireUrl": null,
"LinuxServiceFireUrl": null "LinuxServiceFireUrl": null
} }
@ -19,24 +19,6 @@
"ffmpeg.exe" "ffmpeg.exe"
], ],
"WindowsBackUpIgnoreList": [] "WindowsBackUpIgnoreList": []
},
{
"Name": "阿里云_代理",
"ServerList": [],
"LinuxServerList": [
{
"UserName": "heyang",
"Pwd": "AAC53130AF118B652BCED77C39B959F9",
"Host": "10.127.127.77:10022",
"NickName": "",
"IIsFireUrl": null,
"DockerFireUrl": "",
"WindowsServiceFireUrl": null,
"LinuxServiceFireUrl": null
}
],
"IgnoreList": [],
"WindowsBackUpIgnoreList": []
} }
], ],
"IIsConfig": { "IIsConfig": {
@ -52,15 +34,6 @@
"DockerEnvName": null, "DockerEnvName": null,
"DockerVolume": null, "DockerVolume": null,
"DockerOther": null "DockerOther": null
},
{
"EnvName": "阿里云_代理",
"ConfigName": "",
"LinuxEnvParam": null,
"DockerPort": null,
"DockerEnvName": null,
"DockerVolume": null,
"DockerOther": null
} }
] ]
}, },
@ -77,15 +50,6 @@
"DockerEnvName": null, "DockerEnvName": null,
"DockerVolume": null, "DockerVolume": null,
"DockerOther": null "DockerOther": null
},
{
"EnvName": "阿里云_代理",
"ConfigName": "",
"LinuxEnvParam": null,
"DockerPort": null,
"DockerEnvName": null,
"DockerVolume": null,
"DockerOther": null
} }
] ]
}, },
@ -102,15 +66,6 @@
"DockerEnvName": null, "DockerEnvName": null,
"DockerVolume": null, "DockerVolume": null,
"DockerOther": null "DockerOther": null
},
{
"EnvName": "阿里云_代理",
"ConfigName": "",
"LinuxEnvParam": null,
"DockerPort": null,
"DockerEnvName": null,
"DockerVolume": null,
"DockerOther": null
} }
] ]
}, },
@ -119,9 +74,9 @@
"AspNetCoreEnv": "", "AspNetCoreEnv": "",
"LastEnvName": "10楼刀片机", "LastEnvName": "10楼刀片机",
"RemoveDaysFromPublished": "10", "RemoveDaysFromPublished": "10",
"WorkDir": "/home/heyang/", "WorkDir": "",
"Volume": "/home/hy/VideoAnalysis/AICore:/app/AICore/_Static;/mnt/2tb/VideoAnalysis/TaskCachedFile:/app/TaskCachedFile", "Volume": "/home/hy/VideoAnalysis/AICore:/app/AICore/_Static;/home/hy//VideoAnalysis/TaskCachedFile:/app/TaskCachedFile",
"Other": "-e va_args=\"\"", "Other": "",
"EnvPairList": [ "EnvPairList": [
{ {
"EnvName": "10楼刀片机", "EnvName": "10楼刀片机",
@ -129,17 +84,8 @@
"LinuxEnvParam": null, "LinuxEnvParam": null,
"DockerPort": "9040", "DockerPort": "9040",
"DockerEnvName": "", "DockerEnvName": "",
"DockerVolume": "/home/hy/VideoAnalysis/AICore:/app/AICore/_Static;/mnt/2tb/VideoAnalysis/TaskCachedFile:/app/TaskCachedFile", "DockerVolume": "/home/hy/VideoAnalysis/AICore:/app/AICore/_Static;/home/hy//VideoAnalysis/TaskCachedFile:/app/TaskCachedFile",
"DockerOther": "-e va_args=\"\"" "DockerOther": ""
},
{
"EnvName": "阿里云_代理",
"ConfigName": null,
"LinuxEnvParam": null,
"DockerPort": "9040",
"DockerEnvName": "",
"DockerVolume": "/home/hy/VideoAnalysis/AICore:/app/AICore/_Static;/home/hy/VideoAnalysis/TaskCachedFile:/app/TaskCachedFile",
"DockerOther": "-e va_args=\"IS_Server\" --name videoanalysis"
} }
] ]
}, },

View File

@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link href="_content/AntDesign/css/ant-design-blazor.css" rel="stylesheet" />
<link href="_content/AntDesign.ProLayout/css/ant-design-pro-layout-blazor.css" rel="stylesheet" />
<link href="Learn.VideoAnalysis.styles.css" rel="stylesheet" />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet @rendermode="InteractiveServer" />
</head>
<body>
<Routes @rendermode="InteractiveServer" />
<script type="text/javascript" src="@("https://unpkg.com/@antv/g2plot@2.4.17/dist/g2plot.min.js")"></script>
<script src="_content/AntDesign/js/ant-design-blazor.js"></script>
<script src="_content/AntDesign.Charts/ant-design-charts-blazor.js"></script>
<script src="_framework/blazor.web.js"></script>
</body>
</html>

View File

@ -0,0 +1,36 @@
@page "/Error"
@using System.Diagnostics
<PageTitle>Error</PageTitle>
<h1 class="text-danger">错误页面.</h1>
<h2 class="text-danger">处理您的请求时出错。</h2>
@if (ShowRequestId)
{
<p>
<strong>Request ID:</strong> <code>@RequestId</code>
</p>
}
<h3>开发模式</h3>
<p>
切换到<strong>Development</strong>环境将显示有关发生的错误的更多详细信息。
</p>
<p>
<strong> 不应为已部署的应用程序启用开发环境。</strong>
它可能导致向最终用户显示来自异常的敏感信息。
对于本地调试,通过将 <strong>ASPNETCORE_ENVIRONMENT</strong> 环境变量设置为 <strong>Development</strong> 来启用 <strong> 开发 </strong> 环境
并重新启动应用程序。
</p>
@code{
[CascadingParameter]
private HttpContext? HttpContext { get; set; }
private string? RequestId { get; set; }
private bool ShowRequestId => !string.IsNullOrEmpty(RequestId);
protected override void OnInitialized() =>
RequestId = Activity.Current?.Id ?? HttpContext?.TraceIdentifier;
}

View File

@ -0,0 +1,35 @@
@namespace VideoAnalysisRazor.Layouts
@inherits LayoutComponentBase
<AntDesign.ProLayout.BasicLayout
Logo="@("https://gw.alipayobjects.com/zos/rmsportal/KDpgvguMpGfqaHPjicRK.svg")"
MenuData="_menuData"
Context="学习视频分析"
MenuAccordion
Title="学习视频分析"
@bind-Collapsed="collapsed">
<HeaderContentRender>
<Space Size="@("24")">
<SpaceItem>
<Icon Class="action" Type="@(collapsed?"menu-unfold":"menu-fold")" OnClick="Toggle" />
</SpaceItem>
<SpaceItem>
<Icon Class="action" Type="reload" Theme="outline" OnClick="Reload" />
</SpaceItem>
<SpaceItem>
<Icon Type="api" Theme="outline" OnClick="ToSwagger" />
</SpaceItem>
</Space>
</HeaderContentRender>
<RightContentRender>
</RightContentRender>
<ChildContent>
<ReuseTabs></ReuseTabs>
</ChildContent>
<FooterRender>
<FooterView Copyright="2024 重庆远轩教育科技有限公司" Links="new LinkItem[0]"></FooterView>
</FooterRender>
</AntDesign.ProLayout.BasicLayout>
<SettingDrawer />

View File

@ -0,0 +1,89 @@
using AntDesign.Extensions.Localization;
using AntDesign.ProLayout;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
using Microsoft.Identity.Client.Extensions.Msal;
using Microsoft.JSInterop;
using System.Globalization;
using System.Net.Http.Json;
namespace VideoAnalysisRazor.Layouts
{
public partial class BasicLayout : LayoutComponentBase, IDisposable
{
private MenuDataItem[] _menuData;
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
[Inject] IHttpContextAccessor HttpContextAccessor { get; set; } = default!;
[Inject] private ReuseTabsService TabService { get; set; }
[Inject] private IJSRuntime JSRuntime { get; set; }
[Inject] private ProtectedSessionStorage session { get; set; } = default!;
bool collapsed;
void Toggle()
{
collapsed = !collapsed;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (!await CheckLogin())
{
NavigationManager.NavigateTo("/Login");
}
}
protected override async Task OnInitializedAsync()
{
_menuData = [
new MenuDataItem
{
Path = "/",
Name = "任务队列",
Key = "VideoTaskPage",
Icon = "unordered-list",
},
new MenuDataItem
{
Path = "/Project",
Name = "课堂指标",
Key = "EvaluationProject",
Icon = "question-circle",
},
new MenuDataItem
{
Path = "/Login",
Name = "登录页",
Key = "Login",
HideInMenu = true,
}
];
}
public async Task<bool> CheckLogin()
{
try
{
return (await session.GetAsync<bool>("Login")).Value;
}
catch
{
return false;
}
}
void Reload()
{
TabService.ReloadPage();
}
async Task ToSwagger()
{
await JSRuntime.InvokeVoidAsync("open", "/swagger/index.html", "_blank");
}
public void Dispose()
{
}
}
}

View File

@ -0,0 +1,77 @@
@page "/Project"
@using AntDesign
@using AntDesign.TableModels
@using System.ComponentModel.DataAnnotations
@using SqlSugar
@using VideoAnalysisCore.Model
<Table @ref="_table" Loading="tableLoading" TItem="CourseGradingCriteria" ScrollY="600px" PageSize="15" Total="_total" DataSource="_dataSource" @bind-SelectedRows="_selectedRows" OnChange="OnChange">
<TitleTemplate>
<Flex Justify="end" Gap="10">
<Button Type="primary" @onclick="()=> StartEdit(default)">新增</Button>
@* <Button Disabled="!_selectedRows.Any()" Danger @onclick="DeleteAll">Delete</Button> *@
</Flex>
</TitleTemplate>
<ColumnDefinitions Context="row">
<ActionColumn Title="操作列" Width ="230px">
<a @onclick="() => StartEdit(row)">修改</a>
<Button Type="@ButtonType.Link" Danger @onclick="() => Delete(row)">
删除</Button>
</ActionColumn>
<PropertyColumn Property="c=>c.Id" Width="130px" Filterable="true" Sortable="true" />
<PropertyColumn Property="c=>c.NamePrompt" />
</ColumnDefinitions>
</Table>
@inject ModalService ModalService
@code
{
/// <summary>
/// 新增或者修改
/// </summary>
/// <param name="row"></param>
void StartEdit(CourseGradingCriteria row)
{
var data = row == null ? new() : row;
IForm? form = default;
ModalRef<bool> modalRef = default;
modalRef = ModalService.CreateModal<bool>(new()
{
Title = data.Id > 0 ? "修改" : "新增",
Content =
@<Form @ref="form" Model="data" OnFinish="()=> modalRef.OkAsync(true)"
LabelColSpan="6" WrapperColSpan="18">
<FormItem Label="标准提问词" >
<TextArea Rows="4" @bind-Value="@context.NamePrompt" />
</FormItem>
</Form>
,
OkText = "确定",
CancelText = "取消",
OnOk = async (e) =>
{
if (!form.Validate())
return;
// save db and refresh
modalRef.SetConfirmLoading(true);
if (data.Id > 0)
await criteria.UpdateAsync(data);
else
data.Id = await criteria.InsertReturnBigIdentityAsync(data);
//弹窗按钮 show
modalRef.SetConfirmLoading(false);
await modalRef.CloseAsync();
_table.ReloadData();
StateHasChanged();
},
OnCancel = async (e) =>
{
if (form.IsModified && (!await Comfirm("表格已经更新,您确定要退出吗?")))
return;
await modalRef.CloseAsync();
}
});
}
}

View File

@ -0,0 +1,70 @@
using AntDesign.TableModels;
using Microsoft.AspNetCore.Components;
using SqlSugar;
using System.Linq.Expressions;
using VideoAnalysisCore.Common;
using VideoAnalysisCore.Model;
namespace Learn.VideoAnalysis.Components.Pages
{
public partial class EvaluationProject : ComponentBase
{
[Inject] private ConfirmService ComfirmService { get; set; } = default!;
[Inject] private Repository<CourseGradingCriteria> criteria { get; set; } = default!;
IEnumerable<CourseGradingCriteria> _selectedRows = [];
ITable _table;
List<CourseGradingCriteria> _dataSource = null;
RefAsync<int> _total = 0;
bool tableLoading = false;
/// <summary>
/// 分页 查询 筛选 时
/// </summary>
/// <param name="query"></param>
async void OnChange(QueryModel<CourseGradingCriteria> query)
{
tableLoading = true;
List<IConditionalModel> where = default!;
if (query.FilterModel != null && ((query.FilterModel?.Count() ?? 0) > 0))
{
where = query.ToSqlSugerWhere();
}
_dataSource = await criteria.AsQueryable()
.Where(where)
.ToPageListAsync(query.PageIndex, query.PageSize, _total);
tableLoading = false;
StateHasChanged();
}
/// <summary>
/// 删除行
/// </summary>
/// <param name="row"></param>
/// <returns></returns>
async Task Delete(CourseGradingCriteria row)
{
if (!await Comfirm($"确定要删除这条数据吗? [{row.NamePrompt}]?"))
return;
await criteria.DeleteByIdAsync(row.Id);
_table.ReloadData();
}
/// <summary>
/// 初始化
/// </summary>
protected override void OnInitialized()
{
}
private async Task<bool> Comfirm(string message)
{
return await ComfirmService.Show(message, "提示", ConfirmButtons.YesNo, ConfirmIcon.Warning) == ConfirmResult.Yes;
}
}
}

View File

@ -0,0 +1,3 @@
input[aria-hidden="true"] {
display: none !important;
}

View File

@ -0,0 +1,54 @@
@page "/Login"
@using AntDesign
@using AntDesign.TableModels
@using System.ComponentModel.DataAnnotations
@using SqlSugar
@using VideoAnalysisCore.Model
@using VideoAnalysisCore.Model.Dto
@attribute [ReuseTabsPage(Ignore = true)]
<section style="width:100%;height:100%">
<!-- 背景颜色 -->
<div class="color"></div>
<div class="color"></div>
<div class="color"></div>
<div class="box">
<!-- 背景圆 -->
<div class="circle" style="--x:0"></div>
<div class="circle" style="--x:1"></div>
<div class="circle" style="--x:2"></div>
<div class="circle" style="--x:3"></div>
<div class="circle" style="--x:4"></div>
<!-- 登录框 -->
<div class="container">
<div class="form">
<h2>登录 视频分析平台</h2>
<div class="cform">
<div class="inputBox">
<input type="text" placeholder="账号" @bind="InputAccount">
</div>
<div class="inputBox">
<input type="password" placeholder="密码" @bind="InputPassword">
</div>
<div class="inputBox">
<input type="button" class="submit" value="登录" @onclick="() => LoginFunAsync()">
</div>
@* <p class="forget">
忘记密码?
<a href="#">
点击这里
</a>
</p> *@
@* <p class="forget">
没有账户?
<a href="#">
注册
</a>
</p> *@
</div>
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,72 @@
using AntDesign;
using AntDesign.TableModels;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using Microsoft.AspNetCore.Mvc;
using SqlSugar;
using System.Linq.Expressions;
using System.Threading.Tasks;
using VideoAnalysisCore.Common;
using VideoAnalysisCore.Enum;
using VideoAnalysisCore.Model;
using VideoAnalysisCore.Model.Dto;
namespace Learn.VideoAnalysis.Components.Pages
{
public partial class Login : ComponentBase
{
[Inject] IHttpContextAccessor HttpContextAccessor { get; set; } = default!;
[Inject] private Repository<VideoTask> taskDB { get; set; } = default!;
[Inject] private NavigationManager NavigationManager { get; set; } = default!;
[Inject] private INotificationService _notice { get; set; } = default!;
[Inject] private ProtectedSessionStorage session { get; set; } = default!;
/// <summary>
/// 输入的账号
/// </summary>
public string InputAccount = string.Empty;
/// <summary>
/// 输入的密码
/// </summary>
public string InputPassword= string.Empty;
/// <summary>
/// 初始化
/// </summary>
protected override void OnInitialized()
{
}
/// <summary>
/// 登录函数
/// </summary>
public async Task LoginFunAsync()
{
if (string.IsNullOrEmpty(InputAccount) || string.IsNullOrEmpty(InputPassword))
{
await _notice.Open(new NotificationConfig()
{
Message = "提示",
Description = "账号/密码必填",
NotificationType = NotificationType.Warning
});
}
if (InputAccount ==AppCommon.Config.Admin.Account && InputPassword == AppCommon.Config.Admin.Password)
{
await session.SetAsync("Login", true);
NavigationManager.NavigateTo("/");
}
else
{
await _notice.Open(new NotificationConfig()
{
Message = "提示",
Description = "账号/密码输入错误",
NotificationType = NotificationType.Warning
});
}
}
}
}

View File

@ -0,0 +1,242 @@
input[aria-hidden="true"] {
display: none !important;
}
/* 清除浏览器默认边距
使边框和内边距的值包含在元素的width和height内 */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
/* 使用flex布局让内容垂直和水平居中 */
section {
/* 相对定位 */
position: relative;
overflow: hidden;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
/* linear-gradient() 函数用于创建一个表示两种或多种颜色线性渐变的图片 */
background: linear-gradient(to bottom, #f1f4f9, #dff1ff);
}
/* 背景颜色 */
section .color {
/* 绝对定位 */
position: absolute;
/* 使用filter(滤镜) 属性,给图像设置高斯模糊*/
filter: blur(200px);
}
/* :nth-child(n) 选择器匹配父元素中的第 n 个子元素 */
section .color:nth-child(1) {
top: -350px;
width: 600px;
height: 600px;
background: #ff359b;
}
section .color:nth-child(2) {
bottom: -150px;
left: 100px;
width: 500px;
height: 500px;
background: #fffd87;
}
section .color:nth-child(3) {
bottom: 50px;
right: 100px;
width: 500px;
height: 500px;
background: #00d2ff;
}
.box {
position: relative;
}
/* 背景圆样式 */
.box .circle {
position: absolute;
background: rgba(255, 255, 255, 0.1);
/* backdrop-filter属性为一个元素后面区域添加模糊效果 */
backdrop-filter: blur(5px);
box-shadow: 0 25px 45px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.5);
border-right: 1px solid rgba(255, 255, 255, 0.2);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
border-radius: 50%;
/* 使用filter(滤镜) 属性改变颜色
hue-rotate(deg) 给图像应用色相旋转
calc() 函数用于动态计算长度值
var() 函数调用自定义的CSS属性值x*/
filter: hue-rotate(calc(var(--x) * 70deg));
/* 调用动画animate需要10s完成动画
linear表示动画从头到尾的速度是相同的
infinite指定动画应该循环播放无限次*/
animation: animate 10s linear infinite;
/* 动态计算动画延迟几秒播放 */
animation-delay: calc(var(--x) * -1s);
}
/* 背景圆动画 */
@keyframes animate {
0%, 100%, {
transform: translateY(-50px);
}
50% {
transform: translateY(50px);
}
}
.box .circle:nth-child(1) {
top: -50px;
right: -60px;
width: 100px;
height: 100px;
}
.box .circle:nth-child(2) {
top: 150px;
left: -100px;
width: 120px;
height: 120px;
z-index: 2;
}
.box .circle:nth-child(3) {
bottom: 50px;
right: -60px;
width: 80px;
height: 80px;
z-index: 2;
}
.box .circle:nth-child(4) {
bottom: -80px;
left: 100px;
width: 60px;
height: 60px;
}
.box .circle:nth-child(5) {
top: -80px;
left: 140px;
width: 60px;
height: 60px;
}
/* 登录框样式 */
.container {
position: relative;
width: 400px;
min-height: 400px;
height: 400px;
background: rgba(255, 255, 255, 0.1);
display: flex;
justify-content: center;
align-items: center;
backdrop-filter: blur(5px);
box-shadow: 0 25px 45px rgba(0, 0, 0, 0.1);
border: 1px solid rgba(255, 255, 255, 0.5);
border-right: 1px solid rgba(255, 255, 255, 0.2);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
}
.form {
position: relative;
width: 100%;
height: 100%;
padding: 50px;
background: rgba(255, 255, 255, 0.1);
}
/* 登录标题样式 */
.form h2 {
position: relative;
color: #fff;
font-size: 24px;
font-weight: 600;
letter-spacing: 5px;
margin-bottom: 30px;
cursor: pointer;
}
/* 登录标题的下划线样式 */
.form h2::before {
content: "";
position: absolute;
left: 0;
bottom: -10px;
width: 0px;
height: 3px;
background: #fff;
transition: 0.5s;
}
.form h2:hover:before {
width: 53px;
}
.form .inputBox {
width: 100%;
margin-top: 20px;
}
/* 输入框样式 */
.form .inputBox input {
width: 100%;
padding: 10px 20px;
background: rgba(255, 255, 255, 0.2);
outline: none;
border: none;
border-radius: 30px;
border: 1px solid rgba(255, 255, 255, 0.5);
border-right: 1px solid rgba(255, 255, 255, 0.2);
border-bottom: 1px solid rgba(255, 255, 255, 0.2);
font-size: 16px;
letter-spacing: 1px;
color: #fff;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
}
.form .inputBox input::placeholder {
color: #fff;
}
/* 登录按钮样式 */
.submit {
background: #fff !important;
color: #666 !important;
max-width: 100px;
margin-bottom: 20px;
font-weight: 600;
cursor: pointer !important;
}
.forget {
margin-top: 6px;
color: #fff;
letter-spacing: 1px;
}
.forget a {
color: #fff;
font-weight: 600;
text-decoration: none;
}

View File

@ -0,0 +1,100 @@
@page "/"
@using AntDesign
@using AntDesign.TableModels
@using System.ComponentModel.DataAnnotations
@using Learn.VideoAnalysis.Controllers.Dto
@using SqlSugar
@using VideoAnalysisCore.Model
@using VideoAnalysisCore.Model.Dto
@using VideoAnalysisCore.Enum
<Table @ref="_table" Loading="tableLoading" TItem="VideoTaskDto" ScrollY="600px" PageSize="10" Total="_total" DataSource="_dataSource"
OnRowClick="(r)=>r.Expanded = !r.Expanded"
@bind-SelectedRows="_selectedRows" OnChange="OnChange"
OnExpand="OnExpand">
<TitleTemplate>
<Flex Justify="end" Gap="10">
<Button Type="primary" @onclick="ShowErrorTask">错误任务</Button>
</Flex>
</TitleTemplate>
<ColumnDefinitions Context="row">
<Selection />
<PropertyColumn Property="c=>c.Id" Width="130px" Filterable="true" Sortable="true" />
<PropertyColumn Property="c=>c.TagId" Width="160px" />
<PropertyColumn Property="c=>c.LastEnum" Width="150px" />
<PropertyColumn Property="c=>c.ApiToken" Width="150px" />
<PropertyColumn Property="c=>c.ComeFrom" Width="100px" />
<PropertyColumn Property="c=>c.MediaUrl" Width="320px" />
<PropertyColumn Property="c=>c.TotalTokens" Width="100px" />
<PropertyColumn Property="c=>c.CreateTime" />
</ColumnDefinitions>
<ExpandTemplate Context="rowData">
<Descriptions Title="任务详情" Bordered>
<DescriptionsItem Title="@rowData.Data.LastEnum.ToString()">
@rowData.Data.Progress%
</DescriptionsItem>
<DescriptionsItem Title="操作" Span="2">
<Button Type="@ButtonType.Primary"
Loading="rowRestartLoading"
OnClick="()=>RowRload(rowData)">
刷新数据
</Button>
<Button Type="@ButtonType.Primary" Danger @onclick="() => ReStartClick(rowData.Data)">
重试
</Button>
</DescriptionsItem>
<DescriptionsItem Title="任务时间轴" Span="5">
<Steps Current="@RowSTIndex(rowData)" Status="@RowSTStatus(rowData)">
<Step Title="下载文件"
Description="@RowST(rowData,RedisChannelEnum.DownloadFile)" />
<Step Title="分离音频"
Description="@RowST(rowData,RedisChannelEnum.SeparateAudio)" />
<Step Title="解析字幕"
Description="@RowST(rowData,RedisChannelEnum.ParsingCaptions)" />
<Step Title="解析说话人"
Description="@RowST(rowData,RedisChannelEnum.ParsingSpeaker)" />
<Step Title="Chat模型分析"
Description="@RowST(rowData,RedisChannelEnum.ChatModelAnalysis)" />
<Step Title="结束任务"
Description="@RowST(rowData,RedisChannelEnum.EndTask)" />
</Steps>
</DescriptionsItem>
@if (!string.IsNullOrEmpty(@rowData.Data.ErrorMessage))
{
<DescriptionsItem Title="任务异常" Span="3">
@rowData.Data.ErrorMessage
</DescriptionsItem>
}
</Descriptions>
</ExpandTemplate>
</Table>
<Modal Title="重试任务"
Width="400"
OnOk="ReStart"
@bind-Visible="@modalShow">
<Title Level="3">ID : @reStartTask.Id</Title>
<p></p>
<p>将从哪个步骤重试?</p>
<Select Style="width:220px;"
DataSource="SelectDataSource"
LabelName="@nameof(TextValue.Text)"
ValueName="@nameof(TextValue.Value)"
@bind-Value="@selectEnum">
</Select>
<br />
<br />
</Modal>

View File

@ -0,0 +1,182 @@
using AntDesign;
using AntDesign.TableModels;
using FFmpeg.NET.Services;
using Learn.VideoAnalysis.Controllers.Dto;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.DataProtection.KeyManagement;
using SqlSugar;
using System.Linq.Expressions;
using System.Threading.Tasks;
using VideoAnalysisCore.Common;
using VideoAnalysisCore.Enum;
using VideoAnalysisCore.Model;
using VideoAnalysisCore.Model.Dto;
namespace Learn.VideoAnalysis.Components.Pages
{
public partial class VideoTaskPage : ComponentBase
{
[Inject] private ConfirmService ComfirmService { get; set; } = default!;
[Inject] private Repository<VideoTask> taskDB { get; set; } = default!;
[Inject] private INotificationService _notice { get; set; } = default!;
IEnumerable<VideoTaskDto> _selectedRows = [];
ITable _table;
List<VideoTaskDto> _dataSource = null;
RefAsync<int> _total = 0;
bool modalShow = false;
bool tableLoading = false;
private VideoTaskDto selectData;
private bool rowRestartLoading = false;
private VideoTaskDto reStartTask ;
static TextValue[] SelectDataSource =
Enum.GetValues(typeof(RedisChannelEnum))
.Cast<RedisChannelEnum>()
.Select(s => new TextValue(s.ToString(), (int)s))
.ToArray();
int selectEnum = 1;
int selectDefaultValue =1;
/// <summary>
/// 点击重试
/// </summary>
/// <param name="query"></param>
async void ReStartClick(VideoTaskDto query)
{
selectDefaultValue =
(await RedisExpand.Redis.HMGetAsync<int>(RedisExpandKey.Task(query.Id), "LastEnum")).FirstOrDefault();
selectEnum = selectDefaultValue;
reStartTask = query;
modalShow = true;
}
/// <summary>
/// 重试
/// </summary>
/// <param name="query"></param>
async void ReStart()
{
await RedisExpand.SetTaskErrorMessage(reStartTask.Id, null);
RedisExpand.InsertChannel((RedisChannelEnum)selectEnum, reStartTask.Id);
modalShow = false;
}
private QueryModel<VideoTaskDto> lastQuery = null;
/// <summary>
/// 分页 查询 筛选 时
/// </summary>
/// <param name="query"></param>
/// <param name="changed"></param>
async void ShowErrorTask(MouseEventArgs e)
{
_dataSource = await taskDB.AsQueryable()
.Where(s => s.ErrorMessage != null && s.ErrorMessage != string.Empty)
.Select<VideoTaskDto>()
.ToListAsync();
_total = _dataSource.Count();
tableLoading = false;
StateHasChanged();
}
/// <summary>
/// 分页 查询 筛选 时
/// </summary>
/// <param name="query"></param>
/// <param name="changed"></param>
async void OnChange(QueryModel<VideoTaskDto> query)
{
lastQuery = query;
tableLoading = true;
List<IConditionalModel> where = default!;
if (query.FilterModel != null && ((query.FilterModel?.Count() ?? 0) > 0))
{
where = query.ToSqlSugerWhere();
}
_dataSource = await taskDB.AsQueryable()
.Where(where)
.Select<VideoTaskDto>()
.OrderByDescending(s => s.Id)
.ToPageListAsync(query.PageIndex , query.PageSize, _total);
tableLoading = false;
StateHasChanged();
}
/// <summary>
/// 刷新数据
/// </summary>
/// <param name="rowData"></param>
public void RowRload(RowData<VideoTaskDto> rowData)
{
rowRestartLoading = true;
var item = rowData.Data;
if (item is null)
return;
var data = RedisExpand.Redis.HMGet<string>(RedisExpandKey.Task(item.Id),
"Progress", "LastEnum", "StartTime", "ErrorMessage");
item.Progress = float.Parse(data[0]);
item.LastEnum = data[1].ToEnum<RedisChannelEnum>() ?? default;
item.StartTimeDic = System.Text.Json.JsonSerializer.Deserialize<Dictionary<RedisChannelEnum, DateTime>>(data[2]) ?? null;
item.ErrorMessage = data[3];
rowRestartLoading = false;
StateHasChanged();
}
/// <summary>
///
/// </summary>
/// <returns></returns>
private string RowST(RowData<VideoTaskDto> rowData, RedisChannelEnum e)
{
var dic = rowData.Data.StartTimeDic;
if (dic is null || !dic.ContainsKey(e))
return "--";
return dic[e].ToString();
}
private string RowSTStatus(RowData<VideoTaskDto> rowData)
{
var dic = rowData.Data.StartTimeDic;
if (dic is null)
return "wait";
if (!string.IsNullOrEmpty(rowData.Data.ErrorMessage))
return "error";
if (dic.ContainsKey(RedisChannelEnum.EndTask))
return "finish";
return "wait";
}
private int RowSTIndex(RowData<VideoTaskDto> rowData)
{
return (int)rowData.Data.LastEnum;
}
private void OnExpand(RowData<VideoTaskDto> rowData)
{
RowRload(rowData);
}
/// <summary>
/// 在渲染页面之后
/// </summary>
/// <param name="firstRender"></param>
/// <returns></returns>
protected override async Task OnAfterRenderAsync(bool firstRender)
{
}
/// <summary>
/// 初始化
/// </summary>
protected override void OnInitialized()
{
}
private async Task<bool> Comfirm(string message)
{
return await ComfirmService.Show(message, "提示", ConfirmButtons.YesNo, ConfirmIcon.Warning) == ConfirmResult.Yes;
}
}
}

View File

@ -0,0 +1,8 @@
input[aria-hidden="true"] {
display: none !important;
}
.task_status_tag {
display:flex;
}

View File

@ -0,0 +1,6 @@
namespace VideoAnalysisRazor.Resources;
internal class I18n
{
}

View File

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="menu.account.center" xml:space="preserve">
<value>Account Center</value>
</data>
<data name="menu.account.logout" xml:space="preserve">
<value>Logout</value>
</data>
<data name="menu.account.settings" xml:space="preserve">
<value>Settings</value>
</data>
</root>

View File

@ -0,0 +1,129 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="menu.account.center" xml:space="preserve">
<value>个人中心</value>
</data>
<data name="menu.account.logout" xml:space="preserve">
<value>退出登录</value>
</data>
<data name="menu.account.settings" xml:space="preserve">
<value>个人设置</value>
</data>
</root>

View File

@ -0,0 +1,18 @@
@using Learn.VideoAnalysis.Components.Pages
<Router AppAssembly="typeof(Program).Assembly">
<Found Context="routeData">
<CascadingValue Value="routeData">
@if (routeData.PageType == typeof(Login))
{
<RouteView RouteData="@routeData" />
}
else
{
<RouteView RouteData="routeData" DefaultLayout="typeof(VideoAnalysisRazor.Layouts.BasicLayout)" />
}
</CascadingValue>
<FocusOnNavigate RouteData="routeData" Selector="h1" />
</Found>
</Router>
<AntContainer />

View File

@ -0,0 +1,16 @@
@using System.Net.Http
@using System.Net.Http.Json
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.AspNetCore.Components.Routing
@using Microsoft.AspNetCore.Components.Web
@using static Microsoft.AspNetCore.Components.Web.RenderMode
@using Microsoft.AspNetCore.Components.Web.Virtualization
@using Microsoft.JSInterop
@using AntDesign
@using AntDesign.Charts
@using AntDesign.ProLayout
@using AntDesign.Extensions.Localization
@using Learn.VideoAnalysis
@using VideoAnalysisRazor
@using Learn.VideoAnalysis.Components

View File

@ -0,0 +1,181 @@
using VideoAnalysisCore.Common;
using Learn.VideoAnalysis.Controllers.Dto;
using Microsoft.AspNetCore.Mvc;
using System.Reflection;
using VideoAnalysisCore.Enum;
using VideoAnalysisCore.Model;
using VideoAnalysisCore.AICore.FFMPGE;
using VideoAnalysisCore.Model.Dto;
using VideoAnalysisCore.AICore.ChatGPT.Dto;
using AntDesign;
using System.Threading.Tasks;
using FFmpeg.NET.Services;
using MapsterMapper;
using Mapster;
using VideoAnalysisCore.AICore.SherpaOnnx;
using System.Net;
using System.Security.Policy;
using System.IO;
namespace Learn.VideoAnalysis.Controllers
{
[ApiController]
[Route("[controller]/[action]")]
public class ApiController : ControllerBase
{
private readonly ILogger<ApiController> _logger;
private readonly IMapper mp;
private readonly Repository<VideoTask> videoTaskDB;
public ApiController(ILogger<ApiController> logger, Repository<VideoTask> videoTaskDB, IMapper mp)
{
_logger = logger;
this.videoTaskDB = videoTaskDB;
this.mp = mp;
}
private string GetClientIpAddress()
{
// 检查 X-Forwarded-For 请求头
if (HttpContext.Request.Headers.ContainsKey("X-Forwarded-For")
&& !string.IsNullOrEmpty(HttpContext.Request.Headers["X-Forwarded-For"]))
return HttpContext.Request.Headers["X-Forwarded-For"].ToString();
if (HttpContext.Connection.RemoteIpAddress != null)
return HttpContext.Connection.RemoteIpAddress.ToString();
throw new Exception("未能获取到客户端ip地址");
}
/// <summary>
/// 语音识别
/// </summary>
/// <param name="url">文件流</param>
/// <returns></returns>
[HttpGet(Name = "AudioRecognitionUrl")]
public async Task<IActionResult> AudioRecognitionUrl(string url)
{
try
{
using HttpClient client = new HttpClient();
// 发送GET请求获取网络文件流
using var networkStream = await client.GetStreamAsync(url);
var res = await SenseVoice.RunTask(networkStream);
return Ok(res);
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
/// <summary>
/// 语音识别
/// </summary>
/// <param name="file">文件流</param>
/// <returns></returns>
[HttpPost(Name = "AudioRecognition")]
public async Task<IActionResult> AudioRecognition(IFormFile file)
{
using var s = file.OpenReadStream();
var res = await SenseVoice.RunTask(s);
return Ok(res);
}
[NonAction]
private static List<TimeBase> MergeTimeBases(IEnumerable<TimeBase> timeBases)
{
if (timeBases == null || timeBases.Count() == 0)
{
return new List<TimeBase>();
}
var mergedList = new List<TimeBase>();
// 初始化合并段
var current = timeBases.First();
current.Content = string.Empty;
foreach (var next in timeBases)
{
// 如果类型相同,则扩展时间段
if (current.TimeBaseType == next.TimeBaseType)
current.End = Math.Max(current.End, next.End);
else
{
// 类型不同,将当前时间段加入结果列表,并开始新时间段
mergedList.Add(current);
current = next;
current.Content = string.Empty;
}
}
// 添加最后的时间段
mergedList.Add(current);
return mergedList;
}
/// <summary>
/// 获取视频信息<para>taskId/tagId二选一</para>
/// </summary>
/// <param name="taskId"></param>
/// <param name="tagId">自定义id</param>
/// <returns></returns>
[HttpGet(Name = "TaskInfo")]
public async Task<IActionResult> TaskInfo(long taskId,string? tagId)
{
var task = await videoTaskDB.AsQueryable()
.WhereIF(taskId!=0, s => s.Id == taskId)
.WhereIF(!string.IsNullOrEmpty(tagId), s => s.TagId == tagId)
.FirstAsync();
if (task is null)
return BadRequest();
var taskData = task.ChatAnalysis.Adapt<TaskInfoRes>();
if (taskData is null)
return BadRequest();
taskData.Status = task.LastEnum;
if (task.LastEnum != RedisChannelEnum.EndTask)
return BadRequest(taskData);
if (taskData != null && taskData.TimeBase != null)
taskData.TimeBase = MergeTimeBases(taskData.TimeBase);
return Ok(taskData);
}
/// <summary>
/// 插入队列
/// </summary>
/// <param name="enum"></param>
/// <param name="msg"></param>
/// <returns></returns>
[HttpPost(Name = "TestInsertChannel")]
public IActionResult TestInsertChannel(int @enum=1, string msg= "1")
{
RedisExpand.InsertChannel(@enum.ToEnum<RedisChannelEnum>().Value
, msg);
return Ok();
}
/// <summary>
/// 视频处理
/// </summary>
/// <param name="req">请求体</param>
/// <returns></returns>
[HttpPost(Name = "VideoAnalysis")]
public async Task<IActionResult> VideoAnalysis(VideoAnalysisReq req)
{
if (!ModelState.IsValid) return BadRequest(ModelState);
if(await videoTaskDB.IsAnyAsync(s=>s.TagId == req.TagId) )
return BadRequest("重复添加");
// 自动映射属性到哈希
var task = new VideoTask()
{
ComeFrom = GetClientIpAddress(),
MediaUrl = req.MediaUrl,
ApiToken = req.ApiToken,
Tag = req.Tag,
TagId = req.TagId,
};
//入库
task.Id = await videoTaskDB.InsertReturnBigIdentityAsync(task);
var hashEntries = task.GetType()
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
.ToDictionary(s => s.Name, s => s.GetValue(task));
RedisExpand.Redis.HMSet(RedisExpandKey.Task(task.Id), hashEntries);
RedisExpand.Redis.LPush(RedisExpandKey.ChannelKey, task.Id);
return Ok(task.Id);
}
}
}

View File

@ -0,0 +1,87 @@
using AntDesign;
using System.ComponentModel.DataAnnotations;
using VideoAnalysisCore.AICore.ChatGPT.Dto;
using VideoAnalysisCore.Enum;
namespace Learn.VideoAnalysis.Controllers.Dto
{
/// <summary>
/// 视频处理 请求
/// </summary>
public class VideoAnalysisReq
{
/// <summary>
/// 媒体路径
/// </summary>
[Required(ErrorMessage = "资源URL是必填项")]
[Url(ErrorMessage = "请输入有效的 URL")]
public string MediaUrl { get; set; } = string.Empty;
/// <summary>
/// ApiKey
/// </summary>
[Required(ErrorMessage = "接口Token是必填项")]
public string ApiToken { get; set; } = string.Empty;
/// <summary>
/// 自定义值 任务完成后附带通知
/// </summary>
public string Tag { get; set; } = string.Empty;
/// <summary>
/// 自定义Id可用于任务完成之后的查询
/// </summary>
public string? TagId { get; set; }
/// <summary>
///回调Api地址
/// </summary>
//[Required(ErrorMessage = "回调Api地址是必填项")]
//[Url(ErrorMessage = "请输入有效的 URL")]
//public string CallBackUrl { get; set; } = string.Empty;
}
public class TextValue
{
public TextValue(float v)
{
var s = TimeSpan.FromSeconds((double)v);
var td = new[] { s.Hours, s.Minutes, s.Seconds };
Text = string.Join(':', td.Where(s => s > 0));
Value = v;
}
public TextValue(string t,object v)
{
Text = t;
Value = v;
}
public TextValue()
{
}
public string Text { get; set; }
public object Value { get; set; }
}
public class TaskInfoRes: TaskRes
{
public TaskInfoRes()
{
}
/// <summary>
/// 任务当前执行状态
/// </summary>
public RedisChannelEnum Status { get; set; }
/// <summary>
/// 时间轴状态枚举
/// </summary>
public Dictionary<int, string> TimeTypeEnum =>
Enum.GetValues(typeof(TimeBaseTypeEnum))
.Cast<TimeBaseTypeEnum>()
.ToDictionary(x => (int)x, x => x.ToString());
/// <summary>
/// 时间轴合计
/// </summary>
public Dictionary<TimeBaseTypeEnum, TextValue>? TimeBaseTotal =>
TimeBase?.GroupBy(s => s.TimeBaseType??TimeBaseTypeEnum.)?
.ToDictionary(s => s.Key, s => new TextValue(s.Sum(x => x.End - x.Start)));
}
}

View File

@ -1,18 +1,21 @@
#See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging. #See https://aka.ms/customizecontainer to learn how to customize your debug container and how Visual Studio uses this Dockerfile to build your images for faster debugging.
FROM learnvideo-net8.0:v0.1 AS base FROM dotnet/aspnet:8.0
RUN ["apt-get", "--assume-yes", "update"]
RUN ["apt-get", "--assume-yes", "install", "ffmpeg"]
WORKDIR /app WORKDIR /app
EXPOSE 9040 EXPOSE 9040
COPY . . COPY . .
#设置时间为中国上海 环境为开发环境 #设置时间为中国上海 环境为开发环境
ENV TZ=Asia/Shanghai ENV TZ=Asia/Shanghai
# 给我们要传的参数一个初始值
ENV va_args=
#RUN echo "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ sid main contrib non-free" > /etc/apt/sources.list
#RUN apt-get update && apt-get install -y apt-transport-https ca-certificates apt-utils libgdiplus libc6-dev && apt-get install -y libfreetype6 && apt-get install -y libfontconfig1 && apt-get install -y fontconfig
#
ENV ASPNETCORE_URLS=http://+:9040 ENV ASPNETCORE_URLS=http://+:9040
ENTRYPOINT dotnet Learn.VideoAnalysis.dll $va_args ENTRYPOINT ["dotnet", "Learn.VideoAnalysis.dll"]

View File

@ -1,82 +0,0 @@
using System.IdentityModel.Tokens.Jwt;
using Microsoft.Extensions.DependencyInjection;
using System.Net;
using Microsoft.IdentityModel.Tokens;
using System.Text;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using VideoAnalysisCore.Common;
namespace Learn.VideoAnalysis.Expand
{
public static class AuthorizeExpand
{
/// <summary>
/// 框架API授权
/// </summary>
/// <param name="services"></param>
/// <returns></returns>
public static IServiceCollection AddPermissionAuthentication(this IServiceCollection services)
{
services.AddAuthentication()
.AddJwtBearer(Authentication.vdAdmin, options =>
{
options.RequireHttpsMetadata = false;
options.UseSecurityTokenValidators = true;
options.MapInboundClaims = false; // .NET 5+
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
options.TokenValidationParameters = new TokenValidationParameters
{
SaveSigninToken = false,//保存token,后台验证token是否生效(重要)
RequireExpirationTime = true, // 设置请求需要携带accesstoken的过期时间
ValidateIssuer = false,//必须验证签发人
ValidateAudience = false,//验证受众
ValidateLifetime = true,//是否验证Token有效期
ValidateIssuerSigningKey = true,//是否验证签名,不验证 会被篡改数据,不安全
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AppCommon.Config.AuthKey.Secret)),//解密的密钥
};
options.Events = new JwtBearerEvents
{
OnMessageReceived = context =>
{
var token = context.Request.Headers["Authorization"].FirstOrDefault();
// 3. 安全提取令牌
if (!string.IsNullOrEmpty(token) && token.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
// 移除"Bearer "前缀并清除两端空格
token = token.Substring("Bearer ".Length).Trim();
context.Token = token;
}
return Task.CompletedTask;
},
OnAuthenticationFailed = context =>
{
// 可选:标记一下是否过期
if (context.Exception!=null)
context.Response.Headers["Token-Expired"] = "true";
return Task.CompletedTask;
},
OnChallenge = context =>
{
//if (context.Response.Headers.ContainsKey("Token-Expired"))
//{
//}
context.HandleResponse();
context.Response.StatusCode = 401;
context.Response.ContentType = "application/json";
context.Response.Headers["Access-Control-Allow-Origin"] = "*"; // ✅ 补这个
var data = new
{
Code = 401,
Message = context.Error + context.AuthenticateFailure?.Message
};
return context.Response.WriteAsync(data.ToJson());
}
};
});
return services;
}
}
}

View File

@ -1,43 +0,0 @@
using Coravel;
using Coravel.Scheduling.Schedule;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.DependencyInjection;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VideoAnalysisCore.Job;
namespace Learn.VideoAnalysis.Expand
{
public static class CoravelExpand
{
public static void AddCoravel(this IServiceCollection service)
{
Console.WriteLine($"{DateTime.Now}=>初始化 Coravel");
service.AddScheduler();
#if !DEBUG
service.AddTransient<TaskFileClearJob>();
#endif
service.AddTransient<ClearAllCacheJob>();
service.AddTransient<NodePackageJob>();
// 注册心跳 Job
service.AddTransient<DeviceHeartbeatJob>();
}
public static void UseCoravelExpand(this IApplicationBuilder provider)
{
provider.ApplicationServices.UseScheduler(scheduler =>
{
//任务缓存清理
scheduler.Schedule<ClearAllCacheJob>().HourlyAt(10);
//在线心跳 30秒一次
scheduler.Schedule<DeviceHeartbeatJob>().EveryThirtySeconds();
//强制清理所有缓存内容
//scheduler.Schedule<ClearAllCacheJob>().Hourly();
//scheduler.Schedule<ClearAllCacheJob>().EverySeconds(40);
});
}
}
}

View File

@ -1,106 +0,0 @@
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.OpenApi.Models;
using Swashbuckle.AspNetCore.SwaggerGen;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace Learn.VideoAnalysis.Expand
{
public static class SwaggerExpand
{
public static void AddSwaggerExpand(this IServiceCollection s, string name = "")
{
s.AddSwaggerGen(c =>
{
c.SwaggerDoc("v1", new OpenApiInfo
{
Version = "v1",
Description = name
});
c.OperationFilter<SwaggerFileUploadFilter>();
//按Http类型排序
c.OrderActionsBy(o => o.GroupName);
c.DocInclusionPredicate((docName, apiDesc) =>
{
try
{
if (!apiDesc.TryGetMethodInfo(out MethodInfo methodInfo)) return false;
var versions = methodInfo.DeclaringType.GetCustomAttributes(true)
.OfType<ApiExplorerSettingsAttribute>().Select(attr => attr.GroupName);
if (docName.ToLower() == "v1" && versions.FirstOrDefault() == null)
return true;
return versions.Any(v => v.ToString() == docName);
}
catch (Exception ex)
{
throw;
}
});
//添加全局安全性需求
c.AddSecurityRequirement(new OpenApiSecurityRequirement{
{
new OpenApiSecurityScheme
{
Reference = new OpenApiReference
{
Type = ReferenceType.SecurityScheme,
Id = "bearerAuth"
}
}, Array.Empty<string>()
}
});
//添加一个必须的全局安全信息和AddSecurityDefinition方法指定的方案名称要一致这里是Bearer。
c.AddSecurityDefinition("bearerAuth",
new OpenApiSecurityScheme
{
Description = "使用JWT授权头。示例:\"Authorization: Bearer {token}\"",
Name = "Authorization",
In = ParameterLocation.Header,
Type = SecuritySchemeType.Http,
//内容为以 bearer开头
Scheme = "bearer",
BearerFormat = "JWT"
});
DirectoryInfo dirs = new DirectoryInfo(AppContext.BaseDirectory);
FileInfo[] files = dirs.GetFiles("*.xml");
foreach (var path in files)
{
c.IncludeXmlComments(path.FullName);
}
});
//s.AddSwaggerGenNewtonsoftSupport();
}
}
class SwaggerFileUploadFilter : IOperationFilter
{
public void Apply(OpenApiOperation operation, OperationFilterContext context)
{
if (context.ApiDescription.ActionDescriptor.Parameters.Any(w => w.ParameterType == typeof(IFormCollection)))
{
Dictionary<string, OpenApiSchema> schema = new Dictionary<string, OpenApiSchema>();
schema["fileName"] = new OpenApiSchema { Description = "选择上传文件", Type = "string", Format = "binary" };
Dictionary<string, OpenApiMediaType> content = new Dictionary<string, OpenApiMediaType>();
content["multipart/form-data"] = new OpenApiMediaType { Schema = new OpenApiSchema { Type = "object", Properties = schema } };
operation.RequestBody = new OpenApiRequestBody() { Content = content };
}
}
}
}

View File

@ -1,4 +1,2 @@
 global using VideoAnalysisRazor.Resources;
global using VideoAnalysisCore.Model; global using AntDesign;
global using VideoAnalysisCore.Model.Dto;
global using VideoAnalysisCore.Model.Enum;

View File

@ -2,6 +2,7 @@
<PropertyGroup> <PropertyGroup>
<TargetFramework>net8.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<Version>1.0.0.0</Version>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<InvariantGlobalization>false</InvariantGlobalization> <InvariantGlobalization>false</InvariantGlobalization>
@ -17,30 +18,24 @@
<ItemGroup> <ItemGroup>
<None Remove="Dockerfile" /> <None Remove="Dockerfile" />
<None Remove="sources.list" />
<None Remove="WebUI\dist\index.html" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Content Include="Dockerfile"> <Content Include="Dockerfile">
<CopyToOutputDirectory>Always</CopyToOutputDirectory> <CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
<Content Include="sources.list">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Include="WebUI\dist\index.html">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\VideoAnalysisCore\VideoAnalysisCore.csproj" /> <ProjectReference Include="..\VideoAnalysisCore\VideoAnalysisCore.csproj" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.13.0" /> <PackageReference Include="Mapster.DependencyInjection" Version="1.0.2-pre01" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.18" /> <PackageReference Include="Swashbuckle.AspNetCore" Version="6.4.0" />
<PackageReference Include="Mapster.DependencyInjection" Version="1.0.2-pre01" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.5" />
<PackageReference Include="Microsoft.IdentityModel.JsonWebTokens" Version="8.13.0" />
<PackageReference Include="AlibabaCloud.SDK.Vod20170321" Version="3.11.1" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.3" />
<PackageReference Include="AntDesign.Charts" Version="0.4.0" />
<PackageReference Include="AntDesign.Extensions.Localization" Version="0.20.2.1" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" /> <PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageReference Include="System.Net.Http.Json" Version="8.0.0" /> <PackageReference Include="System.Net.Http.Json" Version="8.0.0" />
@ -48,11 +43,8 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="WebUI\dist\**\*.*"> <Content Update="Components\Pages\Login.razor">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory> <ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</None>
<Content Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content> </Content>
</ItemGroup> </ItemGroup>

View File

@ -1,26 +1,11 @@
using VideoAnalysisCore.Common; using VideoAnalysisCore.Common;
using Learn.VideoAnalysis.Components;
using Microsoft.OpenApi.Models; using Microsoft.OpenApi.Models;
using AntDesign.ProLayout;
using VideoAnalysisCore.AICore.ChatGPT;
using VideoAnalysisCore.AICore.ChatGPT.KIMI;
using VideoAnalysisCore.AICore.SherpaOnnx; using VideoAnalysisCore.AICore.SherpaOnnx;
using Mapster; using Mapster;
using VideoAnalysisCore.AICore.GPT;
using VideoAnalysisCore.AICore.GPT.ChatGPT;
using Microsoft.Extensions.FileProviders;
using VideoAnalysisCore.AICore.GPT.DeepSeek;
using Microsoft.Extensions.DependencyInjection;
using VideoAnalysisCore.Common.Expand;
using Learn.VideoAnalysis.Expand;
using Microsoft.AspNetCore.Mvc.Formatters;
using System.Security.Cryptography;
using System.Diagnostics;
using VideoAnalysisCore.AICore.FFMPGE;
using System.Text.Encodings.Web;
using System.Text.Unicode;
using System.Text.Json;
using Microsoft.AspNetCore.Hosting.Server.Features;
using Microsoft.AspNetCore.Hosting.Server;
using System.IO.Compression;
using System.Text;
using System.Text.Json.Nodes;
@ -30,63 +15,45 @@ namespace Learn.VideoAnalysis
{ {
public static void Main(string[] args) public static void Main(string[] args)
{ {
//交互式环境选择函数
AppConfigExpand.SelectEnvironment(ref args);
var builder = WebApplication.CreateBuilder(args); var builder = WebApplication.CreateBuilder(args);
//设置接口请求体最大100m
builder.WebHost.ConfigureKestrel(serverOptions => // Add services to the container.
{ builder.Services.AddRazorComponents()
serverOptions.Limits.MaxRequestBodySize = 100_000_000; // 100MB .AddInteractiveServerComponents();
}); //.AddInteractiveWebAssemblyComponents();
builder.Services.AddLogging(loggingBuilder =>
{ builder.Services.AddHttpContextAccessor();
loggingBuilder.ClearProviders(); // 清除默认的日志提供程序
loggingBuilder.AddConsole(); // 添加控制台日志提供程序
loggingBuilder.SetMinimumLevel(LogLevel.Warning); // 设置最小日志级别为 Warning
});
//绑定 appsetting 配置 builder.Services.AddControllers();
builder.AddAppConfig(args);
//初始化 插件
builder.Services.AddEndpointsApiExplorer(); builder.Services.AddEndpointsApiExplorer();
//swagger builder.Services.AddSwaggerGen(c =>
builder.Services.AddSwaggerExpand("AI视频分析"); {
//鉴权 c.SwaggerDoc("v1", new OpenApiInfo
builder.Services.AddPermissionAuthentication(); {
//数据库 Title = "Learn.VideoAnalysis",
builder.Services.AddSqlSugarExpand(); Version = "v1",
//reids Description = "教学视频分析平台v1"
builder.Services.AddRedisExpand(); });
//工作流 var file = Path.Combine(AppContext.BaseDirectory, "Learn.VideoAnalysis.xml"); // xml文档绝对路径
builder.Services.AddSimpleTexOcrClient(); c.IncludeXmlComments(file, true); // true : 显示控制器层注释
builder.Services.AddDownloadFileExpand(); c.OrderActionsBy(o => o.RelativePath); // 对action的名称进行排序如果有多个就可以看见效果了。
builder.Services.AddFFMPGEExpand(); });
builder.Services.AddAlibabaCloudVod();
builder.Services.AddAliyunOSS();
//语音转写
builder.Services.AddSenseVoiceExpand();
builder.Services.AddFunASRNanoExpand();
builder.Services.AddSherpaVadExpand();
//builder.Services.AddSpeakerAI();
//定时任务
builder.Services.AddCoravel();
//异常过滤器 //绑定 appsetting 配置
builder.Configuration.GetSection("AppConfig").Bind(AppCommon.Config);
//初始化 插件
builder.Services.InitSqlSugar();
RedisExpand.Init();
Speaker.Init();
//SenseVoice.Init();
builder.Services.AddControllersWithViews(options => builder.Services.AddControllersWithViews(options =>
{ {
options.Filters.Add(typeof(ExceptionFilter)); options.Filters.Add(typeof(ExceptionFilter));
}); });
builder.Services.AddControllers()
.AddJsonOptions(options =>
{
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);//中文转换时不使用Unicode
options.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;// 默认小驼峰 null 大驼峰
options.JsonSerializerOptions.PropertyNameCaseInsensitive = true;
});
builder.Services.AddScoped(sp => builder.Services.AddScoped(sp =>
@ -102,51 +69,47 @@ namespace Learn.VideoAnalysis
return new HttpClient(); return new HttpClient();
}); });
//VideoAnalysisRazor.Program.AddClientServices(builder.Services);
builder.Services.AddAntDesign();
builder.Services.AddMapster(); builder.Services.AddMapster();
builder.Services.AddCorsExpand();
builder.Services.Configure<ProSettings>(builder.Configuration.GetSection("ProSettings"));
builder.Services.AddHttpClient(); builder.Services.AddHttpClient();
builder.Services.AddHttpContextAccessor(); builder.Services.AddSingleton<MoonshotClient>();
builder.Services.AddGPTService(); builder.Services.AddSingleton<IBserGPT, KIMI_GPT>();
builder.Services.AddTaskSubscribe();
var app = builder.Build(); var app = builder.Build();
AppCommon.Services = app.Services; AppCommon.Services = app.Services;
//允许跨域
app.UseCorsExpand();
app.UseMiddleware<BasicAuthMiddleware>("Swagger");
// Configure the HTTP request pipeline.
app.UseSwagger();
app.UseSwaggerUI();
app.UseExceptionHandler("/Error");
//添加wwwroot 静态目录 // Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
app.UseExceptionHandler("/Error");
}
//else
//{
// app.UseExceptionHandler("/Login");
//}
app.UseStaticFiles(); app.UseStaticFiles();
//添加 自定义 静态目录
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(AppCommon.TaskCachedFile),
RequestPath = "/video",
});
app.UseStaticFiles(new StaticFileOptions
{
FileProvider = new PhysicalFileProvider(AppCommon.WebUIFile),
RequestPath = "/ui",
});
app.UseAntiforgery(); app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode();
//.AddInteractiveWebAssemblyRenderMode()
//.AddAdditionalAssemblies(typeof(VideoAnalysisRazor._Imports).Assembly);
app.MapControllers(); app.MapControllers();
//自定义 应用 SqlSugarExpand.InitDB();
app.UseSqlSugarExpand();
app.UseCoravelExpand();
app.UseServiceSystem(() =>
{
//开启redis队列服务
_ = AppCommon.Services.GetRequiredService<RedisInit>();
});
app.Run(); app.Run();

View File

@ -1,15 +1,31 @@
{ {
"$schema": "http://json.schemastore.org/launchsettings.json", "$schema": "http://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:9624",
"sslPort": 0
}
},
"profiles": { "profiles": {
"http:5238": { "http:5238": {
"commandName": "Project", "commandName": "Project",
"dotnetRunMessages": true, "dotnetRunMessages": true,
"launchBrowser": true, "launchBrowser": true,
"launchUrl": "ui/index.html", "launchUrl": "",
"applicationUrl": "http://*:7532", "applicationUrl": "http://*:5238",
"environmentVariables": { "environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development" "ASPNETCORE_ENVIRONMENT": "Development"
} }
}, },
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": true,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
} }
} }

View File

@ -1,4 +0,0 @@
> 1%
last 2 versions
not dead
not ie 11

View File

@ -1,21 +0,0 @@
node_modules
.DS_Store
dist
dist-ssr
*.local
.eslintcache
report.html
yarn.lock
npm-debug.log*
.pnpm-error.log*
.pnpm-debug.log
tests/**/coverage/
# Editor directories and files
.idea
*.suo
*.ntvs*
*.njsproj
*.sln
tsconfig.tsbuildinfo

View File

@ -1,14 +0,0 @@
# http://editorconfig.org
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
[*.md]
insert_final_newline = false
trim_trailing_whitespace = false

View File

@ -1,5 +0,0 @@
# 平台本地运行端口号
VITE_PORT = 8848
# 是否隐藏首页 隐藏 true 不隐藏 false 勿删除VITE_HIDE_HOME只需在.env文件配置
VITE_HIDE_HOME = false

View File

@ -1,17 +0,0 @@
# 平台本地运行端口号
VITE_PORT = 8848
# 开发环境读取配置文件路径
VITE_PUBLIC_PATH = /
# 开发环境路由历史模式Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数"
VITE_ROUTER_HISTORY = "hash"
# 接口地址
VITE_API_BASEURL = "http://192.168.2.33:7532"
# # 接口地址
# VITE_API_BASEURL = "https://learn-archives-admin-dev.23544.com/api"
# #数据中心后台地址
# VITE_API_USERCENTER_URL = "https://dca.w.23544.com:8843/api"

View File

@ -1,16 +0,0 @@
# 线上环境平台打包路径
VITE_PUBLIC_PATH = /ui/
# 线上环境路由历史模式Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数"
VITE_ROUTER_HISTORY = "hash"
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
VITE_CDN = false
# 是否启用gzip压缩或brotli压缩分两种情况删除原始文件和不删除原始文件
# 压缩时不删除原始文件的配置gzip、brotli、both同时开启 gzip 与 brotli 压缩、none不开启压缩默认
# 压缩时删除原始文件的配置gzip-clear、brotli-clear、both-clear同时开启 gzip 与 brotli 压缩、none不开启压缩默认
VITE_COMPRESSION = "none"
VITE_API_BASEURL = "/"

View File

@ -1,22 +0,0 @@
# 预发布也需要生产环境的行为
# https://cn.vitejs.dev/guide/env-and-mode.html#modes
# NODE_ENV = development
VITE_PUBLIC_PATH = /
# 预发布环境路由历史模式Hash模式传"hash"、HTML5模式传"h5"、Hash模式带base参数传"hash,base参数"、HTML5模式带base参数传"h5,base参数"
VITE_ROUTER_HISTORY = "hash"
# 是否在打包时使用cdn替换本地库 替换 true 不替换 false
VITE_CDN = false
# 是否启用gzip压缩或brotli压缩分两种情况删除原始文件和不删除原始文件
# 压缩时不删除原始文件的配置gzip、brotli、both同时开启 gzip 与 brotli 压缩、none不开启压缩默认
# 压缩时删除原始文件的配置gzip-clear、brotli-clear、both-clear同时开启 gzip 与 brotli 压缩、none不开启压缩默认
VITE_COMPRESSION = "none"
# 接口地址
VITE_API_BASEURL = "https://learn-archives-admin-dev.23544.com/api"
#数据中心后台地址
VITE_API_USERCENTER_URL = "https://dca.w.23544.com:8843/api"

View File

@ -1,20 +0,0 @@
{
"*.{js,jsx,ts,tsx}": [
"prettier --cache --ignore-unknown --write",
"eslint --cache --fix"
],
"{!(package)*.json,*.code-snippets,.!({browserslist,npm,nvm})*rc}": [
"prettier --cache --write--parser json"
],
"package.json": ["prettier --cache --write"],
"*.vue": [
"prettier --write",
"eslint --cache --fix",
"stylelint --fix --allow-empty-input"
],
"*.{css,scss,html}": [
"prettier --cache --ignore-unknown --write",
"stylelint --fix --allow-empty-input"
],
"*.md": ["prettier --cache --ignore-unknown --write"]
}

View File

@ -1,11 +0,0 @@
{
"default": true,
"MD003": false,
"MD033": false,
"MD013": false,
"MD001": false,
"MD025": false,
"MD024": false,
"MD007": { "indent": 4 },
"no-hard-tabs": false
}

View File

@ -1,4 +0,0 @@
shell-emulator=true
shamefully-hoist=true
enable-pre-post-scripts=false
strict-peer-dependencies=false

View File

@ -1 +0,0 @@
v22.14.0

View File

@ -1,9 +0,0 @@
// @ts-check
/** @type {import("prettier").Config} */
export default {
bracketSpacing: true,
singleQuote: false,
arrowParens: "avoid",
trailingComma: "none"
};

View File

@ -1,4 +0,0 @@
/dist/*
/public/*
public/*
src/style/reset.scss

View File

@ -1,20 +0,0 @@
FROM node:20-alpine as build-stage
WORKDIR /app
RUN corepack enable
RUN corepack prepare pnpm@latest --activate
RUN npm config set registry https://registry.npmmirror.com
COPY .npmrc package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
COPY . .
RUN pnpm build
FROM nginx:stable-alpine as production-stage
COPY --from=build-stage /app/dist /usr/share/nginx/html
EXPOSE 80
CMD ["nginx", "-g", "daemon off;"]

View File

@ -1,21 +0,0 @@
MIT License
Copyright (c) 2020-present, pure-admin
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,47 +0,0 @@
<h1>vue-pure-admin Lite Editionno i18n version</h1>
[![license](https://img.shields.io/github/license/pure-admin/vue-pure-admin.svg)](LICENSE)
**English** | [中文](./README.md)
## Introduce
The simplified version is based on the shelf extracted from [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin), which contains main functions and is more suitable for actual project development. The packaged size is introduced globally [element-plus](https://element-plus.org) is still below `2.3MB`, and the full version of the code will be permanently synchronized. After enabling `brotli` compression and `cdn` to replace the local library mode, the package size is less than `350kb`
## `js` version
[Click me to view js version](https://pure-admin.cn/pages/js/)
## `max` version
[Click me to view the max version](https://pure-admin.cn/pages/max/)
## Supporting video
[Click me to view UI design](https://www.bilibili.com/video/BV17g411T7rq)
[Click me to view the rapid development tutorial](https://www.bilibili.com/video/BV1kg411v7QT)
## Nanny-level documents
[Click me to view vue-pure-admin documentation](https://pure-admin.cn/)
[Click me to view @pureadmin/utils documentation](https://pure-admin-utils.netlify.app)
## Quality service, software outsourcing, sponsorship support
[Click me to view details](https://pure-admin.cn/pages/service/)
## Preview
[Click me to view the preview station](https://pure-admin-thin.netlify.app/#/login)
## Maintainer
[xiaoxian521](https://github.com/xiaoxian521)
## ⚠️ Attention
The Lite version does not accept any issues and prs. If you have any questions, please go to the full version [issues](https://github.com/pure-admin/vue-pure-admin/issues/new/choose) to mention, thank you!
## License
[MIT © 2020-present, pure-admin](./LICENSE)

View File

@ -1,51 +0,0 @@
<h1>vue-pure-admin精简版非国际化版本</h1>
[![license](https://img.shields.io/github/license/pure-admin/vue-pure-admin.svg)](LICENSE)
**中文** | [English](./README.en-US.md)
## 介绍
精简版是基于 [vue-pure-admin](https://github.com/pure-admin/vue-pure-admin) 提炼出的架子,包含主体功能,更适合实际项目开发,打包后的大小在全局引入 [element-plus](https://element-plus.org) 的情况下仍然低于 `2.3MB`,并且会永久同步完整版的代码。开启 `brotli` 压缩和 `cdn` 替换本地库模式后,打包大小低于 `350kb`
## 版本选择
当前是非国际化版本,如果您需要国际化版本 [请点击](https://github.com/pure-admin/pure-admin-thin/tree/i18n)
## `js` 版本
[点我查看 js 版本](https://pure-admin.cn/pages/js/)
## `max` 版本
[点我查看 max 版本](https://pure-admin.cn/pages/max/)
## 配套视频
[点我查看 UI 设计](https://www.bilibili.com/video/BV17g411T7rq)
[点我查看快速开发教程](https://www.bilibili.com/video/BV1kg411v7QT)
## 配套保姆级文档
[点我查看 vue-pure-admin 文档](https://pure-admin.cn/)
[点我查看 @pureadmin/utils 文档](https://pure-admin-utils.netlify.app)
## 优质服务、软件外包、赞助支持
[点我查看详情](https://pure-admin.cn/pages/service/)
## 预览
[查看预览](https://pure-admin-thin.netlify.app/#/login)
## 维护者
[xiaoxian521](https://github.com/xiaoxian521)
## ⚠️ 注意
精简版不接受任何 `issues``pr`,如果有问题请到完整版 [issues](https://github.com/pure-admin/vue-pure-admin/issues/new/choose) 去提,谢谢!
## 许可证
[MIT © 2020-present, pure-admin](./LICENSE)

View File

@ -1,55 +0,0 @@
import { Plugin as importToCDN } from "vite-plugin-cdn-import";
/**
* @description `cdn`使cdn模式 .env.production VITE_CDN true
* cdnhttps://www.bootcdn.cn当然你也可以选择 https://unpkg.com 或者 https://www.jsdelivr.com
* 使jscss文件cdn
*/
export const cdn = importToCDN({
//prodUrl解释 name: 对应下面modules的nameversion: 自动读取本地package.json中dependencies依赖中对应包的版本号path: 对应下面modules的path当然也可写完整路径会替换prodUrl
prodUrl: "https://cdn.bootcdn.net/ajax/libs/{name}/{version}/{path}",
modules: [
{
name: "vue",
var: "Vue",
path: "vue.global.prod.min.js"
},
{
name: "vue-router",
var: "VueRouter",
path: "vue-router.global.min.js"
},
// 项目中没有直接安装vue-demi但是pinia用到了所以需要在引入pinia前引入vue-demihttps://github.com/vuejs/pinia/blob/v2/packages/pinia/package.json#L77
{
name: "vue-demi",
var: "VueDemi",
path: "index.iife.min.js"
},
{
name: "pinia",
var: "Pinia",
path: "pinia.iife.min.js"
},
{
name: "element-plus",
var: "ElementPlus",
path: "index.full.min.js",
css: "index.min.css"
},
{
name: "axios",
var: "axios",
path: "axios.min.js"
},
{
name: "dayjs",
var: "dayjs",
path: "dayjs.min.js"
},
{
name: "echarts",
var: "echarts",
path: "echarts.min.js"
}
]
});

View File

@ -1,63 +0,0 @@
import type { Plugin } from "vite";
import { isArray } from "@pureadmin/utils";
import compressPlugin from "vite-plugin-compression";
export const configCompressPlugin = (
compress: ViteCompression
): Plugin | Plugin[] => {
if (compress === "none") return null;
const gz = {
// 生成的压缩包后缀
ext: ".gz",
// 体积大于threshold才会被压缩
threshold: 0,
// 默认压缩.js|mjs|json|css|html后缀文件设置成true压缩全部文件
filter: () => true,
// 压缩后是否删除原始文件
deleteOriginFile: false
};
const br = {
ext: ".br",
algorithm: "brotliCompress",
threshold: 0,
filter: () => true,
deleteOriginFile: false
};
const codeList = [
{ k: "gzip", v: gz },
{ k: "brotli", v: br },
{ k: "both", v: [gz, br] }
];
const plugins: Plugin[] = [];
codeList.forEach(item => {
if (compress.includes(item.k)) {
if (compress.includes("clear")) {
if (isArray(item.v)) {
item.v.forEach(vItem => {
plugins.push(
compressPlugin(Object.assign(vItem, { deleteOriginFile: true }))
);
});
} else {
plugins.push(
compressPlugin(Object.assign(item.v, { deleteOriginFile: true }))
);
}
} else {
if (isArray(item.v)) {
item.v.forEach(vItem => {
plugins.push(compressPlugin(vItem));
});
} else {
plugins.push(compressPlugin(item.v));
}
}
}
});
return plugins;
};

View File

@ -1,57 +0,0 @@
import type { Plugin } from "vite";
import gradient from "gradient-string";
import { getPackageSize } from "./utils";
import dayjs, { type Dayjs } from "dayjs";
import duration from "dayjs/plugin/duration";
import boxen, { type Options as BoxenOptions } from "boxen";
dayjs.extend(duration);
const welcomeMessage = gradient(["cyan", "magenta"]).multiline(
`您好! 欢迎使用 pure-admin 开源项目\n我们为您精心准备了下面两个贴心的保姆级文档\nhttps://pure-admin.cn\nhttps://pure-admin-utils.netlify.app`
);
const boxenOptions: BoxenOptions = {
padding: 0.5,
borderColor: "cyan",
borderStyle: "round"
};
export function viteBuildInfo(): Plugin {
let config: { command: string };
let startTime: Dayjs;
let endTime: Dayjs;
let outDir: string;
return {
name: "vite:buildInfo",
configResolved(resolvedConfig) {
config = resolvedConfig;
outDir = resolvedConfig.build?.outDir ?? "dist";
},
buildStart() {
console.log(boxen(welcomeMessage, boxenOptions));
if (config.command === "build") {
startTime = dayjs(new Date());
}
},
closeBundle() {
if (config.command === "build") {
endTime = dayjs(new Date());
getPackageSize({
folder: outDir,
callback: (size: string) => {
console.log(
boxen(
gradient(["cyan", "magenta"]).multiline(
`🎉 恭喜打包完成(总用时${dayjs
.duration(endTime.diff(startTime))
.format("mm分ss秒")}${size}`
),
boxenOptions
)
);
}
});
}
}
};
}

View File

@ -1,29 +0,0 @@
/**
* `vite.config.ts` `optimizeDeps.include`
* `vite` include esm node_modules/.vite
* include里vite 使 node_modules/.vite
* 使 src/main.ts include vite node_modules/.vite
*/
const include = [
"qs",
"mitt",
"dayjs",
"axios",
"pinia",
"vue-types",
"js-cookie",
"vue-tippy",
"pinyin-pro",
"sortablejs",
"@vueuse/core",
"@pureadmin/utils",
"responsive-storage"
];
/**
*
* 使
*/
const exclude = ["@iconify/json"];
export { include, exclude };

View File

@ -1,66 +0,0 @@
import { cdn } from "./cdn";
import vue from "@vitejs/plugin-vue";
import { viteBuildInfo } from "./info";
import svgLoader from "vite-svg-loader";
import Icons from "unplugin-icons/vite";
import type { PluginOption } from "vite";
import vueJsx from "@vitejs/plugin-vue-jsx";
import tailwindcss from "@tailwindcss/vite";
import { configCompressPlugin } from "./compress";
import removeNoMatch from "vite-plugin-router-warn";
import { visualizer } from "rollup-plugin-visualizer";
import removeConsole from "vite-plugin-remove-console";
import { codeInspectorPlugin } from "code-inspector-plugin";
// import { vitePluginFakeServer } from "vite-plugin-fake-server";
export function getPluginsList(
VITE_CDN: boolean,
VITE_COMPRESSION: ViteCompression
): PluginOption[] {
const lifecycle = process.env.npm_lifecycle_event;
return [
tailwindcss(),
vue(),
// jsx、tsx语法支持
vueJsx(),
/**
* DOM IDE
* Mac Option + Shift
* Windows Alt + Shift
* https://inspector.fe-dev.cn/guide/start.html
*/
codeInspectorPlugin({
bundler: "vite",
hideConsole: true
}),
viteBuildInfo(),
/**
* vue-router动态路由警告No match found for location with path
* https://github.com/vuejs/router/issues/521 和 https://github.com/vuejs/router/issues/359
* vite-plugin-router-warn只在开发环境下启用vue-router文件并且只在服务启动或重启时运行一次
*/
removeNoMatch(),
// mock支持
// vitePluginFakeServer({
// logger: false,
// include: "mock",
// infixName: false,
// enableProd: true
// }),
// svg组件化支持
svgLoader(),
// 自动按需加载图标
Icons({
compiler: "vue3",
scale: 1
}),
VITE_CDN ? cdn : null,
configCompressPlugin(VITE_COMPRESSION),
// 线上环境删除console
removeConsole({ external: ["src/assets/iconfont/iconfont.js"] }),
// 打包分析
lifecycle === "report"
? visualizer({ open: true, brotliSize: true, filename: "report.html" })
: (null as any)
];
}

View File

@ -1,110 +0,0 @@
import dayjs from "dayjs";
import { readdir, stat } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, resolve } from "node:path";
import { sum, formatBytes } from "@pureadmin/utils";
import {
name,
version,
engines,
dependencies,
devDependencies
} from "../package.json";
/** 启动`node`进程时所在工作目录的绝对路径 */
const root: string = process.cwd();
/**
* @description
* @param dir `build`
* @param metaUrl `url``build``import.meta.url`
*/
const pathResolve = (dir = ".", metaUrl = import.meta.url) => {
// 当前文件目录的绝对路径
const currentFileDir = dirname(fileURLToPath(metaUrl));
// build 目录的绝对路径
const buildDir = resolve(currentFileDir, "build");
// 解析的绝对路径
const resolvedPath = resolve(currentFileDir, dir);
// 检查解析的绝对路径是否在 build 目录内
if (resolvedPath.startsWith(buildDir)) {
// 在 build 目录内,返回当前文件路径
return fileURLToPath(metaUrl);
}
// 不在 build 目录内,返回解析后的绝对路径
return resolvedPath;
};
/** 设置别名 */
const alias: Record<string, string> = {
"@": pathResolve("../src"),
"@build": pathResolve()
};
/** 平台的名称、版本、运行所需的`node`和`pnpm`版本、依赖、最后构建时间的类型提示 */
const __APP_INFO__ = {
pkg: { name, version, engines, dependencies, devDependencies },
lastBuildTime: dayjs(new Date()).format("YYYY-MM-DD HH:mm:ss")
};
/** 处理环境变量 */
const wrapperEnv = (envConf: Recordable): ViteEnv => {
// 默认值
const ret: ViteEnv = {
VITE_PORT: 8848,
VITE_PUBLIC_PATH: "",
VITE_ROUTER_HISTORY: "",
VITE_CDN: false,
VITE_HIDE_HOME: "false",
VITE_COMPRESSION: "none"
};
for (const envName of Object.keys(envConf)) {
let realName = envConf[envName].replace(/\\n/g, "\n");
realName =
realName === "true" ? true : realName === "false" ? false : realName;
if (envName === "VITE_PORT") {
realName = Number(realName);
}
ret[envName] = realName;
if (typeof realName === "string") {
process.env[envName] = realName;
} else if (typeof realName === "object") {
process.env[envName] = JSON.stringify(realName);
}
}
return ret;
};
const fileListTotal: number[] = [];
/** 获取指定文件夹中所有文件的总大小 */
const getPackageSize = options => {
const { folder = "dist", callback, format = true } = options;
readdir(folder, (err, files: string[]) => {
if (err) throw err;
let count = 0;
const checkEnd = () => {
++count == files.length &&
callback(format ? formatBytes(sum(fileListTotal)) : sum(fileListTotal));
};
files.forEach((item: string) => {
stat(`${folder}/${item}`, async (err, stats) => {
if (err) throw err;
if (stats.isFile()) {
fileListTotal.push(stats.size);
checkEnd();
} else if (stats.isDirectory()) {
getPackageSize({
folder: `${folder}/${item}/`,
callback: checkEnd
});
}
});
});
files.length === 0 && callback(0);
});
};
export { root, pathResolve, alias, __APP_INFO__, wrapperEnv, getPackageSize };

View File

@ -1,35 +0,0 @@
// @ts-check
/** @type {import("@commitlint/types").UserConfig} */
export default {
ignores: [commit => commit.includes("init")],
extends: ["@commitlint/config-conventional"],
rules: {
"body-leading-blank": [2, "always"],
"footer-leading-blank": [1, "always"],
"header-max-length": [2, "always", 108],
"subject-empty": [2, "never"],
"type-empty": [2, "never"],
"type-enum": [
2,
"always",
[
"feat",
"fix",
"perf",
"style",
"docs",
"test",
"refactor",
"build",
"ci",
"chore",
"revert",
"wip",
"workflow",
"types",
"release"
]
]
}
};

View File

@ -1,173 +0,0 @@
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import pluginVue from "eslint-plugin-vue";
import * as parserVue from "vue-eslint-parser";
import configPrettier from "eslint-config-prettier";
import pluginPrettier from "eslint-plugin-prettier";
import { defineConfig, globalIgnores } from "eslint/config";
export default defineConfig([
globalIgnores([
"**/.*",
"dist/*",
"*.d.ts",
"public/*",
"src/assets/**",
"src/**/iconfont/**"
]),
{
...js.configs.recommended,
languageOptions: {
globals: {
// types/index.d.ts
RefType: "readonly",
EmitType: "readonly",
TargetContext: "readonly",
ComponentRef: "readonly",
ElRef: "readonly",
ForDataType: "readonly",
AnyFunction: "readonly",
PropType: "readonly",
Writable: "readonly",
Nullable: "readonly",
NonNullable: "readonly",
Recordable: "readonly",
ReadonlyRecordable: "readonly",
Indexable: "readonly",
DeepPartial: "readonly",
Without: "readonly",
Exclusive: "readonly",
TimeoutHandle: "readonly",
IntervalHandle: "readonly",
Effect: "readonly",
ChangeEvent: "readonly",
WheelEvent: "readonly",
ImportMetaEnv: "readonly",
Fn: "readonly",
PromiseFn: "readonly",
ComponentElRef: "readonly",
parseInt: "readonly",
parseFloat: "readonly"
}
},
plugins: {
prettier: pluginPrettier
},
rules: {
...configPrettier.rules,
...pluginPrettier.configs.recommended.rules,
"no-debugger": "off",
"no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_"
}
],
"prettier/prettier": [
"error",
{
endOfLine: "auto"
}
]
}
},
...tseslint.config({
extends: [...tseslint.configs.recommended, "plugin:prettier/recommended"],
files: ["**/*.?([cm])ts", "**/*.?([cm])tsx"],
rules: {
"@typescript-eslint/no-redeclare": "error",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-explicit-any": "off",
"@typescript-eslint/prefer-as-const": "warn",
"@typescript-eslint/no-empty-function": "off",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/no-unused-expressions": "off",
"@typescript-eslint/no-unsafe-function-type": "off",
"@typescript-eslint/no-import-type-side-effects": "error",
"@typescript-eslint/explicit-module-boundary-types": "off",
"@typescript-eslint/consistent-type-imports": [
"error",
{ disallowTypeAnnotations: false, fixStyle: "inline-type-imports" }
],
"@typescript-eslint/prefer-literal-enum-member": [
"error",
{ allowBitwiseExpressions: true }
],
"@typescript-eslint/no-unused-vars": [
"error",
{
argsIgnorePattern: "^_",
varsIgnorePattern: "^_"
}
]
}
}),
{
files: ["**/*.d.ts"],
rules: {
"eslint-comments/no-unlimited-disable": "off",
"import/no-duplicates": "off",
"no-restricted-syntax": "off",
"unused-imports/no-unused-vars": "off"
}
},
{
files: ["**/*.?([cm])js"],
rules: {
"@typescript-eslint/no-require-imports": "off"
}
},
{
files: ["**/*.vue"],
languageOptions: {
globals: {
$: "readonly",
$$: "readonly",
$computed: "readonly",
$customRef: "readonly",
$ref: "readonly",
$shallowRef: "readonly",
$toRef: "readonly"
},
parser: parserVue,
parserOptions: {
ecmaFeatures: {
jsx: true
},
extraFileExtensions: [".vue"],
parser: tseslint.parser,
sourceType: "module"
}
},
plugins: {
"@typescript-eslint": tseslint.plugin,
vue: pluginVue
},
processor: pluginVue.processors[".vue"],
rules: {
...pluginVue.configs.base.rules,
...pluginVue.configs.essential.rules,
...pluginVue.configs.recommended.rules,
"no-undef": "off",
"no-unused-vars": "off",
"vue/no-v-html": "off",
"vue/require-default-prop": "off",
"vue/require-explicit-emits": "off",
"vue/multi-word-component-names": "off",
"vue/no-setup-props-reactivity-loss": "off",
"vue/html-self-closing": [
"error",
{
html: {
void: "always",
normal: "always",
component: "always"
},
svg: "always",
math: "always"
}
]
}
}
]);

View File

@ -1,88 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="Expires" content="0">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Cache-control" content="no-cache">
<meta http-equiv="Cache" content="no-cache">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="renderer" content="webkit" />
<meta
name="viewport"
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
/>
<title>pure-admin-thin</title>
<link rel="icon" href="/favicon.ico" />
</head>
<body>
<div id="app">
<style>
html,
body,
#app {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
overflow: hidden;
}
.loader,
.loader::before,
.loader::after {
width: 2.5em;
height: 2.5em;
border-radius: 50%;
animation: load-animation 1.8s infinite ease-in-out;
animation-fill-mode: both;
}
.loader {
position: relative;
top: 0;
margin: 80px auto;
font-size: 10px;
color: #406eeb;
text-indent: -9999em;
transform: translateZ(0);
transform: translate(-50%, 0);
animation-delay: -0.16s;
}
.loader::before,
.loader::after {
position: absolute;
top: 0;
content: "";
}
.loader::before {
left: -3.5em;
animation-delay: -0.32s;
}
.loader::after {
left: 3.5em;
}
@keyframes load-animation {
0%,
80%,
100% {
box-shadow: 0 2.5em 0 -1.3em;
}
40% {
box-shadow: 0 2.5em 0 0;
}
}
</style>
<div class="loader"></div>
</div>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

View File

@ -1,159 +0,0 @@
{
"name": "pure-admin-thin",
"version": "6.0.0",
"private": true,
"type": "module",
"scripts": {
"dev": "NODE_OPTIONS=--max-old-space-size=4096 vite",
"serve": "pnpm dev",
"build": "rimraf dist && NODE_OPTIONS=--max-old-space-size=8192 vite build",
"build:staging": "rimraf dist && vite build --mode staging",
"report": "rimraf dist && vite build",
"preview": "vite preview",
"preview:build": "pnpm build && vite preview",
"typecheck": "tsc --noEmit && vue-tsc --noEmit --skipLibCheck",
"svgo": "svgo -f . -r",
"clean:cache": "rimraf .eslintcache && rimraf pnpm-lock.yaml && rimraf node_modules && pnpm store prune && pnpm install",
"lint:eslint": "eslint --cache --max-warnings 0 \"{src,mock,build}/**/*.{vue,js,ts,tsx}\" --fix",
"lint:prettier": "prettier --write \"src/**/*.{js,ts,json,tsx,css,scss,vue,html,md}\"",
"lint:stylelint": "stylelint --cache --fix \"**/*.{html,vue,css,scss}\" --cache-location node_modules/.cache/stylelint/",
"lint": "pnpm lint:eslint && pnpm lint:prettier && pnpm lint:stylelint",
"prepare": "husky",
"preinstall": "npx only-allow pnpm"
},
"keywords": [
"pure-admin-thin",
"vue-pure-admin",
"element-plus",
"tailwindcss",
"pure-admin",
"typescript",
"pinia",
"vue3",
"vite",
"esm"
],
"homepage": "https://github.com/pure-admin/pure-admin-thin",
"repository": {
"type": "git",
"url": "git+https://github.com/pure-admin/pure-admin-thin.git"
},
"bugs": {
"url": "https://github.com/pure-admin/vue-pure-admin/issues"
},
"license": "MIT",
"author": {
"name": "xiaoxian521",
"email": "pureadmin@163.com",
"url": "https://github.com/xiaoxian521"
},
"dependencies": {
"@pureadmin/descriptions": "^1.2.1",
"@pureadmin/table": "^3.2.1",
"@pureadmin/utils": "^2.6.0",
"@vueuse/core": "^13.1.0",
"@vueuse/motion": "^3.0.3",
"animate.css": "^4.1.1",
"axios": "^1.9.0",
"dayjs": "^1.11.13",
"echarts": "^5.6.0",
"element-plus": "^2.9.8",
"js-cookie": "^3.0.5",
"localforage": "^1.10.0",
"mitt": "^3.0.1",
"nprogress": "^0.2.0",
"path-browserify": "^1.0.1",
"pinia": "^3.0.2",
"pinyin-pro": "^3.26.0",
"qs": "^6.14.0",
"responsive-storage": "^2.2.0",
"sortablejs": "^1.15.6",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"vue-tippy": "^6.7.0",
"vue-types": "^6.0.0"
},
"devDependencies": {
"@commitlint/cli": "^19.8.0",
"@commitlint/config-conventional": "^19.8.0",
"@commitlint/types": "^19.8.0",
"@eslint/js": "^9.25.1",
"@faker-js/faker": "^9.7.0",
"@iconify/json": "^2.2.331",
"@iconify/vue": "4.2.0",
"@tailwindcss/vite": "^4.1.4",
"@types/js-cookie": "^3.0.6",
"@types/node": "^20.17.30",
"@types/nprogress": "^0.2.3",
"@types/path-browserify": "^1.0.3",
"@types/qs": "^6.9.18",
"@types/sortablejs": "^1.15.8",
"@vitejs/plugin-vue": "^5.2.3",
"@vitejs/plugin-vue-jsx": "^4.1.2",
"boxen": "^8.0.1",
"code-inspector-plugin": "^0.20.10",
"cssnano": "^7.0.6",
"eslint": "^9.25.1",
"eslint-config-prettier": "^10.1.2",
"eslint-plugin-prettier": "^5.2.6",
"eslint-plugin-vue": "^10.0.0",
"gradient-string": "^3.0.0",
"husky": "^9.1.7",
"lint-staged": "^15.5.1",
"postcss": "^8.5.3",
"postcss-html": "^1.8.0",
"postcss-load-config": "^6.0.1",
"postcss-scss": "^4.0.9",
"prettier": "^3.5.3",
"rimraf": "^6.0.1",
"rollup-plugin-visualizer": "^5.14.0",
"sass": "^1.87.0",
"stylelint": "^16.19.0",
"stylelint-config-recess-order": "^6.0.0",
"stylelint-config-recommended-vue": "^1.6.0",
"stylelint-config-standard-scss": "^14.0.0",
"stylelint-prettier": "^5.0.3",
"svgo": "^3.3.2",
"tailwindcss": "^4.1.4",
"typescript": "^5.8.3",
"typescript-eslint": "^8.31.0",
"unplugin-icons": "^22.1.0",
"vite": "^6.3.3",
"vite-plugin-cdn-import": "^1.0.1",
"vite-plugin-compression": "^0.5.1",
"vite-plugin-fake-server": "^2.2.0",
"vite-plugin-remove-console": "^2.2.0",
"vite-plugin-router-warn": "^1.0.0",
"vite-svg-loader": "^5.1.0",
"vue-eslint-parser": "^10.1.3",
"vue-tsc": "^2.2.10"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=22.0.0",
"pnpm": ">=9"
},
"pnpm": {
"allowedDeprecatedVersions": {
"are-we-there-yet": "*",
"sourcemap-codec": "*",
"lodash.isequal": "*",
"domexception": "*",
"w3c-hr-time": "*",
"inflight": "*",
"npmlog": "*",
"rimraf": "*",
"stable": "*",
"gauge": "*",
"abab": "*",
"glob": "*"
},
"onlyBuiltDependencies": [
"@parcel/watcher",
"core-js",
"es5-ext",
"esbuild",
"typeit",
"vue-demi"
]
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +0,0 @@
// @ts-check
/** @type {import('postcss-load-config').Config} */
export default {
plugins: {
...(process.env.NODE_ENV === "production" ? { cssnano: {} } : {})
}
};

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path fill="#386BF3" d="M410.558.109c0 210.974-300.876 361.752-300.876 633.548 0 174.943 134.704 316.787 300.876 316.787s300.877-141.817 300.877-316.787C711.408 361.752 410.558 210.974 410.558.109"/><path fill="#C3D2FB" d="M613.469 73.665c0 211.055-300.877 361.914-300.877 633.547C312.592 882.156 447.296 1024 613.47 1024s300.876-141.817 300.876-316.788C914.29 435.58 613.469 284.72 613.469 73.665"/><path fill="#303F5B" d="M312.592 707.212c0-183.713 137.636-312.171 226.723-441.39 81.702 106.112 172.12 218.74 172.12 367.726A309.755 309.755 0 0 1 420.36 950.064a323.1 323.1 0 0 1-107.769-242.852z"/></svg>

Before

Width:  |  Height:  |  Size: 706 B

View File

@ -1,26 +0,0 @@
{
"Version": "6.0.0",
"Title": "AI视频分析",
"FixedHeader": true,
"HiddenSideBar": false,
"MultiTagsCache": false,
"KeepAlive": true,
"Layout": "vertical",
"Theme": "light",
"DarkMode": false,
"OverallStyle": "light",
"Grey": false,
"Weak": false,
"HideTabs": false,
"HideFooter": false,
"Stretch": false,
"SidebarStatus": true,
"EpThemeColor": "#409EFF",
"ShowLogo": true,
"ShowModel": "chrome",
"MenuArrowIconNoTransition": false,
"CachingAsyncRoutes": false,
"TooltipEffect": "light",
"ResponsiveStorageNameSpace": "responsive-",
"MenuSearchHistory": 6
}

View File

@ -1,26 +0,0 @@
<template>
<el-config-provider :locale="currentLocale">
<router-view />
<ReDialog />
</el-config-provider>
</template>
<script lang="ts">
import { defineComponent } from "vue";
import { ElConfigProvider } from "element-plus";
import { ReDialog } from "@/components/ReDialog";
import zhCn from "element-plus/es/locale/lang/zh-cn";
export default defineComponent({
name: "app",
components: {
[ElConfigProvider.name]: ElConfigProvider,
ReDialog,
},
computed: {
currentLocale() {
return zhCn;
},
},
});
</script>

View File

@ -1,20 +0,0 @@
import { ComboModel } from "@/components/hTable/hTable";
import { http } from "@/utils/http";
import type { Res } from "@/utils/http/types";
/**
* @description
* @param {string} type type=StatusEnum
* @return {object}
*/
export function getenum(type) {
return http.request<ComboModel[]>("get", `api/Public/enum/${type}`);
}
/**
* @description
* @param {string} type type=StatusEnum
* @return {object}
*/
export function getenumDic(type) {
return http.request<any>("get", `api/Public/enum/${type}/Dic`);
}

View File

@ -1,35 +0,0 @@
import { http } from "@/utils/http";
import type { Res } from "@/utils/http/types";
import type { ComboModel } from "@/components/hTable/hTable";
export class hTableAPI {
url = "";
/** 构造函数 */
constructor(url) {
this.url = url;
}
PageList(data = {}) {
return http.request<any>("post", `api/${this.url}/PageList`, { data });
}
Info(tag) {
const pUrl = `api/${this.url}/${tag}`;
let getUrl = pUrl;
return http.request<any>("get", getUrl);
}
edit(data) {
return http.request<any>("post", `api/${this.url}/Edit`, { data });
}
delete(data) {
return http.request<any>("post", `api/${this.url}/Del`, { data });
}
querycombo(data = {}) {
return http.request<ComboModel[]>(
"post",
`api/${this.url}/QueryCombo`,
{
data
}
);
}
}

View File

@ -1,15 +0,0 @@
import { http } from "@/utils/http";
type Result = {
success: boolean;
data: Array<any>;
};
export const getAsyncRoutes = () => {
return new Promise<Result>((resolve, reject) => {
resolve({
success: true,
data: []
});
});
};

View File

@ -1,43 +0,0 @@
import { http } from "@/utils/http";
import type { Res } from "@/utils/http/types";
export type UserResult = {
/** 头像 */
avatar: string;
/** 用户名 */
userName: string;
/** 昵称 */
nickName: string;
/** 当前登录用户的角色 */
roles: Array<string>;
/** 按钮级别权限 */
permissions: Array<string>;
/** `token` */
accessToken: string;
/** 用于调用刷新`accessToken`的接口时所需的`token` */
refreshToken: string;
/** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx' */
expires: Date;
};
export type RefreshTokenResult = {
success: boolean;
data: {
/** `token` */
accessToken: string;
/** 用于调用刷新`accessToken`的接口时所需的`token` */
refreshToken: string;
/** `accessToken`的过期时间(格式'xxxx/xx/xx xx:xx:xx' */
expires: Date;
};
};
/** 登录 */
export const getLogin = (data?: object) => {
return http.request<UserResult>("post", "/api/Admin/Login", { data });
};
/** 刷新`token` */
export const refreshTokenApi = (data?: object) => {
return http.request<RefreshTokenResult>("post", "/refresh-token", { data });
};

View File

@ -1,105 +0,0 @@
import { http } from "@/utils/http";
import type { Res } from "@/utils/http/types";
// 定义类型
export interface Question {
startTime: number;
topicStem: string;
question: string;
pptImageUrl: string;
}
export interface VideoKnowRes {
theme: string;
content: string;
knowPoint: string;
knowPointId: number;
questionArr: Question[];
startTime: number;
}
export interface SenseVoiceRes {
text: string;
start: number;
end: number;
}
export interface ShowTaskInfoRes {
captions: SenseVoiceRes[];
captions1: SenseVoiceRes[];
videoKnows: VideoKnowRes[];
mediaUrl: string;
}
export interface VideoTaskWorkflow {
id: number;
videoTaskId: number;
workflowName: string;
currentStep: string;
currentStepValue: number;
message?: string;
updateTime: string;
}
export interface RowRloadResult {
progress: string;
lastEnum: string;
startTime: string;
errorMessage: string;
workflows: VideoTaskWorkflow[]; // 新增字段
}
/** 刷新任务实时数据 */
export const RowRload = (id: any) => {
return http.request<RowRloadResult>("get", "/api/VideoTask/RowRload", {
params: { id }
});
};
/** 重试任务 (VideoSliceWorkflow) */
export const ReStart = (id: any, selectEnum: number) => {
return http.request<any>("get", "/api/VideoTask/ReStart", {
params: { id, selectEnum }
});
};
/** 重试任务 (TidySlideWorkflow) */
export const ReStartTidySlide = (id: any, selectEnum: number) => {
return http.request<any>("get", "/api/VideoTask/ReStartTidySlide", {
params: { id, selectEnum }
});
};
/** 展示数据 (VideoSliceWorkflow) */
export const ShowTaskInfo = (id: any) => {
return http.request<ShowTaskInfoRes>("get", "/api/VideoTask/ShowTaskInfo", {
params: { id }
});
};
/** 展示数据 (TidySlideWorkflow) */
export const ShowTidySlideTaskInfo = (id: any) => {
return http.request<any>("get", "/api/VideoTask/ShowTidySlideTaskInfo", {
params: { id }
});
};
/** 展示数据 */
export const RunningTaskList = (data: any) => {
return http.request<any>("post", "/api/VideoTask/RunningTaskList", {
data
});
};
/** 获取在线设备列表 */
export const GetOnlineDevices = () => {
return http.request<string[]>("get", "/api/VideoTask/OnlineDevices");
};
/** 展示数据 */
export const ErrorTaskList = (data: any) => {
return http.request<any>("post", "/api/VideoTask/ErrorTaskList", {
data
});
};

View File

@ -1,27 +0,0 @@
@font-face {
font-family: "iconfont"; /* Project id 2208059 */
src:
url("iconfont.woff2?t=1671895108120") format("woff2"),
url("iconfont.woff?t=1671895108120") format("woff"),
url("iconfont.ttf?t=1671895108120") format("truetype");
}
.iconfont {
font-family: "iconfont" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.pure-iconfont-tabs:before {
content: "\e63e";
}
.pure-iconfont-logo:before {
content: "\e620";
}
.pure-iconfont-new:before {
content: "\e615";
}

File diff suppressed because one or more lines are too long

View File

@ -1,30 +0,0 @@
{
"id": "2208059",
"name": "pure-admin",
"font_family": "iconfont",
"css_prefix_text": "pure-iconfont-",
"description": "pure-admin-iconfont",
"glyphs": [
{
"icon_id": "20594647",
"name": "Tabs",
"font_class": "tabs",
"unicode": "e63e",
"unicode_decimal": 58942
},
{
"icon_id": "22129506",
"name": "PureLogo",
"font_class": "logo",
"unicode": "e620",
"unicode_decimal": 58912
},
{
"icon_id": "7795615",
"name": "New",
"font_class": "new",
"unicode": "e615",
"unicode_decimal": 58901
}
]
}

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" class="icon" viewBox="0 0 1024 1024"><path fill="#386BF3" d="M410.558.109c0 210.974-300.876 361.752-300.876 633.548 0 174.943 134.704 316.787 300.876 316.787s300.877-141.817 300.877-316.787C711.408 361.752 410.558 210.974 410.558.109"/><path fill="#C3D2FB" d="M613.469 73.665c0 211.055-300.877 361.914-300.877 633.547C312.592 882.156 447.296 1024 613.47 1024s300.876-141.817 300.876-316.788C914.29 435.58 613.469 284.72 613.469 73.665"/><path fill="#303F5B" d="M312.592 707.212c0-183.713 137.636-312.171 226.723-441.39 81.702 106.112 172.12 218.74 172.12 367.726A309.755 309.755 0 0 1 420.36 950.064a323.1 323.1 0 0 1-107.769-242.852z"/></svg>

Before

Width:  |  Height:  |  Size: 706 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.7 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 11 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24"><path fill="none" d="M0 0h24v24H0z"/><path d="M2.88 18.054a35.9 35.9 0 0 1 8.531-16.32.8.8 0 0 1 1.178 0q.25.27.413.455a35.9 35.9 0 0 1 8.118 15.865c-2.141.451-4.34.747-6.584.874l-2.089 4.178a.5.5 0 0 1-.894 0l-2.089-4.178a44 44 0 0 1-6.584-.874m6.698-1.123 1.157.066L12 19.527l1.265-2.53 1.157-.066a42 42 0 0 0 4.227-.454A33.9 33.9 0 0 0 12 4.09a33.9 33.9 0 0 0-6.649 12.387q2.093.334 4.227.454M12 15a3 3 0 1 1 0-6 3 3 0 0 1 0 6m0-2a1 1 0 1 0 0-2 1 1 0 0 0 0 2"/></svg>

Before

Width:  |  Height:  |  Size: 533 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.38 2.019a7.5 7.5 0 1 0 10.6 10.6C21.662 17.854 17.316 22 12.001 22 6.477 22 2 17.523 2 12c0-5.315 4.146-9.661 9.38-9.981"/></svg>

Before

Width:  |  Height:  |  Size: 262 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12M11 1h2v3h-2zm0 19h2v3h-2zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414zm2.121-14.85 1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414zM23 11v2h-3v-2zM4 11v2H1v-2z"/></svg>

Before

Width:  |  Height:  |  Size: 435 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" class="iconify iconify--ant-design" viewBox="0 0 1024 1024"><path fill="currentColor" d="M864 170h-60c-4.4 0-8 3.6-8 8v518H310v-73c0-6.7-7.8-10.5-13-6.3l-141.9 112a8 8 0 0 0 0 12.6l141.9 112c5.3 4.2 13 .4 13-6.3v-75h498c35.3 0 64-28.7 64-64V178c0-4.4-3.6-8-8-8"/></svg>

Before

Width:  |  Height:  |  Size: 332 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" class="re-screen" color="#00000073" viewBox="0 0 16 16"><path fill="currentColor" d="M3.5 4H1V3h2V1h1v2.5zM13 3V1h-1v2.5l.5.5H15V3zm-1 9.5V15h1v-2h2v-1h-2.5zM1 12v1h2v2h1v-2.5l-.5-.5zm11-1.5-.5.5h-7l-.5-.5v-5l.5-.5h7l.5.5zM10 7H6v2h4z"/></svg>

Before

Width:  |  Height:  |  Size: 308 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" class="re-screen" color="#00000073" viewBox="0 0 16 16"><path fill="currentColor" d="M3 12h10V4H3zm2-6h6v4H5zM2 6H1V2.5l.5-.5H5v1H2zm13-3.5V6h-1V3h-3V2h3.5zM14 10h1v3.5l-.5.5H11v-1h3zM2 13h3v1H1.5l-.5-.5V10h1z"/></svg>

Before

Width:  |  Height:  |  Size: 283 B

View File

@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" class="iconify iconify--mdi" viewBox="0 0 24 24"><path fill="currentColor" d="M1 7h6v2H3v2h4v2H3v2h4v2H1zm10 0h4v2h-4v2h2a2 2 0 0 1 2 2v2c0 1.11-.89 2-2 2H9v-2h4v-2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2m8 0h2a2 2 0 0 1 2 2v1h-2V9h-2v6h2v-1h2v1c0 1.11-.89 2-2 2h-2a2 2 0 0 1-2-2V9c0-1.1.9-2 2-2"/></svg>

Before

Width:  |  Height:  |  Size: 360 B

Some files were not shown because too many files have changed in this diff Show More