fix(frontend): load persisted case records
This commit is contained in:
@@ -17,6 +17,7 @@ export type CaseSummaryItem = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type CaseRecord = {
|
export type CaseRecord = {
|
||||||
|
caseId: string;
|
||||||
title: string;
|
title: string;
|
||||||
type: string;
|
type: string;
|
||||||
status: string;
|
status: string;
|
||||||
@@ -25,6 +26,18 @@ export type CaseRecord = {
|
|||||||
summary: string;
|
summary: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type CaseDetailResponse = {
|
||||||
|
caseId: string;
|
||||||
|
fileName: string;
|
||||||
|
title: string;
|
||||||
|
type: string;
|
||||||
|
status: string;
|
||||||
|
time: string;
|
||||||
|
owner: string;
|
||||||
|
transcript: string;
|
||||||
|
summary: string;
|
||||||
|
};
|
||||||
|
|
||||||
export type CaseUploadResponse = {
|
export type CaseUploadResponse = {
|
||||||
caseId: string;
|
caseId: string;
|
||||||
fileName: string;
|
fileName: string;
|
||||||
@@ -73,3 +86,17 @@ export function curateKnowledgeCase(data: { caseId: string; criteria: string; li
|
|||||||
headers: { repeatSubmit: false }
|
headers: { repeatSubmit: false }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCaseRecords(limit = 20): Promise<ApiResult<CaseRecord[]>> {
|
||||||
|
return request({
|
||||||
|
url: `/api/knowledge/case/records?limit=${limit}`,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCaseDetail(caseId: string): Promise<ApiResult<CaseDetailResponse>> {
|
||||||
|
return request({
|
||||||
|
url: `/api/knowledge/case/records/${encodeURIComponent(caseId)}`,
|
||||||
|
method: 'get'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|||||||
@@ -87,17 +87,48 @@
|
|||||||
<el-table-column prop="time" label="时间" width="130" />
|
<el-table-column prop="time" label="时间" width="130" />
|
||||||
<el-table-column prop="owner" label="负责人" width="120" />
|
<el-table-column prop="owner" label="负责人" width="120" />
|
||||||
<el-table-column prop="summary" label="摘要" min-width="300" />
|
<el-table-column prop="summary" label="摘要" min-width="300" />
|
||||||
|
<el-table-column label="操作" width="90" fixed="right">
|
||||||
|
<template #default="{ row }">
|
||||||
|
<el-button link type="danger" :disabled="!row.caseId" @click="openCaseDetail(row)">详情</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
</el-table>
|
</el-table>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<el-dialog v-model="detailVisible" title="案例详情" width="720px">
|
||||||
|
<template v-if="caseDetail">
|
||||||
|
<div class="detail-meta">{{ caseDetail.time }} · {{ caseDetail.owner }} · {{ caseDetail.status }} · {{ caseDetail.fileName }}</div>
|
||||||
|
<div class="detail-block">
|
||||||
|
<strong>原始转写</strong>
|
||||||
|
<p>{{ caseDetail.transcript || '暂无转写内容' }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="detail-block">
|
||||||
|
<strong>案例摘要</strong>
|
||||||
|
<p>{{ caseDetail.summary || '暂无案例摘要' }}</p>
|
||||||
|
</div>
|
||||||
|
<el-alert title="当前版本提供转写、摘要和状态详情;视频样片、学习点与主管点评待正式媒体和业务字段接入。" type="info" :closable="false" />
|
||||||
|
</template>
|
||||||
|
<el-empty v-else description="暂无详情" />
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup name="KnowledgeCases" lang="ts">
|
<script setup name="KnowledgeCases" lang="ts">
|
||||||
import { DocumentChecked, FolderChecked, Microphone, Tickets } from '@element-plus/icons-vue';
|
import { DocumentChecked, FolderChecked, Microphone, Tickets } from '@element-plus/icons-vue';
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
import { computed, ref } from 'vue';
|
import { computed, onMounted, ref } from 'vue';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { curateKnowledgeCase, organizeKnowledgeCase, uploadCaseAudio, type CaseRecord, type CaseSummaryItem, type CaseTag } from '@/api/aihr/case';
|
import {
|
||||||
|
curateKnowledgeCase,
|
||||||
|
getCaseDetail,
|
||||||
|
getCaseRecords,
|
||||||
|
organizeKnowledgeCase,
|
||||||
|
uploadCaseAudio,
|
||||||
|
type CaseDetailResponse,
|
||||||
|
type CaseRecord,
|
||||||
|
type CaseSummaryItem,
|
||||||
|
type CaseTag
|
||||||
|
} from '@/api/aihr/case';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
@@ -114,6 +145,9 @@ const apiSummary = ref<CaseSummaryItem[]>([]);
|
|||||||
const apiTags = ref<CaseTag[]>([]);
|
const apiTags = ref<CaseTag[]>([]);
|
||||||
const apiRecord = ref<CaseRecord | null>(null);
|
const apiRecord = ref<CaseRecord | null>(null);
|
||||||
const apiSampleHint = ref('');
|
const apiSampleHint = ref('');
|
||||||
|
const recordsLoading = ref(false);
|
||||||
|
const detailVisible = ref(false);
|
||||||
|
const caseDetail = ref<CaseDetailResponse | null>(null);
|
||||||
|
|
||||||
const displayFileName = computed(() => apiFileName.value || '未选择文件');
|
const displayFileName = computed(() => apiFileName.value || '未选择文件');
|
||||||
const displayTranscript = computed(() => apiTranscript.value || '暂无转写结果');
|
const displayTranscript = computed(() => apiTranscript.value || '暂无转写结果');
|
||||||
@@ -170,6 +204,7 @@ const pipeline = computed(() => [
|
|||||||
|
|
||||||
const baseRecords = [
|
const baseRecords = [
|
||||||
{
|
{
|
||||||
|
caseId: '',
|
||||||
title: '车库积水投诉处理',
|
title: '车库积水投诉处理',
|
||||||
type: '投诉处理',
|
type: '投诉处理',
|
||||||
status: '已入库',
|
status: '已入库',
|
||||||
@@ -178,6 +213,7 @@ const baseRecords = [
|
|||||||
summary: '确认车位、派工排水、群内同步进度,业主确认问题解决。'
|
summary: '确认车位、派工排水、群内同步进度,业主确认问题解决。'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
caseId: '',
|
||||||
title: '催缴沟通争议化解',
|
title: '催缴沟通争议化解',
|
||||||
type: '催缴沟通',
|
type: '催缴沟通',
|
||||||
status: '待审核',
|
status: '待审核',
|
||||||
@@ -186,6 +222,7 @@ const baseRecords = [
|
|||||||
summary: '业主质疑服务质量,管家先回应服务问题再说明缴费安排。'
|
summary: '业主质疑服务质量,管家先回应服务问题再说明缴费安排。'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
caseId: '',
|
||||||
title: '夜间噪音投诉跟进',
|
title: '夜间噪音投诉跟进',
|
||||||
type: '邻里纠纷',
|
type: '邻里纠纷',
|
||||||
status: '整理中',
|
status: '整理中',
|
||||||
@@ -197,6 +234,30 @@ const baseRecords = [
|
|||||||
|
|
||||||
const records = ref([...baseRecords]);
|
const records = ref([...baseRecords]);
|
||||||
|
|
||||||
|
async function loadCaseRecords() {
|
||||||
|
recordsLoading.value = true;
|
||||||
|
try {
|
||||||
|
const { data } = await getCaseRecords();
|
||||||
|
records.value = data ?? [];
|
||||||
|
} catch {
|
||||||
|
records.value = [...baseRecords];
|
||||||
|
ElMessage.warning('案例列表接口暂不可用,当前展示演示记录');
|
||||||
|
} finally {
|
||||||
|
recordsLoading.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openCaseDetail(record: CaseRecord) {
|
||||||
|
if (!record.caseId) return;
|
||||||
|
try {
|
||||||
|
const { data } = await getCaseDetail(record.caseId);
|
||||||
|
caseDetail.value = data;
|
||||||
|
detailVisible.value = true;
|
||||||
|
} catch {
|
||||||
|
ElMessage.error('案例详情加载失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function handleAudioPicked(event: Event) {
|
async function handleAudioPicked(event: Event) {
|
||||||
const input = event.target as HTMLInputElement;
|
const input = event.target as HTMLInputElement;
|
||||||
const file = input.files?.[0];
|
const file = input.files?.[0];
|
||||||
@@ -281,6 +342,7 @@ async function storeCase() {
|
|||||||
if (apiRecord.value && !records.value.some((item) => item.title === apiRecord.value?.title)) {
|
if (apiRecord.value && !records.value.some((item) => item.title === apiRecord.value?.title)) {
|
||||||
records.value.unshift(apiRecord.value);
|
records.value.unshift(apiRecord.value);
|
||||||
}
|
}
|
||||||
|
await loadCaseRecords();
|
||||||
ElMessage.success('案例已由后端 API 入库,可用于训练复用');
|
ElMessage.success('案例已由后端 API 入库,可用于训练复用');
|
||||||
} catch {
|
} catch {
|
||||||
apiMode.value = 'error';
|
apiMode.value = 'error';
|
||||||
@@ -289,6 +351,10 @@ async function storeCase() {
|
|||||||
ElMessage.error('后端案例入库 API 不可用');
|
ElMessage.error('后端案例入库 API 不可用');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
void loadCaseRecords();
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@@ -524,6 +590,30 @@ async function storeCase() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-meta {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #657184;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-block {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
padding: 14px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: #f7f8fb;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: #162033;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 8px 0 0;
|
||||||
|
color: #445064;
|
||||||
|
line-height: 1.65;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.record-table {
|
.record-table {
|
||||||
border: 1px solid #edf0f5;
|
border: 1px solid #edf0f5;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
|
|||||||
Reference in New Issue
Block a user