style: 区分主体条纹与形态块背景

This commit is contained in:
GouJ
2026-09-02 11:05:39 +08:00
parent 12f5fc74ae
commit 6bc07b559e
4 changed files with 68 additions and 2 deletions
+1
View File
@@ -113,6 +113,7 @@ Oxfmt 不负责代码质量,Oxlint 不负责 Vue 的完整类型推导;Vue S
- 拆解分镜区的剧集选择、节拍/镜头统计与“进入分镜设计”共用一条紧凑工具栏;“选择剧集”不拆行。选择器默认显示正文对应的真实剧集,轮询移除当前剧集时同步回退;没有剧情目标与情绪弧线时不保留空信息面板。 - 拆解分镜区的剧集选择、节拍/镜头统计与“进入分镜设计”共用一条紧凑工具栏;“选择剧集”不拆行。选择器默认显示正文对应的真实剧集,轮询移除当前剧集时同步回退;没有剧情目标与情绪弧线时不保留空信息面板。
- 拆解人物、场景、道具共用固定斑马纹列表:奇数行常驻原悬停灰底,偶数行保留次级底色,整行不随鼠标或键盘焦点变色;内部控件仍保留交互与焦点反馈,搜索后的条纹顺序按可见列表重新计算。此类记录(含剧本角色设定)首项同样保留 20px 内边距,主题切换时背景色使用 160ms 过渡,并遵循系统减少动态效果设置。 - 拆解人物、场景、道具共用固定斑马纹列表:奇数行常驻原悬停灰底,偶数行保留次级底色,整行不随鼠标或键盘焦点变色;内部控件仍保留交互与焦点反馈,搜索后的条纹顺序按可见列表重新计算。此类记录(含剧本角色设定)首项同样保留 20px 内边距,主题切换时背景色使用 160ms 过渡,并遵循系统减少动态效果设置。
- 拆解主体列表的搜索框统一靠左,图标使用 `NInput``prefix` 插槽;统计紧随输入框弱化显示,搜索时显示匹配数/总数,清空后恢复总数。搜索与统计垂直居中,窄内容区允许换行,不再左右分散。 - 拆解主体列表的搜索框统一靠左,图标使用 `NInput``prefix` 插槽;统计紧随输入框弱化显示,搜索时显示匹配数/总数,清空后恢复总数。搜索与统计垂直居中,窄内容区允许换行,不再左右分散。
- 展开后的每个形态使用独立直角色块,与所在主体条纹采用相反的主题灰阶;保留 16px 内边距、12px 块间距和名称/默认标签间距,奇偶行及搜索重排后均可区分内外层,不新增悬停变色。
- 窄屏默认收起侧栏,目录与正文改用紧凑布局;执行记录仍可查看。长弹窗在自身内部滚动。 - 窄屏默认收起侧栏,目录与正文改用紧凑布局;执行记录仍可查看。长弹窗在自身内部滚动。
布局样式集中在 `src/admin.css`,业务排版保留在 `src/styles.css`。新增页面应使用 `WorkspacePage`(内容优先页开启 `compact`),低频操作使用 `WorkspaceTools`;避免通过 `body``window.scrollTo` 管理滚动。 布局样式集中在 `src/admin.css`,业务排版保留在 `src/styles.css`。新增页面应使用 `WorkspacePage`(内容优先页开启 `compact`),低频操作使用 `WorkspaceTools`;避免通过 `body``window.scrollTo` 管理滚动。
@@ -178,6 +178,60 @@ describe('拆解页内容滚动', () => {
expect(css + readFileSync('src/styles.css', 'utf8')).not.toContain('.search-field') expect(css + readFileSync('src/styles.css', 'utf8')).not.toContain('.search-field')
}) })
it.each([
['人物', 'character'],
['场景', 'scene'],
['道具', 'prop']
] as const)('%s 的奇偶主体展开后均使用独立形态块,搜索重排不影响形态归属', async (label, module) => {
const record = checkpoint()
// 前两个主体各准备两种形态,覆盖奇偶条纹和默认标签,不调用模型接口。
record.state.breakdownResult!.subjectForms = [0, 1].flatMap(subjectIndex =>
[0, 1].map(formIndex => ({
formId: `${module}-${subjectIndex}-form-${formIndex}`,
profileId: `${module}-${subjectIndex}`,
type: module,
name: `主体 ${subjectIndex + 1} 形态 ${formIndex + 1}`,
isDefault: formIndex === 0,
description: '形态描述',
appearancePrompt: '形态提示词'
}))
)
mountPage([record])
await selectTab(label)
const articles = wrapper!.findAll('.subject-record-list > article')
for (const index of [0, 1]) {
const article = articles[index]!
await article.get('.n-collapse-item__header-main').trigger('click')
await flushPromises()
const cards = article.findAll('.subject-form-card')
expect(cards).toHaveLength(2)
expect(cards[0]!.get('.n-tag').text()).toBe('默认')
for (const card of cards) {
expect(card.classes()).not.toContain('surface-inset')
expect(card.text()).toContain(`主体 ${index + 1} 形态`)
expect(card.text()).toContain('形态描述')
expect(card.text()).toContain('形态提示词')
}
}
// 原偶数主体过滤后成为首项,仍由当前 DOM 的奇偶选择器决定内外层底色。
await wrapper!.get('input[aria-label="搜索主体"]').setValue(`@${module}1`)
const first = wrapper!.get('.subject-record-list > article')
expect(first.findAll('.subject-form-card')).toHaveLength(2)
expect(first.get('.subject-form-card').text()).toContain('主体 2 形态 1')
})
it('形态块在两种主体条纹上采用相反灰阶,以间距分组且没有整块悬停态', () => {
// DOM 环境不提供真实主题绘制,检查奇偶行底色、内边距和状态选择器契约。
const css = readFileSync('src/styles.css', 'utf8')
expect(css).toMatch(
/\.subject-form-card\s*\{[^}]*margin-top:\s*12px;[^}]*padding:\s*16px;[^}]*background:\s*var\(--app-subtle\);/
)
expect(css).toMatch(
/\.subject-record-list > article:nth-child\(even\) \.subject-form-card\s*\{\s*background:\s*var\(--app-control\);/
)
expect(css).not.toMatch(/\.subject-form-card[^{}]*(?::hover|:focus-within)/)
})
it('主体奇数行常驻原悬停底色,偶数行不变,整行不再随鼠标或焦点变色', () => { it('主体奇数行常驻原悬停底色,偶数行不变,整行不再随鼠标或焦点变色', () => {
// DOM 环境无法模拟浏览器 :hover 命中;检查静态底色与无整行交互选择器的契约。 // DOM 环境无法模拟浏览器 :hover 命中;检查静态底色与无整行交互选择器的契约。
const css = readFileSync('src/styles.css', 'utf8') const css = readFileSync('src/styles.css', 'utf8')
@@ -70,9 +70,9 @@ const filtered = computed(() =>
<div <div
v-for="form in forms.filter(item => item.profileId === subject.profileId)" v-for="form in forms.filter(item => item.profileId === subject.profileId)"
:key="form.formId" :key="form.formId"
class="mt-3 surface-inset p-4" class="subject-form-card"
> >
<p class="text-sm font-medium"> <p class="flex flex-wrap items-center gap-2 text-sm font-medium">
{{ form.formName || form.name {{ form.formName || form.name
}}<NTag v-if="form.isDefault" size="small" :bordered="false">默认</NTag> }}<NTag v-if="form.isDefault" size="small" :bordered="false">默认</NTag>
</p> </p>
+11
View File
@@ -210,6 +210,17 @@
.subject-record-list > article:nth-child(odd) { .subject-record-list > article:nth-child(odd) {
background: var(--app-control); background: var(--app-control);
} }
/* 形态块与所在主体条纹使用相反灰阶,避免偶数行内外同色;不增加边框或悬停态。 */
.subject-form-card {
margin-top: 12px;
padding: 16px;
min-width: 0;
overflow-wrap: anywhere;
background: var(--app-subtle);
}
.subject-record-list > article:nth-child(even) .subject-form-card {
background: var(--app-control);
}
.table-scroll { .table-scroll {
overflow: hidden; overflow: hidden;