feat: 关闭已有刷新页面的定时轮询
This commit is contained in:
@@ -40,15 +40,20 @@ const props = defineProps<{
|
||||
}>()
|
||||
const emit = defineEmits<{ changed: [] }>()
|
||||
const key = computed(() => props.shot.shotId)
|
||||
const query = usePolling(key, async (shotId, signal) => {
|
||||
const [keyframes, videos] = await Promise.all([
|
||||
productionApi.listKeyframes(shotId, signal),
|
||||
productionApi.listVideos(shotId, signal)
|
||||
])
|
||||
if (keyframes.some(item => item.shotId !== shotId) || videos.some(item => item.shotId !== shotId))
|
||||
throw new Error('资产记录与当前镜头不匹配,请刷新后重试。')
|
||||
return { keyframes, videos }
|
||||
})
|
||||
/** 单镜头资产已有刷新入口,不再定时轮询。 */
|
||||
const query = usePolling(
|
||||
key,
|
||||
async (shotId, signal) => {
|
||||
const [keyframes, videos] = await Promise.all([
|
||||
productionApi.listKeyframes(shotId, signal),
|
||||
productionApi.listVideos(shotId, signal)
|
||||
])
|
||||
if (keyframes.some(item => item.shotId !== shotId) || videos.some(item => item.shotId !== shotId))
|
||||
throw new Error('资产记录与当前镜头不匹配,请刷新后重试。')
|
||||
return { keyframes, videos }
|
||||
},
|
||||
false
|
||||
)
|
||||
const keyframes = computed(() => query.data.value?.keyframes ?? [])
|
||||
const videos = computed(() => query.data.value?.videos ?? [])
|
||||
const currentKeyframe = computed(() => primaryKeyframe(keyframes.value))
|
||||
|
||||
@@ -42,20 +42,25 @@ export function useProduction() {
|
||||
0
|
||||
)
|
||||
const queryKey = computed(() => JSON.stringify([id.value, episodeNo.value, force.value]))
|
||||
const query = usePolling(queryKey, async (key, signal) => {
|
||||
const [projectId, number, overwrite] = JSON.parse(key) as [string, number, boolean]
|
||||
if (!projectId || !number) return null
|
||||
const [directions, prompts, keyframes, videos, videoStatus] = await Promise.all([
|
||||
storyboardApi.directions(projectId, number, signal),
|
||||
productionApi.promptReadiness(projectId, overwrite, signal),
|
||||
productionApi.keyframeReadiness(projectId, overwrite, signal),
|
||||
productionApi.videoReadiness(projectId, overwrite, signal),
|
||||
productionApi.projectVideoStatus(projectId, signal)
|
||||
])
|
||||
if (directions.projectId !== projectId || directions.episodeNo !== number)
|
||||
throw new Error('镜头生产查询返回了不匹配的项目或剧集,请刷新后重试。')
|
||||
return { directions, prompts, keyframes, videos, videoStatus }
|
||||
})
|
||||
/** 生产页已有刷新按钮,就绪状态改为手动更新。 */
|
||||
const query = usePolling(
|
||||
queryKey,
|
||||
async (key, signal) => {
|
||||
const [projectId, number, overwrite] = JSON.parse(key) as [string, number, boolean]
|
||||
if (!projectId || !number) return null
|
||||
const [directions, prompts, keyframes, videos, videoStatus] = await Promise.all([
|
||||
storyboardApi.directions(projectId, number, signal),
|
||||
productionApi.promptReadiness(projectId, overwrite, signal),
|
||||
productionApi.keyframeReadiness(projectId, overwrite, signal),
|
||||
productionApi.videoReadiness(projectId, overwrite, signal),
|
||||
productionApi.projectVideoStatus(projectId, signal)
|
||||
])
|
||||
if (directions.projectId !== projectId || directions.episodeNo !== number)
|
||||
throw new Error('镜头生产查询返回了不匹配的项目或剧集,请刷新后重试。')
|
||||
return { directions, prompts, keyframes, videos, videoStatus }
|
||||
},
|
||||
false
|
||||
)
|
||||
const source = computed(() => sourceEpisodes.value.find(item => item.episodeNo === episodeNo.value))
|
||||
const shots = computed(() => mergeDesignedShots(query.data.value?.directions ?? null, null, source.value))
|
||||
const shot = computed(() => shots.value.find(item => item.shotId === selectedShot.value) ?? shots.value[0])
|
||||
|
||||
@@ -13,7 +13,8 @@ import CreateProjectDialog from './components/CreateProjectDialog.vue'
|
||||
|
||||
/** 项目表格使用固定表头与独立滚动;搜索筛选仍作用于真实接口数据。 */
|
||||
const router = useRouter()
|
||||
const query = usePolling(ref('projects'), (_, signal) => projectsApi.list(signal), 12_000)
|
||||
/** 列表已有刷新按钮,只在进入、筛选切换和手动刷新时读取。 */
|
||||
const query = usePolling(ref('projects'), (_, signal) => projectsApi.list(signal), false)
|
||||
const search = ref('')
|
||||
const filter = ref('all')
|
||||
const page = ref(1)
|
||||
|
||||
@@ -38,23 +38,28 @@ export function useStoryboard() {
|
||||
0
|
||||
)
|
||||
const queryKey = computed(() => JSON.stringify([id.value, episodeNo.value]))
|
||||
const query = usePolling(queryKey, async (key, signal) => {
|
||||
const [projectId, number] = JSON.parse(key) as [string, number]
|
||||
if (!projectId || !number) return null
|
||||
const [directions, visualStates] = await Promise.all([
|
||||
storyboardApi.directions(projectId, number, signal),
|
||||
storyboardApi.visualStates(projectId, number, signal)
|
||||
])
|
||||
if (
|
||||
directions.projectId !== projectId ||
|
||||
visualStates.projectId !== projectId ||
|
||||
directions.episodeNo !== number ||
|
||||
visualStates.episodeNo !== number
|
||||
) {
|
||||
throw new Error('分镜查询返回了不匹配的项目或剧集,请刷新后重试。')
|
||||
}
|
||||
return { directions, visualStates }
|
||||
})
|
||||
/** 分镜页已有刷新按钮,不再定时轮询当前剧集。 */
|
||||
const query = usePolling(
|
||||
queryKey,
|
||||
async (key, signal) => {
|
||||
const [projectId, number] = JSON.parse(key) as [string, number]
|
||||
if (!projectId || !number) return null
|
||||
const [directions, visualStates] = await Promise.all([
|
||||
storyboardApi.directions(projectId, number, signal),
|
||||
storyboardApi.visualStates(projectId, number, signal)
|
||||
])
|
||||
if (
|
||||
directions.projectId !== projectId ||
|
||||
visualStates.projectId !== projectId ||
|
||||
directions.episodeNo !== number ||
|
||||
visualStates.episodeNo !== number
|
||||
) {
|
||||
throw new Error('分镜查询返回了不匹配的项目或剧集,请刷新后重试。')
|
||||
}
|
||||
return { directions, visualStates }
|
||||
},
|
||||
false
|
||||
)
|
||||
const data = query.data
|
||||
const source = computed(() => sourceEpisodes.value.find(item => item.episodeNo === episodeNo.value))
|
||||
const shots = computed(() =>
|
||||
|
||||
@@ -82,7 +82,7 @@ function save() {
|
||||
>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3">
|
||||
<span class="text-xs text-muted">{{
|
||||
dirty ? '有未保存修改,自动刷新不会覆盖草稿。' : '图片以已保存的身份提示词为准。'
|
||||
dirty ? '有未保存修改,刷新不会覆盖草稿。' : '图片以已保存的身份提示词为准。'
|
||||
}}</span
|
||||
><NButton :disabled="disabled" type="primary" attr-type="submit">保存主体身份</NButton>
|
||||
</div>
|
||||
|
||||
@@ -30,39 +30,59 @@ export function useSubjectIdentity() {
|
||||
const concurrency = ref(3)
|
||||
const candidateLimit = ref(3)
|
||||
const force = ref(false)
|
||||
const catalog = usePolling(projectId, async (id, signal) => {
|
||||
const forms = await subjectImagesApi.listForms(id, signal)
|
||||
if (
|
||||
forms.some(
|
||||
form => form.subject.projectId !== id || form.images.some(image => image.subjectFormId !== form.id)
|
||||
/** 目录与身份已有刷新按钮,查询只在进入、切换和手动刷新时读取。 */
|
||||
const catalog = usePolling(
|
||||
projectId,
|
||||
async (id, signal) => {
|
||||
const forms = await subjectImagesApi.listForms(id, signal)
|
||||
if (
|
||||
forms.some(
|
||||
form => form.subject.projectId !== id || form.images.some(image => image.subjectFormId !== form.id)
|
||||
)
|
||||
)
|
||||
)
|
||||
throw new Error('主体目录与当前项目不匹配,请刷新后重试。')
|
||||
return { subjects: groupIdentitySubjects(forms), running: forms.some(form => hasRunningImages(form.images)) }
|
||||
})
|
||||
const styleQuery = usePolling(projectId, async (id, signal) => {
|
||||
const style = await visualStyleApi.get(id, signal)
|
||||
if (style && style.projectId !== id) throw new Error('视觉风格与当前项目不匹配。')
|
||||
return { style }
|
||||
})
|
||||
const castingQuery = usePolling(projectId, async (id, signal) => {
|
||||
const readiness = await subjectIdentityApi.castingReadiness(id, signal)
|
||||
return readiness
|
||||
})
|
||||
throw new Error('主体目录与当前项目不匹配,请刷新后重试。')
|
||||
return {
|
||||
subjects: groupIdentitySubjects(forms),
|
||||
running: forms.some(form => hasRunningImages(form.images))
|
||||
}
|
||||
},
|
||||
false
|
||||
)
|
||||
const styleQuery = usePolling(
|
||||
projectId,
|
||||
async (id, signal) => {
|
||||
const style = await visualStyleApi.get(id, signal)
|
||||
if (style && style.projectId !== id) throw new Error('视觉风格与当前项目不匹配。')
|
||||
return { style }
|
||||
},
|
||||
false
|
||||
)
|
||||
const castingQuery = usePolling(
|
||||
projectId,
|
||||
async (id, signal) => {
|
||||
const readiness = await subjectIdentityApi.castingReadiness(id, signal)
|
||||
return readiness
|
||||
},
|
||||
false
|
||||
)
|
||||
const subjects = computed(() =>
|
||||
mergeCastingSubjects(catalog.data.value?.subjects ?? [], castingQuery.data.value?.items ?? [], projectId.value)
|
||||
)
|
||||
const subject = computed(() => subjects.value.find(item => item.id === selectedId.value))
|
||||
const selectionKey = computed(() => subject.value?.id ?? '')
|
||||
const detail = usePolling(selectionKey, async (id, signal) => {
|
||||
if (!id) return null
|
||||
const identity = await subjectIdentityApi.get(id, signal)
|
||||
if (!identity) return { identity: null, images: [] as IdentityImage[] }
|
||||
assertIdentity(identity, id)
|
||||
const images = await subjectIdentityApi.listImages(id, signal)
|
||||
if (images.some(image => image.identityId !== identity.id)) throw new Error('身份图片与当前主体不匹配。')
|
||||
return { identity, images }
|
||||
})
|
||||
const detail = usePolling(
|
||||
selectionKey,
|
||||
async (id, signal) => {
|
||||
if (!id) return null
|
||||
const identity = await subjectIdentityApi.get(id, signal)
|
||||
if (!identity) return { identity: null, images: [] as IdentityImage[] }
|
||||
assertIdentity(identity, id)
|
||||
const images = await subjectIdentityApi.listImages(id, signal)
|
||||
if (images.some(image => image.identityId !== identity.id)) throw new Error('身份图片与当前主体不匹配。')
|
||||
return { identity, images }
|
||||
},
|
||||
false
|
||||
)
|
||||
const identity = computed(() => detail.data.value?.identity ?? null)
|
||||
const images = computed(() => detail.data.value?.images ?? [])
|
||||
const castingItem = computed(() =>
|
||||
|
||||
@@ -15,11 +15,16 @@ import StyleImages from './components/StyleImages.vue'
|
||||
|
||||
/** 项目视觉风格工作区:文本编辑、锁定和风格图记录分开管理。 */
|
||||
const { projectId, blocked } = useProjectMutationGuard()
|
||||
const query = usePolling(projectId, async (id, signal) => {
|
||||
const style = await visualStyleApi.get(id, signal)
|
||||
assertProject(style, id)
|
||||
return { style }
|
||||
})
|
||||
/** 风格页已有刷新按钮,不再定时轮询。 */
|
||||
const query = usePolling(
|
||||
projectId,
|
||||
async (id, signal) => {
|
||||
const style = await visualStyleApi.get(id, signal)
|
||||
assertProject(style, id)
|
||||
return { style }
|
||||
},
|
||||
false
|
||||
)
|
||||
const style = computed(() => query.data.value?.style ?? null)
|
||||
const disabled = computed(() => blocked.value || !!query.error.value || !query.data.value)
|
||||
const editorRevision = ref(0)
|
||||
@@ -143,7 +148,7 @@ function removeImage(image: VisualStyleImage) {
|
||||
>{{ query.error.value }} 请确认后端 dev 已更新并重启;查询失败不等同于尚未创建。
|
||||
</NAlert>
|
||||
<p v-if="dirty" class="mb-4 text-xs text-muted">
|
||||
有未保存修改,AI 重生成暂不可用。自动刷新不会覆盖编辑内容;离开页面不会自动保存。
|
||||
有未保存修改,AI 重生成暂不可用。刷新不会覆盖编辑内容;离开页面不会自动保存。
|
||||
</p>
|
||||
<DetailDisclosure title="风格生成规则" class="mb-5">
|
||||
AI
|
||||
|
||||
@@ -12,7 +12,8 @@ import type { Checkpoint } from './types'
|
||||
const props = defineProps<{ projectId: string; checkpoints: Checkpoint[] }>()
|
||||
const open = defineModel<boolean>('open', { default: false })
|
||||
const key = computed(() => (open.value ? props.projectId : ''))
|
||||
const query = usePolling(key, (id, signal) => (id ? loadWorkflowDiagnostics(id, signal) : Promise.resolve(null)), 12000)
|
||||
/** 诊断弹窗已有刷新按钮,打开后只读取一次。 */
|
||||
const query = usePolling(key, (id, signal) => (id ? loadWorkflowDiagnostics(id, signal) : Promise.resolve(null)), false)
|
||||
const tab = ref('timeline')
|
||||
const workflow = ref('all')
|
||||
const search = ref('')
|
||||
|
||||
Reference in New Issue
Block a user