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
-86
View File
@@ -1,86 +0,0 @@
import { effectScope, nextTick, ref } from 'vue'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { usePolling } from '@/composables/usePolling'
afterEach(() => vi.useRealTimers())
describe('异步轮询生命周期', () => {
it('关闭定时刷新后仍支持首次读取、手动刷新及查询目标切换', async () => {
vi.useFakeTimers()
const key = ref('first')
const loader = vi.fn<(id: string) => Promise<string>>(async id => id)
const scope = effectScope()
const query = scope.run(() => usePolling(key, loader, false))!
await Promise.resolve()
expect(query.data.value).toBe('first')
await vi.advanceTimersByTimeAsync(30_000)
expect(loader).toHaveBeenCalledTimes(1)
await query.refresh()
expect(loader).toHaveBeenCalledTimes(2)
key.value = 'second'
await nextTick()
await Promise.resolve()
expect(query.data.value).toBe('second')
await vi.advanceTimersByTimeAsync(30_000)
expect(loader).toHaveBeenCalledTimes(3)
scope.stop()
})
it('响应式暂停清除旧定时器,恢复只创建一个轮询链', async () => {
vi.useFakeTimers()
const interval = ref<number | false>(1000)
const loader = vi.fn<() => Promise<string>>(async () => '数据')
const scope = effectScope()
scope.run(() => usePolling(ref('p'), loader, interval))
await Promise.resolve()
interval.value = false
await nextTick()
await vi.advanceTimersByTimeAsync(5000)
expect(loader).toHaveBeenCalledTimes(1)
interval.value = 1000
await nextTick()
await vi.advanceTimersByTimeAsync(2000)
expect(loader).toHaveBeenCalledTimes(3)
scope.stop()
await vi.advanceTimersByTimeAsync(5000)
expect(loader).toHaveBeenCalledTimes(3)
})
it('切换项目忽略旧响应,卸载时不再排队请求', async () => {
vi.useFakeTimers()
const key = ref('first')
const finish = new Map<string, (value: string) => void>()
const loader = vi.fn<(id: string) => Promise<string>>(
(id: string) =>
new Promise<string>(resolve => {
finish.set(id, resolve)
})
)
const scope = effectScope()
const query = scope.run(() => usePolling(key, loader, 1000))!
key.value = 'second'
await nextTick()
finish.get('second')!('新项目')
await Promise.resolve()
finish.get('first')!('旧项目')
await Promise.resolve()
expect(query.data.value).toBe('新项目')
scope.stop()
await vi.advanceTimersByTimeAsync(10_000)
expect(loader).toHaveBeenCalledTimes(2)
})
it('失败时保留已有数据,并向页面显示错误', async () => {
const scope = effectScope()
const loader = vi
.fn<() => Promise<string>>()
.mockResolvedValueOnce('上次成功数据')
.mockRejectedValueOnce(new Error('连接已断开'))
const query = scope.run(() => usePolling(ref('p'), loader))!
await Promise.resolve()
await query.refresh()
expect(query.data.value).toBe('上次成功数据')
expect(query.error.value).toBe('连接已断开')
scope.stop()
})
})
+73
View File
@@ -0,0 +1,73 @@
import { effectScope, nextTick, ref } from 'vue'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { useQuery } from '@/composables/useQuery'
afterEach(() => vi.useRealTimers())
/** 测试中尚未完成的查询请求。 */
interface PendingRequest {
/** 完成当前查询。 */
resolve: (value: string) => void
/** 当前查询的取消信号。 */
signal: AbortSignal
}
describe('异步查询生命周期', () => {
it('页面停留期间不重复请求,仍支持首次读取、手动刷新及查询目标切换', async () => {
vi.useFakeTimers()
const key = ref('first')
const loader = vi.fn<(id: string) => Promise<string>>(async id => id)
const scope = effectScope()
const query = scope.run(() => useQuery(key, loader))!
await Promise.resolve()
expect(query.data.value).toBe('first')
await vi.advanceTimersByTimeAsync(30_000)
expect(loader).toHaveBeenCalledTimes(1)
await query.refresh()
expect(loader).toHaveBeenCalledTimes(2)
key.value = 'second'
await nextTick()
await Promise.resolve()
expect(query.data.value).toBe('second')
await vi.advanceTimersByTimeAsync(30_000)
expect(loader).toHaveBeenCalledTimes(3)
scope.stop()
})
it('切换目标取消旧请求并忽略迟到响应', async () => {
const key = ref('first')
const requests = new Map<string, PendingRequest>()
const loader = vi.fn<(id: string, signal: AbortSignal) => Promise<string>>(
(id, signal) =>
new Promise<string>(resolve => {
requests.set(id, { resolve, signal })
})
)
const scope = effectScope()
const query = scope.run(() => useQuery(key, loader))!
key.value = 'second'
await nextTick()
expect(requests.get('first')!.signal.aborted).toBe(true)
requests.get('second')!.resolve('新项目')
await Promise.resolve()
requests.get('first')!.resolve('旧项目')
await Promise.resolve()
expect(query.data.value).toBe('新项目')
scope.stop()
expect(requests.get('second')!.signal.aborted).toBe(true)
})
it('失败时保留已有数据,并向页面显示错误', async () => {
const scope = effectScope()
const loader = vi
.fn<() => Promise<string>>()
.mockResolvedValueOnce('上次成功数据')
.mockRejectedValueOnce(new Error('连接已断开'))
const query = scope.run(() => useQuery(ref('p'), loader))!
await Promise.resolve()
await query.refresh()
expect(query.data.value).toBe('上次成功数据')
expect(query.error.value).toBe('连接已断开')
scope.stop()
})
})