style: 优化选角面板间距与角色卡片布局
This commit is contained in:
+55
-2
@@ -221,6 +221,10 @@ body {
|
||||
.workspace-tools-body {
|
||||
padding: 20px;
|
||||
}
|
||||
/* 身份说明与折叠入口分开,避免正文紧贴下一组操作。 */
|
||||
.identity-tools-intro {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
.workspace-tools-body .storyboard-controls,
|
||||
.workspace-tools-body .production-controls {
|
||||
display: grid;
|
||||
@@ -886,7 +890,6 @@ body {
|
||||
.n-button:disabled {
|
||||
opacity: 1;
|
||||
}
|
||||
.n-button.casting-item,
|
||||
.n-button.image-history-item {
|
||||
height: auto;
|
||||
white-space: normal;
|
||||
@@ -897,9 +900,59 @@ body {
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
}
|
||||
.n-button.casting-item .n-button__content {
|
||||
/* 选角进度按抽屉实际宽度排列;按钮尺寸显式覆盖组件默认值。 */
|
||||
.casting-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(100%, 260px), 1fr));
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
padding-top: 20px;
|
||||
border-top: 1px solid var(--app-border);
|
||||
}
|
||||
.n-button.casting-item {
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
height: auto;
|
||||
min-height: 76px;
|
||||
padding: 12px 14px;
|
||||
white-space: normal;
|
||||
text-align: left;
|
||||
background: var(--app-surface);
|
||||
}
|
||||
.n-button.casting-item.selected {
|
||||
background: var(--app-subtle);
|
||||
box-shadow: inset 3px 0 0 var(--app-ink);
|
||||
}
|
||||
.n-button.casting-item .n-button__content {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
text-align: left;
|
||||
}
|
||||
.casting-item-identity {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
min-width: 0;
|
||||
}
|
||||
.casting-item-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.casting-item-ref {
|
||||
color: var(--app-muted);
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 11px;
|
||||
line-height: 1.5;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.casting-item-status {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.search-field {
|
||||
padding: 0;
|
||||
|
||||
@@ -32,6 +32,20 @@ describe('管理后台组件边界', () => {
|
||||
const css = readFileSync('src/admin.css', 'utf8') + readFileSync('src/styles.css', 'utf8')
|
||||
expect(css).not.toMatch(/overflow(?:-[xy])?\s*:\s*(?:auto|scroll)\b/)
|
||||
})
|
||||
it('选角面板保留说明间距,卡片按可用宽度排列并覆盖按钮默认高度', () => {
|
||||
// 保护间距与换行契约,真实宽高和暗色效果仍需浏览器验收。
|
||||
const css = readFileSync('src/admin.css', 'utf8')
|
||||
expect(css).toMatch(/\.identity-tools-intro\s*\{[^}]*margin-bottom:\s*24px/)
|
||||
expect(css).toMatch(
|
||||
/\.casting-list\s*\{[^}]*repeat\(auto-fit, minmax\(min\(100%, 260px\), 1fr\)\);[^}]*gap:\s*12px;[^}]*margin-top:\s*24px/
|
||||
)
|
||||
expect(css).toMatch(
|
||||
/\.n-button\.casting-item\s*\{[^}]*height:\s*auto;[^}]*min-height:\s*76px;[^}]*padding:\s*12px 14px/
|
||||
)
|
||||
expect(css).toMatch(/\.casting-item-name\s*\{[^}]*overflow-wrap:\s*anywhere/)
|
||||
expect(css).toMatch(/\.casting-item-status\s*\{[^}]*flex-shrink:\s*0/)
|
||||
expect(css).toMatch(/\.n-button\.casting-item\.selected\s*\{[^}]*var\(--app-subtle\)[^}]*var\(--app-ink\)/)
|
||||
})
|
||||
it('拆解分栏跟随剩余高度收缩,移动端历史区不挤出结果区', () => {
|
||||
// DOM 环境不计算几何,保护完整的 flex → grid → NScrollbar 高度链。
|
||||
const css = readFileSync('src/admin.css', 'utf8')
|
||||
|
||||
@@ -166,7 +166,7 @@ watch(
|
||||
title="角色选角总览与批量操作"
|
||||
:has-receipt="!!session.receipt"
|
||||
>
|
||||
<div class="flex flex-wrap items-end justify-between gap-4">
|
||||
<div class="identity-tools-intro flex flex-wrap items-end justify-between gap-4">
|
||||
<div>
|
||||
<h2 class="text-lg font-semibold">主体身份</h2>
|
||||
<p class="mt-2 text-sm text-muted">
|
||||
@@ -294,21 +294,21 @@ watch(
|
||||
>
|
||||
并发数与本批上限都必须是正整数。
|
||||
</p>
|
||||
<div class="casting-list mt-5">
|
||||
<div class="casting-list" role="group" aria-label="角色选角进度">
|
||||
<NButton
|
||||
v-for="item in casting.items"
|
||||
:key="item.subjectId"
|
||||
@click="select(item.subjectId)"
|
||||
class="casting-item"
|
||||
:class="{ selected: selectedId === item.subjectId }"
|
||||
><span class="min-w-0">
|
||||
<span class="block truncate text-xs font-medium">{{
|
||||
item.subjectName
|
||||
}}</span>
|
||||
<span class="mt-1 block font-mono text-[10px] text-muted">{{
|
||||
:aria-pressed="selectedId === item.subjectId"
|
||||
><span class="casting-item-identity">
|
||||
<span class="casting-item-name">{{ item.subjectName }}</span>
|
||||
<span class="casting-item-ref">{{
|
||||
item.subjectRef
|
||||
}}</span> </span
|
||||
><StatusBadge
|
||||
class="casting-item-status"
|
||||
:status="item.status"
|
||||
:label="castingStatusLabel(item.status)"
|
||||
/></NButton>
|
||||
|
||||
@@ -527,6 +527,69 @@ describe('视觉风格与主体身份工作区', () => {
|
||||
expect(wrapper!.text()).toContain('当前身份母版')
|
||||
})
|
||||
|
||||
it('选角进度卡片分层显示长姓名、编号和状态,点击仍按正式 ID 切换主体', async () => {
|
||||
const longName = '来自记忆当铺的另一条时间线中尚未丢失记忆的林默'
|
||||
const items = [
|
||||
{
|
||||
subjectId: 'subject-db-1',
|
||||
subjectRef: '@CH0001',
|
||||
subjectName: longName,
|
||||
status: 'ready',
|
||||
isLocked: true
|
||||
},
|
||||
{
|
||||
subjectId: 'subject-db-2',
|
||||
subjectRef: '@CH0002',
|
||||
subjectName: '老周',
|
||||
status: 'candidate_pending',
|
||||
isLocked: false
|
||||
}
|
||||
]
|
||||
const fetcher = vi.fn<typeof fetch>().mockImplementation(async url => {
|
||||
const path = String(url)
|
||||
if (path.endsWith('/subject-forms')) return jsonResponse([formFixture()])
|
||||
if (path.endsWith('/visual-style')) return jsonResponse(styleFixture())
|
||||
if (path.endsWith('/character-casting/readiness'))
|
||||
return jsonResponse({
|
||||
total: 2,
|
||||
ready: 1,
|
||||
missingIdentity: 0,
|
||||
missingAnchor: 0,
|
||||
candidatePending: 1,
|
||||
unlocked: 0,
|
||||
locked: 1,
|
||||
items
|
||||
})
|
||||
if (path.endsWith('/identity/images')) return jsonResponse([])
|
||||
const second = path.includes('/subject-db-2/')
|
||||
return jsonResponse(
|
||||
identityFixture({
|
||||
subjectId: second ? 'subject-db-2' : 'subject-db-1',
|
||||
description: second ? '老周的稳定身份' : '林默的稳定身份'
|
||||
})
|
||||
)
|
||||
})
|
||||
vi.stubGlobal('fetch', fetcher)
|
||||
await mountAssets('identity')
|
||||
expect(wrapper!.get('.identity-tools-intro').text()).toContain('为同一个人物、场景或道具固定稳定特征')
|
||||
const cards = wrapper!.get('[aria-label="角色选角进度"]').findAll('.casting-item')
|
||||
expect(cards).toHaveLength(2)
|
||||
expect(cards[0]!.get('.casting-item-name').text()).toBe(longName)
|
||||
expect(cards[0]!.get('.casting-item-ref').text()).toBe('@CH0001')
|
||||
expect(cards[0]!.get('.casting-item-status').text()).toBe('选角已完成')
|
||||
expect(cards[0]!.find('.truncate').exists()).toBe(false)
|
||||
expect(cards[0]!.attributes('aria-pressed')).toBe('true')
|
||||
expect(cards[1]!.get('.casting-item-status').text()).toBe('等待确认演员')
|
||||
await cards[1]!.trigger('click')
|
||||
await flushPromises()
|
||||
expect(cards[0]!.attributes('aria-pressed')).toBe('false')
|
||||
expect(cards[1]!.attributes('aria-pressed')).toBe('true')
|
||||
expect(cards[1]!.classes()).toContain('selected')
|
||||
expect(wrapper!.get<HTMLTextAreaElement>('#identity-description').element.value).toBe('老周的稳定身份')
|
||||
expect(fetcher.mock.calls.some(([url]) => String(url).endsWith('/subjects/subject-db-2/identity'))).toBe(true)
|
||||
expect(fetcher.mock.calls.every(([, init]) => init?.method === 'GET')).toBe(true)
|
||||
})
|
||||
|
||||
it('Character 确认选角原子切换 Anchor 并锁定 Identity,不调用普通母版接口', async () => {
|
||||
let locked = false
|
||||
let selected = false
|
||||
|
||||
@@ -91,28 +91,6 @@
|
||||
font-family: ui-monospace, monospace;
|
||||
font-size: 13px;
|
||||
}
|
||||
.casting-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 8px;
|
||||
}
|
||||
.casting-item {
|
||||
display: flex;
|
||||
min-width: 0;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 11px 12px;
|
||||
border: 1px solid var(--color-line);
|
||||
border-radius: 5px;
|
||||
background: var(--app-surface);
|
||||
text-align: left;
|
||||
}
|
||||
.casting-item:hover,
|
||||
.casting-item.selected {
|
||||
border-color: var(--app-border);
|
||||
background: var(--app-subtle);
|
||||
}
|
||||
@media (max-width: 900px) {
|
||||
.identity-workspace {
|
||||
grid-template-columns: minmax(0, 1fr);
|
||||
@@ -123,9 +101,6 @@
|
||||
.casting-stats {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
.casting-list {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.page-container {
|
||||
|
||||
Reference in New Issue
Block a user