feat(aihr): complete case learning review loop

This commit is contained in:
2026-07-14 23:41:59 +08:00
parent 4fc394e993
commit 0bfaee7d22
16 changed files with 296 additions and 20 deletions
+12
View File
@@ -38,6 +38,8 @@ export type CaseDetailResponse = {
summary: string;
mediaUrl?: string;
mediaOssId?: number | null;
learningPoints: string[];
supervisorComment: string;
};
export type CaseUploadResponse = {
@@ -52,6 +54,7 @@ export type CaseOrganizeResponse = {
caseId: string;
summary: CaseSummaryItem[];
tags: CaseTag[];
learningPoints: string[];
aiSummary: string;
source: string;
};
@@ -102,3 +105,12 @@ export function getCaseDetail(caseId: string): Promise<ApiResult<CaseDetailRespo
method: 'get'
});
}
export function reviewKnowledgeCase(caseId: string, comment: string): Promise<ApiResult<void>> {
return request({
url: `/api/knowledge/case/records/${encodeURIComponent(caseId)}/review`,
method: 'post',
data: { comment },
headers: { repeatSubmit: false }
});
}
+45 -1
View File
@@ -144,11 +144,24 @@
<strong>案例摘要</strong>
<p>{{ caseDetail.summary || '暂无案例摘要' }}</p>
</div>
<div v-if="caseDetail.learningPoints?.length" class="detail-block">
<strong>学习点</strong>
<ul class="learning-points">
<li v-for="point in caseDetail.learningPoints" :key="point">{{ point }}</li>
</ul>
</div>
<div class="detail-block">
<strong>主管点评</strong>
<el-input v-model="supervisorComment" type="textarea" :rows="3" maxlength="1000" show-word-limit placeholder="记录本案例可复用的带教建议" />
<div class="detail-actions">
<el-button type="danger" :loading="reviewSaving" :disabled="!caseDetail.caseId" @click="submitSupervisorReview">保存点评</el-button>
</div>
</div>
<div v-if="caseDetail.mediaUrl" class="detail-block">
<strong>原始音频</strong>
<audio class="case-audio" :src="caseDetail.mediaUrl" controls preload="metadata" />
</div>
<el-alert title="当前版本提供转写、摘要和状态详情;视频样片、学习点与主管点评待正式媒体和业务字段接入。" type="info" :closable="false" />
<el-alert title="当前版本提供转写、摘要、学习点和主管点评;视频样片仍待正式媒体资源接入。" type="info" :closable="false" />
</template>
<el-empty v-else description="暂无详情" />
</el-dialog>
@@ -165,6 +178,7 @@ import {
getCaseDetail,
getCaseRecords,
organizeKnowledgeCase,
reviewKnowledgeCase,
uploadCaseAudio,
type CaseDetailResponse,
type CaseRecord,
@@ -192,6 +206,8 @@ const recordsLoading = ref(true);
const recordsFallback = ref(false);
const detailVisible = ref(false);
const caseDetail = ref<CaseDetailResponse | null>(null);
const supervisorComment = ref('');
const reviewSaving = ref(false);
const projectExtOrgId = ref('');
const projectOptions = ref<OrgProjectOption[]>([]);
const projectsLoading = ref(false);
@@ -301,12 +317,27 @@ async function openCaseDetail(record: CaseRecord) {
try {
const { data } = await getCaseDetail(record.caseId);
caseDetail.value = data;
supervisorComment.value = data.supervisorComment || '';
detailVisible.value = true;
} catch {
ElMessage.error('案例详情加载失败');
}
}
async function submitSupervisorReview() {
if (!caseDetail.value || reviewSaving.value) return;
reviewSaving.value = true;
try {
await reviewKnowledgeCase(caseDetail.value.caseId, supervisorComment.value.trim());
caseDetail.value.supervisorComment = supervisorComment.value.trim();
ElMessage.success('主管点评已保存');
} catch {
ElMessage.error('主管点评保存失败');
} finally {
reviewSaving.value = false;
}
}
async function handleAudioPicked(event: Event) {
const input = event.target as HTMLInputElement;
const file = input.files?.[0];
@@ -713,6 +744,19 @@ onMounted(() => {
}
}
.learning-points {
margin: 8px 0 0;
padding-left: 20px;
color: #445064;
line-height: 1.65;
}
.detail-actions {
display: flex;
justify-content: flex-end;
margin-top: 10px;
}
.case-audio {
display: block;
width: 100%;