完善 任务列表页面
This commit is contained in:
parent
963448382d
commit
24502a526d
|
|
@ -6,63 +6,63 @@ using System.Text;
|
||||||
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
using Microsoft.AspNetCore.Authentication.JwtBearer;
|
||||||
using VideoAnalysisCore.Common;
|
using VideoAnalysisCore.Common;
|
||||||
|
|
||||||
namespace Learn.Archives.API.Expand
|
namespace Learn.VideoAnalysis.Expand
|
||||||
{
|
{
|
||||||
public static class AuthorizeExpand
|
public static class AuthorizeExpand
|
||||||
{
|
{
|
||||||
public static IServiceCollection AddPermissionAuthentication(this IServiceCollection services)
|
public static IServiceCollection AddPermissionAuthentication(this IServiceCollection services)
|
||||||
{
|
{
|
||||||
services.AddAuthentication()
|
services.AddAuthentication()
|
||||||
.AddJwtBearer(Authentication.vdAdmin, options =>
|
.AddJwtBearer(Authentication.vdAdmin, options =>
|
||||||
{
|
{
|
||||||
options.RequireHttpsMetadata = false;
|
options.RequireHttpsMetadata = false;
|
||||||
options.UseSecurityTokenValidators = true;
|
options.UseSecurityTokenValidators = true;
|
||||||
options.MapInboundClaims = false; // .NET 5+
|
options.MapInboundClaims = false; // .NET 5+
|
||||||
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
|
||||||
options.TokenValidationParameters = new TokenValidationParameters
|
options.TokenValidationParameters = new TokenValidationParameters
|
||||||
{
|
{
|
||||||
SaveSigninToken = false,//保存token,后台验证token是否生效(重要)
|
SaveSigninToken = false,//保存token,后台验证token是否生效(重要)
|
||||||
RequireExpirationTime = true, // 设置请求需要携带accesstoken的过期时间
|
RequireExpirationTime = true, // 设置请求需要携带accesstoken的过期时间
|
||||||
ValidateIssuer = false,//必须验证签发人
|
ValidateIssuer = false,//必须验证签发人
|
||||||
ValidateAudience = false,//验证受众
|
ValidateAudience = false,//验证受众
|
||||||
ValidateLifetime = true,//是否验证Token有效期
|
ValidateLifetime = true,//是否验证Token有效期
|
||||||
ValidateIssuerSigningKey = true,//是否验证签名,不验证 会被篡改数据,不安全
|
ValidateIssuerSigningKey = true,//是否验证签名,不验证 会被篡改数据,不安全
|
||||||
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AppCommon.Config.AuthKey.Secret)),//解密的密钥
|
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(AppCommon.Config.AuthKey.Secret)),//解密的密钥
|
||||||
};
|
};
|
||||||
options.Events = new JwtBearerEvents
|
options.Events = new JwtBearerEvents
|
||||||
{
|
{
|
||||||
OnMessageReceived = context =>
|
OnMessageReceived = context =>
|
||||||
{
|
{
|
||||||
var token = context.Request.Headers["Authorization"].FirstOrDefault();
|
var token = context.Request.Headers["Authorization"].FirstOrDefault();
|
||||||
// 3. 安全提取令牌
|
// 3. 安全提取令牌
|
||||||
if (!string.IsNullOrEmpty(token) && token.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
if (!string.IsNullOrEmpty(token) && token.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
// 移除"Bearer "前缀并清除两端空格
|
// 移除"Bearer "前缀并清除两端空格
|
||||||
token = token.Substring("Bearer ".Length).Trim();
|
token = token.Substring("Bearer ".Length).Trim();
|
||||||
context.Token = token;
|
context.Token = token;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
},
|
},
|
||||||
OnAuthenticationFailed = context =>
|
OnAuthenticationFailed = context =>
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = 403;
|
context.Response.StatusCode = 403;
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
},
|
},
|
||||||
OnChallenge = context =>
|
OnChallenge = context =>
|
||||||
{
|
{
|
||||||
context.HandleResponse();
|
context.HandleResponse();
|
||||||
if (context.Response.StatusCode == 403)
|
if (context.Response.StatusCode == 403)
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
context.Response.Clear();
|
context.Response.Clear();
|
||||||
context.Response.ContentType = "application/json";
|
context.Response.ContentType = "application/json";
|
||||||
context.Response.StatusCode = 401;
|
context.Response.StatusCode = 401;
|
||||||
var data = new { Code = 401, Message = context.Error + context.AuthenticateFailure?.Message };
|
var data = new { Code = 401, Message = context.Error + context.AuthenticateFailure?.Message };
|
||||||
context.Response.WriteAsync(data.ToJson());
|
context.Response.WriteAsync(data.ToJson());
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return services;
|
return services;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,106 @@
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -15,6 +15,7 @@ using System.Diagnostics;
|
||||||
using VideoAnalysisCore.AICore.FFMPGE;
|
using VideoAnalysisCore.AICore.FFMPGE;
|
||||||
using System.Text.Encodings.Web;
|
using System.Text.Encodings.Web;
|
||||||
using System.Text.Unicode;
|
using System.Text.Unicode;
|
||||||
|
using System.Text.Json;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -26,11 +27,6 @@ namespace Learn.VideoAnalysis
|
||||||
{
|
{
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
// Add services to the container.
|
|
||||||
builder.Services.AddRazorComponents()
|
|
||||||
.AddInteractiveServerComponents();
|
|
||||||
//.AddInteractiveWebAssemblyComponents();
|
|
||||||
|
|
||||||
|
|
||||||
builder.Services.AddLogging(loggingBuilder =>
|
builder.Services.AddLogging(loggingBuilder =>
|
||||||
{
|
{
|
||||||
|
|
@ -39,33 +35,12 @@ namespace Learn.VideoAnalysis
|
||||||
loggingBuilder.SetMinimumLevel(LogLevel.Warning); // 设置最小日志级别为 Warning
|
loggingBuilder.SetMinimumLevel(LogLevel.Warning); // 设置最小日志级别为 Warning
|
||||||
});
|
});
|
||||||
|
|
||||||
builder.Services.AddControllers()
|
|
||||||
.AddJsonOptions(options =>
|
|
||||||
{
|
|
||||||
options.JsonSerializerOptions.Encoder = JavaScriptEncoder.Create(UnicodeRanges.All);
|
|
||||||
});
|
|
||||||
|
|
||||||
builder.Services.AddEndpointsApiExplorer();
|
|
||||||
builder.Services.AddSwaggerGen(c =>
|
|
||||||
{
|
|
||||||
c.SwaggerDoc("v1", new OpenApiInfo
|
|
||||||
{
|
|
||||||
Title = "Learn.VideoAnalysis",
|
|
||||||
Version = "v1",
|
|
||||||
Description = "教学视频分析平台v1"
|
|
||||||
});
|
|
||||||
var file = Path.Combine(AppContext.BaseDirectory, "Learn.VideoAnalysis.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的名称进行排序,如果有多个,就可以看见效果了。
|
|
||||||
});
|
|
||||||
|
|
||||||
//绑定 appsetting 配置
|
|
||||||
|
|
||||||
//初始化 插件
|
//初始化 插件
|
||||||
|
builder.Services.AddEndpointsApiExplorer();
|
||||||
|
builder.Services.AddSwaggerExpand("AI视频分析");
|
||||||
|
//绑定 appsetting 配置
|
||||||
builder.Configuration.AddAppConfig(args);
|
builder.Configuration.AddAppConfig(args);
|
||||||
|
builder.Services.AddPermissionAuthentication();
|
||||||
builder.Services.AddSqlSugarExpand();
|
builder.Services.AddSqlSugarExpand();
|
||||||
builder.Services.AddRedisExpand();
|
builder.Services.AddRedisExpand();
|
||||||
|
|
||||||
|
|
@ -84,6 +59,13 @@ namespace Learn.VideoAnalysis
|
||||||
{
|
{
|
||||||
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 =>
|
||||||
|
|
@ -115,11 +97,8 @@ namespace Learn.VideoAnalysis
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
AppCommon.Services = app.Services;
|
AppCommon.Services = app.Services;
|
||||||
|
|
||||||
app.UseMiddleware<BasicAuthMiddleware>("Swagger");
|
app.UseMiddleware<BasicAuthMiddleware>("Swagger");
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
|
|
||||||
_ = app.Services.GetRequiredService<RedisInit>();
|
_ = app.Services.GetRequiredService<RedisInit>();
|
||||||
app.UseSwagger();
|
app.UseSwagger();
|
||||||
app.UseSwaggerUI();
|
app.UseSwaggerUI();
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ VITE_PUBLIC_PATH = /
|
||||||
VITE_ROUTER_HISTORY = "hash"
|
VITE_ROUTER_HISTORY = "hash"
|
||||||
|
|
||||||
# 接口地址
|
# 接口地址
|
||||||
VITE_API_BASEURL = "http://192.168.2.33:5238/api"
|
VITE_API_BASEURL = "http://192.168.2.33:5238"
|
||||||
|
|
||||||
|
|
||||||
# # 接口地址
|
# # 接口地址
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import type { Res } from "@/utils/http/types";
|
||||||
* @return {object}
|
* @return {object}
|
||||||
*/
|
*/
|
||||||
export function getenum(type) {
|
export function getenum(type) {
|
||||||
return http.request<ComboModel[]>("get", `Public/enum/${type}`);
|
return http.request<ComboModel[]>("get", `api/Public/enum/${type}`);
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* @description 获取枚举对象
|
* @description 获取枚举对象
|
||||||
|
|
@ -16,5 +16,5 @@ export function getenum(type) {
|
||||||
* @return {object}
|
* @return {object}
|
||||||
*/
|
*/
|
||||||
export function getenumDic(type) {
|
export function getenumDic(type) {
|
||||||
return http.request<any>("get", `Public/enum/${type}/Dic`);
|
return http.request<any>("get", `api/Public/enum/${type}/Dic`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,23 +9,27 @@ export class hTableAPI {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
}
|
}
|
||||||
PageList(data = {}) {
|
PageList(data = {}) {
|
||||||
return http.request<Res<any>>("post", `${this.url}/PageList`, { data });
|
return http.request<Res<any>>("post", `api/${this.url}/PageList`, { data });
|
||||||
}
|
}
|
||||||
Info(tag) {
|
Info(tag) {
|
||||||
const pUrl = `${this.url}/${tag}`;
|
const pUrl = `api/${this.url}/${tag}`;
|
||||||
let getUrl = pUrl;
|
let getUrl = pUrl;
|
||||||
return http.request<Res<any>>("get", getUrl);
|
return http.request<Res<any>>("get", getUrl);
|
||||||
}
|
}
|
||||||
edit(data) {
|
edit(data) {
|
||||||
return http.request<Res<any>>("post", `${this.url}/Edit`, { data });
|
return http.request<Res<any>>("post", `api/${this.url}/Edit`, { data });
|
||||||
}
|
}
|
||||||
delete(data) {
|
delete(data) {
|
||||||
return http.request<Res<any>>("post", `${this.url}/Del`, { data });
|
return http.request<Res<any>>("post", `api/${this.url}/Del`, { data });
|
||||||
}
|
}
|
||||||
querycombo(data = {}) {
|
querycombo(data = {}) {
|
||||||
return http.request<Res<ComboModel[]>>("post", `${this.url}/QueryCombo`, {
|
return http.request<Res<ComboModel[]>>(
|
||||||
data
|
"post",
|
||||||
});
|
`api/${this.url}/QueryCombo`,
|
||||||
|
{
|
||||||
|
data
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ export type RefreshTokenResult = {
|
||||||
|
|
||||||
/** 登录 */
|
/** 登录 */
|
||||||
export const getLogin = (data?: object) => {
|
export const getLogin = (data?: object) => {
|
||||||
return http.request<UserResult>("post", "/api/Public/Login", { data });
|
return http.request<UserResult>("post", "/api/Admin/Login", { data });
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 刷新`token` */
|
/** 刷新`token` */
|
||||||
|
|
|
||||||
|
|
@ -37,22 +37,22 @@ export interface RowRloadResult {
|
||||||
|
|
||||||
/** 刷新任务实时数据 */
|
/** 刷新任务实时数据 */
|
||||||
export const RowRload = (id: any) => {
|
export const RowRload = (id: any) => {
|
||||||
return http.request<RowRloadResult>("post", "/api/VideoTask/RowRload", {
|
return http.request<RowRloadResult>("get", "/api/VideoTask/RowRload", {
|
||||||
params: id
|
params: { id }
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/** 重试任务 */
|
/** 重试任务 */
|
||||||
export const ReStart = (id: any, selectEnum: number) => {
|
export const ReStart = (id: any, selectEnum: number) => {
|
||||||
return http.request<any>("post", "/api/VideoTask/ReStart", {
|
return http.request<any>("get", "/api/VideoTask/ReStart", {
|
||||||
params: { id, selectEnum }
|
params: { id, selectEnum }
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/** 重试任务 */
|
/** 展示数据 */
|
||||||
export const ShowTaskInfo = (id: any) => {
|
export const ShowTaskInfo = (id: any) => {
|
||||||
return http.request<ShowTaskInfoRes>("post", "/api/VideoTask/ShowTaskInfo", {
|
return http.request<ShowTaskInfoRes>("get", "/api/VideoTask/ShowTaskInfo", {
|
||||||
params: { id }
|
params: { id }
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -210,7 +210,7 @@ function handleDelete(obj, row) {
|
||||||
ElMessageBox.confirm("此操作将永久删除勾选记录, 是否继续?")
|
ElMessageBox.confirm("此操作将永久删除勾选记录, 是否继续?")
|
||||||
.then(() => {
|
.then(() => {
|
||||||
Api.delete(ids).then((res) => {
|
Api.delete(ids).then((res) => {
|
||||||
if (res.code === 200) {
|
if (true) {
|
||||||
handleReloadPaged();
|
handleReloadPaged();
|
||||||
ElMessage.success("删除成功");
|
ElMessage.success("删除成功");
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -371,8 +371,8 @@ function fetchPagedData() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Api.PageList(table.value.search).then((res) => {
|
Api.PageList(table.value.search).then((res) => {
|
||||||
if (res.code === 200) {
|
if (true) {
|
||||||
table.value.data = res.data.data.map((s, i) => {
|
table.value.data = res.data.map((s, i) => {
|
||||||
return { ...s, customId: i };
|
return { ...s, customId: i };
|
||||||
});
|
});
|
||||||
table.value.pageData = res.data;
|
table.value.pageData = res.data;
|
||||||
|
|
|
||||||
|
|
@ -208,6 +208,9 @@ class PureHttp {
|
||||||
router.push({
|
router.push({
|
||||||
path: "/error/403"
|
path: "/error/403"
|
||||||
});
|
});
|
||||||
|
} else if (error.response?.status !== 200) {
|
||||||
|
ElMessage.warning("请求失败" + $error.message + " ");
|
||||||
|
console.log("请求失败" ,$error);
|
||||||
}
|
}
|
||||||
// 所有的响应异常 区分来源为取消请求/非取消请求
|
// 所有的响应异常 区分来源为取消请求/非取消请求
|
||||||
return Promise.reject($error);
|
return Promise.reject($error);
|
||||||
|
|
@ -234,11 +237,7 @@ class PureHttp {
|
||||||
PureHttp.axiosInstance
|
PureHttp.axiosInstance
|
||||||
.request(config)
|
.request(config)
|
||||||
.then((response: any) => {
|
.then((response: any) => {
|
||||||
debugger
|
resolve(response);
|
||||||
if (response.code != null && response.code !== 200) {
|
|
||||||
|
|
||||||
message(response.message, { type: "error" });
|
|
||||||
} resolve(response);
|
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
if (error.status != 200) {
|
if (error.status != 200) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,170 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import Motion from "./utils/motion";
|
||||||
|
import { useRouter } from "vue-router";
|
||||||
|
import { message } from "@/utils/message";
|
||||||
|
import { loginRules } from "./utils/rule";
|
||||||
|
import { ref, reactive, toRaw } from "vue";
|
||||||
|
import { debounce } from "@pureadmin/utils";
|
||||||
|
import { useNav } from "@/layout/hooks/useNav";
|
||||||
|
import { useEventListener } from "@vueuse/core";
|
||||||
|
import type { FormInstance } from "element-plus";
|
||||||
|
import { useLayout } from "@/layout/hooks/useLayout";
|
||||||
|
import { useUserStoreHook } from "@/store/modules/user";
|
||||||
|
import { initRouter, getTopMenu } from "@/router/utils";
|
||||||
|
import { bg, avatar, illustration } from "./utils/static";
|
||||||
|
import { useRenderIcon } from "@/components/ReIcon/src/hooks";
|
||||||
|
import { useDataThemeChange } from "@/layout/hooks/useDataThemeChange";
|
||||||
|
|
||||||
|
import dayIcon from "@/assets/svg/day.svg?component";
|
||||||
|
import darkIcon from "@/assets/svg/dark.svg?component";
|
||||||
|
import Lock from "~icons/ri/lock-fill";
|
||||||
|
import User from "~icons/ri/user-3-fill";
|
||||||
|
|
||||||
|
defineOptions({
|
||||||
|
name: "Login",
|
||||||
|
});
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const loading = ref(false);
|
||||||
|
const disabled = ref(false);
|
||||||
|
const ruleFormRef = ref<FormInstance>();
|
||||||
|
|
||||||
|
const { initStorage } = useLayout();
|
||||||
|
initStorage();
|
||||||
|
|
||||||
|
const { dataTheme, overallStyle, dataThemeChange } = useDataThemeChange();
|
||||||
|
dataThemeChange(overallStyle.value);
|
||||||
|
const { title } = useNav();
|
||||||
|
|
||||||
|
const ruleForm = reactive({
|
||||||
|
account: "",
|
||||||
|
password: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const onLogin = async (formEl: FormInstance | undefined) => {
|
||||||
|
if (!formEl) return;
|
||||||
|
await formEl.validate((valid) => {
|
||||||
|
if (valid) {
|
||||||
|
loading.value = true;
|
||||||
|
useUserStoreHook()
|
||||||
|
.loginByUsername({
|
||||||
|
account: ruleForm.account,
|
||||||
|
password: ruleForm.password,
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
// 获取后端路由
|
||||||
|
return initRouter().then(() => {
|
||||||
|
disabled.value = true;
|
||||||
|
router
|
||||||
|
.push(getTopMenu(true).path)
|
||||||
|
.then(() => {
|
||||||
|
message("登录成功", { type: "success" });
|
||||||
|
})
|
||||||
|
.finally(() => (disabled.value = false));
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.finally(() => (loading.value = false));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const immediateDebounce: any = debounce((formRef) => onLogin(formRef), 1000, true);
|
||||||
|
|
||||||
|
useEventListener(document, "keydown", ({ code }) => {
|
||||||
|
if (["Enter", "NumpadEnter"].includes(code) && !disabled.value && !loading.value)
|
||||||
|
immediateDebounce(ruleFormRef.value);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="select-none">
|
||||||
|
<img :src="bg" class="wave" />
|
||||||
|
<div class="flex-c absolute right-5 top-3">
|
||||||
|
<!-- 主题 -->
|
||||||
|
<el-switch
|
||||||
|
v-model="dataTheme"
|
||||||
|
inline-prompt
|
||||||
|
:active-icon="dayIcon"
|
||||||
|
:inactive-icon="darkIcon"
|
||||||
|
@change="dataThemeChange"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="login-container">
|
||||||
|
<div class="img">
|
||||||
|
<component :is="toRaw(illustration)" />
|
||||||
|
</div>
|
||||||
|
<div class="login-box">
|
||||||
|
<div class="login-form">
|
||||||
|
<avatar class="avatar" />
|
||||||
|
<Motion>
|
||||||
|
<h2 class="outline-hidden">{{ title }}</h2>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<!-- <el-form
|
||||||
|
ref="ruleFormRef"
|
||||||
|
:model="ruleForm"
|
||||||
|
:rules="loginRules"
|
||||||
|
size="large"
|
||||||
|
> -->
|
||||||
|
|
||||||
|
<el-form ref="ruleFormRef" :model="ruleForm" size="large">
|
||||||
|
<Motion :delay="100">
|
||||||
|
<el-form-item
|
||||||
|
:rules="[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: '请输入账号',
|
||||||
|
trigger: 'blur',
|
||||||
|
},
|
||||||
|
]"
|
||||||
|
prop="account"
|
||||||
|
>
|
||||||
|
<el-input
|
||||||
|
v-model="ruleForm.account"
|
||||||
|
clearable
|
||||||
|
placeholder="账号"
|
||||||
|
:prefix-icon="useRenderIcon(User)"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="150">
|
||||||
|
<el-form-item prop="password">
|
||||||
|
<el-input
|
||||||
|
v-model="ruleForm.password"
|
||||||
|
clearable
|
||||||
|
show-password
|
||||||
|
placeholder="密码"
|
||||||
|
:prefix-icon="useRenderIcon(Lock)"
|
||||||
|
/>
|
||||||
|
</el-form-item>
|
||||||
|
</Motion>
|
||||||
|
|
||||||
|
<Motion :delay="250">
|
||||||
|
<el-button
|
||||||
|
class="w-full mt-4!"
|
||||||
|
size="default"
|
||||||
|
type="primary"
|
||||||
|
:loading="loading"
|
||||||
|
:disabled="disabled"
|
||||||
|
@click="onLogin(ruleFormRef)"
|
||||||
|
>
|
||||||
|
登录
|
||||||
|
</el-button>
|
||||||
|
</Motion>
|
||||||
|
</el-form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
@import url("@/style/login.css");
|
||||||
|
</style>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
:deep(.el-input-group__append, .el-input-group__prepend) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
import { h, defineComponent, withDirectives, resolveDirective } from "vue";
|
||||||
|
|
||||||
|
/** 封装@vueuse/motion动画库中的自定义指令v-motion */
|
||||||
|
export default defineComponent({
|
||||||
|
name: "Motion",
|
||||||
|
props: {
|
||||||
|
delay: {
|
||||||
|
type: Number,
|
||||||
|
default: 50
|
||||||
|
}
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
const { delay } = this;
|
||||||
|
const motion = resolveDirective("motion");
|
||||||
|
return withDirectives(
|
||||||
|
h(
|
||||||
|
"div",
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
default: () => [this.$slots.default()]
|
||||||
|
}
|
||||||
|
),
|
||||||
|
[
|
||||||
|
[
|
||||||
|
motion,
|
||||||
|
{
|
||||||
|
initial: { opacity: 0, y: 100 },
|
||||||
|
enter: {
|
||||||
|
opacity: 1,
|
||||||
|
y: 0,
|
||||||
|
transition: {
|
||||||
|
delay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
import { reactive } from "vue";
|
||||||
|
import type { FormRules } from "element-plus";
|
||||||
|
|
||||||
|
/** 密码正则(密码格式应为8-18位数字、字母、符号的任意两种组合) */
|
||||||
|
export const REGEXP_PWD =
|
||||||
|
/^(?![0-9]+$)(?![a-z]+$)(?![A-Z]+$)(?!([^(0-9a-zA-Z)]|[()])+$)(?!^.*[\u4E00-\u9FA5].*$)([^(0-9a-zA-Z)]|[()]|[a-z]|[A-Z]|[0-9]){8,18}$/;
|
||||||
|
|
||||||
|
/** 登录校验 */
|
||||||
|
const loginRules = reactive<FormRules>({
|
||||||
|
password: [
|
||||||
|
{
|
||||||
|
validator: (rule, value, callback) => {
|
||||||
|
if (value === "") {
|
||||||
|
callback(new Error("请输入密码"));
|
||||||
|
} else if (!REGEXP_PWD.test(value)) {
|
||||||
|
callback(
|
||||||
|
new Error("密码格式应为8-18位数字、字母、符号的任意两种组合")
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
callback();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
trigger: "blur"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
export { loginRules };
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
import bg from "@/assets/login/bg.png";
|
||||||
|
import avatar from "@/assets/login/avatar.svg?component";
|
||||||
|
import illustration from "@/assets/login/illustration.svg?component";
|
||||||
|
|
||||||
|
export { bg, avatar, illustration };
|
||||||
|
|
@ -8,17 +8,19 @@ import {
|
||||||
TableConfig,
|
TableConfig,
|
||||||
} from "@/components/hTable/hTable";
|
} from "@/components/hTable/hTable";
|
||||||
import { onMounted, ref } from "vue";
|
import { onMounted, ref } from "vue";
|
||||||
import { fa } from "element-plus/es/locales.mjs";
|
import { de, fa } from "element-plus/es/locales.mjs";
|
||||||
import { hTableAPI } from "@/api/hTable";
|
import { hTableAPI } from "@/api/hTable";
|
||||||
import { getenum } from "@/api/enum";
|
import { getenum } from "@/api/enum";
|
||||||
import { ruleRequired, ruleRequiredNumber } from "@/utils/rules";
|
import { ruleRequired, ruleRequiredNumber } from "@/utils/rules";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import { RowRload } from "@/api/videoTask";
|
import { ReStart, RowRload } from "@/api/videoTask";
|
||||||
import { Refresh } from "@element-plus/icons-vue";
|
import { Refresh } from "@element-plus/icons-vue";
|
||||||
const ControllerName = "ExamClassInfo";
|
import { message } from "@/utils/message";
|
||||||
|
import { json } from "stream/consumers";
|
||||||
|
const ControllerName = "VideoTask";
|
||||||
|
|
||||||
defineOptions({
|
defineOptions({
|
||||||
name: "ClassExam",
|
name: ControllerName,
|
||||||
});
|
});
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|
@ -37,48 +39,50 @@ const tableData: TableConfig = intTableData({
|
||||||
search: {
|
search: {
|
||||||
// 查询条件
|
// 查询条件
|
||||||
show: true,
|
show: true,
|
||||||
PageSize: 999,
|
PageSize: 10,
|
||||||
},
|
},
|
||||||
operationColumn: true, // 显示操作按钮
|
operationColumn: true, // 显示操作按钮
|
||||||
operationColumnData: [
|
operationColumnData: [],
|
||||||
{
|
|
||||||
topBtn: false, // 头部按钮
|
|
||||||
show: true,
|
|
||||||
label: "详情",
|
|
||||||
btnType: "custom",
|
|
||||||
btnStyle: "primary",
|
|
||||||
custom: {
|
|
||||||
title: "考试班级详情", // 弹出框title
|
|
||||||
src: "exam/classExamRecord", // 组件路径
|
|
||||||
width: "1600px", // 弹框宽度
|
|
||||||
height: "800px", // 弹框高度
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
column: {
|
column: {
|
||||||
// 行数据
|
// 行数据
|
||||||
schoolName: {
|
id: {
|
||||||
label: "学校",
|
label: "ID",
|
||||||
width: "180px",
|
width: "140px",
|
||||||
search: new TableColumnSearch(true, ConditionalType.Like),
|
|
||||||
},
|
|
||||||
grade: {
|
|
||||||
label: "年级",
|
|
||||||
width: "120px",
|
|
||||||
custom: (row) => row.gradeLevel + row.gradeYear + "届",
|
|
||||||
search: new TableColumnSearch(true),
|
search: new TableColumnSearch(true),
|
||||||
},
|
},
|
||||||
className: {
|
tagId: {
|
||||||
label: "班级",
|
label: "标签ID",
|
||||||
width: "80px",
|
width: "140px",
|
||||||
search: new TableColumnSearch(true, ConditionalType.Like),
|
search: new TableColumnSearch(true),
|
||||||
},
|
},
|
||||||
examName: {
|
videoType: {
|
||||||
label: "最近考试",
|
label: "任务类型",
|
||||||
|
width: "100px",
|
||||||
|
type: "dropdown",
|
||||||
|
search: new TableColumnSearch(true),
|
||||||
|
},
|
||||||
|
lastEnum: {
|
||||||
|
label: "最后状态",
|
||||||
width: "200px",
|
width: "200px",
|
||||||
|
type: "dropdown",
|
||||||
|
search: new TableColumnSearch(true),
|
||||||
},
|
},
|
||||||
peopleCount: {
|
subject: {
|
||||||
label: "参考人数",
|
label: "学科",
|
||||||
|
width: "100px",
|
||||||
|
type: "dropdown",
|
||||||
|
search: new TableColumnSearch(true),
|
||||||
|
},
|
||||||
|
comeFrom: {
|
||||||
|
label: "IP",
|
||||||
|
width: "120px",
|
||||||
|
},
|
||||||
|
mediaUrl: {
|
||||||
|
label: "媒体地址",
|
||||||
|
width: "300px",
|
||||||
|
},
|
||||||
|
createTime: {
|
||||||
|
label: "创建时间",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
data: [],
|
data: [],
|
||||||
|
|
@ -92,35 +96,67 @@ const dialogRef = ref({
|
||||||
value: null as any,
|
value: null as any,
|
||||||
data: Object as any,
|
data: Object as any,
|
||||||
});
|
});
|
||||||
let subjectEnum = ref<ComboModel[]>([]);
|
let redisChannelEnum = ref<ComboModel[]>([]);
|
||||||
const showTable = ref(false);
|
const showTable = ref(false);
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
//初始化数据
|
//初始化数据
|
||||||
subjectEnum.value = await getenum("SubjectEnum");
|
tableData.column.videoType.setting.datasource = await getenum("AttachmentsInfoType");
|
||||||
|
tableData.column.lastEnum.setting.datasource = await getenum("RedisChannelEnum");
|
||||||
|
tableData.column.subject.setting.datasource = await getenum("SubjectEnum");
|
||||||
|
redisChannelEnum.value = tableData.column.lastEnum.setting.datasource;
|
||||||
showTable.value = true;
|
showTable.value = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
async function showDialog(row) {
|
||||||
|
dialogRef.value.data = row;
|
||||||
|
dialogRef.value.value = row.lastEnum;
|
||||||
|
dialogRef.value.visible = true;
|
||||||
|
}
|
||||||
|
async function submitRowRload() {
|
||||||
|
await ReStart(dialogRef.value.data.id, dialogRef.value.value);
|
||||||
|
dialogRef.value.visible = false;
|
||||||
|
message("重试任务", { type: "success" });
|
||||||
|
}
|
||||||
async function expandChange(row: any, expandedRows: any[]) {
|
async function expandChange(row: any, expandedRows: any[]) {
|
||||||
let res = await RowRload(row.id);
|
if (expandedRows.find((s) => s == row)) RloadTaskInfo(row);
|
||||||
row.TaskInfo = res;
|
|
||||||
}
|
}
|
||||||
async function RloadTaskInfo(row: any) {
|
async function RloadTaskInfo(row: any) {
|
||||||
let res = await RowRload(row.id);
|
let res = await RowRload(row.id);
|
||||||
row.TaskInfo = res;
|
row.TaskInfo = res;
|
||||||
row.TaskInfo.active = stepData.value.findIndex((s) => s.value == row.TaskInfo.lastEnum);
|
row.TaskInfo.stepData = JSON.parse(JSON.stringify(stepData.value));
|
||||||
for (const element of stepData.value) {
|
row.TaskInfo.active = row.TaskInfo.stepData.findIndex(
|
||||||
element.time = row.TaskInfo.startTime[element.title];
|
(s) => s.title == row.TaskInfo.lastEnum
|
||||||
if (element.value < row.TaskInfo.lastEnum) {
|
);
|
||||||
element.status = "finish";
|
if (row.TaskInfo.startTime != null) {
|
||||||
} else if (element.value == 60) {
|
for (const element of row.TaskInfo.stepData) {
|
||||||
element.status = "success";
|
element.time = formatDateToChinese(
|
||||||
} else if (element.value == row.TaskInfo.lastEnum) {
|
row.TaskInfo.startTime[element.title.toLowerCase()]
|
||||||
element.status = "process";
|
);
|
||||||
} else {
|
if (element.value < row.TaskInfo.lastEnum) {
|
||||||
element.status = "wait";
|
element.status = "finish";
|
||||||
|
} else if (element.value == 60) {
|
||||||
|
element.status = "success";
|
||||||
|
} else if (element.value == row.TaskInfo.lastEnum) {
|
||||||
|
element.status = "process";
|
||||||
|
} else {
|
||||||
|
element.status = "wait";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
function formatDateToChinese(dateString) {
|
||||||
|
const date = new Date(dateString);
|
||||||
|
if (isNaN(date.getTime())) return "";
|
||||||
|
|
||||||
|
const year = date.getFullYear();
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||||
|
const day = String(date.getDate()).padStart(2, "0");
|
||||||
|
const hours = String(date.getHours()).padStart(2, "0");
|
||||||
|
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||||
|
const seconds = String(date.getSeconds()).padStart(2, "0");
|
||||||
|
|
||||||
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||||
|
}
|
||||||
interface StepData {
|
interface StepData {
|
||||||
status: "" | "wait" | "process" | "finish" | "error" | "success";
|
status: "" | "wait" | "process" | "finish" | "error" | "success";
|
||||||
time: string | null;
|
time: string | null;
|
||||||
|
|
@ -144,49 +180,59 @@ const stepData = ref<StepData[]>([
|
||||||
<template #expandSlot="{ props }">
|
<template #expandSlot="{ props }">
|
||||||
<!-- 拓展内容 -->
|
<!-- 拓展内容 -->
|
||||||
<div class="expanded-content expandSlot">
|
<div class="expanded-content expandSlot">
|
||||||
<el-descriptions title="任务详情" :column="2" border>
|
<h3>任务详情</h3>
|
||||||
<el-descriptions-item label="操作">
|
<div class="InfoEx">
|
||||||
<el-button
|
<div>
|
||||||
type="primary"
|
<span>进度</span>
|
||||||
:icon="Refresh"
|
<div class="content">{{ props.row.lastEnum }} {{ props.row.progress }}</div>
|
||||||
@click="RloadTaskInfo(props.row.id)"
|
</div>
|
||||||
circle
|
<div>
|
||||||
/>
|
<span>操作</span>
|
||||||
<el-button type="danger">重试</el-button>
|
<div class="content">
|
||||||
<el-button type="primary">预览</el-button>
|
<el-button
|
||||||
</el-descriptions-item>
|
type="primary"
|
||||||
<el-descriptions-item :label="props.row.lastEnum">
|
:icon="Refresh"
|
||||||
{{ props.row.progress }}
|
@click="RloadTaskInfo(props.row)"
|
||||||
</el-descriptions-item>
|
circle
|
||||||
<el-descriptions-item label="步骤" :rowspan="2">
|
/>
|
||||||
|
<el-button type="danger" @click="showDialog(props.row.id)"
|
||||||
|
>重试</el-button
|
||||||
|
>
|
||||||
|
<el-button type="primary">预览</el-button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="grid_item_full_width" v-if="props.row.TaskInfo != null">
|
||||||
|
<span>步骤</span>
|
||||||
<el-steps
|
<el-steps
|
||||||
style="max-width: 1000px"
|
class="content"
|
||||||
:active="stepData.findIndex((s) => s.value == props.row.lastEnum)"
|
style="max-width: 100%"
|
||||||
|
:active="props.row.TaskInfo?.active"
|
||||||
align-center
|
align-center
|
||||||
>
|
>
|
||||||
<el-step
|
<el-step
|
||||||
v-for="s in stepData"
|
v-for="s in props.row.TaskInfo.stepData"
|
||||||
:title="s.title"
|
:title="s.title"
|
||||||
:description="s.time"
|
:description="s.time"
|
||||||
:status="s.status"
|
:status="s.status"
|
||||||
/>
|
/>
|
||||||
</el-steps>
|
</el-steps>
|
||||||
</el-descriptions-item>
|
</div>
|
||||||
<el-descriptions-item
|
<div
|
||||||
v-if="props.row.TaskInfo?.errorMessage != ''"
|
v-if="
|
||||||
label="错误信息"
|
props.row.TaskInfo?.errorMessage != null &&
|
||||||
|
props.row.TaskInfo?.errorMessage.length > 0
|
||||||
|
"
|
||||||
>
|
>
|
||||||
{{ props.row.TaskInfo?.errorMessage }}
|
<span>错误信息</span>
|
||||||
</el-descriptions-item>
|
<div class="content">{{ props.row.TaskInfo?.errorMessage }}</div>
|
||||||
</el-descriptions>
|
</div>
|
||||||
<h2>任务详情</h2>
|
</div>
|
||||||
<div>1</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</ahTable>
|
</ahTable>
|
||||||
|
|
||||||
<el-dialog v-model="dialogRef.visible" title="重试任务" width="800">
|
<el-dialog v-model="dialogRef.visible" title="重试任务" width="430">
|
||||||
<h3>重试任务</h3>
|
|
||||||
<h3>ID : {{ dialogRef.data.id }}</h3>
|
<h3>ID : {{ dialogRef.data.id }}</h3>
|
||||||
<p></p>
|
<p></p>
|
||||||
<p>将从哪个步骤重试?</p>
|
<p>将从哪个步骤重试?</p>
|
||||||
|
|
@ -197,16 +243,22 @@ const stepData = ref<StepData[]>([
|
||||||
style="width: 240px"
|
style="width: 240px"
|
||||||
>
|
>
|
||||||
<el-option
|
<el-option
|
||||||
v-for="item in subjectEnum"
|
v-for="item in redisChannelEnum"
|
||||||
:key="item.value"
|
:key="item.value"
|
||||||
:label="item.text"
|
:label="item.text"
|
||||||
:value="item.value"
|
:value="item.value"
|
||||||
/>
|
/>
|
||||||
</el-select>
|
</el-select>
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="dialogRef.visible = false">取消</el-button>
|
||||||
|
<el-button type="primary" @click="submitRowRload()"> 提交 </el-button>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<style>
|
<style scoped>
|
||||||
.expandSlot {
|
.expandSlot {
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
background: #f5f7fa;
|
background: #f5f7fa;
|
||||||
|
|
@ -220,4 +272,24 @@ const stepData = ref<StepData[]>([
|
||||||
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
.InfoEx {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, 1fr);
|
||||||
|
gap: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.InfoEx span {
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 10px;
|
||||||
|
}
|
||||||
|
.InfoEx .content {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
/* 关键:让某个网格项占满一行(跨所有列) */
|
||||||
|
.grid_item_full_width {
|
||||||
|
grid-column: 1 / -1; /* 从第1列跨到最后一列 */
|
||||||
|
}
|
||||||
|
/* :deep(.el-step__description) {
|
||||||
|
font-size: 16px !important;
|
||||||
|
} */
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,5 @@
|
||||||
using FreeRedis;
|
using FFmpeg.NET.Services;
|
||||||
|
using FreeRedis;
|
||||||
using Microsoft.Extensions.DependencyModel;
|
using Microsoft.Extensions.DependencyModel;
|
||||||
using Microsoft.IdentityModel.Tokens;
|
using Microsoft.IdentityModel.Tokens;
|
||||||
using SqlSugar;
|
using SqlSugar;
|
||||||
|
|
@ -13,7 +14,7 @@ using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using UserCenter.Model.Interface;
|
using UserCenter.Model;
|
||||||
using VideoAnalysisCore.AICore.SherpaOnnx;
|
using VideoAnalysisCore.AICore.SherpaOnnx;
|
||||||
using VideoAnalysisCore.Model;
|
using VideoAnalysisCore.Model;
|
||||||
using VideoAnalysisCore.Model.Dto;
|
using VideoAnalysisCore.Model.Dto;
|
||||||
|
|
@ -203,16 +204,11 @@ namespace VideoAnalysisCore.Common
|
||||||
.Where(u => !u.Name.StartsWith(nameof(Microsoft))
|
.Where(u => !u.Name.StartsWith(nameof(Microsoft))
|
||||||
&& !u.Name.StartsWith(nameof(System))
|
&& !u.Name.StartsWith(nameof(System))
|
||||||
&& !u.Name.StartsWith("netstandard")
|
&& !u.Name.StartsWith("netstandard")
|
||||||
&& (u.Type == "project"));
|
&& u.Type == "project");
|
||||||
|
|
||||||
var assemblies = assembliesStr.Select(a => AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(a.Name))).ToList();
|
var assemblies = assembliesStr.Select(a => AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(a.Name))).ToList();
|
||||||
var assemblies1 = Assembly.GetEntryAssembly().GetReferencedAssemblies().Where(x => x.Name.StartsWith("App.") || x.Name.StartsWith("UserCenter."))
|
var assembly = Assembly.Load("UserCenter.Model");
|
||||||
.Select(a => AssemblyLoadContext.Default.LoadFromAssemblyName(new AssemblyName(a.Name))).ToList();
|
assemblies.Add(assembly);
|
||||||
foreach (var item in assemblies1)
|
|
||||||
{
|
|
||||||
if (!assemblies.Contains(item))
|
|
||||||
assemblies.Add(item);
|
|
||||||
}
|
|
||||||
return assemblies;
|
return assemblies;
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using UserCenter.Model;
|
||||||
|
using VideoAnalysisCore.Common;
|
||||||
|
using VideoAnalysisCore.Controllers.Dto;
|
||||||
|
|
||||||
|
namespace VideoAnalysisCore.Controllers
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 通用接口
|
||||||
|
/// </summary>
|
||||||
|
[Authorize(AuthenticationSchemes = Authentication.vdAdmin)]
|
||||||
|
[Route("api/[controller]/[action]")]
|
||||||
|
public class AdminController : ControllerBase
|
||||||
|
{
|
||||||
|
public AdminController()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
/// <summary>
|
||||||
|
/// 登录
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="req">请求体</param>
|
||||||
|
/// <returns></returns>
|
||||||
|
[HttpPost, AllowAnonymous]
|
||||||
|
public IActionResult Login([FromBody] AdminLoginReq req)
|
||||||
|
{
|
||||||
|
if (!ModelState.IsValid) return BadRequest(ModelState);
|
||||||
|
if (string.IsNullOrWhiteSpace(req.Account)
|
||||||
|
|| string.IsNullOrWhiteSpace(req.Password))
|
||||||
|
return BadRequest("无效的登录信息");
|
||||||
|
|
||||||
|
return Ok(new
|
||||||
|
{
|
||||||
|
//按钮权限
|
||||||
|
Permissions = "*",
|
||||||
|
//用户名
|
||||||
|
UserName = "管理员",
|
||||||
|
NickName = "管理员",
|
||||||
|
AccessToken = JwtHelper.GetToken(AppCommon.Config.AuthKey,
|
||||||
|
[
|
||||||
|
new Claim("Id","999"),
|
||||||
|
])
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
|
|
||||||
using VideoAnalysisCore.Common;
|
|
||||||
using Microsoft.AspNetCore.Mvc;
|
|
||||||
using System.Reflection;
|
|
||||||
using MapsterMapper;
|
|
||||||
using Mapster;
|
|
||||||
using VideoAnalysisCore.AICore.SherpaOnnx;
|
|
||||||
using UserCenter.Model.Enum;
|
|
||||||
using VideoAnalysisCore.AICore.GPT.ChatGPT;
|
|
||||||
using VideoAnalysisCore.AICore.GPT;
|
|
||||||
using System.Text.Json;
|
|
||||||
using VideoAnalysisCore.Model.Enum;
|
|
||||||
using Yitter.IdGenerator;
|
|
||||||
using VideoAnalysisCore.Model;
|
|
||||||
using Microsoft.AspNetCore.Http;
|
|
||||||
using VideoAnalysisCore.Model.Dto;
|
|
||||||
using VideoAnalysisCore.Controllers.Dto;
|
|
||||||
using VideoAnalysisCore.Common.Expand;
|
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
|
||||||
using System.Threading.Tasks;
|
|
||||||
using VideoAnalysisCore.Model.À¶¾¨ÖÇ¿â;
|
|
||||||
using VideoAnalysisCore.Model.Interface;
|
|
||||||
using System.Security.Claims;
|
|
||||||
using UserCenter.Model;
|
|
||||||
using Microsoft.AspNetCore.Authorization;
|
|
||||||
using SqlSugar;
|
|
||||||
|
|
||||||
namespace VideoAnalysisCore.Controllers
|
|
||||||
{
|
|
||||||
[ApiController]
|
|
||||||
[Route("[controller]/[action]")]
|
|
||||||
[Authorize(AuthenticationSchemes = Authentication.vdAdmin)]
|
|
||||||
public class ApiController : ControllerBase
|
|
||||||
{
|
|
||||||
private readonly IMapper mp;
|
|
||||||
private readonly Repository<VideoTask> videoTaskDB;
|
|
||||||
private readonly RedisManager redisManager;
|
|
||||||
private readonly Repository<VideoKonwPoint> videoKonwDB;
|
|
||||||
private readonly Repository<CourseInfo> courseInfoDB;
|
|
||||||
public readonly SenseVoice senseVoice;
|
|
||||||
public ApiController(Repository<VideoTask> videoTaskDB,
|
|
||||||
IMapper mp, Repository<VideoKonwPoint> videoKonwDB, Repository<CourseInfo> courseInfoDB, RedisManager redisManager, SenseVoice senseVoice)
|
|
||||||
{
|
|
||||||
this.videoTaskDB = videoTaskDB;
|
|
||||||
this.mp = mp;
|
|
||||||
this.videoKonwDB = videoKonwDB;
|
|
||||||
this.courseInfoDB = courseInfoDB;
|
|
||||||
this.redisManager = redisManager;
|
|
||||||
this.senseVoice = senseVoice;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -75,33 +75,8 @@ namespace VideoAnalysisCore.Controllers
|
||||||
AppCommon.Config
|
AppCommon.Config
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
#endif
|
|
||||||
/// <summary>
|
|
||||||
/// 视频处理
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="req">请求体</param>
|
|
||||||
/// <returns></returns>
|
|
||||||
[HttpPost, AllowAnonymous]
|
|
||||||
public async Task<ActionResult<object>> Login(AdminLoginReq req)
|
|
||||||
{
|
|
||||||
if (!ModelState.IsValid) return BadRequest(ModelState);
|
|
||||||
if (string.IsNullOrWhiteSpace(req.Account)
|
|
||||||
|| string.IsNullOrWhiteSpace(req.Password))
|
|
||||||
return BadRequest("无效的登录信息");
|
|
||||||
|
|
||||||
return Ok(new
|
#endif
|
||||||
{
|
|
||||||
//按钮权限
|
|
||||||
Permissions = "*",
|
|
||||||
//用户名
|
|
||||||
UserName = "管理员",
|
|
||||||
NickName = "管理员",
|
|
||||||
AccessToken = JwtHelper.GetToken(AppCommon.Config.AuthKey,
|
|
||||||
[
|
|
||||||
new Claim("Id","999"),
|
|
||||||
])
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -177,8 +177,8 @@ namespace VideoAnalysisCore.Controllers
|
||||||
/// <param name="tagId">自定义id</param>
|
/// <param name="tagId">自定义id</param>
|
||||||
/// <param name="subject">切换任务所属学科 null忽略</param>
|
/// <param name="subject">切换任务所属学科 null忽略</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet(Name = "ReStart")]
|
[HttpGet]
|
||||||
public async Task<IActionResult> ReStart(long taskId, string? tagId, SubjectEnum? subject)
|
public async Task<IActionResult> ReStartTask(long taskId, string? tagId, SubjectEnum? subject)
|
||||||
{
|
{
|
||||||
var task = await baseService.AsQueryable()
|
var task = await baseService.AsQueryable()
|
||||||
.WhereIF(taskId != 0, s => s.Id == taskId)
|
.WhereIF(taskId != 0, s => s.Id == taskId)
|
||||||
|
|
@ -192,7 +192,7 @@ namespace VideoAnalysisCore.Controllers
|
||||||
await baseService.UpdateAsync(task);
|
await baseService.UpdateAsync(task);
|
||||||
}
|
}
|
||||||
//todo重新开始执行GPT分析
|
//todo重新开始执行GPT分析
|
||||||
|
return BadRequest("任务为实现");
|
||||||
return Ok();
|
return Ok();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -241,9 +241,9 @@ namespace VideoAnalysisCore.Controllers
|
||||||
public override async Task<dynamic> PageList([FromBody] QueryRequestBase model)
|
public override async Task<dynamic> PageList([FromBody] QueryRequestBase model)
|
||||||
{
|
{
|
||||||
var sqlquery = base.BaseQuery(model)
|
var sqlquery = base.BaseQuery(model)
|
||||||
.Select(s =>new VideoTask
|
.Select(s => new VideoTask
|
||||||
{
|
{
|
||||||
Id = s.Id,
|
Id = s.Id,
|
||||||
TagId = s.TagId,
|
TagId = s.TagId,
|
||||||
VideoType = s.VideoType,
|
VideoType = s.VideoType,
|
||||||
LastEnum = s.LastEnum,
|
LastEnum = s.LastEnum,
|
||||||
|
|
@ -282,17 +282,21 @@ namespace VideoAnalysisCore.Controllers
|
||||||
/// <param name="id">任务id</param>
|
/// <param name="id">任务id</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<object> RowRload(long id)
|
public async Task<IActionResult> RowRload(long id)
|
||||||
{
|
{
|
||||||
|
if (id == 0)
|
||||||
|
return BadRequest("无效id");
|
||||||
var d = await redisManager.Redis.HMGetAsync<string>(RedisExpandKey.Task(id),
|
var d = await redisManager.Redis.HMGetAsync<string>(RedisExpandKey.Task(id),
|
||||||
"Progress", "LastEnum", "StartTime", "ErrorMessage");
|
"Progress", "LastEnum", "StartTime", "ErrorMessage");
|
||||||
return Ok(new
|
return Ok(new
|
||||||
{
|
{
|
||||||
Progress = d[0],
|
Progress = d[0],
|
||||||
LastEnum = d[1]?.ToEnum<RedisChannelEnum>().ToString(),
|
LastEnum = d[1]?.ToEnum<RedisChannelEnum>().ToString(),
|
||||||
StartTime = JsonSerializer.Deserialize<Dictionary<RedisChannelEnum, DateTime>>( d[2]),
|
StartTime = d[2] != null
|
||||||
|
? JsonSerializer.Deserialize<Dictionary<RedisChannelEnum, DateTime>>(d[2])
|
||||||
|
: null,
|
||||||
ErrorMessage = d[3],
|
ErrorMessage = d[3],
|
||||||
}) ;
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -301,7 +305,7 @@ namespace VideoAnalysisCore.Controllers
|
||||||
/// <param name="id">任务id</param>
|
/// <param name="id">任务id</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<object> ShowTaskInfo(long id)
|
public async Task<object> ShowTaskInfo(long id)
|
||||||
{
|
{
|
||||||
var nowTask = await baseService.GetFirstAsync(s => s.Id == id);
|
var nowTask = await baseService.GetFirstAsync(s => s.Id == id);
|
||||||
if (nowTask is null)
|
if (nowTask is null)
|
||||||
|
|
@ -342,7 +346,8 @@ namespace VideoAnalysisCore.Controllers
|
||||||
.Where(s => s.StageId == item.StageId).ToArray();
|
.Where(s => s.StageId == item.StageId).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
return Ok(new {
|
return Ok(new
|
||||||
|
{
|
||||||
Captions = captionsArr,
|
Captions = captionsArr,
|
||||||
Captions1 = captionsArr1,
|
Captions1 = captionsArr1,
|
||||||
VideoKnows = videoKnows,
|
VideoKnows = videoKnows,
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@
|
||||||
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
|
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
|
||||||
<PackageReference Include="SqlSugar.IOC" Version="2.0.0" />
|
<PackageReference Include="SqlSugar.IOC" Version="2.0.0" />
|
||||||
<PackageReference Include="SqlSugarCore" Version="5.1.4.205" />
|
<PackageReference Include="SqlSugarCore" Version="5.1.4.205" />
|
||||||
<PackageReference Include="UserCenter.Model" Version="1.3.5" />
|
<PackageReference Include="UserCenter.Model" Version="1.4.9" />
|
||||||
<PackageReference Include="Whisper.net" Version="1.5.0" />
|
<PackageReference Include="Whisper.net" Version="1.5.0" />
|
||||||
<PackageReference Include="Whisper.net.Runtime" Version="1.5.0" />
|
<PackageReference Include="Whisper.net.Runtime" Version="1.5.0" />
|
||||||
<PackageReference Include="xFFmpeg.NET" Version="6.0.0" />
|
<PackageReference Include="xFFmpeg.NET" Version="6.0.0" />
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue