From fc91908cf92956c51d5ea92b6e43b2b0b43bb5d2 Mon Sep 17 00:00:00 2001 From: long <452591453@qq.com> Date: Sat, 10 Oct 2026 15:03:06 +0800 Subject: [PATCH] fix(followup-audio): preserve roles chosen before first audio file --- .../components/FollowupAudioPanel.vue | 18 ++++++++-- admin/tests/followup-audio.test.cjs | 36 ++++++++++++++++++- 2 files changed, 51 insertions(+), 3 deletions(-) diff --git a/admin/src/views/tcm/diagnosis/components/FollowupAudioPanel.vue b/admin/src/views/tcm/diagnosis/components/FollowupAudioPanel.vue index a5e70af45..41a6be0d9 100644 --- a/admin/src/views/tcm/diagnosis/components/FollowupAudioPanel.vue +++ b/admin/src/views/tcm/diagnosis/components/FollowupAudioPanel.vue @@ -59,6 +59,7 @@

声道不等于已核实的说话人身份。角色标注不自动重新提炼;变更后保存草稿会取消全部采纳,须重新核对。

+

当前角色已确认,无需再次选择;只有左右标错时才需修改。

角色标注尚未保存,请先保存草稿,再逐项核对。

@@ -292,7 +293,11 @@ async function backToList() { stop(); resetRoles(); selectedId.value = 0; detail.value = null; reviewItems.value = []; snapshot.value = '[]'; detailSections.value = []; errorMessage.value = '' await refresh() } -function openUpload() { if (canUpload.value && !busy.value && !createUncertain.value) uploadVisible.value = true } +function openUpload() { + if (!canUpload.value || busy.value || createUncertain.value) return + if (!file.value) uploadChannelRoles.value = 'unconfirmed' + uploadVisible.value = true +} function closeUpload(done?: () => void) { if (uploading.value) return; uploadVisible.value = false; done?.() } function statusType(status: string) { if (status === 'failed' || status === 'needs_reconciliation') return 'danger' @@ -300,7 +305,16 @@ function statusType(status: string) { if (status === 'review') return 'warning' return 'info' } -function chooseFile(event: Event) { uploadChannelRoles.value = 'unconfirmed'; file.value = (event.target as HTMLInputElement).files?.[0] || null; uploadProgress.value = 0; uploadStage.value = '' } +function chooseFile(event: Event) { + const nextFile = (event.target as HTMLInputElement).files?.[0] || null + // Cancelling the picker or selecting the first file must not discard a role chosen earlier. + if (!nextFile) return + if (file.value && nextFile !== file.value) { + uploadChannelRoles.value = 'unconfirmed' + ElMessage.warning('录音已更换,请重新核对本次录音的声道角色') + } + file.value = nextFile; uploadProgress.value = 0; uploadStage.value = '' +} async function submitUpload() { if (busy.value || createUncertain.value) return const problem = uploadIssue(file.value, recordedAt.value, modelKey.value, props.capabilities, props.viewOnly) diff --git a/admin/tests/followup-audio.test.cjs b/admin/tests/followup-audio.test.cjs index 6a5af3875..e520e139f 100644 --- a/admin/tests/followup-audio.test.cjs +++ b/admin/tests/followup-audio.test.cjs @@ -1512,10 +1512,44 @@ test('upload sends explicitly selected pre-extraction roles and clears them on p }) -test('a new recording never inherits the previous file role assignment', async () => { +test('a replacement recording never inherits the previous file role assignment', async () => { const f = setupPanel(); await settle() + f.panel.file.value = new File(['previous'], 'previous.wav') f.panel.uploadChannelRoles.value = 'left_service' f.panel.chooseFile({ target: { files: [new File(['synthetic'], 'new.wav')] } }) assert.equal(f.panel.uploadChannelRoles.value, 'unconfirmed') f.dispose() }) + +for (const order of ['role-first', 'file-first']) test(`upload ${order} preserves roles through create and initial review without reselection`, async () => { + let createdRole = 'unconfirmed' + const f = setupPanel({ + followupAudioCreate: async payload => { createdRole = payload.channel_roles; return { task_id: 2, status: 'queued' } }, + followupAudioDetail: async id => stereoTask({ id, channel_roles: createdRole }) + }) + await settle(); f.panel.openUpload() + if (order === 'role-first') f.panel.uploadChannelRoles.value = 'left_service' + f.panel.chooseFile({ target: { files: [new File(['synthetic'], 'first.wav')] } }) + if (order === 'file-first') f.panel.uploadChannelRoles.value = 'left_service' + f.panel.recordedAt.value = '2026-09-29 09:00:00' + await f.panel.submitUpload() + assert.equal(createdRole, 'left_service') + assert.equal(f.panel.channelRoles.value, 'left_service') + assert.equal(f.panel.savedChannelRoles.value, 'left_service') + assert.equal(f.panel.roleDirty.value, false) + assert.doesNotMatch(f.panel.applyIssue.value, /声道角色/) + f.dispose() +}) + + +test('cancelled file selection keeps current file and roles; new upload starts unconfirmed', async () => { + const f = setupPanel(); await settle(); f.panel.openUpload() + const recording = new File(['synthetic'], 'existing.wav') + f.panel.file.value = recording; f.panel.uploadChannelRoles.value = 'left_service' + f.panel.chooseFile({ target: { files: [] } }) + assert.equal(f.panel.file.value, recording) + assert.equal(f.panel.uploadChannelRoles.value, 'left_service') + f.panel.file.value = null; f.panel.closeUpload(); f.panel.openUpload() + assert.equal(f.panel.uploadChannelRoles.value, 'unconfirmed') + f.dispose() +})