feat: 🎸 对接了企业岗位的部分信息
This commit is contained in:
@@ -15,7 +15,7 @@
|
|||||||
"deploy:prod": "vercel --prod"
|
"deploy:prod": "vercel --prod"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@arco-design/web-react": "^2.66.4",
|
"@arco-design/web-react": "^2.66.5",
|
||||||
"@reduxjs/toolkit": "^2.8.2",
|
"@reduxjs/toolkit": "^2.8.2",
|
||||||
"axios": "^1.11.0",
|
"axios": "^1.11.0",
|
||||||
"echarts": "^6.0.0",
|
"echarts": "^6.0.0",
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -9,8 +9,8 @@ importers:
|
|||||||
.:
|
.:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@arco-design/web-react':
|
'@arco-design/web-react':
|
||||||
specifier: ^2.66.4
|
specifier: ^2.66.5
|
||||||
version: 2.66.4(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
version: 2.66.5(@types/react@19.1.10)(react-dom@19.1.1(react@19.1.1))(react@19.1.1)
|
||||||
'@reduxjs/toolkit':
|
'@reduxjs/toolkit':
|
||||||
specifier: ^2.8.2
|
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)
|
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':
|
'@arco-design/color@0.4.0':
|
||||||
resolution: {integrity: sha512-s7p9MSwJgHeL8DwcATaXvWT3m2SigKpxx4JA1BGPHL4gfvaQsmQfrLBDpjOJFJuJ2jG2dMt3R3P8Pm9E65q18g==}
|
resolution: {integrity: sha512-s7p9MSwJgHeL8DwcATaXvWT3m2SigKpxx4JA1BGPHL4gfvaQsmQfrLBDpjOJFJuJ2jG2dMt3R3P8Pm9E65q18g==}
|
||||||
|
|
||||||
'@arco-design/web-react@2.66.4':
|
'@arco-design/web-react@2.66.5':
|
||||||
resolution: {integrity: sha512-vl7sJBLvbVyJhYRPoQ8kHc8BuXNkJIXca5h9ync2J1TuKglFMLNbQwjIvJLW3ciabqTZ5g1O7H1GQ+lLIEMsWA==}
|
resolution: {integrity: sha512-ity0kG+B6pmuJ2/Zh3wUtBV78XxWmRtGEwazL8f4KAjoQpMkisgLMXibUpAGfcqph3vycNFq4yHgHujjgwrJMQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
react: '>=16'
|
react: '>=16'
|
||||||
react-dom: '>=16'
|
react-dom: '>=16'
|
||||||
@@ -1334,7 +1334,7 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
color: 3.2.1
|
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:
|
dependencies:
|
||||||
'@arco-design/color': 0.4.0
|
'@arco-design/color': 0.4.0
|
||||||
'@babel/runtime': 7.28.3
|
'@babel/runtime': 7.28.3
|
||||||
|
|||||||
147
src/components/Toast/README.md
Normal file
147
src/components/Toast/README.md
Normal file
@@ -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 (
|
||||||
|
<ToastProvider>
|
||||||
|
<YourComponent />
|
||||||
|
</ToastProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function YourComponent() {
|
||||||
|
const { addToast, removeAllToasts } = useToast();
|
||||||
|
|
||||||
|
const handleClick = () => {
|
||||||
|
addToast({
|
||||||
|
type: "success",
|
||||||
|
message: "这是通过 Hook 创建的 Toast",
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<button onClick={handleClick}>显示 Toast</button>
|
||||||
|
<button onClick={removeAllToasts}>清空所有 Toast</button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 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. 支持暗色主题自动切换
|
||||||
178
src/components/Toast/index.css
Normal file
178
src/components/Toast/index.css
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
259
src/components/Toast/index.jsx
Normal file
259
src/components/Toast/index.jsx
Normal file
@@ -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 (
|
||||||
|
<div
|
||||||
|
className={`toast-item toast-${toast.type} ${
|
||||||
|
isVisible ? "toast-visible" : ""
|
||||||
|
} ${isRemoving ? "toast-removing" : ""}`}
|
||||||
|
onClick={toast.closable !== false ? handleRemove : undefined}
|
||||||
|
>
|
||||||
|
<div className="toast-content">
|
||||||
|
{toast.title && <div className="toast-title">{toast.title}</div>}
|
||||||
|
<div className="toast-message">{toast.message}</div>
|
||||||
|
</div>
|
||||||
|
{toast.closable !== false && (
|
||||||
|
<button className="toast-close" onClick={handleRemove}>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Toast 容器组件
|
||||||
|
const ToastContainer = () => {
|
||||||
|
const { toasts, removeToast } = useContext(ToastContext);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="toast-container">
|
||||||
|
{toasts.map((toast) => (
|
||||||
|
<ToastItem key={toast.id} toast={toast} onRemove={removeToast} />
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
<ToastContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
<ToastContainer />
|
||||||
|
</ToastContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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(
|
||||||
|
<div className="toast-container">
|
||||||
|
{this.toasts.map((toast) => (
|
||||||
|
<ToastItem
|
||||||
|
key={toast.id}
|
||||||
|
toast={toast}
|
||||||
|
onRemove={this.remove.bind(this)}
|
||||||
|
/>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加 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;
|
||||||
@@ -10,6 +10,7 @@ const InputSearch = Input.Search;
|
|||||||
const { userResumes } = mockData;
|
const { userResumes } = mockData;
|
||||||
|
|
||||||
export default ({ visible, onClose, data }) => {
|
export default ({ visible, onClose, data }) => {
|
||||||
|
console.log(data);
|
||||||
const [resumeModalShow, setResumeModalShow] = useState(false);
|
const [resumeModalShow, setResumeModalShow] = useState(false);
|
||||||
const [resumeInfoModalShow, setResumeInfoModalShow] = useState(false);
|
const [resumeInfoModalShow, setResumeInfoModalShow] = useState(false);
|
||||||
|
|
||||||
@@ -90,7 +91,7 @@ export default ({ visible, onClose, data }) => {
|
|||||||
{data?.position}
|
{data?.position}
|
||||||
</span>
|
</span>
|
||||||
<span className="job-info-modal-content-position-info-num">
|
<span className="job-info-modal-content-position-info-num">
|
||||||
该岗位仅剩9人
|
该岗位仅剩{data?.remainingPositions}人
|
||||||
</span>
|
</span>
|
||||||
<span className="job-info-modal-content-position-info-salary">
|
<span className="job-info-modal-content-position-info-salary">
|
||||||
{data?.salary}
|
{data?.salary}
|
||||||
@@ -105,15 +106,19 @@ export default ({ visible, onClose, data }) => {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
)}
|
)}
|
||||||
{data?.details?.description && (
|
{data?.description && (
|
||||||
<div className="job-info-modal-content-position-info-description">
|
<div className="job-info-modal-content-position-info-description">
|
||||||
<p className="description-title">岗位描述</p>
|
<p className="description-title">岗位描述</p>
|
||||||
<p className="description-content">
|
<p className="description-content">{data?.description}</p>
|
||||||
{data?.details?.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
{data?.details?.requirements?.length > 0 && (
|
{data?.requirements && (
|
||||||
|
<div className="job-info-modal-content-position-info-description">
|
||||||
|
<p className="description-title">岗位要求</p>
|
||||||
|
<p className="description-content">{data?.requirements}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{/* {data?.details?.requirements?.length > 0 && (
|
||||||
<div className="job-info-modal-content-position-info-requirements">
|
<div className="job-info-modal-content-position-info-requirements">
|
||||||
<p className="requirements-title">岗位要求</p>
|
<p className="requirements-title">岗位要求</p>
|
||||||
<ul className="requirements-content">
|
<ul className="requirements-content">
|
||||||
@@ -124,12 +129,12 @@ export default ({ visible, onClose, data }) => {
|
|||||||
))}
|
))}
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)} */}
|
||||||
{data?.details?.companyInfo && (
|
{data?.company?.industry && (
|
||||||
<div className="job-info-modal-content-position-info-companyInfo">
|
<div className="job-info-modal-content-position-info-companyInfo">
|
||||||
<p className="companyInfo-title">公司介绍</p>
|
<p className="companyInfo-title">公司介绍</p>
|
||||||
<p className="companyInfo-content">
|
<p className="companyInfo-content">
|
||||||
{data?.details?.companyInfo}
|
{data?.company?.industry}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -1,17 +1,23 @@
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useNavigate } from "react-router-dom";
|
import toast from "@/components/Toast";
|
||||||
import JobInfoModal from "../JobInfoModal";
|
import JobInfoModal from "../JobInfoModal";
|
||||||
|
import { getJobsDetail } from "@/services";
|
||||||
|
import { mapJob } from "@/utils/dataMapper";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
export default ({ className = "", data = [], backgroundColor = "#FFFFFF" }) => {
|
export default ({ className = "", data = [], backgroundColor = "#FFFFFF" }) => {
|
||||||
const navigate = useNavigate();
|
|
||||||
const [jobInfoData, setJobInfoData] = useState(undefined);
|
const [jobInfoData, setJobInfoData] = useState(undefined);
|
||||||
const [jobInfoModalVisible, setJobInfoModalVisible] = useState(false);
|
const [jobInfoModalVisible, setJobInfoModalVisible] = useState(false);
|
||||||
|
|
||||||
const handleJobClick = (e, item) => {
|
const handleJobClick = async (e, item) => {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
const res = await getJobsDetail(item.id);
|
||||||
|
if (res.success) {
|
||||||
|
setJobInfoData(mapJob(res.data));
|
||||||
setJobInfoModalVisible(true);
|
setJobInfoModalVisible(true);
|
||||||
setJobInfoData(item);
|
} else {
|
||||||
|
toast.error(res.message);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const onClickJobInfoModalClose = () => {
|
const onClickJobInfoModalClose = () => {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import StudyStatus from "./components/StudyStatus";
|
|||||||
import Rank from "@/components/Rank";
|
import Rank from "@/components/Rank";
|
||||||
import StageProgress from "@/components/StageProgress";
|
import StageProgress from "@/components/StageProgress";
|
||||||
import TaskList from "./components/TaskList";
|
import TaskList from "./components/TaskList";
|
||||||
import { getClassRanking, getLearningProgressSummary } from "@/services";
|
import { getClassRanking, getStudyRecordsProgress } from "@/services";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
const Dashboard = () => {
|
const Dashboard = () => {
|
||||||
@@ -14,7 +14,7 @@ const Dashboard = () => {
|
|||||||
|
|
||||||
// 获取整体学习进度
|
// 获取整体学习进度
|
||||||
const queryLearningProgressSummary = async () => {
|
const queryLearningProgressSummary = async () => {
|
||||||
const res = await getLearningProgressSummary({ period: "semester" });
|
const res = await getStudyRecordsProgress();
|
||||||
console.log("learningProgressSummary", res);
|
console.log("learningProgressSummary", res);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import Rank from "@/components/Rank";
|
|||||||
import StageProgress from "@/components/StageProgress";
|
import StageProgress from "@/components/StageProgress";
|
||||||
import StudyStudes from "./components/StudyStudes";
|
import StudyStudes from "./components/StudyStudes";
|
||||||
import { updateStudentInfo } from "@/store/slices/studentSlice";
|
import { updateStudentInfo } from "@/store/slices/studentSlice";
|
||||||
import { getClassRanking, getLearningProgressSummary } from "@/services";
|
import { getClassRanking, getStudyRecordsProgress } from "@/services";
|
||||||
import "./index.css";
|
import "./index.css";
|
||||||
|
|
||||||
const PersonalProfile = () => {
|
const PersonalProfile = () => {
|
||||||
@@ -13,7 +13,7 @@ const PersonalProfile = () => {
|
|||||||
const [rankData, setRankData] = useState([]); // 班级排名数据
|
const [rankData, setRankData] = useState([]); // 班级排名数据
|
||||||
|
|
||||||
const queryLearningProgressSummary = async () => {
|
const queryLearningProgressSummary = async () => {
|
||||||
const res = await getLearningProgressSummary({ period: "semester" });
|
const res = await getStudyRecordsProgress();
|
||||||
console.log("learningProgressSummary", res);
|
console.log("learningProgressSummary", res);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,15 @@ export async function getJobsList(params) {
|
|||||||
params,
|
params,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取企业内推岗位详情
|
||||||
|
export async function getJobsDetail(id) {
|
||||||
|
return request({
|
||||||
|
url: `/api/jobs/${id}`,
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 获取企业内推岗位面试
|
// 获取企业内推岗位面试
|
||||||
export async function getInterviewsList(params) {
|
export async function getInterviewsList(params) {
|
||||||
return request({
|
return request({
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
import request from "@/utils/request";
|
import request from "@/utils/request";
|
||||||
|
|
||||||
// 获取当前学生的学习进度汇总
|
// 获取学生的整体学习进度
|
||||||
export async function getLearningProgressSummary(queryParams = {}) {
|
export async function getStudyRecordsProgress() {
|
||||||
return request({
|
return request({
|
||||||
url: `/api/dashboard/learning-summary`,
|
url: `/api/study-records/progress`,
|
||||||
method: "GET",
|
method: "GET",
|
||||||
params: queryParams,
|
|
||||||
namespace: "dashboardLoading",
|
namespace: "dashboardLoading",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
// 统一的API服务接口 - 基于当前认证用户
|
// 统一的API服务接口 - 基于当前认证用户
|
||||||
import {
|
import {
|
||||||
getLearningProgressSummary,
|
getStudyRecordsProgress,
|
||||||
getMyTasks,
|
getMyTasks,
|
||||||
getClassRanking,
|
getClassRanking,
|
||||||
} from "./dashboard";
|
} from "./dashboard";
|
||||||
import { getProjectsList } from "./projectLibrary";
|
import { getProjectsList } from "./projectLibrary";
|
||||||
import { getJobsList, getInterviewsList } from "./companyJobs";
|
import { getJobsList, getJobsDetail, getInterviewsList } from "./companyJobs";
|
||||||
import { getLoginStudentInfo } from "./global";
|
import { getLoginStudentInfo } from "./global";
|
||||||
import {
|
import {
|
||||||
getDashboardStatistics,
|
getDashboardStatistics,
|
||||||
@@ -19,7 +19,7 @@ export {
|
|||||||
// 仪表盘相关
|
// 仪表盘相关
|
||||||
getMyTasks, // 获取我的任务
|
getMyTasks, // 获取我的任务
|
||||||
getDashboardStatistics, // 获取当前学生仪表盘统计
|
getDashboardStatistics, // 获取当前学生仪表盘统计
|
||||||
getLearningProgressSummary, // 获取当前学生学习进度汇总
|
getStudyRecordsProgress, // 获取学生的整体学习进度
|
||||||
|
|
||||||
// 排名相关
|
// 排名相关
|
||||||
getClassRanking, // 获取当前学生班级排名
|
getClassRanking, // 获取当前学生班级排名
|
||||||
@@ -35,6 +35,7 @@ export {
|
|||||||
|
|
||||||
// 求职相关
|
// 求职相关
|
||||||
getJobsList, // 获取岗位列表
|
getJobsList, // 获取岗位列表
|
||||||
|
getJobsDetail, // 岗位详情
|
||||||
getInterviewsList, // 获取面试列表
|
getInterviewsList, // 获取面试列表
|
||||||
getResumesList, // 获取简历列表
|
getResumesList, // 获取简历列表
|
||||||
getResumesDetail, // 获取简历详情
|
getResumesDetail, // 获取简历详情
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export const mapStudent = (backendData) => {
|
|||||||
id: backendData.id,
|
id: backendData.id,
|
||||||
name: backendData.realName, // realName -> name
|
name: backendData.realName, // realName -> name
|
||||||
studentId: backendData.studentNo, // studentNo -> studentId
|
studentId: backendData.studentNo, // studentNo -> studentId
|
||||||
gender: backendData.gender === 'MALE' ? '男' : '女',
|
gender: backendData.gender === "MALE" ? "男" : "女",
|
||||||
school: backendData.school,
|
school: backendData.school,
|
||||||
major: backendData.major,
|
major: backendData.major,
|
||||||
enrollDate: backendData.enrollDate,
|
enrollDate: backendData.enrollDate,
|
||||||
@@ -45,12 +45,15 @@ export const mapCourse = (backendData) => {
|
|||||||
credits: backendData.credits,
|
credits: backendData.credits,
|
||||||
hours: backendData.hours,
|
hours: backendData.hours,
|
||||||
isAiCourse: backendData.isAiCourse,
|
isAiCourse: backendData.isAiCourse,
|
||||||
teacher: backendData.teacher ? {
|
teacher: backendData.teacher
|
||||||
|
? {
|
||||||
id: backendData.teacher.id,
|
id: backendData.teacher.id,
|
||||||
name: backendData.teacher.realName,
|
name: backendData.teacher.realName,
|
||||||
} : null,
|
}
|
||||||
|
: null,
|
||||||
stage: backendData.stage,
|
stage: backendData.stage,
|
||||||
enrollmentCount: backendData.enrollmentCount || backendData._count?.enrollments || 0,
|
enrollmentCount:
|
||||||
|
backendData.enrollmentCount || backendData._count?.enrollments || 0,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,7 +68,7 @@ export const mapJob = (backendData) => {
|
|||||||
if (!backendData) return null;
|
if (!backendData) return null;
|
||||||
|
|
||||||
// Format salary range
|
// Format salary range
|
||||||
let salary = '面议';
|
let salary = "面议";
|
||||||
if (backendData.salaryMin && backendData.salaryMax) {
|
if (backendData.salaryMin && backendData.salaryMax) {
|
||||||
const min = Math.floor(backendData.salaryMin / 1000);
|
const min = Math.floor(backendData.salaryMin / 1000);
|
||||||
const max = Math.floor(backendData.salaryMax / 1000);
|
const max = Math.floor(backendData.salaryMax / 1000);
|
||||||
@@ -74,14 +77,15 @@ export const mapJob = (backendData) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
id: backendData.id,
|
id: backendData.id,
|
||||||
|
company: backendData.company,
|
||||||
position: backendData.title, // title -> position
|
position: backendData.title, // title -> position
|
||||||
description: backendData.description,
|
description: backendData.description,
|
||||||
requirements: backendData.requirements,
|
requirements: backendData.requirements,
|
||||||
responsibilities: backendData.responsibilities,
|
responsibilities: backendData.responsibilities,
|
||||||
company: backendData.company?.name || '',
|
companyName: backendData.company?.name || "",
|
||||||
companyId: backendData.companyId,
|
companyId: backendData.companyId,
|
||||||
type: mapJobType(backendData.type),
|
type: mapJobType(backendData.type),
|
||||||
jobType: backendData.type === 'INTERNSHIP' ? 'internship' : 'fulltime',
|
jobType: backendData.type === "INTERNSHIP" ? "internship" : "fulltime",
|
||||||
level: backendData.level,
|
level: backendData.level,
|
||||||
location: backendData.location,
|
location: backendData.location,
|
||||||
salary: salary,
|
salary: salary,
|
||||||
@@ -90,9 +94,9 @@ export const mapJob = (backendData) => {
|
|||||||
benefits: backendData.benefits || [],
|
benefits: backendData.benefits || [],
|
||||||
skills: backendData.skills || [],
|
skills: backendData.skills || [],
|
||||||
isActive: backendData.isActive,
|
isActive: backendData.isActive,
|
||||||
status: backendData.isActive ? 'available' : 'closed',
|
status: backendData.isActive ? "available" : "closed",
|
||||||
remainingPositions: backendData._count?.interviews || 5, // Mock remaining positions
|
remainingPositions: backendData._count?.interviews || 5, // Mock remaining positions
|
||||||
applicationStatus: 'not_applied', // Default status
|
applicationStatus: "not_applied", // Default status
|
||||||
tags: generateJobTags(backendData),
|
tags: generateJobTags(backendData),
|
||||||
deadline: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000).toISOString(), // 30 days from now
|
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
|
// Map job type
|
||||||
const mapJobType = (type) => {
|
const mapJobType = (type) => {
|
||||||
const typeMap = {
|
const typeMap = {
|
||||||
'FULLTIME': '全职',
|
FULLTIME: "全职",
|
||||||
'PARTTIME': '兼职',
|
PARTTIME: "兼职",
|
||||||
'INTERNSHIP': '实习',
|
INTERNSHIP: "实习",
|
||||||
'CONTRACT': '合同制',
|
CONTRACT: "合同制",
|
||||||
'REMOTE': '远程',
|
REMOTE: "远程",
|
||||||
};
|
};
|
||||||
return typeMap[type] || type;
|
return typeMap[type] || type;
|
||||||
};
|
};
|
||||||
@@ -119,10 +123,10 @@ const mapJobType = (type) => {
|
|||||||
// Generate job tags
|
// Generate job tags
|
||||||
const generateJobTags = (job) => {
|
const generateJobTags = (job) => {
|
||||||
const tags = [];
|
const tags = [];
|
||||||
if (job.location) tags.push(job.location.split('市')[0] + '市');
|
if (job.location) tags.push(job.location.split("市")[0] + "市");
|
||||||
if (job.type === 'FULLTIME') tags.push('五险一金');
|
if (job.type === "FULLTIME") tags.push("五险一金");
|
||||||
if (job.benefits?.includes('双休')) tags.push('双休');
|
if (job.benefits?.includes("双休")) tags.push("双休");
|
||||||
if (job.benefits?.includes('弹性工作')) tags.push('弹性工作');
|
if (job.benefits?.includes("弹性工作")) tags.push("弹性工作");
|
||||||
return tags.slice(0, 4); // Max 4 tags
|
return tags.slice(0, 4); // Max 4 tags
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -155,10 +159,10 @@ export const mapCompanyList = (backendList) => {
|
|||||||
// Map company scale
|
// Map company scale
|
||||||
const mapCompanyScale = (scale) => {
|
const mapCompanyScale = (scale) => {
|
||||||
const scaleMap = {
|
const scaleMap = {
|
||||||
'SMALL': '50人以下',
|
SMALL: "50人以下",
|
||||||
'MEDIUM': '50-200人',
|
MEDIUM: "50-200人",
|
||||||
'LARGE': '200-1000人',
|
LARGE: "200-1000人",
|
||||||
'ENTERPRISE': '1000人以上',
|
ENTERPRISE: "1000人以上",
|
||||||
};
|
};
|
||||||
return scaleMap[scale] || scale;
|
return scaleMap[scale] || scale;
|
||||||
};
|
};
|
||||||
@@ -187,7 +191,7 @@ export const mapInterview = (backendData) => {
|
|||||||
return {
|
return {
|
||||||
id: backendData.id,
|
id: backendData.id,
|
||||||
scheduledAt: backendData.scheduledAt,
|
scheduledAt: backendData.scheduledAt,
|
||||||
interviewTime: new Date(backendData.scheduledAt).toLocaleString('zh-CN'),
|
interviewTime: new Date(backendData.scheduledAt).toLocaleString("zh-CN"),
|
||||||
type: backendData.type,
|
type: backendData.type,
|
||||||
status: backendData.status,
|
status: backendData.status,
|
||||||
location: backendData.location,
|
location: backendData.location,
|
||||||
@@ -196,8 +200,8 @@ export const mapInterview = (backendData) => {
|
|||||||
result: backendData.result,
|
result: backendData.result,
|
||||||
student: backendData.student ? mapStudent(backendData.student) : null,
|
student: backendData.student ? mapStudent(backendData.student) : null,
|
||||||
job: backendData.job ? mapJob(backendData.job) : null,
|
job: backendData.job ? mapJob(backendData.job) : null,
|
||||||
company: backendData.job?.company?.name || '',
|
company: backendData.job?.company?.name || "",
|
||||||
position: backendData.job?.title || '',
|
position: backendData.job?.title || "",
|
||||||
// Map status for frontend
|
// Map status for frontend
|
||||||
statusText: mapInterviewStatus(backendData.status, backendData.result),
|
statusText: mapInterviewStatus(backendData.status, backendData.result),
|
||||||
};
|
};
|
||||||
@@ -211,16 +215,16 @@ export const mapInterviewList = (backendList) => {
|
|||||||
|
|
||||||
// Map interview status
|
// Map interview status
|
||||||
const mapInterviewStatus = (status, result) => {
|
const mapInterviewStatus = (status, result) => {
|
||||||
if (status === 'COMPLETED') {
|
if (status === "COMPLETED") {
|
||||||
if (result === 'PASS' || result === 'OFFER') return '面试成功';
|
if (result === "PASS" || result === "OFFER") return "面试成功";
|
||||||
if (result === 'FAIL') return '面试失败';
|
if (result === "FAIL") return "面试失败";
|
||||||
return '已完成';
|
return "已完成";
|
||||||
}
|
}
|
||||||
|
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
'SCHEDULED': '待面试',
|
SCHEDULED: "待面试",
|
||||||
'CANCELLED': '已取消',
|
CANCELLED: "已取消",
|
||||||
'NO_SHOW': '未到场',
|
NO_SHOW: "未到场",
|
||||||
};
|
};
|
||||||
return statusMap[status] || status;
|
return statusMap[status] || status;
|
||||||
};
|
};
|
||||||
@@ -248,9 +252,9 @@ export const mapEnrollment = (backendData) => {
|
|||||||
// Map enrollment status
|
// Map enrollment status
|
||||||
const mapEnrollmentStatus = (status) => {
|
const mapEnrollmentStatus = (status) => {
|
||||||
const statusMap = {
|
const statusMap = {
|
||||||
'NOT_STARTED': '未开始',
|
NOT_STARTED: "未开始",
|
||||||
'IN_PROGRESS': '学习中',
|
IN_PROGRESS: "学习中",
|
||||||
'COMPLETED': '已完成',
|
COMPLETED: "已完成",
|
||||||
};
|
};
|
||||||
return statusMap[status] || status;
|
return statusMap[status] || status;
|
||||||
};
|
};
|
||||||
@@ -267,10 +271,12 @@ export const mapClass = (backendData) => {
|
|||||||
startDate: backendData.startDate,
|
startDate: backendData.startDate,
|
||||||
endDate: backendData.endDate,
|
endDate: backendData.endDate,
|
||||||
isActive: backendData.isActive,
|
isActive: backendData.isActive,
|
||||||
teacher: backendData.teacher ? {
|
teacher: backendData.teacher
|
||||||
|
? {
|
||||||
id: backendData.teacher.id,
|
id: backendData.teacher.id,
|
||||||
name: backendData.teacher.realName,
|
name: backendData.teacher.realName,
|
||||||
} : null,
|
}
|
||||||
|
: null,
|
||||||
studentCount: backendData._count?.students || 0,
|
studentCount: backendData._count?.students || 0,
|
||||||
students: backendData.students ? mapStudentList(backendData.students) : [],
|
students: backendData.students ? mapStudentList(backendData.students) : [],
|
||||||
};
|
};
|
||||||
@@ -317,28 +323,30 @@ export const mapProfile = (studentData) => {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
...mapped,
|
...mapped,
|
||||||
avatar: '/api/placeholder/80/80', // Default avatar
|
avatar: "/api/placeholder/80/80", // Default avatar
|
||||||
badges: {
|
badges: {
|
||||||
credits: 84, // Mock data, should come from backend
|
credits: 84, // Mock data, should come from backend
|
||||||
classRank: 9, // 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 ?
|
courses: studentData.enrollments
|
||||||
studentData.enrollments.map(e => e.course?.name).filter(Boolean) : [],
|
? studentData.enrollments.map((e) => e.course?.name).filter(Boolean)
|
||||||
mbtiReport: studentData.mbtiReport || generateMockMBTIReport(studentData.mbtiType),
|
: [],
|
||||||
|
mbtiReport:
|
||||||
|
studentData.mbtiReport || generateMockMBTIReport(studentData.mbtiType),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Generate mock MBTI report (temporary until backend provides)
|
// Generate mock MBTI report (temporary until backend provides)
|
||||||
const generateMockMBTIReport = (type) => {
|
const generateMockMBTIReport = (type) => {
|
||||||
return {
|
return {
|
||||||
type: type || 'ENTP',
|
type: type || "ENTP",
|
||||||
title: 'Personality Type',
|
title: "Personality Type",
|
||||||
description: 'Your personality type description',
|
description: "Your personality type description",
|
||||||
characteristics: ['Creative', 'Analytical', 'Strategic'],
|
characteristics: ["Creative", "Analytical", "Strategic"],
|
||||||
strengths: ['Problem-solving', 'Leadership', 'Innovation'],
|
strengths: ["Problem-solving", "Leadership", "Innovation"],
|
||||||
recommendations: ['Focus on execution', 'Develop patience', 'Listen more'],
|
recommendations: ["Focus on execution", "Develop patience", "Listen more"],
|
||||||
careerSuggestions: ['Product Manager', 'Consultant', 'Entrepreneur'],
|
careerSuggestions: ["Product Manager", "Consultant", "Entrepreneur"],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,7 @@ export default defineConfig({
|
|||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
"@": "/src",
|
"@": "/src",
|
||||||
|
"@/services": "/src/services",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user