feat(aihr): finalize practice growth and access boundaries

This commit is contained in:
2026-07-25 00:42:19 +08:00
parent a90f40e4d8
commit c9d2012ec1
87 changed files with 6804 additions and 2538 deletions
+1 -240
View File
@@ -1,5 +1,4 @@
import axios from 'axios';
import request, { globalHeaders, reconcileTenantContextBlobResponse } from '@/utils/request';
import request from '@/utils/request';
type ApiResult<T> = {
code: number;
@@ -7,123 +6,6 @@ type ApiResult<T> = {
data: T;
};
export type ReviewRecord = {
id: number;
sessionId: string;
time: string;
trainee: string;
scene: string;
score: number;
status: string;
summary: string;
extPartyId: string;
};
export type ReviewDetail = ReviewRecord & {
mentorRewrite: string;
aiComment: string;
reviewAdvice: string;
incentivePoint: number | null;
scoreItems: Array<{ label: string; value: number | string; note: string }>;
dialogue: Array<{ role: string; label: string; text: string; audioUrl?: string; audioOssId?: number | string }>;
annotations: Array<{ turnIndex: number; sourceText: string; issue: string; rewrite: string; sopPoint: string }>;
};
export type ReviewAdviceRequest = {
reviewer?: string;
advice?: string;
incentivePoint?: number;
nextScenarioId?: string;
nextReason?: string;
};
export type CompetencyProfile = {
score: number;
completed: number;
pendingReview: number;
trainingMinutes: number;
contribution: number;
assessmentScore: number;
aiLevel: string;
dimensions: Array<{ label: string; value: number | string | null; note: string }>;
growthPath: Array<{ level: string; progress: number; status: string; evidence: string }>;
};
export type PracticeAlert = {
type: string;
title: string;
desc: string;
severity: string;
extPartyId: string;
};
export type PracticeAssignment = {
id: number;
extPartyId: string;
traineeName: string;
scenarioId: string;
scenarioName: string;
source: string;
reason: string;
status: string;
createTime: string;
question?: string;
referenceAnswer?: string;
userAnswer?: string;
feedback?: string;
correct?: boolean;
};
export type PracticeAssignmentRequest = {
extPartyId: string;
traineeName?: string;
scenarioId: string;
scenarioName?: string;
source: string;
reason: string;
};
export type PracticeDifficulty = {
scenarioId: string;
scenarioName: string;
cause: string;
count: number;
affectedPeople: number;
averageScore: number;
latestTime: string;
position?: string;
assignmentStatus?: string;
attempts?: number;
mistakeCount?: number;
difficultyScore?: number;
};
export type PracticeAssignmentBatchResponse = {
requestId: string;
total: number;
created: number;
duplicate: number;
failed: number;
results: Array<{ extPartyId: string; traineeName: string; status: 'CREATED' | 'DUPLICATE' | 'FAILED'; message: string }>;
};
export type PromotionEvidence = {
extPartyId: string;
currentLevel: string;
assessmentScore: number;
trainingMinutes: number;
contribution: number;
completed: number;
pendingReview: number;
dimensions: Array<{ label: string; value: number | string | null; note: string }>;
latestRecords: ReviewRecord[];
assignments: PracticeAssignment[];
evidenceItems: string[];
humanAssessmentScore?: number | null;
finalAdoptedScore?: number | null;
calibrationCount?: number;
};
export type OrgSyncRequest = {
dryRun?: boolean;
replaceExisting?: boolean;
@@ -188,127 +70,6 @@ export type OrgSnapshotResponse = {
rows: OrgPersonRow[];
};
export type ReviewListParams = {
projectCode?: string;
extPartyId?: string;
limit?: number;
};
export function listPracticeReviews(params?: ReviewListParams): Promise<ApiResult<ReviewRecord[]>> {
return request({
url: '/api/aihr/mobile/practice/reviews',
method: 'get',
params
});
}
export function getPracticeReview(id: number): Promise<ApiResult<ReviewDetail>> {
return request({
url: `/api/aihr/mobile/practice/reviews/${id}`,
method: 'get'
});
}
export async function getPracticeAudioBlobUrl(ossId: number | string): Promise<string> {
try {
const response = await axios({
method: 'get',
url: `${import.meta.env.VITE_APP_BASE_API}/api/aihr/mobile/oss/${ossId}`,
responseType: 'blob',
headers: globalHeaders()
});
const contentType = String(response.headers['content-type'] || '');
if (contentType.includes('application/json') && (await reconcileTenantContextBlobResponse(response.data))) {
throw new Error('租户上下文已变更,请刷新后重试');
}
return URL.createObjectURL(response.data);
} catch (error) {
await reconcileTenantContextBlobResponse(axios.isAxiosError(error) ? error.response?.data : undefined);
throw error;
}
}
export function markPracticeReviewed(id: number, data: ReviewAdviceRequest): Promise<ApiResult<null>> {
return request({
url: `/api/aihr/mobile/practice/reviews/${id}/reviewed`,
method: 'post',
data,
headers: { repeatSubmit: false }
});
}
export function getCompetencyProfile(extPartyId?: string): Promise<ApiResult<CompetencyProfile>> {
return request({
url: '/api/aihr/mobile/profile',
method: 'get',
params: { extPartyId }
});
}
export function getPromotionEvidence(extPartyId?: string): Promise<ApiResult<PromotionEvidence>> {
return request({
url: '/api/aihr/mobile/profile/promotion-evidence',
method: 'get',
params: { extPartyId }
});
}
export function listPracticeAlerts(): Promise<ApiResult<PracticeAlert[]>> {
return request({
url: '/api/aihr/mobile/practice/alerts',
method: 'get'
});
}
export function listPracticeAssignments(extPartyId?: string): Promise<ApiResult<PracticeAssignment[]>> {
return request({
url: '/api/aihr/mobile/practice/assignments',
method: 'get',
params: { extPartyId }
});
}
export function createPracticeAssignment(data: PracticeAssignmentRequest): Promise<ApiResult<PracticeAssignment>> {
return request({
url: '/api/aihr/mobile/practice/assignments',
method: 'post',
data,
headers: { repeatSubmit: false }
});
}
export function listPracticeDifficulties(params?: { windowDays?: number; position?: string }): Promise<ApiResult<PracticeDifficulty[]>> {
type RawDifficulty = Omit<PracticeDifficulty, 'cause' | 'count'> & { attempts: number; mistakeCount: number };
return request<unknown, ApiResult<RawDifficulty[]>>({
url: '/api/aihr/mobile/practice/difficulties',
method: 'get',
params
}).then((result) => ({
...result,
data: (result.data || []).map((item) => ({
...item,
cause: item.mistakeCount >= Math.max(2, Math.ceil(item.attempts / 2)) ? '高频错题或低分' : '近期低分记录',
count: item.mistakeCount
}))
}));
}
export function assignPracticeDifficulty(data: {
scenarioId: string;
cause: string;
dueDate: string;
requestId: string;
windowDays: number;
position?: string;
}): Promise<ApiResult<PracticeAssignmentBatchResponse>> {
return request({
url: '/api/aihr/mobile/practice/difficulties/assign',
method: 'post',
data,
headers: { repeatSubmit: false }
});
}
export function syncOrgSnapshot(data: OrgSyncRequest): Promise<ApiResult<OrgSyncResponse>> {
return request({
url: '/api/aihr/org/sync',
+69
View File
@@ -18,6 +18,17 @@ export type PracticeScenarioResponse = {
position: string;
scenarioType: string;
projectType: string;
growthLevel: string;
competencyCode: string;
collaborationPositions: string;
reviewStatus: string;
riskLevel: '待评估' | '常规' | '高风险';
requiresDualReview: boolean;
curriculumVersion: string;
reviewer?: string;
reviewedTime?: string;
secondReviewer?: string;
secondReviewedTime?: string;
difficulty: number;
customerPersona: string;
openingText: string;
@@ -58,6 +69,11 @@ export type PracticeScenarioSaveRequest = {
position: string;
scenarioType: string;
projectType: string;
growthLevel: string;
competencyCode: string;
collaborationPositions: string;
riskLevel: '待评估' | '常规' | '高风险';
curriculumVersion: string;
difficulty: number;
customerPersona: string;
openingText: string;
@@ -73,6 +89,35 @@ export type PracticeScenarioSaveRequest = {
rounds: PracticeScenarioRound[];
};
export type PracticeScenarioReviewStatusRequest = {
reviewStatus: '草稿' | '待业务审核' | '已发布' | '已下线';
};
export type PracticeScenarioReviewStatusResponse = {
reviewStatus: '草稿' | '待业务审核' | '已发布' | '已下线';
awaitingSecondReview: boolean;
message: string;
};
export type PracticeCurriculumCapability = {
position: string;
growthLevel: string;
code: string;
name: string;
};
export type PracticeCurriculumMatrixRow = {
position: string;
growthLevel: string;
competencyCode: string;
competencyName: string;
publishedCount: number;
pendingReviewCount: number;
draftCount: number;
offlineCount: number;
coverageStatus: string;
};
export type PracticeRubricDimension = {
code: string;
name: string;
@@ -289,6 +334,30 @@ export function updatePracticeScenarioEnabled(id: string, data: PracticeScenario
});
}
export function updatePracticeScenarioReviewStatus(
id: string,
data: PracticeScenarioReviewStatusRequest
): Promise<ApiResult<PracticeScenarioReviewStatusResponse>> {
return request({
url: `/api/train/practice/scenarios/${encodeURIComponent(id)}/review-status`,
method: 'patch',
data
});
}
export function listPracticeCurriculumCapabilities(params?: {
position?: string;
growthLevel?: string;
}): Promise<ApiResult<PracticeCurriculumCapability[]>> {
return request({ url: '/api/train/practice/curriculum/capabilities', method: 'get', params });
}
export function listPracticeCurriculumMatrix(params?: {
position?: string;
}): Promise<ApiResult<PracticeCurriculumMatrixRow[]>> {
return request({ url: '/api/train/practice/curriculum/matrix', method: 'get', params });
}
export function savePracticeScenario(data: PracticeScenarioSaveRequest): Promise<ApiResult<PracticeScenarioResponse>> {
return request({
url: '/api/train/practice/scenarios',