Repository navigation
feat(front): 谱面管理左侧列表搜索、1440 断点修复、右侧编辑区空态 - #80
Conversation
- 按名称 / 作曲 / 谱师 / ID 过滤,命中口径复用 utils/musicSearch 的 matchMusicKeyword, 和批量操作页保持同一口径 - 过滤只作用于渲染列表(displayedMusic),selectedMusic 仍由未过滤的 musicList 派生, 所以选中曲被过滤掉时右侧编辑面板不会变空 - 无匹配显示空态;有内容时显示清空按钮,Esc 也能清空;输入法组字中按 Esc 不清空 - 修既有跳转失效(只选中不滚动):批量页调用方只等 100ms,而 tab 切换是 0.3s 的 out-in 过渡,那时 VList 还没挂载。改成等 ref 就绪(setTimeout 轮询,2s 兜底) - 修既有跳转失效(左侧停在目录管理时):跳转先把 leftPanel 切回列表再定位 - 搜索框占位符去掉「别名」:matchMusicKeyword 本来就不搜别名,且这个 key 被批量操作页 与工具页共用,三处文案随实现一起对齐 AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
- 抽屉布局原来写 max-[1440px],而汉堡按钮是 min-[1440px]:hidden,两边在正好 1440 宽时 同时命中:左侧被 translate 推出屏幕、打开它的按钮又被隐藏,谱面管理页完全没法用。 1440 宽的屏幕把窗口最大化就会踩到 - 抽屉改成 max-[1439px],与 min-[1440px] 以 1440 为界且不重叠;1440 及以上固定展开 - 1439 及以下行为不变(原来的 max-width:1440 同样覆盖 1439),只有正好 1440 从坏掉变成桌面布局 AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
- 原来选中 A000(游戏本体目录)时,右上角直接换成一句提示文案,按钮消失,位置跟着跳 - 改成按钮始终在,用 pointer-events-none + op-50 禁用;提示改为灰色小字放在按钮下面 - 复制与导出不在此列,它不改 A000 的内容,保持可用 AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
- 没选中任何曲目时右侧编辑区原本整块空白,看不出是「没选」还是「没加载出来」 - 补一个居中的「请选择谱面以编辑」,0.5 不透明度、稍大字号(三语同步) AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
审查者指南此 PR 为图表列表添加了共享逻辑的音乐搜索,增强了跳转到虚拟化列表的可靠性,修复了精确的 1440px 响应式断点,并在所有受支持的语言环境中明确了编辑器的只读状态和未选择状态。 筛选后的图表导航时序图sequenceDiagram
participant BatchPage
participant MusicList
participant VList
participant MusicStore
BatchPage->>MusicList: scrollToMusic(musicId)
MusicList->>MusicStore: Check musicList and selectedADir
MusicList->>MusicList: clearMusicSearch()
MusicList->>MusicList: nextTick()
loop Until VList mounts or timeout
MusicList->>VList: waitForMusicList()
end
MusicList->>VList: scrollToIndex(index, align, smooth)
VList-->>BatchPage: Target chart visible
图表列表搜索与编辑器状态流程图flowchart LR
Input["搜索关键词"] --> Match[matchMusicKeyword]
MusicList["musicList"] --> Match
Match --> Displayed["displayedMusic"]
Displayed --> VList["筛选后的虚拟列表"]
MusicList --> Selected["selectedMusic"]
Selected --> Editor["编辑器面板"]
Selected -->|无| Empty["选择一个图表进行编辑"]
Displayed -->|无匹配项| NoMatch["没有匹配的歌曲"]
文件级变更
提示与命令使用 Sourcery
自定义使用体验访问你的控制面板,以便:
获取帮助Original review guide in EnglishReviewer's GuideThe PR adds shared-logic music search to the chart list, hardens navigation into the virtualized list, fixes the exact 1440px responsive breakpoint, and clarifies read-only and no-selection states in the editor across all supported locales. Sequence diagram for filtered chart navigationsequenceDiagram
participant BatchPage
participant MusicList
participant VList
participant MusicStore
BatchPage->>MusicList: scrollToMusic(musicId)
MusicList->>MusicStore: Check musicList and selectedADir
MusicList->>MusicList: clearMusicSearch()
MusicList->>MusicList: nextTick()
loop Until VList mounts or timeout
MusicList->>VList: waitForMusicList()
end
MusicList->>VList: scrollToIndex(index, align, smooth)
VList-->>BatchPage: Target chart visible
Flow diagram for chart list search and editor stateflowchart LR
Input["Search keyword"] --> Match[matchMusicKeyword]
MusicList["musicList"] --> Match
Match --> Displayed["displayedMusic"]
Displayed --> VList["Filtered virtual list"]
MusicList --> Selected["selectedMusic"]
Selected --> Editor["Editor panel"]
Selected -->|none| Empty["Select a chart to edit"]
Displayed -->|no matches| NoMatch["No matching songs"]
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
All reported issues were addressed across 5 files
Reply to a comment to ask cubic a question or push back. It learns from your replies.
View guided diff | Re-trigger cubic
There was a problem hiding this comment.
您好——我发现了 4 个问题
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="MaiChartManager/Front/src/views/Charts/index.tsx" line_range="76" />
<code_context>
+ <div class="flex flex-wrap items-center justify-end gap-2 max-[639px]:w-full">
+ {!!selectedMusic.value && <div class="shrink-0"><CopyToButton /></div>}
+ {/* A000 是游戏本体目录,不允许编辑:按钮保留但禁用,下面用灰字说明原因 */}
+ <div class={['flex flex-wrap items-center justify-end gap-2', isA000.value && 'pointer-events-none op-50']}>
<div class="flex gap-2 shrink-0"><MusicSelectedTopRightToolbar /></div>
<div class="shrink-0"><ImportCreateChartButton /></div>
</code_context>
<issue_to_address>
**A000 remains editable by keyboard**
When A000 is selected and a user activates a wrapped control with the keyboard or presses Ctrl+S, the `pointer-events-none` wrapper does not disable its child buttons or guard their handlers, so `DeleteMusic` and import/create actions can still modify the read-only directory; the mounted save shortcut can also run through Ctrl+S.
Disable or guard the wrapped actions and save shortcut whenever A000 is selected.
</issue_to_address>
### Comment 2
<location path="MaiChartManager/Front/src/views/Charts/index.tsx" line_range="78" />
<code_context>
+ {/* A000 是游戏本体目录,不允许编辑:按钮保留但禁用,下面用灰字说明原因 */}
+ <div class={['flex flex-wrap items-center justify-end gap-2', isA000.value && 'pointer-events-none op-50']}>
<div class="flex gap-2 shrink-0"><MusicSelectedTopRightToolbar /></div>
<div class="shrink-0"><ImportCreateChartButton /></div>
- </>
- )}
</code_context>
<issue_to_address>
**Open create dialog targets A000**
When a create dialog is open in an editable directory and another tab changes the shared selected directory to A000 before confirmation, `ImportCreateChartButton` stays mounted when the selected directory changes, so its already-open create dialog remains active. `CreateMusicModal.save` reads the live `selectedADir` rather than the directory where the dialog was opened; confirming after another tab changes the shared directory selection to A000 calls `AddMusic` for A000, which the controller accepts.
Capture the directory when opening the dialog and reject or close the dialog if that directory is A000; also enforce the read-only rule in the mutation endpoint.
</issue_to_address>
### Comment 3
<location path="MaiChartManager/Front/src/views/Charts/MusicList/index.tsx" line_range="40" />
<code_context>
+ if (!musicList.value.some(m => m.id === musicId)) return;
+
+ // 跳转的含义是「回到曲目列表并看到这首歌」:左侧停在目录管理时也要切回列表
+ if (leftPanel.value !== 'musicList') leftPanel.value = 'musicList';
+
+ // 搜索词把目标挡住了就先清掉,否则 scrollToIndex 找不到它
</code_context>
<issue_to_address>
**Jump target stays hidden**
When a batch-page jump runs below 1440px, `scrollToMusic` switches `leftPanel` to `musicList` but does not open the responsive drawer, which starts closed and remains offscreen, so the user sees no navigation to the target.
Open the responsive drawer when a batch jump switches to the music list.
</issue_to_address>
### Comment 4
<location path="MaiChartManager/Front/src/views/Charts/MusicList/index.tsx" line_range="55" />
<code_context>
+ // 等 VList 挂载的这段时间里列表可能已经变了,下标要按最新数据重算
+ const idx = displayedMusic.value.findIndex(m => m.id === musicId);
+ if (idx === -1) return;
+ list.scrollToIndex(idx, { align: 'center', smooth: true });
+};
+
</code_context>
<issue_to_address>
**Selection and viewport diverge**
When a user selects another row while a batch jump is waiting for the list to mount, `scrollToMusic` waits for the list to mount, but a manual row click only changes `selectMusicId` and does not invalidate `lastJump`. The pending continuation then scrolls to the earlier batch target while a different song remains selected, leaving the selected song out of view.
Invalidate pending jumps on manual row selection, or check that the target is still selected before scrolling.
</issue_to_address>Sourcery 评估
等待批准。 请先处理 4 项发现。
阻塞性发现:MaiChartManager/Front/src/views/Charts/index.tsx:76、MaiChartManager/Front/src/views/Charts/index.tsx:78、MaiChartManager/Front/src/views/Charts/MusicList/index.tsx:40、MaiChartManager/Front/src/views/Charts/MusicList/index.tsx:55
Original comment in English
Hey - I've found 4 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="MaiChartManager/Front/src/views/Charts/index.tsx" line_range="76" />
<code_context>
+ <div class="flex flex-wrap items-center justify-end gap-2 max-[639px]:w-full">
+ {!!selectedMusic.value && <div class="shrink-0"><CopyToButton /></div>}
+ {/* A000 是游戏本体目录,不允许编辑:按钮保留但禁用,下面用灰字说明原因 */}
+ <div class={['flex flex-wrap items-center justify-end gap-2', isA000.value && 'pointer-events-none op-50']}>
<div class="flex gap-2 shrink-0"><MusicSelectedTopRightToolbar /></div>
<div class="shrink-0"><ImportCreateChartButton /></div>
</code_context>
<issue_to_address>
**A000 remains editable by keyboard**
When A000 is selected and a user activates a wrapped control with the keyboard or presses Ctrl+S, the `pointer-events-none` wrapper does not disable its child buttons or guard their handlers, so `DeleteMusic` and import/create actions can still modify the read-only directory; the mounted save shortcut can also run through Ctrl+S.
Disable or guard the wrapped actions and save shortcut whenever A000 is selected.
</issue_to_address>
### Comment 2
<location path="MaiChartManager/Front/src/views/Charts/index.tsx" line_range="78" />
<code_context>
+ {/* A000 是游戏本体目录,不允许编辑:按钮保留但禁用,下面用灰字说明原因 */}
+ <div class={['flex flex-wrap items-center justify-end gap-2', isA000.value && 'pointer-events-none op-50']}>
<div class="flex gap-2 shrink-0"><MusicSelectedTopRightToolbar /></div>
<div class="shrink-0"><ImportCreateChartButton /></div>
- </>
- )}
</code_context>
<issue_to_address>
**Open create dialog targets A000**
When a create dialog is open in an editable directory and another tab changes the shared selected directory to A000 before confirmation, `ImportCreateChartButton` stays mounted when the selected directory changes, so its already-open create dialog remains active. `CreateMusicModal.save` reads the live `selectedADir` rather than the directory where the dialog was opened; confirming after another tab changes the shared directory selection to A000 calls `AddMusic` for A000, which the controller accepts.
Capture the directory when opening the dialog and reject or close the dialog if that directory is A000; also enforce the read-only rule in the mutation endpoint.
</issue_to_address>
### Comment 3
<location path="MaiChartManager/Front/src/views/Charts/MusicList/index.tsx" line_range="40" />
<code_context>
+ if (!musicList.value.some(m => m.id === musicId)) return;
+
+ // 跳转的含义是「回到曲目列表并看到这首歌」:左侧停在目录管理时也要切回列表
+ if (leftPanel.value !== 'musicList') leftPanel.value = 'musicList';
+
+ // 搜索词把目标挡住了就先清掉,否则 scrollToIndex 找不到它
</code_context>
<issue_to_address>
**Jump target stays hidden**
When a batch-page jump runs below 1440px, `scrollToMusic` switches `leftPanel` to `musicList` but does not open the responsive drawer, which starts closed and remains offscreen, so the user sees no navigation to the target.
Open the responsive drawer when a batch jump switches to the music list.
</issue_to_address>
### Comment 4
<location path="MaiChartManager/Front/src/views/Charts/MusicList/index.tsx" line_range="55" />
<code_context>
+ // 等 VList 挂载的这段时间里列表可能已经变了,下标要按最新数据重算
+ const idx = displayedMusic.value.findIndex(m => m.id === musicId);
+ if (idx === -1) return;
+ list.scrollToIndex(idx, { align: 'center', smooth: true });
+};
+
</code_context>
<issue_to_address>
**Selection and viewport diverge**
When a user selects another row while a batch jump is waiting for the list to mount, `scrollToMusic` waits for the list to mount, but a manual row click only changes `selectMusicId` and does not invalidate `lastJump`. The pending continuation then scrolls to the earlier batch target while a different song remains selected, leaving the selected song out of view.
Invalidate pending jumps on manual row selection, or check that the target is still selected before scrolling.
</issue_to_address>Sourcery assessment
Approval pending. 4 findings to address first.
Blocking findings: MaiChartManager/Front/src/views/Charts/index.tsx:76, MaiChartManager/Front/src/views/Charts/index.tsx:78, MaiChartManager/Front/src/views/Charts/MusicList/index.tsx:40, MaiChartManager/Front/src/views/Charts/MusicList/index.tsx:55
浏览器审查指出 pointer-events-none 只挡鼠标:A000 下用 Tab 聚焦后回车、或按 Ctrl+S, 仍能触发导入/保存,会往游戏本体目录写数据。原来的「禁用」只有视觉效果。 - 新增 store.refs 的 isReadOnlyDir(selectedADir === 'A000'),所有写入口先看它 - 保存:按钮 disabled,save() 里再判一次(Ctrl+S 走的就是这个函数) - 删除 / 导入 / 创建:按钮 disabled + 入口函数早退 - 替换谱面(ChartPanel 两个入口)与封面替换(JacketBox,含拖拽路径):入口早退 - 创建曲目对话框在确认时重新判目录,堵住「打开后别处切到 A000 再确认」的竞态 验证:A000 下四个按钮均为 disabled,模拟 Ctrl+S 与点击均未产生任何写请求(用一次 性 mock 观察,未打真实后端);pnpm typecheck / build 通过。 AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
- 列表里的 ID 是补零的 6 位显示(000123),但搜索只匹配原始数值,搜 000123 无结果; matchMusicKeyword 现在同时匹配补零形式(批量操作页与工具页搜索一并修好) - 窄屏(<1440)下列表是抽屉:批量跳转只切了 leftPanel,抽屉仍关着,用户看不到目标; 抽屉状态移到 views/Charts/refs.ts,跳转时一并拉开 - 等待列表挂载期间用户手动点了别的曲目时,之前那次跳转不再作数(避免选中的是 A、 列表滚到 B) 验证:mock 数据下 000123→标准谱、010123→DX 谱、123→两者;typecheck / build 通过。 AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
There was a problem hiding this comment.
All reported issues were addressed across 13 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
审查指出:入口的只读判断和真正的写入之间隔着若干异步步骤(选文件、确认弹窗、逐条处理), 而写入时又读了一次 selectedADir。期间目录被切到 A000,写操作就会落到只读目录。 - 导入:startProcess 入口快照 importDir,之后 AddMusic / ImportChart / SetAudio / SetAudioPreview / SetMusicJacket / SetMovie / 回滚用的 DeleteMusic 全部用它 - 替换谱面:prepareReplaceChart 时快照 replaceDir,确认后 ReplaceChart 用它 这样「中途切目录」只会影响下一次导入,不会把正在进行的那次带偏。 AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
- JacketBox 的 SetMusicJacket 原来传的是「当前选中目录」;选文件是异步的,期间切目录会 写错地方。改成传曲目自己的 assetDir(本来也该如此) - A000 下封面不再显示 cursor-pointer:现在点了没反应,手型会让人以为操作失败 - musicDrawerOpen 是模块级状态,切到别的侧栏页再回来会保留;Charts 卸载时复位 AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
There was a problem hiding this comment.
0 issues found across 4 files (changes from recent commits).
Auto-approved: 谱面管理前端改动:列表新增搜索与空态、修复 1440 断点,并将 A000 只读统一收敛到保存/删除/导入/替换/换封面等写入入口;改动有界、降低误写风险,memo 写入属旁注文件,不在本次只读范围内。
View guided diff | Re-trigger cubic
- 绝对定位居中改用 top-0 bottom-0 + my-auto,不再依赖 -translate-y-1/2
- 原因:@munet/ui 全局的 button:hover { transform: scale(1.02) } 特异性更高,会把 UnoCSS 的 translate 整条顶掉,导致悬停时按钮向下掉半个高度
- innerClass 里的 pr-10 改用 pr-10! - 原因:MuNET-UI TextInput 的 .wrapper input 选择器特异性 0,1,1 高于普通工具类,原来的 padding-right 没生效,长关键词尾部会被清空按钮盖住
- saveMusicIfNeeded 由窗口失焦和切歌自动触发,原来用 selectedADir,切目录后会写错目录 - 增加 A000 拦截,避免待保存的内容被写进只读目录
- ChartPanel 的 sync 直接调 EditChart* 接口,之前只靠输入组件的 disabled 注入 - 入口处补 isReadOnlyDir 判断,与保存 / 删除 / 导入的处理保持一致
- 在清空按钮上阻止 mousedown 的默认行为,焦点不会离开输入框 - 否则点完清空焦点落在按钮上,想接着打字还得再点一次输入框
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
View guided diff | Re-trigger cubic
Dismissed because Cubic found issues in a newer review.
- 同 ID 曲目可能同时存在于 A000 和用户目录,按 id 直接 find 会命中别目录那条,modified 为 false 导致自动保存被误判成不需要保存、直接跳过 - 查询加上 modified 条件定位真正被编辑的条目,资产目录仍取曲目自身的 assetDir
There was a problem hiding this comment.
0 issues found across 1 file (changes from recent commits).
Requires human review: 谱面管理改动(列表搜索、1440 断点、空态、A000 写入拦截)整体有界,但 A000 只读边界是否应覆盖 memo 写入仍未解决,需人确认该数据写入策略。
View guided diff | Re-trigger cubic
改动
1. 谱面管理左侧列表加搜索框 —— 按名称 / 作曲 / 谱师 / ID 过滤(补零的 6 位 ID 也能搜),复用
utils/musicSearch的matchMusicKeyword,与批量操作页同口径;过滤只作用于渲染列表,选中曲被过滤掉时右侧编辑面板不会变空。2. 修 1440 宽断点重叠 —— 抽屉布局的
max-[1440px]与汉堡按钮的min-[1440px]:hidden在正好 1440px 时同时命中:左侧被推出屏幕、打开它的按钮又被隐藏,整页不可用;抽屉改为max-[1439px]。上面这张和下面两张都是在 1440 宽下采集的,左侧列表固定展开即为修复后的效果。3. A000 目录下编辑按钮保留但禁用 —— 按钮不再消失(避免位置跳动);并且是真正禁用:按钮
disabled、保存的Ctrl+S快捷键与导入入口都在入口处拦截,不再只靠样式挡住鼠标;提示改为灰色小字放在按钮下面。4. 未选中谱面时右侧给空态提示 —— 编辑区不再整块空白,居中「请选择谱面以编辑」,24px、0.5 不透明度,三语同步。
验证
真实后端(1671 首)浏览器实测:
Glorious→2、Mournfinale→4、1005→2、zzzz→0、夜→11;深位置跳转scrollTop0→83833 定位成功;1440 宽下matchMedia('(max-width:1439px)')为 false;pnpm typecheck与pnpm build通过。两轮 code review。AI-assisted-by: DeepSeek-V4-Flash (DeepSeek Harness)
Sourcery 摘要
改进谱面管理搜索和空状态,修复 1440px 布局边界问题,并确保 A000 目录为只读。
新功能:
错误修复:
Original summary in English
Sourcery 总结
通过添加可搜索的导航、可靠的响应式行为、清晰的编辑器空状态,以及强制对 A000 目录进行只读处理,改进图表管理。
新功能:
错误修复:
改进:
Original summary in English
Sourcery 总结
通过支持搜索的导航、可靠的响应式行为、清晰的编辑器空状态,以及对 A000 目录统一的只读处理,改进图表管理。
新功能:
错误修复:
改进:
Original summary in English
Sourcery 摘要
通过支持搜索的导航、可靠的响应式布局、清晰的编辑器空状态,以及对 A000 目录强制执行只读处理,改进图表管理。
新功能:
错误修复:
改进:
Original summary in English
Summary by Sourcery
Improve chart management with searchable navigation, reliable responsive behavior, a clear editor empty state, and enforced read-only handling for the A000 directory.
New Features:
Bug Fixes:
Enhancements: