Skip to content

Commit 7256d7e

Browse files
committed
feat: 每曲问题校验徽标
抽出 MusicValidator 共享校验逻辑(无音频/无封面/无启用难度), CLI validate 与曲库列表复用;列表项内联警告图标,详情面板问题列表
1 parent d630d26 commit 7256d7e

9 files changed

Lines changed: 106 additions & 21 deletions

File tree

ChuChartManager.CLI/Commands/ValidateCommand.cs

Lines changed: 14 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,34 +23,29 @@ public override async Task<int> ExecuteAsync(CommandContext context, Settings se
2323

2424
int total = 0, missingAudio = 0, missingJacket = 0, noDiffs = 0;
2525

26+
var labels = new Dictionary<string, string>
27+
{
28+
[MusicValidator.NoAudio] = "[yellow]无音频[/]",
29+
[MusicValidator.NoJacket] = "[yellow]无封面[/]",
30+
[MusicValidator.NoEnabledFumen] = "[yellow]无启用难度[/]",
31+
};
32+
2633
foreach (var list in scanner.MusicBySource.Values)
2734
{
2835
foreach (var music in list)
2936
{
3037
total++;
31-
var issues = new List<string>();
32-
33-
if (AudioHelper.FindAwbPath(music) == null)
34-
{
35-
missingAudio++;
36-
issues.Add("[yellow]无音频[/]");
37-
}
38+
var problems = MusicValidator.Validate(music);
3839

39-
if (music.GetJacketFullPath() == null)
40-
{
41-
missingJacket++;
42-
issues.Add("[yellow]无封面[/]");
43-
}
40+
if (problems.Contains(MusicValidator.NoAudio)) missingAudio++;
41+
if (problems.Contains(MusicValidator.NoJacket)) missingJacket++;
42+
if (problems.Contains(MusicValidator.NoEnabledFumen)) noDiffs++;
4443

45-
var enabledCount = music.Fumens.Count(f => f is { Enable: true });
46-
if (enabledCount == 0)
44+
if (problems.Count > 0)
4745
{
48-
noDiffs++;
49-
issues.Add("[yellow]无启用难度[/]");
50-
}
51-
52-
if (issues.Count > 0)
46+
var issues = problems.Select(p => labels.GetValueOrDefault(p, p));
5347
AnsiConsole.MarkupLine($" #{music.Id:D4} {Markup.Escape(music.Name)}: {string.Join(", ", issues)}");
48+
}
5449
}
5550
}
5651

ChuChartManager/Controllers/MusicController.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ public ActionResult<List<MusicListItem>> GetMusicList([FromQuery] string? source
5959
LevelDisplay = f.LevelDisplay,
6060
NotesDesigner = f.NotesDesigner,
6161
NoteCount = f.NoteCount
62-
}).ToArray()
62+
}).ToArray(),
63+
Problems = MusicValidator.Validate(m)
6364
}).ToList();
6465

6566
return Ok(result);
@@ -1402,6 +1403,7 @@ public class MusicListItem
14021403
public string WorldsEndTag { get; set; } = "";
14031404
public bool IsWorldsEnd { get; set; }
14041405
public FumenSummary?[] Fumens { get; set; } = new FumenSummary?[6];
1406+
public List<string> Problems { get; set; } = [];
14051407
}
14061408

14071409
public class FumenSummary

ChuChartManager/Front/src/api/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export interface MusicListItem {
4343
worldsEndTag: string
4444
isWorldsEnd: boolean
4545
fumens: (FumenSummary | null)[]
46+
problems: string[]
4647
}
4748

4849
export interface FumenSummary {
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { defineComponent, PropType } from 'vue'
2+
import { Popover } from '@munet/ui'
3+
import { useI18n } from 'vue-i18n'
4+
5+
export default defineComponent({
6+
props: {
7+
problems: { type: Array as PropType<string[]>, required: true },
8+
inline: { type: Boolean, default: false },
9+
},
10+
setup(props) {
11+
const { t } = useI18n()
12+
const label = (code: string) => t(`problems.${code}`)
13+
14+
return () => {
15+
if (!props.problems.length) return null
16+
17+
if (props.inline) {
18+
return (
19+
<Popover trigger="hover">
20+
{{
21+
trigger: () => <div class="text-#f0a020 i-mdi-alert-circle-outline text-1.1em shrink-0" />,
22+
default: () => (
23+
<div class="flex flex-col gap-1">
24+
{props.problems.map(p => <div key={p}>{label(p)}</div>)}
25+
</div>
26+
),
27+
}}
28+
</Popover>
29+
)
30+
}
31+
32+
return (
33+
<div class="flex flex-col gap-1 c-#d99000 bg-#f0a02018 rd p-2 text-sm">
34+
{props.problems.map(p => (
35+
<div key={p} class="flex items-center gap-1.5">
36+
<div class="i-mdi-alert-circle-outline shrink-0" />
37+
{label(p)}
38+
</div>
39+
))}
40+
</div>
41+
)
42+
}
43+
},
44+
})

ChuChartManager/Front/src/locales/en.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ about:
6161
startup:
6262
errorTitle: Errors occurred during startup
6363
fixPrompt: Please review and fix the issues above. Edit or remove the affected files manually if necessary.
64+
problems:
65+
NoAudio: Missing audio
66+
NoJacket: Missing jacket
67+
NoEnabledFumen: No enabled difficulty
6468
oobe:
6569
welcomeMessage: Welcome to ChuChartManager
6670
selectGameDir: Select Game Directory

ChuChartManager/Front/src/locales/ja.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ about:
6161
startup:
6262
errorTitle: 起動時にエラーが発生しました
6363
fixPrompt: 上記の問題を確認して修正してください。必要に応じて該当ファイルを手動で編集または削除してください。
64+
problems:
65+
NoAudio: 音声がありません
66+
NoJacket: ジャケットがありません
67+
NoEnabledFumen: 有効な難易度がありません
6468
oobe:
6569
welcomeMessage: ChuChartManager へようこそ
6670
selectGameDir: ゲームディレクトリを選択

ChuChartManager/Front/src/locales/zh.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,10 @@ about:
6161
startup:
6262
errorTitle: 启动时发生错误
6363
fixPrompt: 请检查并修复以上问题,必要时手动修改或删除对应文件。
64+
problems:
65+
NoAudio: 缺少音频
66+
NoJacket: 缺少封面
67+
NoEnabledFumen: 没有启用的难度
6468
oobe:
6569
welcomeMessage: 欢迎使用 ChuChartManager
6670
selectGameDir: 选择游戏目录

ChuChartManager/Front/src/views/MusicList.vue

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import PlayerBar from '@/components/PlayerBar.vue'
1616
import BottomOverlay from '@/components/BottomOverlay.vue'
1717
import FileTypeIcon from '@/components/FileTypeIcon.vue'
1818
import MusicIdConflictNotifier from '@/components/MusicIdConflictNotifier'
19+
import ProblemsDisplay from '@/components/ProblemsDisplay'
1920
import { BlobWriter, ZipReader } from '@zip.js/zip.js'
2021
import getSubDirFile from '@/utils/getSubDirFile'
2122
import { useI18n } from 'vue-i18n'
@@ -566,7 +567,10 @@ const copyExportOptions = computed(() => {
566567
<div v-else class="h-16 w-16 shrink-0 bg-white/10 flex items-center justify-center text-xs op-40 rd">?</div>
567568
<div class="flex flex-col grow-1 w-0">
568569
<div class="text-xs op-50">{{ String(music.id).padStart(4, '0') }}</div>
569-
<div class="text-ellipsis of-hidden ws-nowrap">{{ music.name }}</div>
570+
<div class="text-ellipsis of-hidden ws-nowrap flex items-center gap-1">
571+
<span class="text-ellipsis of-hidden ws-nowrap">{{ music.name }}</span>
572+
<ProblemsDisplay :problems="music.problems" inline />
573+
</div>
570574
<div class="flex gap-1 mt-auto items-center">
571575
<template v-for="(f, i) in music.fumens" :key="i">
572576
<span v-if="f?.enable" class="rounded-full px-2 text-sm leading-6 font-medium" :style="getDiffBadgeStyle(i)">{{ i === 5 && music.worldsEndTag ? music.worldsEndTag : f.levelDisplay }}</span>
@@ -619,6 +623,7 @@ const copyExportOptions = computed(() => {
619623
<span v-if="f?.enable" class="rounded-full px-2.5 py-0.5 text-sm font-medium" :style="getDiffBadgeStyle(i)">{{ diffNames[i] }} {{ i === 5 && selectedMusic.worldsEndTag ? selectedMusic.worldsEndTag : f.levelDisplay }}</span>
620624
</template>
621625
</div>
626+
<ProblemsDisplay v-if="selectedMusic.problems.length" :problems="selectedMusic.problems" class="mt-3" />
622627
</div>
623628
</div>
624629

ChuChartManager/MusicValidator.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
using ChuChartManager.Models;
2+
3+
namespace ChuChartManager;
4+
5+
public static class MusicValidator
6+
{
7+
public const string NoAudio = "NoAudio";
8+
public const string NoJacket = "NoJacket";
9+
public const string NoEnabledFumen = "NoEnabledFumen";
10+
11+
public static List<string> Validate(MusicXml music)
12+
{
13+
var problems = new List<string>();
14+
15+
if (AudioHelper.FindAwbPath(music) == null)
16+
problems.Add(NoAudio);
17+
18+
if (music.GetJacketFullPath() == null)
19+
problems.Add(NoJacket);
20+
21+
if (!music.Fumens.Any(f => f is { Enable: true }))
22+
problems.Add(NoEnabledFumen);
23+
24+
return problems;
25+
}
26+
}

0 commit comments

Comments
 (0)