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 @@ const filtered = computed(() =>
)
)
/** 深链接仅在目标 ID 变化时选择,不因目录轮询把用户切回原主体。 */
/** 深链接仅在目标 ID 变化时选择,不因目录刷新把用户切回原主体。 */
const linkedSubjectId = computed(
() => subjects.value.find(item => item.id === route.query.subjectId || item.ref === route.query.subjectRef)?.id
)
@@ -6,7 +6,7 @@ import { NButton, NCheckbox, NInput } from 'naive-ui'
import { computed, reactive, ref, watch } from 'vue'
import type { SaveIdentityInput, SubjectIdentity } from '../types'
/** 身份文本草稿不随询覆盖;父页面切换主体或保存成功后重建编辑器。 */
/** 身份文本草稿不随询覆盖;父页面切换主体或保存成功后重建编辑器。 */
const props = defineProps<{ identity: SubjectIdentity | null; disabled: boolean; module: string }>()
const emit = defineEmits<{ save: [input: SaveIdentityInput]; dirty: [value: boolean] }>()
const form = reactive({ description: '', generationPrompt: '', isLocked: false })
@@ -53,7 +53,7 @@ watch(url, () => {
imageFailed.value = false
})
/** 仅读取正式主体的身份图库;切项目、筛选或刷新时中止旧请求,不轮询每个缩略图。 */
/** 仅读取正式主体的身份图库;切项目、筛选或刷新时中止旧请求。 */
watch(
() => [props.projectId, props.subjectId, props.identityId, props.source, props.refreshKey, visible.value],
async (_value, _oldValue, onCleanup) => {
@@ -1,5 +1,5 @@
import { computed, ref, watch } from 'vue'
import { usePolling } from '../../composables/usePolling'
import { useQuery } from '../../composables/useQuery'
import { visualStyleApi } from '../visual-style'
import { subjectImagesApi } from '../subject-images/api'
import { hasRunningImages } from '../subject-images/model'
@@ -30,58 +30,42 @@ export function useSubjectIdentity() {
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 = useQuery(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))
}
},
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
)
)
throw new Error('主体目录与当前项目不匹配,请刷新后重试。')
return {
subjects: groupIdentitySubjects(forms),
running: forms.some(form => hasRunningImages(form.images))
}
})
const styleQuery = useQuery(projectId, async (id, signal) => {
const style = await visualStyleApi.get(id, signal)
if (style && style.projectId !== id) throw new Error('视觉风格与当前项目不匹配。')
return { style }
})
const castingQuery = useQuery(projectId, async (id, signal) => {
const readiness = await subjectIdentityApi.castingReadiness(id, signal)
return readiness
})
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 }
},
false
)
const detail = useQuery(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 identity = computed(() => detail.data.value?.identity ?? null)
const images = computed(() => detail.data.value?.images ?? [])
const castingItem = computed(() =>