feat(aihr): complete phase two pilot readiness

This commit is contained in:
2026-07-10 19:13:31 +08:00
parent 0219de6fa8
commit 13230e6b9a
48 changed files with 7040 additions and 1647 deletions
+2 -1
View File
@@ -217,10 +217,11 @@ export function calibratePracticeSession(sessionId: string, data: PracticeCalibr
});
}
export function exportPracticePilotCsv(): Promise<Blob> {
export function exportPracticePilotCsv(params: { startDate: string; endDate: string }): Promise<Blob> {
return request({
url: '/api/train/practice/export',
method: 'get',
params,
responseType: 'blob'
}) as unknown as Promise<Blob>;
}
@@ -0,0 +1,16 @@
import { describe, expect, it } from 'vitest';
import { resolvePilotDateWindow } from './pilot-export';
describe('resolvePilotDateWindow', () => {
it('returns null when the Element Plus date range is cleared', () => {
expect(resolvePilotDateWindow(null)).toBeNull();
});
it('returns an explicit start and end date only for a complete range', () => {
expect(resolvePilotDateWindow(['2026-07-07', '2026-07-10'])).toEqual({
startDate: '2026-07-07',
endDate: '2026-07-10'
});
});
});
+15
View File
@@ -0,0 +1,15 @@
export type PilotDateWindow = {
startDate: string;
endDate: string;
};
export function resolvePilotDateWindow(value: unknown): PilotDateWindow | null {
if (!Array.isArray(value) || value.length !== 2) {
return null;
}
const [startDate, endDate] = value;
if (typeof startDate !== 'string' || typeof endDate !== 'string' || !startDate || !endDate) {
return null;
}
return { startDate, endDate };
}
+27 -4
View File
@@ -8,7 +8,17 @@
<div class="head-actions">
<span class="status-pill" :class="apiPill.className">{{ apiPill.text }}</span>
<span class="status-pill" :class="flowPill.className">{{ flowPill.text }}</span>
<el-button :loading="exportingPilot" @click="exportPilotCsv">导出试点CSV</el-button>
<el-date-picker
v-model="pilotDateRange"
class="pilot-date-range"
type="daterange"
value-format="YYYY-MM-DD"
range-separator="至"
start-placeholder="试点开始"
end-placeholder="试点结束"
unlink-panels
/>
<el-button :icon="Download" :loading="exportingPilot" :disabled="!pilotExportWindow" @click="exportPilotCsv">导出试点CSV</el-button>
<el-button @click="router.push('/index')">返回首页</el-button>
<el-button type="danger" :icon="Refresh" @click="resetPractice">重置流程</el-button>
</div>
@@ -292,7 +302,7 @@
<script setup name="TrainPractice" lang="ts">
import { computed, onMounted, ref } from 'vue';
import { Microphone, Refresh, Tickets, TrendCharts, UserFilled } from '@element-plus/icons-vue';
import { Download, Microphone, Refresh, Tickets, TrendCharts, UserFilled } from '@element-plus/icons-vue';
import { ElMessage } from 'element-plus';
import { useRouter } from 'vue-router';
import FileSaver from 'file-saver';
@@ -313,6 +323,7 @@ import {
type PracticeScenarioResponse
} from '@/api/aihr/practice';
import { searchKnowledgeSop, type SopSearchResponse } from '@/api/aihr/sop';
import { resolvePilotDateWindow } from './pilot-export';
type ChatRole = 'customer' | 'trainee' | 'coach';
@@ -461,6 +472,8 @@ const calibrationReason = ref('管理端人工校准');
const calibrationSaving = ref(false);
const calibrationMessage = ref('');
const exportingPilot = ref(false);
const pilotDateRange = ref<string[] | null>(null);
const pilotExportWindow = computed(() => resolvePilotDateWindow(pilotDateRange.value));
const promptCategoryOptions = ['practice', 'knowledge', 'supervision'];
const defaultPromptForm = (): PromptTemplateForm => ({
id: null,
@@ -797,10 +810,16 @@ const saveCalibration = async () => {
};
const exportPilotCsv = async () => {
const dateWindow = pilotExportWindow.value;
if (!dateWindow) {
ElMessage.warning('请选择完整的试点批次日期');
return;
}
const { startDate, endDate } = dateWindow;
exportingPilot.value = true;
try {
const data = await exportPracticePilotCsv();
FileSaver.saveAs(new Blob([data], { type: 'text/csv;charset=utf-8' }), 'aihr-practice-pilot.csv');
const data = await exportPracticePilotCsv({ startDate, endDate });
FileSaver.saveAs(new Blob([data], { type: 'text/csv;charset=utf-8' }), `aihr-practice-pilot-${startDate}-${endDate}.csv`);
ElMessage.success('试点数据 CSV 已导出');
} catch {
ElMessage.error('试点数据导出失败,请检查权限');
@@ -967,6 +986,10 @@ onMounted(() => {
flex-wrap: wrap;
}
.pilot-date-range {
width: 250px;
}
.flow-strip {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));