配置后端对接
This commit is contained in:
363
src/utils/dataMapper.js
Normal file
363
src/utils/dataMapper.js
Normal file
@@ -0,0 +1,363 @@
|
||||
// Data mapping utilities for converting backend data to frontend format
|
||||
|
||||
// Map student data from backend to frontend format
|
||||
export const mapStudent = (backendData) => {
|
||||
if (!backendData) return null;
|
||||
|
||||
return {
|
||||
id: backendData.id,
|
||||
name: backendData.realName, // realName -> name
|
||||
studentId: backendData.studentNo, // studentNo -> studentId
|
||||
gender: backendData.gender === 'MALE' ? '男' : '女',
|
||||
school: backendData.school,
|
||||
major: backendData.major,
|
||||
enrollDate: backendData.enrollDate,
|
||||
mbtiType: backendData.mbtiType,
|
||||
className: backendData.class?.name,
|
||||
classId: backendData.classId,
|
||||
stageName: backendData.currentStage?.name,
|
||||
stageId: backendData.currentStageId,
|
||||
// User info
|
||||
email: backendData.user?.email,
|
||||
phone: backendData.user?.phone,
|
||||
username: backendData.user?.username,
|
||||
lastLogin: backendData.user?.lastLogin,
|
||||
};
|
||||
};
|
||||
|
||||
// Map student list
|
||||
export const mapStudentList = (backendList) => {
|
||||
if (!Array.isArray(backendList)) return [];
|
||||
return backendList.map(mapStudent);
|
||||
};
|
||||
|
||||
// Map course data
|
||||
export const mapCourse = (backendData) => {
|
||||
if (!backendData) return null;
|
||||
|
||||
return {
|
||||
id: backendData.id,
|
||||
name: backendData.name,
|
||||
code: backendData.code,
|
||||
description: backendData.description,
|
||||
category: backendData.category,
|
||||
type: backendData.type,
|
||||
credits: backendData.credits,
|
||||
hours: backendData.hours,
|
||||
isAiCourse: backendData.isAiCourse,
|
||||
teacher: backendData.teacher ? {
|
||||
id: backendData.teacher.id,
|
||||
name: backendData.teacher.realName,
|
||||
} : null,
|
||||
stage: backendData.stage,
|
||||
enrollmentCount: backendData.enrollmentCount || backendData._count?.enrollments || 0,
|
||||
};
|
||||
};
|
||||
|
||||
// Map course list
|
||||
export const mapCourseList = (backendList) => {
|
||||
if (!Array.isArray(backendList)) return [];
|
||||
return backendList.map(mapCourse);
|
||||
};
|
||||
|
||||
// Map job data
|
||||
export const mapJob = (backendData) => {
|
||||
if (!backendData) return null;
|
||||
|
||||
// Format salary range
|
||||
let salary = '面议';
|
||||
if (backendData.salaryMin && backendData.salaryMax) {
|
||||
const min = Math.floor(backendData.salaryMin / 1000);
|
||||
const max = Math.floor(backendData.salaryMax / 1000);
|
||||
salary = `${min}K-${max}K`;
|
||||
}
|
||||
|
||||
return {
|
||||
id: backendData.id,
|
||||
position: backendData.title, // title -> position
|
||||
description: backendData.description,
|
||||
requirements: backendData.requirements,
|
||||
responsibilities: backendData.responsibilities,
|
||||
company: backendData.company?.name || '',
|
||||
companyId: backendData.companyId,
|
||||
type: mapJobType(backendData.type),
|
||||
jobType: backendData.type === 'INTERNSHIP' ? 'internship' : 'fulltime',
|
||||
level: backendData.level,
|
||||
location: backendData.location,
|
||||
salary: salary,
|
||||
salaryMin: backendData.salaryMin,
|
||||
salaryMax: backendData.salaryMax,
|
||||
benefits: backendData.benefits || [],
|
||||
skills: backendData.skills || [],
|
||||
isActive: backendData.isActive,
|
||||
status: backendData.isActive ? 'available' : 'closed',
|
||||
remainingPositions: backendData._count?.interviews || 5, // Mock remaining positions
|
||||
applicationStatus: 'not_applied', // Default status
|
||||
tags: generateJobTags(backendData),
|
||||
deadline: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30 days from now
|
||||
};
|
||||
};
|
||||
|
||||
// Map job list
|
||||
export const mapJobList = (backendList) => {
|
||||
if (!Array.isArray(backendList)) return [];
|
||||
return backendList.map(mapJob);
|
||||
};
|
||||
|
||||
// Map job type
|
||||
const mapJobType = (type) => {
|
||||
const typeMap = {
|
||||
'FULLTIME': '全职',
|
||||
'PARTTIME': '兼职',
|
||||
'INTERNSHIP': '实习',
|
||||
'CONTRACT': '合同制',
|
||||
'REMOTE': '远程',
|
||||
};
|
||||
return typeMap[type] || type;
|
||||
};
|
||||
|
||||
// Generate job tags
|
||||
const generateJobTags = (job) => {
|
||||
const tags = [];
|
||||
if (job.location) tags.push(job.location.split('市')[0] + '市');
|
||||
if (job.type === 'FULLTIME') tags.push('五险一金');
|
||||
if (job.benefits?.includes('双休')) tags.push('双休');
|
||||
if (job.benefits?.includes('弹性工作')) tags.push('弹性工作');
|
||||
return tags.slice(0, 4); // Max 4 tags
|
||||
};
|
||||
|
||||
// Map company data
|
||||
export const mapCompany = (backendData) => {
|
||||
if (!backendData) return null;
|
||||
|
||||
return {
|
||||
id: backendData.id,
|
||||
name: backendData.name,
|
||||
companyName: backendData.name, // Alias for compatibility
|
||||
description: backendData.description,
|
||||
industry: backendData.industry,
|
||||
scale: mapCompanyScale(backendData.scale),
|
||||
location: backendData.location,
|
||||
website: backendData.website,
|
||||
logo: backendData.logo,
|
||||
contact: backendData.contact,
|
||||
jobCount: backendData._count?.jobs || 0,
|
||||
jobs: backendData.jobs ? mapJobList(backendData.jobs) : [],
|
||||
};
|
||||
};
|
||||
|
||||
// Map company list
|
||||
export const mapCompanyList = (backendList) => {
|
||||
if (!Array.isArray(backendList)) return [];
|
||||
return backendList.map(mapCompany);
|
||||
};
|
||||
|
||||
// Map company scale
|
||||
const mapCompanyScale = (scale) => {
|
||||
const scaleMap = {
|
||||
'SMALL': '50人以下',
|
||||
'MEDIUM': '50-200人',
|
||||
'LARGE': '200-1000人',
|
||||
'ENTERPRISE': '1000人以上',
|
||||
};
|
||||
return scaleMap[scale] || scale;
|
||||
};
|
||||
|
||||
// Map resume data
|
||||
export const mapResume = (backendData) => {
|
||||
if (!backendData) return null;
|
||||
|
||||
return {
|
||||
id: backendData.id,
|
||||
title: backendData.title,
|
||||
content: backendData.content,
|
||||
isActive: backendData.isActive,
|
||||
version: backendData.version,
|
||||
student: backendData.student ? mapStudent(backendData.student) : null,
|
||||
studentId: backendData.studentId,
|
||||
createdAt: backendData.createdAt,
|
||||
updatedAt: backendData.updatedAt,
|
||||
};
|
||||
};
|
||||
|
||||
// Map interview data
|
||||
export const mapInterview = (backendData) => {
|
||||
if (!backendData) return null;
|
||||
|
||||
return {
|
||||
id: backendData.id,
|
||||
scheduledAt: backendData.scheduledAt,
|
||||
interviewTime: new Date(backendData.scheduledAt).toLocaleString('zh-CN'),
|
||||
type: backendData.type,
|
||||
status: backendData.status,
|
||||
location: backendData.location,
|
||||
notes: backendData.notes,
|
||||
feedback: backendData.feedback,
|
||||
result: backendData.result,
|
||||
student: backendData.student ? mapStudent(backendData.student) : null,
|
||||
job: backendData.job ? mapJob(backendData.job) : null,
|
||||
company: backendData.job?.company?.name || '',
|
||||
position: backendData.job?.title || '',
|
||||
// Map status for frontend
|
||||
statusText: mapInterviewStatus(backendData.status, backendData.result),
|
||||
};
|
||||
};
|
||||
|
||||
// Map interview list
|
||||
export const mapInterviewList = (backendList) => {
|
||||
if (!Array.isArray(backendList)) return [];
|
||||
return backendList.map(mapInterview);
|
||||
};
|
||||
|
||||
// Map interview status
|
||||
const mapInterviewStatus = (status, result) => {
|
||||
if (status === 'COMPLETED') {
|
||||
if (result === 'PASS' || result === 'OFFER') return '面试成功';
|
||||
if (result === 'FAIL') return '面试失败';
|
||||
return '已完成';
|
||||
}
|
||||
|
||||
const statusMap = {
|
||||
'SCHEDULED': '待面试',
|
||||
'CANCELLED': '已取消',
|
||||
'NO_SHOW': '未到场',
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
};
|
||||
|
||||
// Map enrollment data
|
||||
export const mapEnrollment = (backendData) => {
|
||||
if (!backendData) return null;
|
||||
|
||||
return {
|
||||
id: backendData.id,
|
||||
courseId: backendData.courseId,
|
||||
studentId: backendData.studentId,
|
||||
status: backendData.status,
|
||||
progress: backendData.progress || 0,
|
||||
score: backendData.score,
|
||||
enrolledAt: backendData.enrolledAt,
|
||||
completedAt: backendData.completedAt,
|
||||
course: backendData.course ? mapCourse(backendData.course) : null,
|
||||
student: backendData.student ? mapStudent(backendData.student) : null,
|
||||
// Map status for display
|
||||
statusText: mapEnrollmentStatus(backendData.status),
|
||||
};
|
||||
};
|
||||
|
||||
// Map enrollment status
|
||||
const mapEnrollmentStatus = (status) => {
|
||||
const statusMap = {
|
||||
'NOT_STARTED': '未开始',
|
||||
'IN_PROGRESS': '学习中',
|
||||
'COMPLETED': '已完成',
|
||||
};
|
||||
return statusMap[status] || status;
|
||||
};
|
||||
|
||||
// Map class data
|
||||
export const mapClass = (backendData) => {
|
||||
if (!backendData) return null;
|
||||
|
||||
return {
|
||||
id: backendData.id,
|
||||
name: backendData.name,
|
||||
className: backendData.name, // Alias for compatibility
|
||||
description: backendData.description,
|
||||
startDate: backendData.startDate,
|
||||
endDate: backendData.endDate,
|
||||
isActive: backendData.isActive,
|
||||
teacher: backendData.teacher ? {
|
||||
id: backendData.teacher.id,
|
||||
name: backendData.teacher.realName,
|
||||
} : null,
|
||||
studentCount: backendData._count?.students || 0,
|
||||
students: backendData.students ? mapStudentList(backendData.students) : [],
|
||||
};
|
||||
};
|
||||
|
||||
// Map stage data
|
||||
export const mapStage = (backendData) => {
|
||||
if (!backendData) return null;
|
||||
|
||||
return {
|
||||
id: backendData.id,
|
||||
name: backendData.name,
|
||||
description: backendData.description,
|
||||
order: backendData.order,
|
||||
duration: backendData.duration,
|
||||
requirements: backendData.requirements,
|
||||
courseCount: backendData._count?.courses || 0,
|
||||
studentCount: backendData._count?.students || 0,
|
||||
courses: backendData.courses ? mapCourseList(backendData.courses) : [],
|
||||
students: backendData.students ? mapStudentList(backendData.students) : [],
|
||||
};
|
||||
};
|
||||
|
||||
// Map learning record
|
||||
export const mapLearningRecord = (backendData) => {
|
||||
if (!backendData) return null;
|
||||
|
||||
return {
|
||||
id: backendData.id,
|
||||
studentId: backendData.studentId,
|
||||
courseId: backendData.courseId,
|
||||
date: backendData.date,
|
||||
duration: backendData.duration,
|
||||
progress: backendData.progress,
|
||||
content: backendData.content,
|
||||
};
|
||||
};
|
||||
|
||||
// Map profile data (for personal profile page)
|
||||
export const mapProfile = (studentData) => {
|
||||
if (!studentData) return null;
|
||||
|
||||
const mapped = mapStudent(studentData);
|
||||
|
||||
return {
|
||||
...mapped,
|
||||
avatar: '/api/placeholder/80/80', // Default avatar
|
||||
badges: {
|
||||
credits: 84, // Mock data, should come from backend
|
||||
classRank: 9, // Mock data, should come from backend
|
||||
mbti: studentData.mbtiType || 'ENTP',
|
||||
},
|
||||
courses: studentData.enrollments ?
|
||||
studentData.enrollments.map(e => e.course?.name).filter(Boolean) : [],
|
||||
mbtiReport: studentData.mbtiReport || generateMockMBTIReport(studentData.mbtiType),
|
||||
};
|
||||
};
|
||||
|
||||
// Generate mock MBTI report (temporary until backend provides)
|
||||
const generateMockMBTIReport = (type) => {
|
||||
return {
|
||||
type: type || 'ENTP',
|
||||
title: 'Personality Type',
|
||||
description: 'Your personality type description',
|
||||
characteristics: ['Creative', 'Analytical', 'Strategic'],
|
||||
strengths: ['Problem-solving', 'Leadership', 'Innovation'],
|
||||
recommendations: ['Focus on execution', 'Develop patience', 'Listen more'],
|
||||
careerSuggestions: ['Product Manager', 'Consultant', 'Entrepreneur'],
|
||||
};
|
||||
};
|
||||
|
||||
// Export all mappers
|
||||
export default {
|
||||
mapStudent,
|
||||
mapStudentList,
|
||||
mapCourse,
|
||||
mapCourseList,
|
||||
mapJob,
|
||||
mapJobList,
|
||||
mapCompany,
|
||||
mapCompanyList,
|
||||
mapResume,
|
||||
mapInterview,
|
||||
mapInterviewList,
|
||||
mapEnrollment,
|
||||
mapClass,
|
||||
mapStage,
|
||||
mapLearningRecord,
|
||||
mapProfile,
|
||||
};
|
||||
@@ -3,8 +3,8 @@ import axios from "axios";
|
||||
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
baseURL: "", // 基础URL,根据实际项目配置
|
||||
timeout: 5000, // 请求超时时间
|
||||
baseURL: import.meta.env.VITE_API_BASE_URL || "http://localhost:3000", // 基础URL
|
||||
timeout: 10000, // 请求超时时间
|
||||
headers: {
|
||||
"Content-Type": "application/json;charset=utf-8",
|
||||
},
|
||||
@@ -13,11 +13,15 @@ const service = axios.create({
|
||||
// 请求拦截器
|
||||
service.interceptors.request.use(
|
||||
(config) => {
|
||||
// 可以在这里添加token等信息
|
||||
const token = localStorage.getItem("token");
|
||||
if (token) {
|
||||
config.headers["Authorization"] = `Bearer ${token}`;
|
||||
}
|
||||
// 开发阶段使用固定的 x-user-id
|
||||
// 这个ID对应种子数据中的开发默认用户
|
||||
config.headers["x-user-id"] = "dev-user-id";
|
||||
|
||||
// 后续对接飞书后使用token
|
||||
// const token = localStorage.getItem("token");
|
||||
// if (token) {
|
||||
// config.headers["Authorization"] = `Bearer ${token}`;
|
||||
// }
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
@@ -30,12 +34,31 @@ service.interceptors.response.use(
|
||||
(response) => {
|
||||
// 处理响应数据
|
||||
const res = response.data;
|
||||
|
||||
// 后端统一返回格式 {success, data, message}
|
||||
if (res.success !== undefined) {
|
||||
if (res.success) {
|
||||
// 如果有分页信息,保留完整结构
|
||||
if (res.total !== undefined) {
|
||||
return res;
|
||||
}
|
||||
// 否则只返回data
|
||||
return res.data || res;
|
||||
} else {
|
||||
// 处理业务错误
|
||||
console.error("业务错误:", res.message);
|
||||
return Promise.reject(new Error(res.message || "请求失败"));
|
||||
}
|
||||
}
|
||||
|
||||
// 兼容直接返回数据的情况
|
||||
return res;
|
||||
},
|
||||
(error) => {
|
||||
// 处理响应错误
|
||||
console.error("请求错误:", error);
|
||||
return Promise.reject(error);
|
||||
const message = error.response?.data?.message || error.message || "网络错误";
|
||||
return Promise.reject(new Error(message));
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user