diff --git a/deploy/easyvqd/1.png b/deploy/easyvqd/1.png index 14ab8dd..6afbb7d 100644 Binary files a/deploy/easyvqd/1.png and b/deploy/easyvqd/1.png differ diff --git a/deploy/easyvqd/EasyVQD.ico b/deploy/easyvqd/EasyVQD.ico index 85a1228..a6da128 100644 Binary files a/deploy/easyvqd/EasyVQD.ico and b/deploy/easyvqd/EasyVQD.ico differ diff --git a/internal/app/wire_gen.go b/internal/app/wire_gen.go index a070da9..83c74d8 100644 --- a/internal/app/wire_gen.go +++ b/internal/app/wire_gen.go @@ -30,7 +30,7 @@ func WireApp(bc *conf.Bootstrap, log *slog.Logger) (http.Handler, func(), error) vqdTaskCore := api.NewVqdTaskCore(db) hostCore := host.NewCore(bc) mediaCore := media.NewCore(hostCore) - vqdSdkCore := vqdsdk.NewCore(hostCore, vqdTaskCore) + vqdSdkCore := vqdsdk.NewCore(hostCore, vqdTaskCore, bc) vqdTaskAPI := api.NewVqdTaskAPI(vqdTaskCore, mediaCore,vqdSdkCore,hostCore, bc) usecase := &api.Usecase{ diff --git a/internal/core/vqd/store/audioencodedb/vqdalarm.go b/internal/core/vqd/store/audioencodedb/vqdalarm.go index a68f4e4..4ae1210 100644 --- a/internal/core/vqd/store/audioencodedb/vqdalarm.go +++ b/internal/core/vqd/store/audioencodedb/vqdalarm.go @@ -5,6 +5,7 @@ import ( "context" "easyvqd/internal/core/vqd" "git.lnton.com/lnton/pkg/orm" + "time" ) var _ vqd.VqdAlarmStorer = VqdAlarm{} @@ -48,6 +49,15 @@ func (d VqdAlarm) Del(ctx context.Context, model *vqd.VqdAlarm, opts ...orm.Quer return orm.DeleteWithContext(ctx, d.db, model, opts...) } +// DelAll implements vqd.VqdTaskTemplateStorer. +func (d VqdAlarm) DelAll(expireTime time.Time, batchSize int) (int, error) { + result := d.db.Where(`created_at < ?`, expireTime).Limit(batchSize).Delete(&vqd.VqdAlarm{}) + if result.Error != nil { + return 0, result.Error + } + return int(result.RowsAffected), nil +} + // EditStatus implements vqd.VqdAlarmStorer. func (d VqdAlarm) EditStatus(status int, id int) error { if err := d.db.Model(&vqd.VqdAlarm{}).Where(`id = ?`, id).Update("task_status", status).Error; err != nil { diff --git a/internal/core/vqd/store/audioencodedb/vqdtasktemplate.go b/internal/core/vqd/store/audioencodedb/vqdtasktemplate.go index 86e92e5..aa3a6ed 100644 --- a/internal/core/vqd/store/audioencodedb/vqdtasktemplate.go +++ b/internal/core/vqd/store/audioencodedb/vqdtasktemplate.go @@ -55,3 +55,6 @@ func (d VqdTaskTemplate) EditStatus(status vqd.EncodeStatus, id int) error { } return nil } +func (d VqdTaskTemplate) FirstOrCreate(b any) error { + return d.db.FirstOrCreate(b).Error +} diff --git a/internal/core/vqd/vqdalarm.go b/internal/core/vqd/vqdalarm.go index be8c3da..be1e516 100644 --- a/internal/core/vqd/vqdalarm.go +++ b/internal/core/vqd/vqdalarm.go @@ -7,6 +7,7 @@ import ( "git.lnton.com/lnton/pkg/reason" "github.com/jinzhu/copier" "log/slog" + "time" ) // VqdAlarmStorer Instantiation interface @@ -17,6 +18,7 @@ type VqdAlarmStorer interface { Add(context.Context, *VqdAlarm) error Edit(context.Context, *VqdAlarm, func(*VqdAlarm), ...orm.QueryOption) error Del(context.Context, *VqdAlarm, ...orm.QueryOption) error + DelAll(expireTime time.Time, batchSize int) (int, error) } // FindVqdAlarmAll Paginated search @@ -113,3 +115,8 @@ func (c Core) DelVqdAlarmAll(ctx context.Context, ids []int) (*VqdAlarm, error) } return &out, nil } + +// DelVqdTaskAlarmAll Delete object +func (c Core) DelVqdTaskAlarmAll(expireTime time.Time, batchSize int) (int, error) { + return c.store.VqdAlarm().DelAll(expireTime, batchSize) +} diff --git a/internal/core/vqd/vqdtask.go b/internal/core/vqd/vqdtask.go index 032bf1a..c0bffa2 100644 --- a/internal/core/vqd/vqdtask.go +++ b/internal/core/vqd/vqdtask.go @@ -52,6 +52,18 @@ func (c Core) FindVqdTask(ctx context.Context, in *FindVqdTaskInput) ([]*VqdTask } } +// FindVqdTaskTemplateID Query a single object +func (c Core) FindVqdTaskTemplateID(ctx context.Context, id int) (*VqdTask, error) { + var out VqdTask + if err := c.store.VqdTask().Get(ctx, &out, orm.Where("task_template_id=?", id)); err != nil { + if orm.IsErrRecordNotFound(err) { + return nil, reason.ErrNotFound.Withf(`Get err[%s]`, err.Error()) + } + return nil, reason.ErrDB.Withf(`Get err[%s]`, err.Error()) + } + return &out, nil +} + // GetVqdTask Query a single object func (c Core) GetVqdTask(ctx context.Context, id int) (*VqdTask, error) { var out VqdTask @@ -63,6 +75,7 @@ func (c Core) GetVqdTask(ctx context.Context, id int) (*VqdTask, error) { } return &out, nil } + func (c Core) GetNameVqdTask(ctx context.Context, name string) (*VqdTask, error) { var out VqdTask if err := c.store.VqdTask().Get(ctx, &out, orm.Where("name=?", name)); err != nil { diff --git a/internal/core/vqd/vqdtask.param.go b/internal/core/vqd/vqdtask.param.go index c0d13e6..122829e 100644 --- a/internal/core/vqd/vqdtask.param.go +++ b/internal/core/vqd/vqdtask.param.go @@ -14,7 +14,7 @@ type EditVqdTaskInput struct { Name string `json:"name"` // 名称 ChannelID string `json:"channel_id"` // 关联通道 ChannelName string `json:"channel_name"` // 通道名称 - TaskTemplateID string `json:"task_template_id"` // 关联模板 + TaskTemplateID int `json:"task_template_id"` // 关联模板 TaskTemplateName string `json:"task_template_name"` // 模板名称 Enable bool `form:"enable"` // 启用 Des string `json:"des"` // 描述 @@ -24,7 +24,7 @@ type AddVqdTaskInput struct { Name string `json:"name"` // 名称 ChannelID string `json:"channel_id"` // 关联通道 ChannelName string `json:"channel_name"` // 通道名称 - TaskTemplateID string `json:"task_template_id"` // 关联模板 + TaskTemplateID int `json:"task_template_id"` // 关联模板 TaskTemplateName string `json:"task_template_name"` // 模板名称 Enable bool `form:"enable"` // 启用 Des string `json:"des"` // 描述 diff --git a/internal/core/vqd/vqdtasktemplate.go b/internal/core/vqd/vqdtasktemplate.go index cb6e32d..bd11ada 100644 --- a/internal/core/vqd/vqdtasktemplate.go +++ b/internal/core/vqd/vqdtasktemplate.go @@ -17,6 +17,8 @@ type VqdTaskTemplateStorer interface { Add(context.Context, *VqdTaskTemplate) error Edit(context.Context, *VqdTaskTemplate, func(*VqdTaskTemplate), ...orm.QueryOption) error Del(context.Context, *VqdTaskTemplate, ...orm.QueryOption) error + + FirstOrCreate(b any) error } // FindVqdTaskTemplateAll Paginated search @@ -33,16 +35,14 @@ func (c Core) FindVqdTaskTemplateAll() ([]*VqdTaskTemplate, int64, error) { func (c Core) FindVqdTaskTemplate(ctx context.Context, in *FindVqdTaskTemplateInput) ([]*VqdTaskTemplate, int64, error) { items := make([]*VqdTaskTemplate, 0) if in.Name != "" { - query := orm.NewQuery(8). - Where("name like ? ", "%"+in.Name+"%").OrderBy("created_at DESC") + query := orm.NewQuery(8).Where("name like ? ", "%"+in.Name+"%") total, err := c.store.VqdTaskTemplate().Find(ctx, &items, in, query.Encode()...) if err != nil { return nil, 0, reason.ErrDB.Withf(`Find err[%s]`, err.Error()) } return items, total, nil } else { - query := orm.NewQuery(2).OrderBy("created_at DESC") - total, err := c.store.VqdTaskTemplate().Find(ctx, &items, in, query.Encode()...) + total, err := c.store.VqdTaskTemplate().Find(ctx, &items, in) if err != nil { return nil, 0, reason.ErrDB.Withf(`Find err[%s]`, err.Error()) } @@ -61,6 +61,16 @@ func (c Core) GetVqdTaskTemplate(ctx context.Context, id int) (*VqdTaskTemplate, } return &out, nil } +func (c Core) GetIDVqdTaskTemplate(ctx context.Context, ID int64) (*VqdTaskTemplate, error) { + var out VqdTaskTemplate + if err := c.store.VqdTaskTemplate().Get(ctx, &out, orm.Where("id=?", ID)); err != nil { + if orm.IsErrRecordNotFound(err) { + return nil, reason.ErrNotFound.Withf(`Get err[%s]`, err.Error()) + } + return nil, reason.ErrDB.Withf(`Get err[%s]`, err.Error()) + } + return &out, nil +} func (c Core) GetNameVqdTaskTemplate(ctx context.Context, name string) (*VqdTaskTemplate, error) { var out VqdTaskTemplate if err := c.store.VqdTaskTemplate().Get(ctx, &out, orm.Where("name=?", name)); err != nil { @@ -72,6 +82,11 @@ func (c Core) GetNameVqdTaskTemplate(ctx context.Context, name string) (*VqdTask return &out, nil } +// FirstOrCreateTemplate Insert into database +func (c Core) FirstOrCreateTemplate(b any) error { + return c.store.VqdTaskTemplate().FirstOrCreate(b) +} + // AddVqdTaskTemplate Insert into database func (c Core) AddVqdTaskTemplate(ctx context.Context, in *AddVqdTaskTemplateInput) (*VqdTaskTemplate, error) { var out VqdTaskTemplate diff --git a/internal/core/vqd/vqdtasktemplate.param.go b/internal/core/vqd/vqdtasktemplate.param.go index 0541bef..b0ce2e0 100644 --- a/internal/core/vqd/vqdtasktemplate.param.go +++ b/internal/core/vqd/vqdtasktemplate.param.go @@ -11,39 +11,39 @@ type FindVqdTaskTemplateInput struct { } type EditVqdTaskTemplateInput struct { - Name string `json:"name"` - Plans string `json:"plans"` - Enable bool `json:"enable"` - //VqdConfig VqdConfig `json:"vqd_config"` // 诊断基础配置 - //VqdLgtDark VqdLgtDark `json:"vqd_lgt_dark"` // 亮度检测 - //VqdBlue VqdBlue `json:"vqd_blue"` // 蓝屏检查 - //VqdClarity VqdClarity `json:"vqd_clarity"` // 清晰度检查 - //VqdShark VqdShark `json:"vqd_shark"` // 抖动检查 - //VqdFreeze VqdFreeze `json:"vqd_freeze"` // 冻结检测 - //VqdColor VqdColor `json:"vqd_color"` // 偏色检测 - //VqdOcclusion VqdOcclusion `json:"vqd_occlusion"` // 遮挡检测 - //VqdNoise VqdNoise `json:"vqd_noise"` // 噪声检测 - //VqdContrast VqdContrast `json:"vqd_contrast"` // 对比度检测 - //VqdMosaic VqdMosaic `json:"vqd_mosaic"` // 马赛克检测 - //VqdFlower VqdFlower `json:"vqd_flower"` // 花屏检测 - Des string ` json:"des"` // 描述 + Name string `json:"name"` + Plans string `json:"plans"` + Enable bool `json:"enable"` + VqdConfig VqdConfig `json:"vqd_config"` // 诊断基础配置 + VqdLgtDark VqdLgtDark `json:"vqd_lgt_dark"` // 亮度检测 + VqdBlue VqdBlue `json:"vqd_blue"` // 蓝屏检查 + VqdClarity VqdClarity `json:"vqd_clarity"` // 清晰度检查 + VqdShark VqdShark `json:"vqd_shark"` // 抖动检查 + VqdFreeze VqdFreeze `json:"vqd_freeze"` // 冻结检测 + VqdColor VqdColor `json:"vqd_color"` // 偏色检测 + VqdOcclusion VqdOcclusion `json:"vqd_occlusion"` // 遮挡检测 + VqdNoise VqdNoise `json:"vqd_noise"` // 噪声检测 + VqdContrast VqdContrast `json:"vqd_contrast"` // 对比度检测 + VqdMosaic VqdMosaic `json:"vqd_mosaic"` // 马赛克检测 + VqdFlower VqdFlower `json:"vqd_flower"` // 花屏检测 + Des string ` json:"des"` // 描述 } type AddVqdTaskTemplateInput struct { - Name string `json:"name"` - Plans string `json:"plans"` - Enable bool `json:"enable"` - //VqdConfig VqdConfig `json:"vqd_config"` // 诊断基础配置 - //VqdLgtDark VqdLgtDark `json:"vqd_lgt_dark"` // 亮度检测 - //VqdBlue VqdBlue `json:"vqd_blue"` // 蓝屏检查 - //VqdClarity VqdClarity `json:"vqd_clarity"` // 清晰度检查 - //VqdShark VqdShark `json:"vqd_shark"` // 抖动检查 - //VqdFreeze VqdFreeze `json:"vqd_freeze"` // 冻结检测 - //VqdColor VqdColor `json:"vqd_color"` // 偏色检测 - //VqdOcclusion VqdOcclusion `json:"vqd_occlusion"` // 遮挡检测 - //VqdNoise VqdNoise `json:"vqd_noise"` // 噪声检测 - //VqdContrast VqdContrast `json:"vqd_contrast"` // 对比度检测 - //VqdMosaic VqdMosaic `json:"vqd_mosaic"` // 马赛克检测 - //VqdFlower VqdFlower `json:"vqd_flower"` // 花屏检测 - Des string ` json:"des"` // 描述 + Name string `json:"name"` + Plans string `json:"plans"` + Enable bool `json:"enable"` + VqdConfig VqdConfig `json:"vqd_config"` // 诊断基础配置 + VqdLgtDark VqdLgtDark `json:"vqd_lgt_dark"` // 亮度检测 + VqdBlue VqdBlue `json:"vqd_blue"` // 蓝屏检查 + VqdClarity VqdClarity `json:"vqd_clarity"` // 清晰度检查 + VqdShark VqdShark `json:"vqd_shark"` // 抖动检查 + VqdFreeze VqdFreeze `json:"vqd_freeze"` // 冻结检测 + VqdColor VqdColor `json:"vqd_color"` // 偏色检测 + VqdOcclusion VqdOcclusion `json:"vqd_occlusion"` // 遮挡检测 + VqdNoise VqdNoise `json:"vqd_noise"` // 噪声检测 + VqdContrast VqdContrast `json:"vqd_contrast"` // 对比度检测 + VqdMosaic VqdMosaic `json:"vqd_mosaic"` // 马赛克检测 + VqdFlower VqdFlower `json:"vqd_flower"` // 花屏检测 + Des string ` json:"des"` // 描述 } diff --git a/internal/core/vqdsdk/clean.go b/internal/core/vqdsdk/clean.go new file mode 100644 index 0000000..f7a07c8 --- /dev/null +++ b/internal/core/vqdsdk/clean.go @@ -0,0 +1,227 @@ +package vqdsdk + +import ( + "fmt" + "io/fs" + "log/slog" + "os" + "path/filepath" + "time" +) + +// 配置参数 +const ( + // 要清理的目标目录(请替换为你实际的目录路径) + cleanDir = "/snap" + // 定时任务执行间隔(每天执行一次) + interval = 24 * time.Hour + // 批量删除大小(避免单次删除过多锁表) + batchSize = 1000 + // 日期目录的格式(如 20260117) + dateDirLayout = "20060102" + // 定时任务首次执行时间(每天凌晨1点) + executeHour = 1 +) + +// scheduleCleanFile 定时执行清理任务 +func (c Core) scheduleCleanTask() { + // 计算首次执行时间(今天/明天的凌晨1点) + now := time.Now() + nextExec := time.Date(now.Year(), now.Month(), now.Day(), executeHour, 0, 0, 0, now.Location()) + if nextExec.Before(now) { + nextExec = nextExec.Add(24 * time.Hour) + } + + // 计算首次执行的等待时间 + initialDelay := nextExec.Sub(now) + slog.Info(fmt.Sprintf("定时任务已启动,首次执行时间:%s(等待 %v)", nextExec.Format(time.RFC3339), initialDelay)) + + // 首次执行等待 + time.Sleep(initialDelay) + + // 执行首次清理 + if err := c.cleanExpiredFiles(); err != nil { + slog.Error("首次清理任务执行失败 Files", "err", err) + } + time.Sleep(20 * time.Minute) + if err := c.cleanExpiredDbs(); err != nil { + slog.Error("首次清理任务执行失败 Dbs", "err", err) + } + + // 循环执行定时任务 + ticker := time.NewTicker(interval) + defer ticker.Stop() + + for range ticker.C { + if err := c.cleanExpiredFiles(); err != nil { + slog.Error("定时清理任务执行失败 Files", "err", err) + } + time.Sleep(20 * time.Minute) + if err := c.cleanExpiredDbs(); err != nil { + slog.Error("定时清理任务执行失败 Dbs", "err", err) + } + } +} + +// cleanExpiredDbs 清理超过expireDays天的数据 +func (c Core) cleanExpiredDbs() error { + // 计算过期时间阈值 + expireDays := time.Duration(c.Cfg.VqdConfig.SaveDay) + if expireDays < 1 { + expireDays = 1 + } + expireTime := time.Now().Add(-expireDays * 24 * time.Hour) + slog.Info(fmt.Sprintf("开始清理中超过 %d 天的数据,过期时间阈值:%s", expireDays, expireTime.Format(time.RFC3339))) + + totalDeleted := 0 + + for { + deletedCount, err := c.VqdTaskCore.DelVqdTaskAlarmAll(expireTime, batchSize) + if err != nil { + slog.Error("数据清理失败", "err", err) + return err + } + // 获取本次删除的行数 + totalDeleted += deletedCount + + // 无更多数据则退出循环 + if deletedCount < batchSize { + break + } + + // 批量删除间隔,降低数据库压力 + time.Sleep(100 * time.Millisecond) + } + slog.Info("本次数据清理任务执行完成") + return nil +} + +// deleteDirContents 删除目录下的所有文件(保留目录结构,仅删文件) +func deleteTaskDirContents(dir string, expireTime time.Time) error { + // 遍历目录 + err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + // 处理遍历过程中的错误(如权限问题) + if err != nil { + slog.Error("访问路径失败", "path", path, "err", err) + return nil + } + // 只处理目录(跳过文件) + if !d.IsDir() { + return nil + } + // 获取当前目录的名称(如 20260117) + dirName := filepath.Base(path) + dirDate, err := time.Parse(dateDirLayout, dirName) + if err != nil { + slog.Error("解析目录日期失败", "path", path, "err", err) + return nil + } + // 判断日期目录是否早于阈值(即过期) + isExpired := dirDate.Before(expireTime) + + // 非日期目录,继续遍历子目录 + if dirDate.IsZero() { + return nil + } + + // 日期目录未过期,跳过 + if !isExpired { + slog.Error("目录日期未过期跳过", "path", path, "dirDate", dirDate.Format(dateDirLayout)) + return nil + } + + // 过期日期目录:先删除目录内所有文件 + slog.Error("目录已过期开始清理其中文件", "path", path, "dirDate", dirDate.Format(dateDirLayout)) + if err := deleteDirContents(path, expireTime); err != nil { + slog.Error("清理目录内容失败", "path", path, "err", err) + return nil + } + + // 删除空的日期目录 + if err := os.Remove(path); err != nil { + slog.Error("删除空目录失败(可能非空)", "path", path, "err", err) + } else { + slog.Info("成功删除过期目录", "path", path) + } + + // 跳过已删除目录的子目录遍历(避免无效操作) + return fs.SkipDir + }) + if err != nil { + slog.Error("遍历目录失败", "dir", dir, "err", err) + return fmt.Errorf("遍历目录 [%s] 失败:%w", dir, err) + } + return nil +} +func deleteDirContents(dir string, expireTime time.Time) error { + err := filepath.WalkDir(dir, func(path string, d fs.DirEntry, err error) error { + if err != nil { + slog.Error("访问路径失败", "path", path, "err", err) + return nil // 跳过错误路径,继续处理 + } + + // 只删除文件,跳过目录 + if !d.IsDir() { + // 获取文件信息(包含修改时间) + fileInfo, err := d.Info() + if err != nil { + slog.Error("获取文件失败", "path", path, "err", err) + return nil + } + // 判断文件是否过期 + if fileInfo.ModTime().Before(expireTime) { + // 删除过期文件 + if errs := os.Remove(path); errs != nil { + slog.Error("删除文件失败", "path", path, "err", err) + } else { + slog.Info("成功删除文件", "path", path) + } + } + } + return nil + }) + + if err != nil { + return fmt.Errorf("遍历目录 [%s] 失败:%w", dir, err) + } + return nil +} + +// cleanExpiredFiles 清理指定目录下超过expireDays天未修改的文件 +func (c Core) cleanExpiredFiles() error { + // 计算过期时间阈值 + expireDays := time.Duration(c.Cfg.VqdConfig.SaveDay) + if expireDays < 1 { + expireDays = 1 + } + expireTime := time.Now().Add(-expireDays * 24 * time.Hour) + slog.Info(fmt.Sprintf("开始清理目录 [%s] 中超过 %d 天的文件,过期时间阈值:%s", cleanDir, expireDays, expireTime.Format(time.RFC3339))) + + dir, _ := os.Getwd() + rootDir := filepath.Join(dir, cleanDir) + dateDirs, err := os.ReadDir(rootDir) + if err != nil { + slog.Error("访问根目录路径失败", "path", rootDir, "err", err) + return nil + } + for _, d := range dateDirs { + // 只处理目录(跳过文件) + if !d.IsDir() { + return nil + } + path := filepath.Join(rootDir, d.Name()) + if err := deleteTaskDirContents(path, expireTime); err != nil { + slog.Error("清理目录内容失败", "path", path, "err", err) + return nil + } + // 删除空的日期目录 + if err := os.Remove(path); err != nil { + slog.Error("删除空目录失败(可能非空)", "path", path, "err", err) + } else { + slog.Info("成功删除过期目录", "path", path) + } + } + + slog.Info("本次文件清理任务执行完成") + return nil +} diff --git a/internal/core/vqdsdk/core.go b/internal/core/vqdsdk/core.go index a5132cb..5892e26 100644 --- a/internal/core/vqdsdk/core.go +++ b/internal/core/vqdsdk/core.go @@ -2,6 +2,7 @@ package vqdsdk import ( "context" + "easyvqd/internal/conf" "easyvqd/internal/core/host" "easyvqd/internal/core/vqd" "time" @@ -10,14 +11,14 @@ import ( type Core struct { HostCore *host.Core VqdTaskCore *vqd.Core - //WorkflowCore *Workflow + Cfg *conf.Bootstrap } -func NewCore(HostCore *host.Core, VqdTaskCore *vqd.Core) *Core { +func NewCore(HostCore *host.Core, VqdTaskCore *vqd.Core, Cfg *conf.Bootstrap) *Core { core := &Core{ HostCore: HostCore, VqdTaskCore: VqdTaskCore, - //WorkflowCore: OpenVqdTask(VqdTaskCore), + Cfg: Cfg, } time.AfterFunc(time.Duration(5)*time.Second, func() { in := &vqd.AddVqdAlarmInput{ @@ -31,10 +32,13 @@ func NewCore(HostCore *host.Core, VqdTaskCore *vqd.Core) *Core { TaskName: "", FilePath: "", } - core.VqdTaskCore.AddVqdAlarm(context.TODO(), in) - core.VqdTaskCore.AddVqdAlarm(context.TODO(), in) + for i := 0; i < 40; i++ { + core.VqdTaskCore.AddVqdAlarm(context.TODO(), in) + } }) + // 启用定时清理任务 + core.scheduleCleanTask() // 启用任务管理器 return core } diff --git a/internal/web/api/api.go b/internal/web/api/api.go index 2b4f28e..95b33da 100644 --- a/internal/web/api/api.go +++ b/internal/web/api/api.go @@ -1,8 +1,10 @@ package api import ( + "easyvqd/internal/core/vqd" "easyvqd/internal/web/api/static" "expvar" + "git.lnton.com/lnton/pkg/orm" statics "github.com/gin-contrib/static" "log/slog" "net/http" @@ -24,6 +26,12 @@ import ( var startRuntime = time.Now() +// recordErr 记录错误 +func recordErr(err error) { + if err != nil { + panic(err) + } +} func setupRouter(r *gin.Engine, uc *Usecase) { r.Use( // 格式化输出到控制台,然后记录到日志 @@ -55,7 +63,9 @@ func setupRouter(r *gin.Engine, uc *Usecase) { registerConfig(r, ConfigAPI{uc: uc, cfg: uc.Conf}) RegisterHostAPI(r, uc) RegisterVqdTask(r, uc.VqdTaskAPI) - + if !orm.GetEnabledAutoMigrate() { + recordErr(InitTemplate(uc)) + } r.NoRoute(func(ctx *gin.Context) { p := ctx.Request.URL.Path if strings.HasPrefix(p, "/web/") { @@ -151,6 +161,105 @@ type KV struct { Value int64 } +func InitTemplate(uc *Usecase) error { + cfg := uc.Conf + in := vqd.VqdTaskTemplate{ + Enable: true, + IsDefault: true, + VqdConfig: vqd.VqdConfig{ + Enable: true, + FrmNum: cfg.VqdConfig.FrmNum, + IsDeepLearn: cfg.VqdConfig.IsDeepLearn, + }, + VqdLgtDark: vqd.VqdLgtDark{ + Enable: true, + DarkThr: cfg.VqdLgtDark.DarkThr, + LgtThr: cfg.VqdLgtDark.LgtThr, + LgtDarkAbnNumRatio: cfg.VqdLgtDark.LgtDarkAbnNumRatio, + }, + VqdBlue: vqd.VqdBlue{ + Enable: true, + BlueThr: cfg.VqdBlue.BlueThr, + BlueAbnNumRatio: cfg.VqdBlue.BlueAbnNumRatio, + }, + VqdClarity: vqd.VqdClarity{ + Enable: true, + ClarityThr: cfg.VqdClarity.ClarityThr, + ClarityAbnNumRatio: cfg.VqdClarity.ClarityAbnNumRatio, + }, + VqdShark: vqd.VqdShark{ + Enable: true, + SharkThr: cfg.VqdShark.SharkThr, + SharkAbnNumRatio: cfg.VqdShark.SharkAbnNumRatio, + }, + VqdFreeze: vqd.VqdFreeze{ + Enable: true, + FreezeThr: cfg.VqdFreeze.FreezeThr, + FreezeAbnNumRatio: cfg.VqdFreeze.FreezeAbnNumRatio, + }, + VqdColor: vqd.VqdColor{ + Enable: true, + ColorThr: cfg.VqdColor.ColorThr, + ColorAbnNumRatio: cfg.VqdColor.ColorAbnNumRatio, + }, + VqdOcclusion: vqd.VqdOcclusion{ + Enable: true, + OcclusionThr: cfg.VqdOcclusion.OcclusionThr, + OcclusionAbnNumRatio: cfg.VqdOcclusion.OcclusionAbnNumRatio, + }, + VqdNoise: vqd.VqdNoise{ + Enable: true, + NoiseThr: cfg.VqdNoise.NoiseThr, + NoiseAbnNumRatio: cfg.VqdNoise.NoiseAbnNumRatio, + }, + VqdContrast: vqd.VqdContrast{ + Enable: true, + CtraLowThr: cfg.VqdContrast.CtraLowThr, + CtraHighThr: cfg.VqdContrast.CtraHighThr, + CtraAbnNumRatio: cfg.VqdContrast.CtraAbnNumRatio, + }, + VqdMosaic: vqd.VqdMosaic{ + Enable: true, + MosaicThr: cfg.VqdMosaic.MosaicThr, + MosaicAbnNumRatio: cfg.VqdMosaic.MosaicAbnNumRatio, + }, + VqdFlower: vqd.VqdFlower{ + Enable: true, + FlowerThr: cfg.VqdFlower.FlowerThr, + FlowerAbnNumRatio: cfg.VqdFlower.FlowerAbnNumRatio, + MosaicThr: cfg.VqdFlower.MosaicThr, + }, + } + + in.Name = "每天" + in.Model.ID = 1 + in.Model.CreatedAt = orm.Time{Time: time.Now()} + in.Plans = "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111" + in.Des = "每天分析,启用全部分析模块。" + if err := uc.VqdTaskCore.FirstOrCreateTemplate(&in); err != nil { + slog.Error("FirstOrCreateTemplate", "err", err) + return err + } + in.Name = "工作日" + in.Model.ID = 2 + in.Model.CreatedAt = orm.Time{Time: time.Now()} + in.Plans = "111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111000000000000000000000000000000000000000000000000" + in.Des = "工作日分析,启用全部分析模块。" + if err := uc.VqdTaskCore.FirstOrCreateTemplate(&in); err != nil { + slog.Error("FirstOrCreateTemplate", "err", err) + return err + } + in.Name = "双休日" + in.Model.ID = 3 + in.Model.CreatedAt = orm.Time{Time: time.Now()} + in.Plans = "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000111111111111111111111111111111111111111111111111" + in.Des = "休息日分析,启用全部分析模块。" + if err := uc.VqdTaskCore.FirstOrCreateTemplate(&in); err != nil { + slog.Error("FirstOrCreateTemplate", "err", err) + return err + } + return nil +} func sortExpvarMap(data *expvar.Map, top int) []KV { kvs := make([]KV, 0, 8) data.Do(func(kv expvar.KeyValue) { diff --git a/internal/web/api/config.go b/internal/web/api/config.go index c475be5..4036341 100644 --- a/internal/web/api/config.go +++ b/internal/web/api/config.go @@ -27,27 +27,60 @@ func registerConfig(g gin.IRouter, api ConfigAPI, handler ...gin.HandlerFunc) { group.GET("/base", web.WarpH(api.getBase)) group.PUT("/base", web.WarpH(api.editBase)) + group.GET("/default", web.WarpH(api.getDefaultConfig)) } -type getBaseOutput conf.VqdConfig -type editBaseInput conf.VqdConfig +type getBaseOutput struct { + SaveDay int32 `json:"save_day"` +} +type editBaseInput struct { + SaveDay int32 `json:"save_day"` +} +type getBaseConfigOutput struct { + FrmNum int32 `json:"frm_num"` + IsDeepLearn bool `json:"is_deep_learn"` + VqdLgtDark conf.VqdLgtDark `json:"vqd_lgt_dark"` // 亮度检测 + VqdBlue conf.VqdBlue `json:"vqd_blue"` // 蓝屏检查 + VqdClarity conf.VqdClarity `json:"vqd_clarity"` // 清晰度检查 + VqdShark conf.VqdShark `json:"vqd_shark"` // 抖动检查 + VqdFreeze conf.VqdFreeze `json:"vqd_freeze"` // 冻结检测 + VqdColor conf.VqdColor `json:"vqd_color"` // 偏色检测 + VqdOcclusion conf.VqdOcclusion `json:"vqd_occlusion"` // 遮挡检测 + VqdNoise conf.VqdNoise `json:"vqd_noise"` // 噪声检测 + VqdContrast conf.VqdContrast `json:"vqd_contrast"` // 对比度检测 + VqdMosaic conf.VqdMosaic `json:"vqd_mosaic"` // 马赛克检测 + VqdFlower conf.VqdFlower `json:"vqd_flower"` // 花屏检测 +} func (uc *ConfigAPI) editBase(c *gin.Context, in *editBaseInput) (any, error) { - uc.cfg.VqdConfig.FrmNum = in.FrmNum uc.cfg.VqdConfig.SaveDay = in.SaveDay - uc.cfg.VqdConfig.IsDeepLearn = in.IsDeepLearn conf.WriteConfig(uc.cfg, uc.cfg.ConfigDirPath()) return in, nil } +func (uc *ConfigAPI) getDefaultConfig(_ *gin.Context, _ *struct{}) (getBaseConfigOutput, error) { + return getBaseConfigOutput{ + FrmNum: uc.cfg.VqdConfig.FrmNum, + IsDeepLearn: uc.cfg.VqdConfig.IsDeepLearn, + VqdLgtDark: uc.cfg.VqdLgtDark, + VqdBlue: uc.cfg.VqdBlue, + VqdClarity: uc.cfg.VqdClarity, + VqdShark: uc.cfg.VqdShark, + VqdFreeze: uc.cfg.VqdFreeze, + VqdColor: uc.cfg.VqdColor, + VqdOcclusion: uc.cfg.VqdOcclusion, + VqdNoise: uc.cfg.VqdNoise, + VqdContrast: uc.cfg.VqdContrast, + VqdMosaic: uc.cfg.VqdMosaic, + VqdFlower: uc.cfg.VqdFlower, + }, nil +} func (uc *ConfigAPI) getBase(_ *gin.Context, _ *struct{}) (getBaseOutput, error) { confMutex.Lock() defer confMutex.Unlock() return getBaseOutput{ - FrmNum: uc.cfg.VqdConfig.FrmNum, - IsDeepLearn: uc.cfg.VqdConfig.IsDeepLearn, - SaveDay: uc.cfg.VqdConfig.SaveDay, + SaveDay: uc.cfg.VqdConfig.SaveDay, }, nil } func (uc *ConfigAPI) getToml(c *gin.Context) { diff --git a/internal/web/api/vqdtask.go b/internal/web/api/vqdtask.go index 38f73c7..f2dd3d1 100644 --- a/internal/web/api/vqdtask.go +++ b/internal/web/api/vqdtask.go @@ -64,6 +64,10 @@ func (a VqdTaskAPI) findVqdTask(c *gin.Context, in *vqd.FindVqdTaskInput) (any, row["channel_name"] = item.ChannelName row["task_template_id"] = item.TaskTemplateID row["task_template_name"] = item.TaskTemplateName + template, errs := a.core.GetIDVqdTaskTemplate(c.Request.Context(), item.TaskTemplateID) + if errs == nil && template != nil { + row["task_template_name"] = template.Name + } row["created_at"] = item.CreatedAt row["updated_at"] = item.UpdatedAt diff --git a/internal/web/api/vqdtasktemplate.go b/internal/web/api/vqdtasktemplate.go index cec3f91..db40782 100644 --- a/internal/web/api/vqdtasktemplate.go +++ b/internal/web/api/vqdtasktemplate.go @@ -20,6 +20,19 @@ func (a VqdTaskAPI) findVqdTaskTemplate(c *gin.Context, in *vqd.FindVqdTaskTempl row["des"] = item.Des row["plans"] = item.Plans row["enable"] = item.Enable + row["is_default"] = item.IsDefault + row["vqd_config"] = item.VqdConfig + row["vqd_lgt_dark"] = item.VqdLgtDark + row["vqd_blue"] = item.VqdBlue + row["vqd_clarity"] = item.VqdClarity + row["vqd_shark"] = item.VqdShark + row["vqd_freeze"] = item.VqdFreeze + row["vqd_color"] = item.VqdColor + row["vqd_occlusion"] = item.VqdOcclusion + row["vqd_noise"] = item.VqdNoise + row["vqd_contrast"] = item.VqdContrast + row["vqd_mosaic"] = item.VqdMosaic + row["vqd_flower"] = item.VqdFlower row["created_at"] = item.CreatedAt row["updated_at"] = item.UpdatedAt @@ -38,6 +51,19 @@ func (a VqdTaskAPI) getVqdTaskTemplate(c *gin.Context, _ *struct{}) (any, error) row["id"] = item.ID row["name"] = item.Name + row["is_default"] = item.IsDefault + row["vqd_config"] = item.VqdConfig + row["vqd_lgt_dark"] = item.VqdLgtDark + row["vqd_blue"] = item.VqdBlue + row["vqd_clarity"] = item.VqdClarity + row["vqd_shark"] = item.VqdShark + row["vqd_freeze"] = item.VqdFreeze + row["vqd_color"] = item.VqdColor + row["vqd_occlusion"] = item.VqdOcclusion + row["vqd_noise"] = item.VqdNoise + row["vqd_contrast"] = item.VqdContrast + row["vqd_mosaic"] = item.VqdMosaic + row["vqd_flower"] = item.VqdFlower row["des"] = item.Des row["plans"] = item.Plans row["enable"] = item.Enable @@ -71,7 +97,20 @@ func (a VqdTaskAPI) editVqdTaskTemplate(c *gin.Context, in *vqd.EditVqdTaskTempl func (a VqdTaskAPI) delVqdTaskTemplate(c *gin.Context, _ *struct{}) (any, error) { ID, _ := strconv.Atoi(c.Param("id")) - _, err := a.core.DelVqdTaskTemplate(c.Request.Context(), ID) + info, err := a.core.GetVqdTaskTemplate(c.Request.Context(), ID) + if err != nil { + return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`find vqd [%d] err [%s]`, ID, err.Error())) + } + if info.IsDefault { + return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`默认模板不支持删除 [%s] `, info.Name)) + } + templateInfo, err := a.core.FindVqdTaskTemplateID(c.Request.Context(), ID) + if err == nil { + if templateInfo != nil { + return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`模板关联【%s】任务,需先清理【%s】任务后才能删除模板!`, templateInfo.Name, templateInfo.Name)) + } + } + _, err = a.core.DelVqdTaskTemplate(c.Request.Context(), ID) if err != nil { return nil, reason.ErrServer.SetMsg(fmt.Sprintf(`del vqd [%d] err [%s]`, ID, err.Error())) } diff --git a/web/src/api/config.ts b/web/src/api/config.ts index f0379f6..f9faeb9 100644 --- a/web/src/api/config.ts +++ b/web/src/api/config.ts @@ -1,5 +1,5 @@ import { GET, PUT } from "./http"; -import type { UpdateConfigBaseReq, VqdConfigBaseDetailRes } from "../types/config"; +import type { UpdateConfigBaseReq, VqdConfigBaseDetailRes, VqdConfigDefaultDetailRes } from "../types/config"; /** * 获取详情 @@ -7,6 +7,12 @@ import type { UpdateConfigBaseReq, VqdConfigBaseDetailRes } from "../types/confi export async function GetVqdConfigBase() { return await GET(`/configs/base`); } +/** + * 获取默认详情 + */ +export async function GetVqdConfigDefault() { + return await GET(`/configs/default`); +} /** * 更新 diff --git a/web/src/api/vqdtasktemplate.ts b/web/src/api/vqdtasktemplate.ts index 5b8a37f..28ee60c 100644 --- a/web/src/api/vqdtasktemplate.ts +++ b/web/src/api/vqdtasktemplate.ts @@ -31,6 +31,8 @@ export async function GetVqdTaskTemplateById(id: string) { */ export async function UpdateVqdTaskTemplate(data: UpdateVqdTaskTemplateReq) { const { id, ...payload } = data; + console.log(data); + return await PUT(`/template/${id}`, payload); } diff --git a/web/src/components/AddVqdTask.tsx b/web/src/components/AddVqdTask.tsx index 5f1a5f8..2501725 100644 --- a/web/src/components/AddVqdTask.tsx +++ b/web/src/components/AddVqdTask.tsx @@ -1,10 +1,11 @@ import { forwardRef, useImperativeHandle, useState, useRef } from "react"; -import { Modal, Form, Input, Radio, Button, message, Space } from "antd"; -import { useMutation } from "@tanstack/react-query"; +import { Modal, Form, Input, Select, Button, message, Space } from "antd"; +import { useMutation, useQuery } from "@tanstack/react-query"; import { CreateVqdTask, UpdateVqdTask } from "../api/vqdtask"; +import { GetVqdTaskTemplate } from "../api/vqdtasktemplate"; import type { CreateVqdTaskReq, VqdTaskItem } from "../types/vqdtask"; import { useGlobal } from "../Context"; - +import ChannelModel, { IChannelModelFunc } from "./channel/Channel"; interface AddVqdTaskProps { title: string; @@ -19,12 +20,15 @@ const AddVqdTask = forwardRef( ({ title, onSuccess }, ref) => { const [open, setOpen] = useState(false); const [editing, setEditing] = useState(false); + const [channelId, setChannelId] = useState(""); + const channelRef = useRef(null); const [form] = Form.useForm(); const { ErrorHandle } = useGlobal(); useImperativeHandle(ref, () => ({ open: (task?: VqdTaskItem) => { if (task) { setEditing(true); + setChannelId(task.channel_id) const formValues = { name: task.name, id: task.id, @@ -45,7 +49,29 @@ const AddVqdTask = forwardRef( setOpen(true); }, })); + const [pagination, setPagination] = useState({ + page: 1, + size: 999, + name: "" + }); + // 获取任务列表 + const { + data: storageResponse, + isLoading, + refetch, + } = useQuery({ + queryKey: ["storage", pagination], + queryFn: () => + GetVqdTaskTemplate({ ...pagination }) + .then((res) => res.data) + .catch((err) => { + ErrorHandle(err); + throw err; + }), + // refetchInterval: 4000, + retry: 1, + }); const { mutate: createMutate, isPending: creating } = useMutation({ mutationFn: CreateVqdTask, onSuccess: () => { @@ -69,6 +95,7 @@ const AddVqdTask = forwardRef( const handleClose = () => { setOpen(false); setEditing(false); + setChannelId(""); form.resetFields(); }; @@ -76,7 +103,7 @@ const AddVqdTask = forwardRef( ( > + + + + + + + + + + ( )} + + + { + form.setFieldsValue({ channel_id: id, channel_name: name }); + setChannelId(id) + }} /> ); } diff --git a/web/src/components/AddVqdTaskTemplate.tsx b/web/src/components/AddVqdTaskTemplate.tsx index 0cd8876..8ba33f5 100644 --- a/web/src/components/AddVqdTaskTemplate.tsx +++ b/web/src/components/AddVqdTaskTemplate.tsx @@ -1,16 +1,77 @@ -import { forwardRef, useImperativeHandle, useState, useRef } from "react"; -import { Modal, Form, Input, Radio, Button, message, Space } from "antd"; +import { forwardRef, useImperativeHandle, useState, useRef, createContext, useContext, useEffect } from "react"; +import { Modal, Form, Input, InputNumber, Button, message, Row, Col, Card, Flex, Switch, Tabs, FormInstance } from "antd"; import { useMutation } from "@tanstack/react-query"; -import { CreateVqdTaskTemplate, UpdateVqdTaskTemplate } from "../api/vqdtasktemplate"; +import { GetVqdConfigDefault } from "../api/config"; +import { CreateVqdTaskTemplate, UpdateVqdTaskTemplate, } from "../api/vqdtasktemplate"; import type { CreateVqdTaskTemplateReq, VqdTaskTemplateItem } from "../types/vqdtasktemplate"; import { useGlobal } from "../Context"; +import type { TabsProps } from 'antd'; +const week = [ + '星期一', + '星期二', + '星期三', + '星期四', + '星期五', + '星期六', + '星期日', +]; +const hour = [ + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, + 22, 23, +]; + +const gridStyle: React.CSSProperties = { + border: '0.1px solid #ccc', + textAlign: 'center', + lineHeight: '40px', + userSelect: 'none', +}; +const titleStyle: React.CSSProperties = { + border: '0.1px solid #ccc', + width: '90px', + textAlign: 'center', + lineHeight: '40px', + userSelect: 'none', +}; +type PlansSpan = { + start: string; + end: string; +}; + +export const emptyList = [ + Array(24).fill(0), + Array(24).fill(0), + Array(24).fill(0), + Array(24).fill(0), + Array(24).fill(0), + Array(24).fill(0), + Array(24).fill(0), +]; interface AddVqdTaskTemplateProps { title: string; onSuccess: () => void; } - +interface IAddTemplateContext { + editing: boolean; + checkList: number[][]; + form: FormInstance; + setCheckList: React.Dispatch>; + onDelPullDeviceData: (index: number) => void; +} +const layout = { + labelCol: { span: 10 }, + wrapperCol: { span: 14 }, +}; +const AddTemplateContext = createContext(null); +const useAddTemplate = () => { + const context = useContext(AddTemplateContext); + if (!context) { + throw new Error('useAddTemplate must be used within a AddTemplateProvider'); + } + return context; +}; export interface AddVqdTaskTemplateRef { open: (task?: VqdTaskTemplateItem) => void; } @@ -19,8 +80,81 @@ const AddVqdTaskTemplate = forwardRef { const [open, setOpen] = useState(false); const [editing, setEditing] = useState(false); + const [checkList, setCheckList] = useState([...emptyList.map((list) => [...list])]); + const [form] = Form.useForm(); const { ErrorHandle } = useGlobal(); + + const arrayToString = (arr: number[][]): string => { + return arr.map((subArr) => subArr.join('')).join(''); + } + const parsePlans = (value: string | undefined): number[][] => { + const result: number[][] = []; + if (!value) return result; + const binaryArray: number[] = value.split('').map(Number); + while (binaryArray.length) { + result.push(binaryArray.splice(0, 24)); + } + return result; + }; + const onChange = (key: string) => { + console.log(key); + }; + + const itemsTabs: TabsProps['items'] = [ + { + key: '1', + forceRender: true, + label: '诊断参数', + children: + }, + { + key: '2', + forceRender: true, + label: '诊断时间', + children: + }, + ]; + const { mutate: getVqdConfigDefault } = useMutation({ + mutationFn: GetVqdConfigDefault, + onSuccess: (res) => { + const formValues = { + vqd_config: { + enable: false, + frm_num: res.data.frm_num, + is_deep_learn: res.data.is_deep_learn, + }, + enable: true, + plans: '', + des: '', + name: '', + vqd_lgt_dark: res.data.vqd_lgt_dark, + vqd_blue: res.data.vqd_blue, + vqd_clarity: res.data.vqd_clarity, + vqd_shark: res.data.vqd_shark, + vqd_freeze: res.data.vqd_freeze, + vqd_color: res.data.vqd_color, + vqd_occlusion: res.data.vqd_occlusion, + vqd_noise: res.data.vqd_noise, + vqd_contrast: res.data.vqd_contrast, + vqd_mosaic: res.data.vqd_mosaic, + vqd_flower: res.data.vqd_flower, + }; + formValues.vqd_lgt_dark.enable = false + formValues.vqd_blue.enable = false + formValues.vqd_clarity.enable = false + formValues.vqd_shark.enable = false + formValues.vqd_freeze.enable = false + formValues.vqd_color.enable = false + formValues.vqd_occlusion.enable = false + formValues.vqd_noise.enable = false + formValues.vqd_contrast.enable = false + formValues.vqd_mosaic.enable = false + formValues.vqd_flower.enable = false + form.setFieldsValue(formValues); + }, + onError: ErrorHandle, + }); useImperativeHandle(ref, () => ({ open: (task?: VqdTaskTemplateItem) => { if (task) { @@ -30,14 +164,29 @@ const AddVqdTaskTemplate = forwardRef { + } return ( - form.submit()} - confirmLoading={creating || updating} + -
{ - if (creating || updating) return - console.log(values); - - const { - name, - des, - plans, - enable } = values as { - name: string; - des: string; - id?: number; - plans: string; - - enable: boolean; - }; - const payload: CreateVqdTaskTemplateReq = { - name, - des, - plans, - enable, - }; - - if (editing) { - const id = (values as any).id; - updateMutate({ id: String(id), ...payload }); - } else { - createMutate(payload); - } - }} + form.submit()} + confirmLoading={creating || updating} > - - - { + if (creating || updating) return + const payload = values as CreateVqdTaskTemplateReq; + payload.plans = arrayToString(checkList) + console.log(payload); + if (editing) { + const id = (values as any).id; + updateMutate({ id: String(id), ...payload }); + } else { + createMutate(payload); + } + }} > - - +
+ - - - - {editing && ( - - )} - -
+ + + + + + + + + + + + + + + + {editing && ( + + )} + +
+ ); } ); export default AddVqdTaskTemplate; + + + + +const TemplatePlans: React.FC = () => { + const { + checkList, + editing, + setCheckList, + form, + } = useAddTemplate(); + // 开始滑动选择 + const [config, setConfig] = useState<{ + start: boolean; + }>({ start: false }); + + const selector = useRef(true); + + const [coping, setCoping] = useState< + { index: number; value: number[] } | undefined + >(); + + const handler = (list: number[]) => { + let out: PlansSpan[] = []; + let start = false; + let timeParam: PlansSpan = { start: '', end: '' }; + for (let i = 0; i < list.length; i++) { + const v = list[i]; + + let s = i.toString().padStart(2, '0'); + if (i == 23 && v == 1) { + s = '24'; + } + + if (!start && v == 1) { + start = true; + timeParam = { start: `${i}:00`, end: '' }; + } + + if (start && (v == 0 || i == list.length - 1)) { + start = false; + timeParam.end = `${s}:00`; + out.push(timeParam); + } + } + return out; + }; + + return <> +
+ +
+ Week/Time +
+ + {[...hour].map((v, idx) => { + return ( + + {v} + + ); + })} + +
+ {week.map((v, weekIdx) => { + return ( + setConfig({ ...config, start: true })} + onMouseUp={() => setConfig({ ...config, start: false })} + wrap={false} + > +
{v}
+ + {[...hour].map((v, hourIdx) => { + // 时间选择 + return ( + { + if (!config.start) return; + setCheckList((v) => { + let list = [...v]; + list[weekIdx][hourIdx] = selector.current ? 1 : 0; + return list; + }); + }} + onMouseDown={() => { + selector.current = checkList[weekIdx][hourIdx] == 0; + setCheckList((v) => { + let list = [...v]; + list[weekIdx][hourIdx] = selector.current ? 1 : 0; + return list; + }); + }} + key={v} + span={1} + style={{ + ...gridStyle, + backgroundColor: + checkList[weekIdx][hourIdx] == 1 + ? '#658EE0' + : 'white', + }} + > + ); + })} +
+ +
+
+
+ ); + })} +
+ +
+ + +
+ 时间段 +
+
+ {week.map((v, weekIdx) => { + return ( + // 周 + +
+ {v} : +
+ + {handler(checkList[weekIdx]).map((v, idx, arr) => { + // 已选时间段 + return ( + + {`${v.start}~${v.end}` + + (idx < arr.length - 1 ? ',' : '')} + + ); + })} + +
+ ); + })} +
+
+ +} +const BoxInputNumber: React.FC<{ + parent: string; + children: string; + defaultValue: number + labelName: string; +}> = ({ parent, children, defaultValue, labelName }) => { + return ( + { + return value ? parseFloat(value) : defaultValue; + }} + > + + + ); +}; +const TemplateConfig: React.FC = () => { + const { + editing, + form, + } = useAddTemplate(); + + const handleValuesChange = (value: string) => { + if (value) { + // form.setFieldsValue({ }); + } + }; + return <> + + + +

基础配置

+ + + +
+
+ + + + + + +
+
+ + + +

蓝屏检查

+ + + +
+
+ + +
+
+ + +

清晰度检查

+ + + +
+
+ + +
+
+ + +

抖动检查

+ + + +
+
+ + +
+
+ + +

冻结检测

+ + + +
+
+ + +
+
+ + +

偏色检测

+ + + +
+
+ + +
+
+ + +

遮挡检测

+ + + +
+
+ + +
+
+ + +

马赛克检测

+ + + +
+
+ + +
+
+ + +

噪声检测

+ + + +
+
+ + +
+
+ + +

对比度检测

+ + + +
+
+ + + +
+
+ + + +

亮度检测

+ + + +
+
+ + + +
+
+ + +

花屏检测

+ + + +
+
+ + + +
+
+
+ + +} \ No newline at end of file diff --git a/web/src/components/Snap.tsx b/web/src/components/Snap.tsx new file mode 100644 index 0000000..0feb62f --- /dev/null +++ b/web/src/components/Snap.tsx @@ -0,0 +1,34 @@ +// import { +// FindAlarmSnapshot, +// } from '@/service/http/aiserver'; +import { Image } from 'antd'; +import React, { useState, useEffect } from 'react'; + +interface ISnapshot { + filePath: string; +} + +const Snapshot: React.FC = ({ + filePath, +}) => { + // const [base64, setBase64] = useState(""); + // useEffect(() => { + // if (filePath != "") { + // FindAlarmSnapshot(filePath).then(data => { + // setBase64(data.data.data) + // }) + // } + // }, [filePath]); + return ( + <> + + + ); +}; + +export default Snapshot; diff --git a/web/src/components/VqdAlarm.tsx b/web/src/components/VqdAlarm.tsx index de6cac7..59e1bfa 100644 --- a/web/src/components/VqdAlarm.tsx +++ b/web/src/components/VqdAlarm.tsx @@ -1,15 +1,16 @@ import { useRef, useState, useMemo } from "react"; -import { Table, Button, Space, Popconfirm, Flex, message, Tooltip } from "antd"; +import { Table, Button, Space, Popconfirm, Flex, message, Tooltip, Select, Row, Col, Empty, Pagination, Tag } from "antd"; import { EditOutlined, DeleteOutlined, PlusOutlined } from "@ant-design/icons"; import { useQuery, useMutation } from "@tanstack/react-query"; import { GetVqdAlarm, DeleteVqdAlarm, DeleteVqdAlarmAll } from "../api/vqdalarm"; import type { VqdAlarmItem } from "../types/vqdalarm"; import type { ColumnsType } from "antd/es/table"; -import ChannelModel, { IChannelModelFunc } from "./channel/Channel"; import { useGlobal } from "../Context"; import { FormatFileSizeToString } from "../utils/rate"; import { formatSecondsToHMS } from "../utils/time"; import Filter from "./Filter"; +import AlarmSnap from './snap'; +const variants = ['filled'] as const; export default function VqdAlarmPage() { const { ErrorHandle } = useGlobal(); const [pagination, setPagination] = useState({ @@ -17,7 +18,8 @@ export default function VqdAlarmPage() { size: 10, name: "" }); - + const [arrList, setArrList] = useState([{ name: "全部类型", id: 0 }]); + const [templateData, setTemplateData] = useState([]); // 获取任务列表 const { data: storageResponse, @@ -55,6 +57,13 @@ export default function VqdAlarmPage() { }); // 处理分页变化 + const onAlarmPageChange = (page: number, pageSize?: number) => { + setPagination((prev) => ({ + ...prev, + page: page, + size: pageSize || prev.size, + })); + } const handleTableChange = (page: number, pageSize?: number) => { setPagination((prev) => ({ ...prev, @@ -80,7 +89,7 @@ export default function VqdAlarmPage() { setSelectedRowKeys([...newSelectedRowKeys]); }, }; - // 批量删除任务 + // 批量删除任务 const { mutate: deleteMutationAll, isPending: delAllLoadings } = useMutation({ mutationFn: DeleteVqdAlarmAll, onSuccess: () => { @@ -129,7 +138,7 @@ export default function VqdAlarmPage() { { if (record.id) { deleteMutation(record.id); @@ -151,7 +160,7 @@ export default function VqdAlarmPage() { return (
- + {/* - + */} {/* 表格 */} - + /> */} + + + {/* + + { + deleteMutationAll({ ids: selectedRowKeys as number[] }) + }} + okText="确定" + cancelText="取消" + > + + + + */} + {/* */} +
+