fix(aihr): validate pilot export date window
This commit is contained in:
@@ -13,4 +13,10 @@ describe('resolvePilotDateWindow', () => {
|
||||
endDate: '2026-07-10'
|
||||
});
|
||||
});
|
||||
|
||||
it('rejects malformed or reversed date windows', () => {
|
||||
expect(resolvePilotDateWindow(['2026-7-07', '2026-07-10'])).toBeNull();
|
||||
expect(resolvePilotDateWindow(['2026-02-30', '2026-03-01'])).toBeNull();
|
||||
expect(resolvePilotDateWindow(['2026-07-10', '2026-07-07'])).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,12 +3,24 @@ export type PilotDateWindow = {
|
||||
endDate: string;
|
||||
};
|
||||
|
||||
const isIsoDate = (value: string) => {
|
||||
if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) {
|
||||
return false;
|
||||
}
|
||||
const [year, month, day] = value.split('-').map(Number);
|
||||
if (year < 1000 || month < 1 || month > 12 || day < 1) {
|
||||
return false;
|
||||
}
|
||||
const date = new Date(Date.UTC(year, month - 1, day));
|
||||
return date.getUTCFullYear() === year && date.getUTCMonth() === month - 1 && date.getUTCDate() === day;
|
||||
};
|
||||
|
||||
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) {
|
||||
if (typeof startDate !== 'string' || typeof endDate !== 'string' || !isIsoDate(startDate) || !isIsoDate(endDate) || startDate > endDate) {
|
||||
return null;
|
||||
}
|
||||
return { startDate, endDate };
|
||||
|
||||
@@ -147,6 +147,7 @@ import { Refresh } from '@element-plus/icons-vue';
|
||||
import { ElMessage } from 'element-plus';
|
||||
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch } from 'vue';
|
||||
import { calibratePracticeSession, exportPracticePilotCsv, listPracticeScenarios, type PracticeScenarioResponse } from '@/api/aihr/practice';
|
||||
import { resolvePilotDateWindow } from './pilot-export';
|
||||
import {
|
||||
getPracticeAudioBlobUrl,
|
||||
getOrgSnapshot,
|
||||
@@ -174,6 +175,7 @@ const metrics = reactive({ calibrationCount: 0, consistency: '--', sopUsable: '-
|
||||
const audioSources = ref<Record<string, string>>({});
|
||||
const audioLoading = ref<Record<string, boolean>>({});
|
||||
const audioErrors = ref<Record<string, string>>({});
|
||||
const pilotDateWindow = computed(() => resolvePilotDateWindow([startDate.value, endDate.value]));
|
||||
|
||||
const reviewForm = reactive({
|
||||
reviewer: '主管',
|
||||
@@ -258,8 +260,16 @@ const loadReviews = async () => {
|
||||
};
|
||||
|
||||
const loadMetrics = async () => {
|
||||
const dateWindow = pilotDateWindow.value;
|
||||
if (!dateWindow) {
|
||||
metrics.calibrationCount = 0;
|
||||
metrics.consistency = '--';
|
||||
metrics.sopUsable = '--';
|
||||
metrics.ready = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const blob = await exportPracticePilotCsv({ startDate: startDate.value, endDate: endDate.value });
|
||||
const blob = await exportPracticePilotCsv(dateWindow);
|
||||
const csv = await blob.text();
|
||||
const value = (key: string) => csv.match(new RegExp(`(?:^|\\n)"?${key}"?,"?([^"\\n]*)`))?.[1] || '';
|
||||
metrics.calibrationCount = Number(value('calibration_sample_count')) || 0;
|
||||
@@ -275,13 +285,18 @@ const loadMetrics = async () => {
|
||||
};
|
||||
|
||||
const exportPilot = async () => {
|
||||
const dateWindow = pilotDateWindow.value;
|
||||
if (!dateWindow) {
|
||||
ElMessage.warning('请选择有效的试点起止日期');
|
||||
return;
|
||||
}
|
||||
exporting.value = true;
|
||||
try {
|
||||
const blob = await exportPracticePilotCsv({ startDate: startDate.value, endDate: endDate.value });
|
||||
const blob = await exportPracticePilotCsv(dateWindow);
|
||||
const url = URL.createObjectURL(blob);
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.download = `aihr-pilot-${startDate.value}-${endDate.value}.csv`;
|
||||
link.download = `aihr-pilot-${dateWindow.startDate}-${dateWindow.endDate}.csv`;
|
||||
link.click();
|
||||
URL.revokeObjectURL(url);
|
||||
ElMessage.success('试点 CSV 已导出');
|
||||
|
||||
Reference in New Issue
Block a user