feat: 补齐前端主流程并收紧工作台布局

统一工作台紧凑头部与形态图库间距,修复主体身份布局,并接入正式严格视频质量流水线。
This commit is contained in:
GouJ
2026-09-08 09:25:14 +08:00
committed by GitHub
parent 0f8b2074e4
commit 4edb0ff6cc
25 changed files with 355 additions and 233 deletions
+38 -30
View File
@@ -1111,7 +1111,7 @@ describe('工作台页面交互', () => {
vi.stubGlobal('fetch', fetcher)
await mountStoryboard()
await flushPromises()
await confirmGeneration('生成本集导演设计')
await confirmGeneration('补齐项目导演设计')
expect(button('补齐项目导演设计').disabled).toBe(true)
await drawerPanel().get('[aria-label="关闭操作面板"]').trigger('click')
await flushPromises()
@@ -1123,61 +1123,69 @@ describe('工作台页面交互', () => {
wrapper = undefined
finish(
new Response(
'{"data":{"episodeNo":1,"direction":{"episodeNo":1,"beatDirections":[]},"validation":{"valid":true,"issues":[]}}}'
`{"data":{"projectId":"${fixture.id}","episodeCount":2,"completedEpisodes":1,"skippedEpisodes":1,"failedEpisodes":0,"shotCount":4,"directionCount":4,"coverage":1,"episodes":[]}}`
)
)
await flushPromises()
expect(getStoryboardSession(fixture.id).receipt).toMatchObject({
kind: 'direction',
episodeNo: 1,
title: '第 1 集导演设计'
kind: 'batch',
title: '项目导演设计'
})
expect(getOperation(fixture.id).pending).toBe(false)
const posts = fetcher.mock.calls.filter(([, init]) => init?.method === 'POST')
expect(posts).toHaveLength(1)
expect(JSON.parse(posts[0]![1]!.body as string)).toEqual({ episodeNo: 1, persist: true })
expect(JSON.parse(posts[0]![1]!.body as string)).toEqual({ concurrency: 2, force: false })
})
it('分镜必须先补齐导演设计,校验失败显示未保存,修复次数 0 不被默认值覆盖', async () => {
let complete = false
it('分镜只提供正式项目生成,逐集失败回执保留零次修复参数', async () => {
const fetcher = vi.fn<typeof fetch>().mockImplementation(async (url, init) => {
if (init?.method === 'POST')
return new Response(
JSON.stringify({
data: {
episodeNo: 1,
visualState: {},
validation: {
valid: false,
issues: [{ episodeNo: 1, beatNo: 1, shotNo: 1, message: '缺少主体状态' }]
},
repaired: false,
repairAttempts: 0,
remainingIssues: [],
persisted: false
projectId: fixture.id,
episodeCount: 1,
completedEpisodes: 0,
skippedEpisodes: 0,
failedEpisodes: 1,
shotCount: 2,
visualStateCount: 0,
coverage: 0,
episodes: [
{
episodeNo: 1,
status: 'failed',
shotCount: 2,
visualStateCount: 0,
repairAttempts: 0,
error: '缺少主体状态'
}
]
}
})
)
const result = String(url).includes('storyboard-directions')
? directionsResult(fixture.id, 1, complete)
? directionsResult(fixture.id, 1, true)
: visualStatesResult(fixture.id)
return new Response(JSON.stringify({ data: result }))
})
vi.stubGlobal('fetch', fetcher)
await mountStoryboard()
await flushPromises()
expect(button('生成本集导演设计').disabled).toBe(false)
expect(button('生成本集即时状态').disabled).toBe(true)
expect(wrapper!.text()).not.toContain('生成本集导演设计')
expect(wrapper!.text()).not.toContain('生成本集即时状态')
expect(button('补齐项目导演设计').disabled).toBe(false)
expect(button('补齐项目即时状态').disabled).toBe(false)
expect(fetcher.mock.calls.every(([, init]) => init?.method !== 'POST')).toBe(true)
complete = true
button('刷新分镜').click()
await flushPromises()
expect(button('生成本集即时状态').disabled).toBe(false)
await drawerPanel().get('#storyboard-repairs').setValue('0')
await confirmGeneration('生成本集即时状态')
await confirmGeneration('补齐项目即时状态')
const post = fetcher.mock.calls.find(([, init]) => init?.method === 'POST')!
expect(JSON.parse(post[1]!.body as string)).toEqual({ episodeNo: 1, persist: true, maxRepairAttempts: 0 })
expect(drawerPanel().text()).toContain('校验失败,未保存本次生成结果')
expect(JSON.parse(post[1]!.body as string)).toEqual({
concurrency: 2,
force: false,
maxRepairAttempts: 0
})
expect(drawerPanel().text()).toContain('本次存在失败')
expect(drawerPanel().text()).toContain('缺少主体状态')
})
@@ -1291,11 +1299,11 @@ describe('工作台页面交互', () => {
await flushPromises()
expect(wrapper!.text()).toContain('shot-db-1-1')
expect(wrapper!.text()).toContain('数据库暂不可用')
expect(button('生成本集导演设计').disabled).toBe(true)
expect(button('补齐项目导演设计').disabled).toBe(true)
fail = false
button('重试查询').click()
await flushPromises()
expect(button('生成本集导演设计').disabled).toBe(false)
expect(button('补齐项目导演设计').disabled).toBe(false)
})
it('镜头工具按需 GET,切换镜头会废弃旧参考图,未完成设计不能编译规格', async () => {