fix(aihr): protect case media playback

This commit is contained in:
2026-07-15 01:23:41 +08:00
parent 05ba1a249b
commit 09c3cbecb0
10 changed files with 146 additions and 31 deletions
+8
View File
@@ -106,6 +106,14 @@ export function getCaseDetail(caseId: string): Promise<ApiResult<CaseDetailRespo
});
}
export function getCaseMedia(caseId: string): Promise<Blob> {
return request({
url: `/api/knowledge/case/records/${encodeURIComponent(caseId)}/media`,
method: 'get',
responseType: 'blob'
});
}
export function reviewKnowledgeCase(caseId: string, comment: string): Promise<ApiResult<void>> {
return request({
url: `/api/knowledge/case/records/${encodeURIComponent(caseId)}/review`,
+29 -4
View File
@@ -133,7 +133,7 @@
</el-table>
</section>
<el-dialog v-model="detailVisible" title="案例详情" width="720px">
<el-dialog v-model="detailVisible" title="案例详情" width="720px" @closed="releaseCaseMediaUrl">
<template v-if="caseDetail">
<div class="detail-meta">{{ caseDetail.time }} · {{ caseDetail.owner }} · {{ caseDetail.status }} · {{ caseDetail.fileName }}</div>
<div class="detail-block">
@@ -157,10 +157,12 @@
<el-button type="danger" :loading="reviewSaving" :disabled="!caseDetail.caseId" @click="submitSupervisorReview">保存点评</el-button>
</div>
</div>
<div v-if="caseDetail.mediaUrl" class="detail-block">
<div v-if="caseMediaUrl" class="detail-block">
<strong>原始音频</strong>
<audio class="case-audio" :src="caseDetail.mediaUrl" controls preload="metadata" />
<audio class="case-audio" :src="caseMediaUrl" controls preload="metadata" />
</div>
<div v-else-if="caseMediaLoading" class="detail-block detail-muted">音频加载中…</div>
<div v-else-if="caseDetail.mediaOssId" class="detail-block detail-muted">音频加载失败,请重试或联系管理员</div>
<el-alert title="当前版本提供转写、摘要、学习点和主管点评;视频样片仍待正式媒体资源接入。" type="info" :closable="false" />
</template>
<el-empty v-else description="暂无详情" />
@@ -171,11 +173,12 @@
<script setup name="KnowledgeCases" lang="ts">
import { DocumentChecked, FolderChecked, Microphone, Tickets } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import { computed, onMounted, ref } from 'vue';
import { computed, onBeforeUnmount, onMounted, ref } from 'vue';
import { useRouter } from 'vue-router';
import {
curateKnowledgeCase,
getCaseDetail,
getCaseMedia,
getCaseRecords,
organizeKnowledgeCase,
reviewKnowledgeCase,
@@ -206,6 +209,8 @@ const recordsLoading = ref(true);
const recordsFallback = ref(false);
const detailVisible = ref(false);
const caseDetail = ref<CaseDetailResponse | null>(null);
const caseMediaUrl = ref('');
const caseMediaLoading = ref(false);
const supervisorComment = ref('');
const reviewSaving = ref(false);
const projectExtOrgId = ref('');
@@ -314,16 +319,36 @@ async function loadCaseRecords() {
async function openCaseDetail(record: CaseRecord) {
if (!record.caseId) return;
releaseCaseMediaUrl();
caseMediaLoading.value = true;
try {
const { data } = await getCaseDetail(record.caseId);
caseDetail.value = data;
if (data.mediaOssId) {
try {
caseMediaUrl.value = URL.createObjectURL(await getCaseMedia(record.caseId));
} catch {
caseMediaUrl.value = '';
}
}
supervisorComment.value = data.supervisorComment || '';
detailVisible.value = true;
} catch {
ElMessage.error('案例详情加载失败');
} finally {
caseMediaLoading.value = false;
}
}
function releaseCaseMediaUrl() {
if (caseMediaUrl.value.startsWith('blob:')) {
URL.revokeObjectURL(caseMediaUrl.value);
}
caseMediaUrl.value = '';
}
onBeforeUnmount(releaseCaseMediaUrl);
async function submitSupervisorReview() {
if (!caseDetail.value || reviewSaving.value) return;
reviewSaving.value = true;