feat(short-drama-agent-front): 优化项目查询与工作区界面

This commit is contained in:
GJ
2026-09-21 19:36:47 +08:00
parent 227db5450d
commit e6d02a20a7
33 changed files with 439 additions and 513 deletions
@@ -99,7 +99,7 @@ function checkpoint(): Checkpoint {
}
}
/** 提供响应式 checkpoint,模拟轮询更新但不连接后端或启动模型任务。 */
/** 提供响应式 checkpoint,模拟手动刷新但不连接后端或启动模型任务。 */
function mountPage(records = [checkpoint()], episodes = 1): ReturnType<typeof useProjectData> {
const project: ProjectDetail = {
id: 'breakdown-scroll-test',
@@ -341,7 +341,7 @@ describe('拆解页内容滚动', () => {
expect(toolbar.text()).toContain('第二集')
expect(toolbar.get('.breakdown-storyboard-summary').text()).toContain('2 个节拍 · 2 个镜头')
expect(wrapper!.findAll('.beat-section')).toHaveLength(2)
// 轮询删除当前选项时,输入框与正文一起回到仍存在的第一集。
// 刷新删除当前选项时,输入框与正文一起回到仍存在的第一集。
provided.data.value!.checkpoints = [checkpoint()]
await flushPromises()
expect(select.props('value')).toBe(1)
+11 -22
View File
@@ -66,21 +66,14 @@ async function openProject(initialPath: string) {
}
describe('剧本完成前的下游访问限制', () => {
it('进入图库暂停父级轮询,返回其他工作区恢复定时更新', async () => {
it('项目工作区停留期间不重复读取项目详情和工作流记录', async () => {
vi.useFakeTimers()
const detail = vi.spyOn(projectsApi, 'detail').mockResolvedValue(project('manual-gallery', 'completed'))
const detail = vi.spyOn(projectsApi, 'detail').mockResolvedValue(project('manual-project', 'completed'))
const checkpoints = vi.spyOn(projectsApi, 'checkpoints').mockResolvedValue([])
const { router } = await openProject('/projects/manual-gallery/production')
await router.push('/projects/manual-gallery/subject-images')
await flushPromises()
await openProject('/projects/manual-project/production')
await vi.advanceTimersByTimeAsync(30_000)
expect(detail).toHaveBeenCalledTimes(1)
expect(checkpoints).toHaveBeenCalledTimes(1)
await router.push('/projects/manual-gallery/production')
await flushPromises()
await vi.advanceTimersByTimeAsync(6000)
expect(detail).toHaveBeenCalledTimes(2)
expect(checkpoints).toHaveBeenCalledTimes(2)
})
it('图库直接链接只读取一次项目,未完成时可手动刷新解锁', async () => {
@@ -91,7 +84,7 @@ describe('剧本完成前的下游访问限制', () => {
await openProject('/projects/manual-gate/subject-images')
await vi.advanceTimersByTimeAsync(30_000)
expect(detail).toHaveBeenCalledTimes(1)
expect(wrapper!.get('.project-access-gate').text()).not.toContain('状态会自动更新')
expect(wrapper!.get('.project-access-gate').text()).toContain('请手动刷新项目状态')
status = 'completed'
const refresh = wrapper!.findAll('button').find(button => button.text() === '刷新项目状态')!
await refresh.trigger('click')
@@ -123,24 +116,21 @@ describe('剧本完成前的下游访问限制', () => {
}
)
it('轮询完成状态自动解锁;重新变为待审核时撤下下游面板', async () => {
it('未完成项目停留期间不自动解锁,仅在手动刷新后进入下游页面', async () => {
vi.useFakeTimers()
let status: ProjectStatus = 'generating'
vi.spyOn(projectsApi, 'detail').mockImplementation(async id => project(id, status))
vi.spyOn(projectsApi, 'checkpoints').mockResolvedValue([])
const { mounted } = await openProject('/projects/polling/production')
const { mounted } = await openProject('/projects/manual-refresh/production')
expect(mounted).not.toHaveBeenCalled()
status = 'completed'
await vi.advanceTimersByTimeAsync(6000)
await flushPromises()
expect(wrapper!.find('.workspace-probe').exists()).toBe(false)
await wrapper!.get('.project-access-gate button:nth-of-type(2)').trigger('click')
await flushPromises()
expect(wrapper!.get('.workspace-probe').text()).toBe('production')
expect(wrapper!.findAll('.n-menu a')).toHaveLength(8)
status = 'need_review'
await vi.advanceTimersByTimeAsync(6000)
await flushPromises()
expect(wrapper!.find('.workspace-probe').exists()).toBe(false)
expect(wrapper!.get('.project-access-gate').text()).toContain('请先完成剧本创作')
expect(wrapper!.findAll('.n-menu a')).toHaveLength(2)
expect(mounted).toHaveBeenCalledExactlyOnceWith('production')
})
@@ -168,7 +158,7 @@ describe('剧本完成前的下游访问限制', () => {
expect(wrapper!.findAll('.n-menu a')).toHaveLength(1)
})
it('读取失败保持锁定;错误区重试成功后解锁,页头不恢复标签与刷新按钮', async () => {
it('读取失败保持锁定;错误区重试成功后解锁且不恢复项目标题栏', async () => {
const detail = vi
.spyOn(projectsApi, 'detail')
.mockRejectedValueOnce(new Error('项目读取失败'))
@@ -177,8 +167,7 @@ describe('剧本完成前的下游访问限制', () => {
const { mounted } = await openProject('/projects/retry/storyboard')
expect(mounted).not.toHaveBeenCalled()
expect(wrapper!.findAll('.n-menu a')).toHaveLength(2)
expect(wrapper!.find('.project-header .n-button').exists()).toBe(false)
expect(wrapper!.find('.project-header .n-tag').exists()).toBe(false)
expect(wrapper!.find('.project-header').exists()).toBe(false)
await wrapper!.get('.project-notices button').trigger('click')
await flushPromises()
expect(detail).toHaveBeenCalledTimes(2)
@@ -159,7 +159,7 @@ describe('身份图与母版契约', () => {
expect(wrapper.emitted('anchor')).toBeUndefined()
})
it('轮询新增历史不会切回母版或重置横向位置,当前记录移除后才回退', async () => {
it('刷新新增历史不会切回母版或重置横向位置,当前记录移除后才回退', async () => {
const anchor = identityImageFixture({ id: 'anchor' })
const front = identityImageFixture({ id: 'front', viewType: 'front', isAnchor: false })
wrapper = mount(IdentityGallery, {
@@ -133,6 +133,15 @@ async function gallery(query = '') {
}
describe('过期首帧到具体素材定位', () => {
it('没有图片的形态卡片标记为方形占位', async () => {
const { first } = await gallery()
first.images = []
await wrapper!.get('[aria-label="刷新图库"]').trigger('click')
await flushPromises()
const card = wrapper!.findAll('.form-image-card').find(item => item.attributes('data-form-id') === first.id)!
expect(card.get('.asset-image').classes()).toContain('form-image-empty')
})
it('网格与瀑布流切换保留卡片、滚动容器和筛选,不发起额外请求', async () => {
const { fetcher } = await gallery()
const requestCount = fetcher.mock.calls.length