style: 统一主体列表搜索与统计布局
This commit is contained in:
@@ -112,6 +112,7 @@ Oxfmt 不负责代码质量,Oxlint 不负责 Vue 的完整类型推导;Vue S
|
|||||||
- 剧本正文的执行记录/导出/进入拆解,以及拆解结果的 JSON 导出统一放在 `NTabs` 的 `suffix` 插槽,与标签垂直居中并共用底线。浅色 Tabs 分隔线单独加深,不影响其它组件的低边框风格;窄内容区由容器断点把操作放到下一行,标签仍使用内置横向滚动。
|
- 剧本正文的执行记录/导出/进入拆解,以及拆解结果的 JSON 导出统一放在 `NTabs` 的 `suffix` 插槽,与标签垂直居中并共用底线。浅色 Tabs 分隔线单独加深,不影响其它组件的低边框风格;窄内容区由容器断点把操作放到下一行,标签仍使用内置横向滚动。
|
||||||
- 拆解分镜区的剧集选择、节拍/镜头统计与“进入分镜设计”共用一条紧凑工具栏;“选择剧集”不拆行。选择器默认显示正文对应的真实剧集,轮询移除当前剧集时同步回退;没有剧情目标与情绪弧线时不保留空信息面板。
|
- 拆解分镜区的剧集选择、节拍/镜头统计与“进入分镜设计”共用一条紧凑工具栏;“选择剧集”不拆行。选择器默认显示正文对应的真实剧集,轮询移除当前剧集时同步回退;没有剧情目标与情绪弧线时不保留空信息面板。
|
||||||
- 拆解人物、场景、道具共用斑马纹列表:无底色行在鼠标悬停或内部控件获得键盘焦点时显示主题化灰底,已有条纹保持原样;触屏不启用悬停态,搜索后的条纹顺序按可见列表重新计算。此类记录(含剧本角色设定)首项同样保留 20px 内边距,背景色使用 160ms 过渡,并遵循系统减少动态效果设置。
|
- 拆解人物、场景、道具共用斑马纹列表:无底色行在鼠标悬停或内部控件获得键盘焦点时显示主题化灰底,已有条纹保持原样;触屏不启用悬停态,搜索后的条纹顺序按可见列表重新计算。此类记录(含剧本角色设定)首项同样保留 20px 内边距,背景色使用 160ms 过渡,并遵循系统减少动态效果设置。
|
||||||
|
- 拆解主体列表的搜索框统一靠左,图标使用 `NInput` 的 `prefix` 插槽;统计紧随输入框弱化显示,搜索时显示匹配数/总数,清空后恢复总数。搜索与统计垂直居中,窄内容区允许换行,不再左右分散。
|
||||||
- 窄屏默认收起侧栏,目录与正文改用紧凑布局;执行记录仍可查看。长弹窗在自身内部滚动。
|
- 窄屏默认收起侧栏,目录与正文改用紧凑布局;执行记录仍可查看。长弹窗在自身内部滚动。
|
||||||
|
|
||||||
布局样式集中在 `src/admin.css`,业务排版保留在 `src/styles.css`。新增页面应使用 `WorkspacePage`(内容优先页开启 `compact`),低频操作使用 `WorkspaceTools`;避免通过 `body` 或 `window.scrollTo` 管理滚动。
|
布局样式集中在 `src/admin.css`,业务排版保留在 `src/styles.css`。新增页面应使用 `WorkspacePage`(内容优先页开启 `compact`),低频操作使用 `WorkspaceTools`;避免通过 `body` 或 `window.scrollTo` 管理滚动。
|
||||||
|
|||||||
+18
-8
@@ -1340,15 +1340,25 @@ body {
|
|||||||
.casting-item-status {
|
.casting-item-status {
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.search-field {
|
/* 主体搜索靠左,统计紧随输入框;窄内容区自然换行,不占满整行或挤压控件。 */
|
||||||
padding: 0;
|
.subject-list-toolbar {
|
||||||
border: 0;
|
display: flex;
|
||||||
background: transparent;
|
flex-wrap: wrap;
|
||||||
min-width: 180px;
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: 10px 12px;
|
||||||
|
margin-bottom: 20px;
|
||||||
}
|
}
|
||||||
.search-field input {
|
.subject-list-search.n-input {
|
||||||
width: 100%;
|
flex: 0 1 260px;
|
||||||
font-size: inherit;
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
.subject-list-count {
|
||||||
|
flex-shrink: 0;
|
||||||
|
color: var(--app-muted);
|
||||||
|
font-size: 12px;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
.select-control {
|
.select-control {
|
||||||
min-width: 130px;
|
min-width: 130px;
|
||||||
|
|||||||
@@ -146,9 +146,36 @@ describe('拆解页内容滚动', () => {
|
|||||||
const list = wrapper!.get('.subject-record-list')
|
const list = wrapper!.get('.subject-record-list')
|
||||||
expect(list.classes()).toContain('record-list')
|
expect(list.classes()).toContain('record-list')
|
||||||
expect(list.findAll(':scope > article')).toHaveLength(30)
|
expect(list.findAll(':scope > article')).toHaveLength(30)
|
||||||
await wrapper!.get('input[aria-label="搜索主体"]').setValue('主体 30')
|
const toolbar = wrapper!.get('.subject-list-toolbar')
|
||||||
|
expect(toolbar.element.firstElementChild).toBe(toolbar.get('.subject-list-search').element)
|
||||||
|
expect(toolbar.element.lastElementChild).toBe(toolbar.get('.subject-list-count').element)
|
||||||
|
expect(toolbar.get('.n-input__prefix svg').attributes('aria-hidden')).toBe('true')
|
||||||
|
expect(toolbar.find(':scope > svg').exists()).toBe(false)
|
||||||
|
expect(toolbar.get('[role="status"]').text()).toBe('共 30 个主体')
|
||||||
|
const input = toolbar.get('input[aria-label="搜索主体"]')
|
||||||
|
await input.setValue('主体 30')
|
||||||
expect(list.findAll(':scope > article')).toHaveLength(1)
|
expect(list.findAll(':scope > article')).toHaveLength(1)
|
||||||
expect(list.get(':scope > article h3').text()).toContain('主体 30')
|
expect(list.get(':scope > article h3').text()).toContain('主体 30')
|
||||||
|
expect(toolbar.get('[role="status"]').text()).toBe('匹配 1 / 30 个主体')
|
||||||
|
await input.setValue('不存在的主体')
|
||||||
|
expect(toolbar.get('[role="status"]').text()).toBe('匹配 0 / 30 个主体')
|
||||||
|
expect(wrapper!.text()).toContain('没有匹配的主体')
|
||||||
|
await input.setValue('')
|
||||||
|
expect(toolbar.get('[role="status"]').text()).toBe('共 30 个主体')
|
||||||
|
expect(wrapper!.findAll('.subject-record-list > article')).toHaveLength(30)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('主体搜索与统计靠左居中,窄内容区可换行,输入框不再使用外置图标容器', () => {
|
||||||
|
// DOM 环境不计算布局,保护输入框宽度、主题统计色与窄屏换行的样式契约。
|
||||||
|
const css = readFileSync('src/admin.css', 'utf8')
|
||||||
|
expect(css).toMatch(
|
||||||
|
/\.subject-list-toolbar\s*\{[^}]*display:\s*flex;[^}]*flex-wrap:\s*wrap;[^}]*align-items:\s*center;[^}]*justify-content:\s*flex-start;/
|
||||||
|
)
|
||||||
|
expect(css).toMatch(
|
||||||
|
/\.subject-list-search\.n-input\s*\{[^}]*flex:\s*0 1 260px;[^}]*min-width:\s*0;[^}]*max-width:\s*100%;/
|
||||||
|
)
|
||||||
|
expect(css).toMatch(/\.subject-list-count\s*\{[^}]*color:\s*var\(--app-muted\);/)
|
||||||
|
expect(css + readFileSync('src/styles.css', 'utf8')).not.toContain('.search-field')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('只为主体斑马纹空白行添加主题化悬停底色,不覆盖已有条纹和操作状态', () => {
|
it('只为主体斑马纹空白行添加主题化悬停底色,不覆盖已有条纹和操作状态', () => {
|
||||||
|
|||||||
@@ -19,16 +19,20 @@ const filtered = computed(() =>
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="p-5 lg:p-7">
|
<div class="p-5 lg:p-7">
|
||||||
<div class="mb-5 flex items-center justify-between gap-3">
|
<div class="subject-list-toolbar">
|
||||||
<p class="text-sm text-muted">{{ subjects.length }} 个主体</p>
|
<NInput
|
||||||
<div class="search-field">
|
class="subject-list-search"
|
||||||
<Search :size="14" /><NInput
|
|
||||||
placeholder="搜索名称或引用"
|
placeholder="搜索名称或引用"
|
||||||
aria-label="搜索主体"
|
aria-label="搜索主体"
|
||||||
:input-props="{ 'aria-label': '搜索主体' }"
|
:input-props="{ 'aria-label': '搜索主体' }"
|
||||||
v-model:value="search"
|
v-model:value="search"
|
||||||
|
clearable
|
||||||
|
><template #prefix><Search :size="14" aria-hidden="true" /></template
|
||||||
></NInput>
|
></NInput>
|
||||||
</div>
|
<p class="subject-list-count" role="status">
|
||||||
|
<template v-if="search.trim()">匹配 {{ filtered.length }} / {{ subjects.length }} 个主体</template>
|
||||||
|
<template v-else>共 {{ subjects.length }} 个主体</template>
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="filtered.length" class="record-list subject-record-list">
|
<div v-if="filtered.length" class="record-list subject-record-list">
|
||||||
<article v-for="subject in filtered" :key="subject.profileId">
|
<article v-for="subject in filtered" :key="subject.profileId">
|
||||||
|
|||||||
@@ -216,27 +216,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-field {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
background: var(--app-surface);
|
|
||||||
border-radius: 0;
|
|
||||||
padding: 7px 10px;
|
|
||||||
color: var(--color-muted);
|
|
||||||
}
|
|
||||||
.search-field input {
|
|
||||||
background: transparent;
|
|
||||||
border: none;
|
|
||||||
outline: none;
|
|
||||||
font-size: 11px;
|
|
||||||
width: 145px;
|
|
||||||
color: var(--color-ink);
|
|
||||||
}
|
|
||||||
.search-field:has(> input):focus-within {
|
|
||||||
outline: 2px solid var(--color-accent);
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
.table-scroll {
|
.table-scroll {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user