diff --git a/package.json b/package.json
index 123018d..28a9761 100644
--- a/package.json
+++ b/package.json
@@ -15,7 +15,7 @@
"deploy:prod": "vercel --prod"
},
"dependencies": {
- "@arco-design/web-react": "^2.66.4",
+ "@arco-design/web-react": "^2.66.5",
"@reduxjs/toolkit": "^2.8.2",
"axios": "^1.11.0",
"echarts": "^6.0.0",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index e2dbc25..3c3c0b9 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -9,8 +9,8 @@ importers:
.:
dependencies:
'@arco-design/web-react':
- specifier: ^2.66.4
- version: 2.66.4(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
+ specifier: ^2.66.5
+ version: 2.66.5(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
'@reduxjs/toolkit':
specifier: ^2.8.2
version: 2.8.2(react-redux@9.2.0(@types/react@19.1.10)(react@19.1.1)(redux@5.0.1))(react@19.1.1)
@@ -85,8 +85,8 @@ packages:
'@arco-design/color@0.4.0':
resolution: {integrity: sha512-s7p9MSwJgHeL8DwcATaXvWT3m2SigKpxx4JA1BGPHL4gfvaQsmQfrLBDpjOJFJuJ2jG2dMt3R3P8Pm9E65q18g==}
- '@arco-design/web-react@2.66.4':
- resolution: {integrity: sha512-vl7sJBLvbVyJhYRPoQ8kHc8BuXNkJIXca5h9ync2J1TuKglFMLNbQwjIvJLW3ciabqTZ5g1O7H1GQ+lLIEMsWA==}
+ '@arco-design/web-react@2.66.5':
+ resolution: {integrity: sha512-ity0kG+B6pmuJ2/Zh3wUtBV78XxWmRtGEwazL8f4KAjoQpMkisgLMXibUpAGfcqph3vycNFq4yHgHujjgwrJMQ==}
peerDependencies:
react: '>=16'
react-dom: '>=16'
@@ -1334,7 +1334,7 @@ snapshots:
dependencies:
color: 3.2.1
- '@arco-design/web-react@2.66.4(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
+ '@arco-design/web-react@2.66.5(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)':
dependencies:
'@arco-design/color': 0.4.0
'@babel/runtime': 7.28.3
diff --git a/src/components/Toast/README.md b/src/components/Toast/README.md
new file mode 100644
index 0000000..a3e0bd5
--- /dev/null
+++ b/src/components/Toast/README.md
@@ -0,0 +1,147 @@
+# Toast 组件使用指南
+
+## 概述
+
+自定义的全局 Toast 组件,支持命令式调用,无需在组件树中添加 Provider。
+
+## 特性
+
+- 🚀 轻量级,零依赖
+- 📱 响应式设计
+- 🎨 支持多种类型(success, error, warning, info)
+- ⚡ 命令式 API,简单易用
+- 🔧 高度可定制
+- 🌙 支持暗色主题
+
+## 快速开始
+
+### 1. 基本用法
+
+```jsx
+import toast from "@/components/Toast";
+
+// 成功提示
+toast.success("操作成功!");
+
+// 错误提示
+toast.error("操作失败!");
+
+// 警告提示
+toast.warning("请注意!");
+
+// 信息提示
+toast.info("这是一条信息");
+```
+
+### 2. 高级用法
+
+```jsx
+// 自定义持续时间
+toast.success("3秒后消失", { duration: 3000 });
+
+// 永不自动消失
+toast.info("手动关闭", { duration: 0 });
+
+// 禁用关闭按钮
+toast.error("无法关闭", { closable: false });
+
+// 带标题的提示
+toast.success("操作完成", {
+ title: "成功",
+ duration: 5000,
+});
+
+// 获取 Toast ID,手动关闭
+const toastId = toast.info("loading...");
+setTimeout(() => {
+ toast.remove(toastId);
+}, 2000);
+```
+
+### 3. 在组件中使用 Hook(可选)
+
+如果需要在组件内部管理 Toast 状态,可以使用 Provider 方式:
+
+```jsx
+import { ToastProvider, useToast } from "@/components/Toast";
+
+function App() {
+ return (
+
+
+
+ );
+}
+
+function YourComponent() {
+ const { addToast, removeAllToasts } = useToast();
+
+ const handleClick = () => {
+ addToast({
+ type: "success",
+ message: "这是通过 Hook 创建的 Toast",
+ });
+ };
+
+ return (
+
+
+
+
+ );
+}
+```
+
+## API 参考
+
+### 全局方法
+
+- `toast.success(message, options?)` - 显示成功提示
+- `toast.error(message, options?)` - 显示错误提示
+- `toast.warning(message, options?)` - 显示警告提示
+- `toast.info(message, options?)` - 显示信息提示
+- `toast.remove(id)` - 移除指定 Toast
+- `toast.removeAll()` - 移除所有 Toast
+
+### Options 参数
+
+```typescript
+interface ToastOptions {
+ title?: string; // 标题
+ duration?: number; // 持续时间(毫秒),0 表示不自动关闭
+ closable?: boolean; // 是否显示关闭按钮,默认 true
+ type?: "success" | "error" | "warning" | "info"; // Toast 类型
+}
+```
+
+### Hook API
+
+- `useToast()` - 返回 Toast 管理方法
+ - `addToast(options)` - 添加 Toast
+ - `removeToast(id)` - 移除指定 Toast
+ - `removeAllToasts()` - 移除所有 Toast
+ - `toasts` - 当前 Toast 列表
+
+## 样式定制
+
+可以通过修改 CSS 变量来定制样式:
+
+```css
+.toast-item {
+ /* 自定义背景色 */
+ background: your-color;
+
+ /* 自定义圆角 */
+ border-radius: your-radius;
+
+ /* 自定义阴影 */
+ box-shadow: your-shadow;
+}
+```
+
+## 注意事项
+
+1. Toast 容器会自动创建和销毁,无需手动管理
+2. 支持同时显示多个 Toast
+3. 在移动端会自动适配屏幕宽度
+4. 支持暗色主题自动切换
diff --git a/src/components/Toast/index.css b/src/components/Toast/index.css
new file mode 100644
index 0000000..87f2f30
--- /dev/null
+++ b/src/components/Toast/index.css
@@ -0,0 +1,178 @@
+/* Toast 容器 */
+.toast-container,
+.global-toast-container {
+ position: fixed;
+ top: 20px;
+ right: 20px;
+ z-index: 9999;
+ pointer-events: none;
+}
+
+.global-toast-container .toast-container {
+ position: static;
+}
+
+/* Toast 项目 */
+.toast-item {
+ display: flex;
+ align-items: flex-start;
+ gap: 12px;
+ padding: 16px;
+ margin-bottom: 12px;
+ min-width: 320px;
+ max-width: 480px;
+ background: #ffffff;
+ border-radius: 8px;
+ box-shadow: 0 6px 16px -8px rgba(0, 0, 0, 0.08),
+ 0 9px 28px 0 rgba(0, 0, 0, 0.05),
+ 0 3px 6px -4px rgba(0, 0, 0, 0.12);
+ border-left: 4px solid #d9d9d9;
+ pointer-events: auto;
+ cursor: pointer;
+ transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
+ transform: translateX(100%);
+ opacity: 0;
+}
+
+/* 显示状态 */
+.toast-item.toast-visible {
+ transform: translateX(0);
+ opacity: 1;
+}
+
+/* 移除状态 */
+.toast-item.toast-removing {
+ transform: translateX(100%);
+ opacity: 0;
+}
+
+/* Toast 类型样式 */
+.toast-item.toast-success {
+ border-left-color: #52c41a;
+}
+
+.toast-item.toast-success .toast-icon {
+ color: #52c41a;
+}
+
+.toast-item.toast-error {
+ border-left-color: #ff4d4f;
+}
+
+.toast-item.toast-error .toast-icon {
+ color: #ff4d4f;
+}
+
+.toast-item.toast-warning {
+ border-left-color: #faad14;
+}
+
+.toast-item.toast-warning .toast-icon {
+ color: #faad14;
+}
+
+.toast-item.toast-info {
+ border-left-color: #1890ff;
+}
+
+.toast-item.toast-info .toast-icon {
+ color: #1890ff;
+}
+
+
+/* Toast 内容 */
+.toast-content {
+ flex: 1;
+ min-width: 0;
+}
+
+.toast-title {
+ font-size: 16px;
+ font-weight: 600;
+ color: #262626;
+ margin-bottom: 4px;
+ line-height: 1.4;
+}
+
+.toast-message {
+ font-size: 14px;
+ color: #595959;
+ line-height: 1.5;
+ word-wrap: break-word;
+}
+
+/* 关闭按钮 */
+.toast-close {
+ flex-shrink: 0;
+ width: 20px;
+ height: 20px;
+ border: none;
+ background: none;
+ color: #bfbfbf;
+ font-size: 18px;
+ line-height: 1;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ border-radius: 50%;
+ transition: all 0.2s;
+ margin-top: 2px;
+ padding: 0;
+}
+
+.toast-close:hover {
+ color: #8c8c8c;
+ background: #f5f5f5;
+}
+
+/* 悬停效果 */
+.toast-item:hover {
+ box-shadow: 0 6px 16px -8px rgba(0, 0, 0, 0.12),
+ 0 9px 28px 0 rgba(0, 0, 0, 0.08),
+ 0 3px 6px -4px rgba(0, 0, 0, 0.16);
+}
+
+/* 响应式设计 */
+@media (max-width: 768px) {
+ .toast-container,
+ .global-toast-container {
+ top: 10px;
+ right: 10px;
+ left: 10px;
+ }
+
+ .toast-item {
+ min-width: auto;
+ max-width: none;
+ margin-bottom: 8px;
+ }
+}
+
+/* 暗色主题支持 */
+@media (prefers-color-scheme: dark) {
+ .toast-item {
+ background: #1f1f1f;
+ color: #ffffff;
+ box-shadow: 0 6px 16px -8px rgba(0, 0, 0, 0.32),
+ 0 9px 28px 0 rgba(0, 0, 0, 0.24),
+ 0 3px 6px -4px rgba(0, 0, 0, 0.48);
+ }
+
+ .toast-title {
+ color: #ffffff;
+ }
+
+ .toast-message {
+ color: #d9d9d9;
+ }
+
+ .toast-close {
+ color: #8c8c8c;
+ }
+
+ .toast-close:hover {
+ color: #bfbfbf;
+ background: #262626;
+ }
+}
\ No newline at end of file
diff --git a/src/components/Toast/index.jsx b/src/components/Toast/index.jsx
new file mode 100644
index 0000000..d886112
--- /dev/null
+++ b/src/components/Toast/index.jsx
@@ -0,0 +1,259 @@
+import React, { useState, useEffect, createContext, useContext } from "react";
+import { createRoot } from "react-dom/client";
+import "./index.css";
+
+// Toast 上下文
+const ToastContext = createContext();
+
+// Toast 类型
+const TOAST_TYPES = {
+ SUCCESS: "success",
+ ERROR: "error",
+ WARNING: "warning",
+ INFO: "info",
+};
+
+// // Toast 图标
+// const TOAST_ICONS = {
+// [TOAST_TYPES.SUCCESS]: "✓",
+// [TOAST_TYPES.ERROR]: "✕",
+// [TOAST_TYPES.WARNING]: "⚠",
+// [TOAST_TYPES.INFO]: "ℹ",
+// };
+
+// 单个 Toast 组件
+const ToastItem = ({ toast, onRemove }) => {
+ const [isVisible, setIsVisible] = useState(false);
+ const [isRemoving, setIsRemoving] = useState(false);
+
+ useEffect(() => {
+ // 显示动画
+ const showTimer = setTimeout(() => setIsVisible(true), 10);
+
+ // 自动移除
+ const removeTimer = setTimeout(() => {
+ handleRemove();
+ }, toast.duration || 3000);
+
+ return () => {
+ clearTimeout(showTimer);
+ clearTimeout(removeTimer);
+ };
+ }, []);
+
+ const handleRemove = () => {
+ setIsRemoving(true);
+ setTimeout(() => {
+ onRemove(toast.id);
+ }, 300);
+ };
+
+ return (
+
+
+ {toast.title &&
{toast.title}
}
+
{toast.message}
+
+ {toast.closable !== false && (
+
+ )}
+
+ );
+};
+
+// Toast 容器组件
+const ToastContainer = () => {
+ const { toasts, removeToast } = useContext(ToastContext);
+
+ return (
+
+ {toasts.map((toast) => (
+
+ ))}
+
+ );
+};
+
+// Toast Provider 组件
+export const ToastProvider = ({ children }) => {
+ const [toasts, setToasts] = useState([]);
+
+ const addToast = (toast) => {
+ const id = Date.now() + Math.random();
+ const newToast = {
+ id,
+ type: TOAST_TYPES.INFO,
+ duration: 3000,
+ closable: true,
+ ...toast,
+ };
+
+ setToasts((prev) => [...prev, newToast]);
+ return id;
+ };
+
+ const removeToast = (id) => {
+ setToasts((prev) => prev.filter((toast) => toast.id !== id));
+ };
+
+ const removeAllToasts = () => {
+ setToasts([]);
+ };
+
+ const value = {
+ toasts,
+ addToast,
+ removeToast,
+ removeAllToasts,
+ };
+
+ return (
+
+ {children}
+
+
+ );
+};
+
+// Hook for using toast
+export const useToast = () => {
+ const context = useContext(ToastContext);
+ if (!context) {
+ throw new Error("useToast must be used within a ToastProvider");
+ }
+ return context;
+};
+
+// 全局 Toast 管理器
+class ToastManager {
+ constructor() {
+ this.container = null;
+ this.root = null;
+ this.toasts = [];
+ this.listeners = [];
+ }
+
+ // 初始化容器
+ init() {
+ if (this.container) return;
+
+ this.container = document.createElement("div");
+ this.container.className = "global-toast-container";
+ document.body.appendChild(this.container);
+
+ this.root = createRoot(this.container);
+ this.render();
+ }
+
+ // 渲染 Toast 列表
+ render() {
+ if (!this.root) return;
+
+ this.root.render(
+
+ {this.toasts.map((toast) => (
+
+ ))}
+
+ );
+ }
+
+ // 添加 Toast
+ add(options) {
+ this.init();
+
+ const id = Date.now() + Math.random();
+ const toast = {
+ id,
+ type: TOAST_TYPES.INFO,
+ duration: 3000,
+ closable: true,
+ ...options,
+ };
+
+ this.toasts.push(toast);
+ this.render();
+
+ // 自动移除
+ if (toast.duration > 0) {
+ setTimeout(() => {
+ this.remove(id);
+ }, toast.duration);
+ }
+
+ return id;
+ }
+
+ // 移除 Toast
+ remove(id) {
+ this.toasts = this.toasts.filter((toast) => toast.id !== id);
+ this.render();
+
+ // 如果没有 Toast 了,清理容器
+ if (this.toasts.length === 0) {
+ setTimeout(() => {
+ this.cleanup();
+ }, 300);
+ }
+ }
+
+ // 移除所有 Toast
+ removeAll() {
+ this.toasts = [];
+ this.render();
+ this.cleanup();
+ }
+
+ // 清理容器
+ cleanup() {
+ if (this.container && this.toasts.length === 0) {
+ this.root?.unmount();
+ document.body.removeChild(this.container);
+ this.container = null;
+ this.root = null;
+ }
+ }
+
+ // 便捷方法
+ success(message, options = {}) {
+ return this.add({ ...options, message, type: TOAST_TYPES.SUCCESS });
+ }
+
+ error(message, options = {}) {
+ return this.add({ ...options, message, type: TOAST_TYPES.ERROR });
+ }
+
+ warning(message, options = {}) {
+ return this.add({ ...options, message, type: TOAST_TYPES.WARNING });
+ }
+
+ info(message, options = {}) {
+ return this.add({ ...options, message, type: TOAST_TYPES.INFO });
+ }
+}
+
+// 创建全局实例
+const toastManager = new ToastManager();
+
+// 导出全局 Toast API
+export const toast = {
+ success: (message, options) => toastManager.success(message, options),
+ error: (message, options) => toastManager.error(message, options),
+ warning: (message, options) => toastManager.warning(message, options),
+ info: (message, options) => toastManager.info(message, options),
+ remove: (id) => toastManager.remove(id),
+ removeAll: () => toastManager.removeAll(),
+};
+
+export default toast;
diff --git a/src/pages/CompanyJobsPage/components/JobInfoModal/index.jsx b/src/pages/CompanyJobsPage/components/JobInfoModal/index.jsx
index 57611b4..8f0b1f4 100644
--- a/src/pages/CompanyJobsPage/components/JobInfoModal/index.jsx
+++ b/src/pages/CompanyJobsPage/components/JobInfoModal/index.jsx
@@ -10,6 +10,7 @@ const InputSearch = Input.Search;
const { userResumes } = mockData;
export default ({ visible, onClose, data }) => {
+ console.log(data);
const [resumeModalShow, setResumeModalShow] = useState(false);
const [resumeInfoModalShow, setResumeInfoModalShow] = useState(false);
@@ -90,7 +91,7 @@ export default ({ visible, onClose, data }) => {
{data?.position}
- 该岗位仅剩9人
+ 该岗位仅剩{data?.remainingPositions}人
{data?.salary}
@@ -105,15 +106,19 @@ export default ({ visible, onClose, data }) => {
))}
)}
- {data?.details?.description && (
+ {data?.description && (
岗位描述
-
- {data?.details?.description}
-
+
{data?.description}
)}
- {data?.details?.requirements?.length > 0 && (
+ {data?.requirements && (
+
+
岗位要求
+
{data?.requirements}
+
+ )}
+ {/* {data?.details?.requirements?.length > 0 && (
岗位要求
@@ -124,12 +129,12 @@ export default ({ visible, onClose, data }) => {
))}
- )}
- {data?.details?.companyInfo && (
+ )} */}
+ {data?.company?.industry && (
公司介绍
- {data?.details?.companyInfo}
+ {data?.company?.industry}
)}
diff --git a/src/pages/CompanyJobsPage/components/JobList/index.jsx b/src/pages/CompanyJobsPage/components/JobList/index.jsx
index 9871b01..3995ee7 100644
--- a/src/pages/CompanyJobsPage/components/JobList/index.jsx
+++ b/src/pages/CompanyJobsPage/components/JobList/index.jsx
@@ -1,17 +1,23 @@
import { useState } from "react";
-import { useNavigate } from "react-router-dom";
+import toast from "@/components/Toast";
import JobInfoModal from "../JobInfoModal";
+import { getJobsDetail } from "@/services";
+import { mapJob } from "@/utils/dataMapper";
import "./index.css";
export default ({ className = "", data = [], backgroundColor = "#FFFFFF" }) => {
- const navigate = useNavigate();
const [jobInfoData, setJobInfoData] = useState(undefined);
const [jobInfoModalVisible, setJobInfoModalVisible] = useState(false);
- const handleJobClick = (e, item) => {
+ const handleJobClick = async (e, item) => {
e.stopPropagation();
- setJobInfoModalVisible(true);
- setJobInfoData(item);
+ const res = await getJobsDetail(item.id);
+ if (res.success) {
+ setJobInfoData(mapJob(res.data));
+ setJobInfoModalVisible(true);
+ } else {
+ toast.error(res.message);
+ }
};
const onClickJobInfoModalClose = () => {
diff --git a/src/pages/Dashboard/index.jsx b/src/pages/Dashboard/index.jsx
index 8c6ef95..60bb30c 100644
--- a/src/pages/Dashboard/index.jsx
+++ b/src/pages/Dashboard/index.jsx
@@ -6,7 +6,7 @@ import StudyStatus from "./components/StudyStatus";
import Rank from "@/components/Rank";
import StageProgress from "@/components/StageProgress";
import TaskList from "./components/TaskList";
-import { getClassRanking, getLearningProgressSummary } from "@/services";
+import { getClassRanking, getStudyRecordsProgress } from "@/services";
import "./index.css";
const Dashboard = () => {
@@ -14,7 +14,7 @@ const Dashboard = () => {
// 获取整体学习进度
const queryLearningProgressSummary = async () => {
- const res = await getLearningProgressSummary({ period: "semester" });
+ const res = await getStudyRecordsProgress();
console.log("learningProgressSummary", res);
};
diff --git a/src/pages/PersonalProfile/index.jsx b/src/pages/PersonalProfile/index.jsx
index 07489fb..3912a77 100644
--- a/src/pages/PersonalProfile/index.jsx
+++ b/src/pages/PersonalProfile/index.jsx
@@ -5,7 +5,7 @@ import Rank from "@/components/Rank";
import StageProgress from "@/components/StageProgress";
import StudyStudes from "./components/StudyStudes";
import { updateStudentInfo } from "@/store/slices/studentSlice";
-import { getClassRanking, getLearningProgressSummary } from "@/services";
+import { getClassRanking, getStudyRecordsProgress } from "@/services";
import "./index.css";
const PersonalProfile = () => {
@@ -13,7 +13,7 @@ const PersonalProfile = () => {
const [rankData, setRankData] = useState([]); // 班级排名数据
const queryLearningProgressSummary = async () => {
- const res = await getLearningProgressSummary({ period: "semester" });
+ const res = await getStudyRecordsProgress();
console.log("learningProgressSummary", res);
};
diff --git a/src/services/companyJobs.js b/src/services/companyJobs.js
index d4b308a..354f6d0 100644
--- a/src/services/companyJobs.js
+++ b/src/services/companyJobs.js
@@ -8,6 +8,15 @@ export async function getJobsList(params) {
params,
});
}
+
+// 获取企业内推岗位详情
+export async function getJobsDetail(id) {
+ return request({
+ url: `/api/jobs/${id}`,
+ method: "GET",
+ });
+}
+
// 获取企业内推岗位面试
export async function getInterviewsList(params) {
return request({
diff --git a/src/services/dashboard.js b/src/services/dashboard.js
index aaa4d68..493b50a 100644
--- a/src/services/dashboard.js
+++ b/src/services/dashboard.js
@@ -1,11 +1,10 @@
import request from "@/utils/request";
-// 获取当前学生的学习进度汇总
-export async function getLearningProgressSummary(queryParams = {}) {
+// 获取学生的整体学习进度
+export async function getStudyRecordsProgress() {
return request({
- url: `/api/dashboard/learning-summary`,
+ url: `/api/study-records/progress`,
method: "GET",
- params: queryParams,
namespace: "dashboardLoading",
});
}
diff --git a/src/services/index.js b/src/services/index.js
index 9238453..8394cc8 100644
--- a/src/services/index.js
+++ b/src/services/index.js
@@ -1,11 +1,11 @@
// 统一的API服务接口 - 基于当前认证用户
import {
- getLearningProgressSummary,
+ getStudyRecordsProgress,
getMyTasks,
getClassRanking,
} from "./dashboard";
import { getProjectsList } from "./projectLibrary";
-import { getJobsList, getInterviewsList } from "./companyJobs";
+import { getJobsList, getJobsDetail, getInterviewsList } from "./companyJobs";
import { getLoginStudentInfo } from "./global";
import {
getDashboardStatistics,
@@ -19,7 +19,7 @@ export {
// 仪表盘相关
getMyTasks, // 获取我的任务
getDashboardStatistics, // 获取当前学生仪表盘统计
- getLearningProgressSummary, // 获取当前学生学习进度汇总
+ getStudyRecordsProgress, // 获取学生的整体学习进度
// 排名相关
getClassRanking, // 获取当前学生班级排名
@@ -35,6 +35,7 @@ export {
// 求职相关
getJobsList, // 获取岗位列表
+ getJobsDetail, // 岗位详情
getInterviewsList, // 获取面试列表
getResumesList, // 获取简历列表
getResumesDetail, // 获取简历详情
diff --git a/src/utils/dataMapper.js b/src/utils/dataMapper.js
index 96e182d..2d2881b 100644
--- a/src/utils/dataMapper.js
+++ b/src/utils/dataMapper.js
@@ -3,12 +3,12 @@
// 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' ? '男' : '女',
+ name: backendData.realName, // realName -> name
+ studentId: backendData.studentNo, // studentNo -> studentId
+ gender: backendData.gender === "MALE" ? "男" : "女",
school: backendData.school,
major: backendData.major,
enrollDate: backendData.enrollDate,
@@ -34,7 +34,7 @@ export const mapStudentList = (backendList) => {
// Map course data
export const mapCourse = (backendData) => {
if (!backendData) return null;
-
+
return {
id: backendData.id,
name: backendData.name,
@@ -45,12 +45,15 @@ export const mapCourse = (backendData) => {
credits: backendData.credits,
hours: backendData.hours,
isAiCourse: backendData.isAiCourse,
- teacher: backendData.teacher ? {
- id: backendData.teacher.id,
- name: backendData.teacher.realName,
- } : null,
+ teacher: backendData.teacher
+ ? {
+ id: backendData.teacher.id,
+ name: backendData.teacher.realName,
+ }
+ : null,
stage: backendData.stage,
- enrollmentCount: backendData.enrollmentCount || backendData._count?.enrollments || 0,
+ enrollmentCount:
+ backendData.enrollmentCount || backendData._count?.enrollments || 0,
};
};
@@ -63,25 +66,26 @@ export const mapCourseList = (backendList) => {
// Map job data
export const mapJob = (backendData) => {
if (!backendData) return null;
-
+
// Format salary range
- let salary = '面议';
+ 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
+ company: backendData.company,
+ position: backendData.title, // title -> position
description: backendData.description,
requirements: backendData.requirements,
responsibilities: backendData.responsibilities,
- company: backendData.company?.name || '',
+ companyName: backendData.company?.name || "",
companyId: backendData.companyId,
type: mapJobType(backendData.type),
- jobType: backendData.type === 'INTERNSHIP' ? 'internship' : 'fulltime',
+ jobType: backendData.type === "INTERNSHIP" ? "internship" : "fulltime",
level: backendData.level,
location: backendData.location,
salary: salary,
@@ -90,9 +94,9 @@ export const mapJob = (backendData) => {
benefits: backendData.benefits || [],
skills: backendData.skills || [],
isActive: backendData.isActive,
- status: backendData.isActive ? 'available' : 'closed',
+ status: backendData.isActive ? "available" : "closed",
remainingPositions: backendData._count?.interviews || 5, // Mock remaining positions
- applicationStatus: 'not_applied', // Default status
+ applicationStatus: "not_applied", // Default status
tags: generateJobTags(backendData),
deadline: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30 days from now
};
@@ -107,11 +111,11 @@ export const mapJobList = (backendList) => {
// Map job type
const mapJobType = (type) => {
const typeMap = {
- 'FULLTIME': '全职',
- 'PARTTIME': '兼职',
- 'INTERNSHIP': '实习',
- 'CONTRACT': '合同制',
- 'REMOTE': '远程',
+ FULLTIME: "全职",
+ PARTTIME: "兼职",
+ INTERNSHIP: "实习",
+ CONTRACT: "合同制",
+ REMOTE: "远程",
};
return typeMap[type] || type;
};
@@ -119,21 +123,21 @@ const mapJobType = (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('弹性工作');
+ 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
+ companyName: backendData.name, // Alias for compatibility
description: backendData.description,
industry: backendData.industry,
scale: mapCompanyScale(backendData.scale),
@@ -155,10 +159,10 @@ export const mapCompanyList = (backendList) => {
// Map company scale
const mapCompanyScale = (scale) => {
const scaleMap = {
- 'SMALL': '50人以下',
- 'MEDIUM': '50-200人',
- 'LARGE': '200-1000人',
- 'ENTERPRISE': '1000人以上',
+ SMALL: "50人以下",
+ MEDIUM: "50-200人",
+ LARGE: "200-1000人",
+ ENTERPRISE: "1000人以上",
};
return scaleMap[scale] || scale;
};
@@ -166,7 +170,7 @@ const mapCompanyScale = (scale) => {
// Map resume data
export const mapResume = (backendData) => {
if (!backendData) return null;
-
+
return {
id: backendData.id,
title: backendData.title,
@@ -183,11 +187,11 @@ export const mapResume = (backendData) => {
// 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'),
+ interviewTime: new Date(backendData.scheduledAt).toLocaleString("zh-CN"),
type: backendData.type,
status: backendData.status,
location: backendData.location,
@@ -196,8 +200,8 @@ export const mapInterview = (backendData) => {
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 || '',
+ company: backendData.job?.company?.name || "",
+ position: backendData.job?.title || "",
// Map status for frontend
statusText: mapInterviewStatus(backendData.status, backendData.result),
};
@@ -211,16 +215,16 @@ export const mapInterviewList = (backendList) => {
// Map interview status
const mapInterviewStatus = (status, result) => {
- if (status === 'COMPLETED') {
- if (result === 'PASS' || result === 'OFFER') return '面试成功';
- if (result === 'FAIL') return '面试失败';
- return '已完成';
+ if (status === "COMPLETED") {
+ if (result === "PASS" || result === "OFFER") return "面试成功";
+ if (result === "FAIL") return "面试失败";
+ return "已完成";
}
-
+
const statusMap = {
- 'SCHEDULED': '待面试',
- 'CANCELLED': '已取消',
- 'NO_SHOW': '未到场',
+ SCHEDULED: "待面试",
+ CANCELLED: "已取消",
+ NO_SHOW: "未到场",
};
return statusMap[status] || status;
};
@@ -228,7 +232,7 @@ const mapInterviewStatus = (status, result) => {
// Map enrollment data
export const mapEnrollment = (backendData) => {
if (!backendData) return null;
-
+
return {
id: backendData.id,
courseId: backendData.courseId,
@@ -248,9 +252,9 @@ export const mapEnrollment = (backendData) => {
// Map enrollment status
const mapEnrollmentStatus = (status) => {
const statusMap = {
- 'NOT_STARTED': '未开始',
- 'IN_PROGRESS': '学习中',
- 'COMPLETED': '已完成',
+ NOT_STARTED: "未开始",
+ IN_PROGRESS: "学习中",
+ COMPLETED: "已完成",
};
return statusMap[status] || status;
};
@@ -258,19 +262,21 @@ const mapEnrollmentStatus = (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
+ 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,
+ teacher: backendData.teacher
+ ? {
+ id: backendData.teacher.id,
+ name: backendData.teacher.realName,
+ }
+ : null,
studentCount: backendData._count?.students || 0,
students: backendData.students ? mapStudentList(backendData.students) : [],
};
@@ -279,7 +285,7 @@ export const mapClass = (backendData) => {
// Map stage data
export const mapStage = (backendData) => {
if (!backendData) return null;
-
+
return {
id: backendData.id,
name: backendData.name,
@@ -297,7 +303,7 @@ export const mapStage = (backendData) => {
// Map learning record
export const mapLearningRecord = (backendData) => {
if (!backendData) return null;
-
+
return {
id: backendData.id,
studentId: backendData.studentId,
@@ -312,33 +318,35 @@ export const mapLearningRecord = (backendData) => {
// 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
+ 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',
+ mbti: studentData.mbtiType || "ENTP",
},
- courses: studentData.enrollments ?
- studentData.enrollments.map(e => e.course?.name).filter(Boolean) : [],
- mbtiReport: studentData.mbtiReport || generateMockMBTIReport(studentData.mbtiType),
+ 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'],
+ 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"],
};
};
@@ -360,4 +368,4 @@ export default {
mapStage,
mapLearningRecord,
mapProfile,
-};
\ No newline at end of file
+};
diff --git a/vite.config.js b/vite.config.js
index 3516d09..4d093b7 100644
--- a/vite.config.js
+++ b/vite.config.js
@@ -57,6 +57,7 @@ export default defineConfig({
resolve: {
alias: {
"@": "/src",
+ "@/services": "/src/services",
},
},
});