style: 为拆解主体斑马纹空白行补充悬停背景

This commit is contained in:
GouJ
2026-09-02 10:43:21 +08:00
parent a125164103
commit 5b0e6578dc
4 changed files with 34 additions and 1 deletions
+1
View File
@@ -111,6 +111,7 @@ Oxfmt 不负责代码质量,Oxlint 不负责 Vue 的完整类型推导;Vue S
- Tabs 例外保留底部分隔线,浅色和暗黑模式均区分导航与正文;选中下划线仍使用绿色。视觉风格工具栏将“图标+状态说明”与等高操作按钮分组,读取失败不再显示为“尚未创建”,锁定与未保存草稿的生成限制不变。 - Tabs 例外保留底部分隔线,浅色和暗黑模式均区分导航与正文;选中下划线仍使用绿色。视觉风格工具栏将“图标+状态说明”与等高操作按钮分组,读取失败不再显示为“尚未创建”,锁定与未保存草稿的生成限制不变。
- 剧本正文的执行记录/导出/进入拆解,以及拆解结果的 JSON 导出统一放在 `NTabs``suffix` 插槽,与标签垂直居中并共用底线。浅色 Tabs 分隔线单独加深,不影响其它组件的低边框风格;窄内容区由容器断点把操作放到下一行,标签仍使用内置横向滚动。 - 剧本正文的执行记录/导出/进入拆解,以及拆解结果的 JSON 导出统一放在 `NTabs``suffix` 插槽,与标签垂直居中并共用底线。浅色 Tabs 分隔线单独加深,不影响其它组件的低边框风格;窄内容区由容器断点把操作放到下一行,标签仍使用内置横向滚动。
- 拆解分镜区的剧集选择、节拍/镜头统计与“进入分镜设计”共用一条紧凑工具栏;“选择剧集”不拆行。选择器默认显示正文对应的真实剧集,轮询移除当前剧集时同步回退;没有剧情目标与情绪弧线时不保留空信息面板。 - 拆解分镜区的剧集选择、节拍/镜头统计与“进入分镜设计”共用一条紧凑工具栏;“选择剧集”不拆行。选择器默认显示正文对应的真实剧集,轮询移除当前剧集时同步回退;没有剧情目标与情绪弧线时不保留空信息面板。
- 拆解人物、场景、道具共用斑马纹列表:无底色行在鼠标悬停或内部控件获得键盘焦点时显示主题化灰底,已有条纹保持原样;触屏不启用悬停态,搜索后的条纹顺序按可见列表重新计算。
- 窄屏默认收起侧栏,目录与正文改用紧凑布局;执行记录仍可查看。长弹窗在自身内部滚动。 - 窄屏默认收起侧栏,目录与正文改用紧凑布局;执行记录仍可查看。长弹窗在自身内部滚动。
布局样式集中在 `src/admin.css`,业务排版保留在 `src/styles.css`。新增页面应使用 `WorkspacePage`(内容优先页开启 `compact`),低频操作使用 `WorkspaceTools`;避免通过 `body``window.scrollTo` 管理滚动。 布局样式集中在 `src/admin.css`,业务排版保留在 `src/styles.css`。新增页面应使用 `WorkspacePage`(内容优先页开启 `compact`),低频操作使用 `WorkspaceTools`;避免通过 `body``window.scrollTo` 管理滚动。
@@ -140,6 +140,29 @@ async function selectTab(label: string) {
} }
describe('拆解页内容滚动', () => { describe('拆解页内容滚动', () => {
it.each(['人物', '场景', '道具'])('%s 使用共用斑马纹悬停样式,搜索后保持列表条目结构', async label => {
mountPage()
await selectTab(label)
const list = wrapper!.get('.subject-record-list')
expect(list.classes()).toContain('record-list')
expect(list.findAll(':scope > article')).toHaveLength(30)
await wrapper!.get('input[aria-label="搜索主体"]').setValue('主体 30')
expect(list.findAll(':scope > article')).toHaveLength(1)
expect(list.get(':scope > article h3').text()).toContain('主体 30')
})
it('只为主体斑马纹空白行添加主题化悬停底色,不覆盖已有条纹和操作状态', () => {
// DOM 环境无法模拟浏览器 :hover 命中;此项检查状态选择器与主题变量契约。
const css = readFileSync('src/styles.css', 'utf8')
expect(css).toMatch(/\.record-list > article:nth-child\(even\)\s*\{\s*background:\s*var\(--app-subtle\);/)
expect(css).toMatch(
/@media \(hover: hover\)\s*\{\s*\.subject-record-list > article:nth-child\(odd\):hover\s*\{\s*background:\s*var\(--app-control\);/
)
expect(css).toMatch(
/\.subject-record-list > article:nth-child\(odd\):focus-within\s*\{\s*background:\s*var\(--app-control\);/
)
})
it('分镜选择与计数、入口同排,默认显示实际剧集,切换后内容和统计同步', async () => { it('分镜选择与计数、入口同排,默认显示实际剧集,切换后内容和统计同步', async () => {
const record = checkpoint() const record = checkpoint()
const result = record.state.breakdownResult! const result = record.state.breakdownResult!
@@ -30,7 +30,7 @@ const filtered = computed(() =>
></NInput> ></NInput>
</div> </div>
</div> </div>
<div v-if="filtered.length" class="record-list"> <div v-if="filtered.length" class="record-list subject-record-list">
<article v-for="subject in filtered" :key="subject.profileId" class="py-5 first:pt-0"> <article v-for="subject in filtered" :key="subject.profileId" class="py-5 first:pt-0">
<div class="mb-3 flex flex-wrap items-center gap-3"> <div class="mb-3 flex flex-wrap items-center gap-3">
<h3 class="font-semibold">{{ subject.name }}</h3> <h3 class="font-semibold">{{ subject.name }}</h3>
+9
View File
@@ -204,6 +204,15 @@
.record-list > article:nth-child(even) { .record-list > article:nth-child(even) {
background: var(--app-subtle); background: var(--app-subtle);
} }
/* 主体斑马纹的空白行补充悬停/键盘焦点底色,不改变有底色行和其它列表。 */
.subject-record-list > article:nth-child(odd):focus-within {
background: var(--app-control);
}
@media (hover: hover) {
.subject-record-list > article:nth-child(odd):hover {
background: var(--app-control);
}
}
.search-field { .search-field {
display: flex; display: flex;