first commit

This commit is contained in:
2026-03-17 14:30:02 +08:00
commit 313f5b3550
136 changed files with 57671 additions and 0 deletions

174
mock/appointment.ts Normal file
View File

@@ -0,0 +1,174 @@
const allAppointments = [
// 已预约
{ id: 1, name: '能力提升面试', category: '模拟面试', time: '2025.08.10', session: '14:00:00-16:00:00', teacher: '李老师', location: '教室1', status: 'booked' },
{ id: 2, name: '能力提升面试', category: '模拟面试', time: '2025.08.10', session: '14:00:00-16:00:00', teacher: '李老师', location: '教室1', status: 'booked' },
{ id: 3, name: '能力提升面试', category: '模拟面试', time: '2025.08.10', session: '14:00:00-16:00:00', teacher: '李老师', location: '教室1', status: 'booked' },
{ id: 4, name: '职业规划咨询', category: '职业咨询', time: '2025.08.12', session: '09:00:00-11:00:00', teacher: '王老师', location: '会议室A', status: 'booked' },
{ id: 5, name: '简历修改指导', category: '简历指导', time: '2025.08.15', session: '10:00:00-12:00:00', teacher: '张老师', location: '教室3', status: 'booked' },
// 预约待确认
{ id: 6, name: '模拟群面练习', category: '模拟面试', time: '2025.08.18', session: '14:00:00-16:00:00', teacher: '赵老师', location: '教室2', status: 'pending' },
{ id: 7, name: '行业分析讲座', category: '讲座', time: '2025.08.20', session: '09:00:00-11:00:00', teacher: '刘老师', location: '报告厅', status: 'pending' },
{ id: 8, name: '职业规划咨询', category: '职业咨询', time: '2025.08.22', session: '14:00:00-16:00:00', teacher: '王老师', location: '会议室A', status: 'pending' },
// 已完成
{ id: 9, name: '能力提升面试', category: '模拟面试', time: '2025.07.20', session: '14:00:00-16:00:00', teacher: '李老师', location: '教室1', status: 'completed' },
{ id: 10, name: '简历修改指导', category: '简历指导', time: '2025.07.15', session: '10:00:00-12:00:00', teacher: '张老师', location: '教室3', status: 'completed' },
{ id: 11, name: '职业规划咨询', category: '职业咨询', time: '2025.07.10', session: '09:00:00-11:00:00', teacher: '王老师', location: '会议室A', status: 'completed' },
{ id: 12, name: '模拟群面练习', category: '模拟面试', time: '2025.07.05', session: '14:00:00-16:00:00', teacher: '赵老师', location: '教室2', status: 'completed' },
// 已取消
{ id: 13, name: '行业分析讲座', category: '讲座', time: '2025.07.25', session: '09:00:00-11:00:00', teacher: '刘老师', location: '报告厅', status: 'cancelled' },
{ id: 14, name: '能力提升面试', category: '模拟面试', time: '2025.07.22', session: '14:00:00-16:00:00', teacher: '李老师', location: '教室1', status: 'cancelled' },
];
// 预约管理数据
const specialManage = [
{ id: 101, name: '能力提升面试', category: '模拟面试', startTime: '2025.09.01', endTime: '2026.10.01', remaining: 20, total: 300, sessions: 20, teacher: '李老师', status: 'ongoing' },
{ id: 102, name: '能力提升面试', category: '模拟面试', startTime: '2025.09.01', endTime: '2026.10.01', remaining: 20, total: 300, sessions: 20, teacher: '李老师', status: 'ended' },
{ id: 103, name: '能力提升面试', category: '模拟面试', startTime: '2025.09.01', endTime: '2026.10.01', remaining: 0, total: 300, sessions: 20, teacher: '李老师', status: 'ongoing' },
];
const categories = ['模拟面试', '职业咨询', '简历指导'];
const teachers = ['李老师', '王老师', '张老师', '赵老师', '刘老师', '陈老师', '周老师', '吴老师'];
const statuses = ['ongoing', 'notStarted', 'ongoing', 'ongoing', 'ongoing', 'ongoing', 'ended', 'ongoing'];
const names = ['能力提升面试', '职业规划咨询', '简历修改指导', '模拟群面练习', '行业分析讲座', '求职技巧培训'];
const manageAppointments = [
...specialManage,
...Array.from({ length: 97 }, (_, i) => ({
id: 104 + i,
name: names[i % names.length],
category: categories[i % categories.length],
startTime: '2025.09.01',
endTime: '2026.10.01',
remaining: 20 + (i % 50),
total: 300,
sessions: 10 + (i % 20),
teacher: teachers[i % teachers.length],
status: statuses[i % statuses.length],
})),
];
// 用户映射userId=2 的用户没有任何预约数据
const userAppointments: Record<number, number[]> = {
1: allAppointments.map((a) => a.id), // 默认用户有所有数据
2: [], // 空白用户
};
export default {
// 获取预约列表
'GET /api/appointment/list': (req: any, res: any) => {
const { status, name, student, date, page = 1, pageSize = 10, userId = 1 } = req.query;
const uid = Number(userId);
const userIds = userAppointments[uid] ?? userAppointments[1];
let filtered = allAppointments.filter((item) => userIds.includes(item.id));
// 按状态筛选
if (status) {
filtered = filtered.filter((item) => item.status === status);
}
// 按预约名称模糊搜索
if (name) {
filtered = filtered.filter((item) => item.name.includes(name));
}
// 按日期筛选
if (date) {
filtered = filtered.filter((item) => item.time === date);
}
const total = filtered.length;
const start = (Number(page) - 1) * Number(pageSize);
const end = start + Number(pageSize);
const list = filtered.slice(start, end);
res.json({
code: 0,
data: {
list,
total,
page: Number(page),
pageSize: Number(pageSize),
},
message: 'ok',
});
},
// 预约管理列表
'GET /api/appointment/manage': (req: any, res: any) => {
const { name, category, teacher, status, page = 1, pageSize = 10 } = req.query;
let filtered = [...manageAppointments];
if (name) {
filtered = filtered.filter((item) => item.name.includes(name));
}
if (category) {
filtered = filtered.filter((item) => item.category === category);
}
if (teacher) {
filtered = filtered.filter((item) => item.teacher.includes(teacher));
}
if (status) {
filtered = filtered.filter((item) => item.status === status);
}
const total = filtered.length;
const start = (Number(page) - 1) * Number(pageSize);
const end = start + Number(pageSize);
const list = filtered.slice(start, end);
res.json({
code: 0,
data: { list, total, page: Number(page), pageSize: Number(pageSize) },
message: 'ok',
});
},
// 预约操作
'POST /api/appointment/book': (req: any, res: any) => {
const { id } = req.body;
const item = manageAppointments.find((a) => a.id === Number(id));
if (!item) {
res.json({ code: 404, message: '预约项目不存在' });
return;
}
if (item.status === 'ended') {
res.json({ code: 400, message: '该项目已结束,不可预约' });
return;
}
if (item.remaining <= 0) {
res.json({ code: 400, message: '名额已满,不可预约' });
return;
}
item.remaining -= 1;
res.json({ code: 0, message: '预约成功' });
},
// 取消预约
'POST /api/appointment/cancel': (req: any, res: any) => {
const { id } = req.body;
const appointment = allAppointments.find((a) => a.id === Number(id));
if (!appointment) {
res.json({ code: 404, message: '预约不存在' });
return;
}
// 已完成的不能取消
if (appointment.status === 'completed') {
res.json({ code: 400, message: '已结束的预约不能取消' });
return;
}
// 已取消的不能重复取消
if (appointment.status === 'cancelled') {
res.json({ code: 400, message: '该预约已取消' });
return;
}
// 只有 booked 和 pending 可以取消
appointment.status = 'cancelled';
res.json({ code: 0, message: '取消成功' });
},
};

215
mock/college.ts Normal file
View File

@@ -0,0 +1,215 @@
// Mock data for college management
const collegeTree = [
{
id: '1',
name: '设计学院',
depts: [
{
id: '1-1',
name: '视觉传达设计系',
majors: [
{
id: '1-1-1',
name: '平面设计',
classes: [
{ id: '1-1-1-1', name: '平面设计1班' },
{ id: '1-1-1-2', name: '平面设计2班' },
],
},
{
id: '1-1-2',
name: '数字媒体设计',
classes: [
{ id: '1-1-2-1', name: '数字媒体1班' },
],
},
],
},
{
id: '1-2',
name: '环境设计系',
majors: [
{
id: '1-2-1',
name: '室内设计',
classes: [
{ id: '1-2-1-1', name: '室内设计1班' },
{ id: '1-2-1-2', name: '室内设计2班' },
{ id: '1-2-1-3', name: '室内设计3班' },
],
},
],
},
],
},
{
id: '2',
name: '管理学院',
depts: [
{
id: '2-1',
name: '工商管理系',
majors: [
{
id: '2-1-1',
name: '工商管理',
classes: [
{ id: '2-1-1-1', name: '工商管理1班' },
{ id: '2-1-1-2', name: '工商管理2班' },
],
},
{
id: '2-1-2',
name: '市场营销',
classes: [
{ id: '2-1-2-1', name: '市场营销1班' },
],
},
],
},
{
id: '2-2',
name: '会计系',
majors: [
{
id: '2-2-1',
name: '财务管理',
classes: [
{ id: '2-2-1-1', name: '财务管理1班' },
{ id: '2-2-1-2', name: '财务管理2班' },
],
},
{
id: '2-2-2',
name: '审计学',
classes: [],
},
],
},
],
},
{
id: '3',
name: '艺术学院',
depts: [
{
id: '3-1',
name: '音乐系',
majors: [
{
id: '3-1-1',
name: '声乐表演',
classes: [
{ id: '3-1-1-1', name: '声乐表演1班' },
],
},
],
},
],
},
{
id: '4',
name: '信息工程学院',
depts: [
{
id: '4-1',
name: '计算机系',
majors: [
{
id: '4-1-1',
name: '软件工程',
classes: [
{ id: '4-1-1-1', name: '软件工程1班' },
{ id: '4-1-1-2', name: '软件工程2班' },
{ id: '4-1-1-3', name: '软件工程3班' },
],
},
{
id: '4-1-2',
name: '计算机科学与技术',
classes: [
{ id: '4-1-2-1', name: '计科1班' },
{ id: '4-1-2-2', name: '计科2班' },
],
},
{
id: '4-1-3',
name: '人工智能',
classes: [
{ id: '4-1-3-1', name: 'AI1班' },
],
},
],
},
{
id: '4-2',
name: '电子信息系',
majors: [
{
id: '4-2-1',
name: '通信工程',
classes: [
{ id: '4-2-1-1', name: '通信工程1班' },
],
},
],
},
],
},
{
id: '5',
name: '外国语学院',
depts: [],
},
];
export default {
'GET /api/admin/college': (_req: any, res: any) => {
res.json({
code: 0,
data: collegeTree,
message: 'ok',
});
},
'POST /api/admin/college': (req: any, res: any) => {
const { data } = req.body;
if (!data || !Array.isArray(data)) {
return res.json({ code: 40000, data: null, message: '参数错误' });
}
// Validate: all names must be non-empty
const validate = (items: any[], level: string): string | null => {
for (const item of items) {
if (!item.name || !item.name.trim()) {
return `${level}名称不能为空`;
}
if (item.depts) {
const err = validate(item.depts, '系');
if (err) return err;
}
if (item.majors) {
const err = validate(item.majors, '专业');
if (err) return err;
}
if (item.classes) {
const err = validate(item.classes, '班级');
if (err) return err;
}
}
return null;
};
const err = validate(data, '学院');
if (err) {
return res.json({ code: 40001, data: null, message: err });
}
console.log('[Mock] 保存学校管理数据, 学院数量:', data.length);
res.json({
code: 0,
data: null,
message: '保存成功',
});
},
};

42
mock/home.ts Normal file
View File

@@ -0,0 +1,42 @@
export default {
// 获取首页轮播 Banner 列表
'GET /api/home/banners': {
code: 0,
data: [
{
id: 1,
title: '武汉高校校园招聘会',
highlightText: '校园',
subtitle: '科技无限 · 海量职位等你来',
date: '活动时间2026/01/01',
imageUrl: 'https://test02-1302947942.cos.ap-shanghai.myqcloud.com/%E6%B3%BD%E6%9E%97-banner/banner.png',
linkUrl: '/jobs?event=1',
sort: 1,
status: 1,
},
{
id: 2,
title: 'AI助力职业发展',
highlightText: '职业',
subtitle: '智能匹配 · 精准推荐好工作',
date: '平台持续更新中',
imageUrl: 'https://test02-1302947942.cos.ap-shanghai.myqcloud.com/%E6%B3%BD%E6%9E%97-banner/banner.png',
linkUrl: '/jobs',
sort: 2,
status: 1,
},
{
id: 3,
title: '春季校园专场招聘',
highlightText: '专场',
subtitle: '名企云集 · 优质岗位等你来',
date: '活动时间2026/03/15',
imageUrl: 'https://test02-1302947942.cos.ap-shanghai.myqcloud.com/%E6%B3%BD%E6%9E%97-banner/banner.png',
linkUrl: '/jobs?event=3',
sort: 3,
status: 1,
},
],
message: 'ok',
},
};

41
mock/overview.ts Normal file
View File

@@ -0,0 +1,41 @@
import { Request, Response } from 'express';
// 模拟数据总览数据
const mockOverviewData = {
aiInterviewUsage: 900,
aiResumeUsage: 900,
currentTasks: 0,
completedPersons: 150,
todayInterviewUsage: 10,
todayResumeUsage: 8,
};
export default {
// 获取数据总览
'GET /api/overview/data': (req: Request, res: Response) => {
setTimeout(() => {
res.json({
code: 200,
message: 'success',
data: mockOverviewData,
});
}, 500);
},
// 更新数据总览
'POST /api/overview/update': (req: Request, res: Response) => {
const { type, value } = req.body;
if (mockOverviewData.hasOwnProperty(type)) {
(mockOverviewData as any)[type] = value;
}
setTimeout(() => {
res.json({
code: 200,
message: 'success',
data: mockOverviewData,
});
}, 300);
},
};

18
mock/resume.ts Normal file
View File

@@ -0,0 +1,18 @@
/**
* 简历相关 Mock
*
* 以下接口已接入真实后端 192.168.0.55:8080通过 proxy 转发:
* - GET /api/resume/student/templates 模板列表
* - POST /api/resume/student/ai/generate AI生成简历
*
* 以下仅保留尚未对接真实接口的 mock。
*/
const generatedResumes: any[] = [];
export default {
// 获取我的简历列表(暂无真实接口)
'GET /api/resume/mine': (_req: any, res: any) => {
res.json({ code: 0, data: generatedResumes, message: 'ok' });
},
};

320
mock/role.ts Normal file
View File

@@ -0,0 +1,320 @@
import { Request, Response } from 'express';
// 模拟角色数据
const roleList = [
{
id: '1',
name: '超级管理员',
description: '拥有系统所有权限',
permissions: ['user_manage', 'role_manage', 'system_config', 'data_view'],
status: 'active',
createTime: '2024-01-01 10:00:00',
updateTime: '2024-01-01 10:00:00',
},
{
id: '2',
name: '学院管理员',
description: '管理学院相关事务',
permissions: ['student_manage', 'staff_manage', 'data_view'],
status: 'active',
createTime: '2024-01-02 10:00:00',
updateTime: '2024-01-02 10:00:00',
},
{
id: '3',
name: '教师',
description: '教师角色,可查看学生信息',
permissions: ['student_view', 'data_view'],
status: 'active',
createTime: '2024-01-03 10:00:00',
updateTime: '2024-01-03 10:00:00',
},
{
id: '4',
name: '学生',
description: '学生角色,基础权限',
permissions: ['profile_view'],
status: 'inactive',
createTime: '2024-01-04 10:00:00',
updateTime: '2024-01-04 10:00:00',
},
];
// 权限树数据
const permissionTree = [
{
id: 'user_manage',
name: '用户管理',
code: 'user_manage',
type: 'menu',
children: [
{
id: 'student_manage',
name: '学生管理',
code: 'student_manage',
type: 'menu',
parentId: 'user_manage',
},
{
id: 'staff_manage',
name: '教职工管理',
code: 'staff_manage',
type: 'menu',
parentId: 'user_manage',
},
{
id: 'student_view',
name: '学生查看',
code: 'student_view',
type: 'button',
parentId: 'user_manage',
},
],
},
{
id: 'role_manage',
name: '角色管理',
code: 'role_manage',
type: 'menu',
},
{
id: 'system_config',
name: '系统配置',
code: 'system_config',
type: 'menu',
children: [
{
id: 'banner_manage',
name: '轮播图管理',
code: 'banner_manage',
type: 'menu',
parentId: 'system_config',
children: [
{
id: 'banner_add',
name: '新增轮播图',
code: 'banner_add',
type: 'button',
parentId: 'banner_manage',
},
{
id: 'banner_edit',
name: '编辑轮播图',
code: 'banner_edit',
type: 'button',
parentId: 'banner_manage',
},
],
},
{
id: 'security_config',
name: '安全配置',
code: 'security_config',
type: 'menu',
parentId: 'system_config',
},
],
},
{
id: 'data_view',
name: '数据查看',
code: 'data_view',
type: 'menu',
},
{
id: 'profile_view',
name: '个人信息查看',
code: 'profile_view',
type: 'button',
},
];
// 组织架构数据
const organizationData = [
{
id: 'school_1',
name: '学校管理',
type: 'school',
expanded: true,
children: [
{
id: 'school_2',
name: '学校管理',
type: 'school',
expanded: false,
},
{
id: 'dept_1',
name: '编辑',
type: 'department',
expanded: true,
children: [
{
id: 'major_1',
name: '批量导入',
type: 'major',
expanded: false,
children: [
{
id: 'class_1',
name: '批量导入',
type: 'class',
},
],
},
{
id: 'major_2',
name: '新增',
type: 'major',
expanded: false,
},
],
},
{
id: 'school_3',
name: '角色管理',
type: 'school',
expanded: true,
},
],
},
];
// 获取角色列表
export default {
'GET /api/admin/role': (req: Request, res: Response) => {
const { page = 1, pageSize = 10, name } = req.query;
let filteredList = [...roleList];
// 按名称筛选
if (name) {
filteredList = filteredList.filter(item =>
item.name.includes(name as string)
);
}
const start = (Number(page) - 1) * Number(pageSize);
const end = start + Number(pageSize);
const list = filteredList.slice(start, end);
res.json({
code: 0,
message: 'success',
data: {
list,
total: filteredList.length,
},
});
},
// 获取组织架构数据
'GET /api/admin/organization': (req: Request, res: Response) => {
res.json({
code: 0,
message: 'success',
data: organizationData,
});
},
// 获取权限树
'GET /api/admin/role/permissions': (req: Request, res: Response) => {
res.json({
code: 0,
message: 'success',
data: permissionTree,
});
},
// 创建角色
'POST /api/admin/role/create': (req: Request, res: Response) => {
const { name, description, permissions } = req.body;
const newRole = {
id: String(Date.now()),
name,
description: description || '',
permissions: permissions || [],
status: 'active',
createTime: new Date().toLocaleString('zh-CN'),
updateTime: new Date().toLocaleString('zh-CN'),
};
roleList.push(newRole);
res.json({
code: 0,
message: '创建成功',
data: newRole,
});
},
// 更新角色
'POST /api/admin/role/update': (req: Request, res: Response) => {
const { id, name, description, permissions } = req.body;
const index = roleList.findIndex(item => item.id === id);
if (index === -1) {
return res.json({
code: 1,
message: '角色不存在',
});
}
roleList[index] = {
...roleList[index],
name,
description: description || '',
permissions: permissions || [],
updateTime: new Date().toLocaleString('zh-CN'),
};
res.json({
code: 0,
message: '更新成功',
data: roleList[index],
});
},
// 切换角色状态
'POST /api/admin/role/toggle-status': (req: Request, res: Response) => {
const { id } = req.body;
const role = roleList.find(item => item.id === id);
if (!role) {
return res.json({
code: 1,
message: '角色不存在',
});
}
role.status = role.status === 'active' ? 'inactive' : 'active';
role.updateTime = new Date().toLocaleString('zh-CN');
res.json({
code: 0,
message: '状态更新成功',
data: role,
});
},
// 删除角色
'POST /api/admin/role/delete': (req: Request, res: Response) => {
const { id } = req.body;
const index = roleList.findIndex(item => item.id === id);
if (index === -1) {
return res.json({
code: 1,
message: '角色不存在',
});
}
roleList.splice(index, 1);
res.json({
code: 0,
message: '删除成功',
});
},
};

150
mock/staff.ts Normal file
View File

@@ -0,0 +1,150 @@
const familyNames = ['赵', '冯', '钱', '刘', '孙', '李', '周', '吴', '郑', '王', '陈', '杨', '黄', '张', '林', '何', '高', '马', '罗', '梁'];
const givenNames = ['玉岚', '艺莲', '若蓉', '思钰', '婷雅', '恩铭', '浩然', '子涵', '欣怡', '梓萱', '宇轩', '雨桐', '佳琪', '思远', '天乐', '晓峰', '文博', '嘉欣', '雅静', '明辉'];
const colleges = ['设计学院', '管理学院', '艺术学院'];
const deptMap: Record<string, string[]> = {
'设计学院': ['视觉传达设计系', '环境设计系'],
'管理学院': ['工商管理系', '会计系', '人力资源系'],
'艺术学院': ['音乐系', '美术系'],
};
const classMap: Record<string, string[]> = {
'工商管理系': ['工商管理1班', '工商管理2班'],
'会计系': ['财务管理1班', '财务管理2班'],
'人力资源系': ['人力资源1班'],
'视觉传达设计系': ['平面设计1班', '平面设计2班'],
'环境设计系': ['室内设计1班', '室内设计2班', '室内设计3班'],
'音乐系': ['声乐表演1班'],
'美术系': ['国画1班', '油画1班'],
};
const statuses: Array<'normal' | 'disabled'> = ['normal', 'normal', 'normal', 'normal', 'disabled'];
function randomPick<T>(arr: T[]): T {
return arr[Math.floor(Math.random() * arr.length)];
}
function maskPhone(phone: string) {
return phone.substring(0, 3) + '****' + phone.substring(7);
}
// 生成100条教职工数据
const staffList: any[] = [];
for (let i = 0; i < 100; i++) {
const college = randomPick(colleges);
const depts = deptMap[college] || [];
const dept = randomPick(depts);
const classes = classMap[dept] || [];
const cls = classes.length > 0 ? randomPick(classes) : '';
const status = randomPick(statuses);
const phone = `1${randomPick(['39', '56', '78', '38', '59'])}${String(Math.floor(Math.random() * 100000000)).padStart(8, '0')}`;
staffList.push({
id: String(i + 1),
name: randomPick(familyNames) + randomPick(givenNames),
phone,
maskedPhone: maskPhone(phone),
status,
college,
dept,
className: cls,
});
}
// 学院树结构(用于左侧导航)
const collegeTree = colleges.map((c) => ({
id: c,
name: c,
children: (deptMap[c] || []).map((d) => ({
id: d,
name: d,
children: (classMap[d] || []).map((cls) => ({
id: cls,
name: cls,
})),
})),
}));
export default {
'GET /api/admin/staff': (req: any, res: any) => {
const { page = '1', pageSize = '10', name, phone, college, dept, className, status } = req.query;
let filtered = [...staffList];
if (name) filtered = filtered.filter((s) => s.name.includes(name));
if (phone) filtered = filtered.filter((s) => s.phone.includes(phone));
if (college) filtered = filtered.filter((s) => s.college === college);
if (dept) filtered = filtered.filter((s) => s.dept === dept);
if (className) filtered = filtered.filter((s) => s.className === className);
if (status) filtered = filtered.filter((s) => s.status === status);
const p = Number(page);
const ps = Number(pageSize);
const start = (p - 1) * ps;
res.json({ code: 0, data: { list: filtered.slice(start, start + ps), total: filtered.length, page: p, pageSize: ps }, message: 'ok' });
},
'GET /api/admin/staff/college-tree': (_req: any, res: any) => {
res.json({ code: 0, data: collegeTree, message: 'ok' });
},
'POST /api/admin/staff/add': (req: any, res: any) => {
const { name, phone, college, dept, className } = req.body;
if (!name || !phone) { res.json({ code: 40000, message: '请填写完整信息' }); return; }
const newStaff = {
id: String(staffList.length + 1000),
name, phone, maskedPhone: maskPhone(phone), status: 'normal' as const,
college: college || '', dept: dept || '', className: className || '',
};
staffList.unshift(newStaff);
res.json({ code: 0, data: newStaff, message: '新增成功' });
},
'POST /api/admin/staff/edit': (req: any, res: any) => {
const { id, name, phone, college, dept, className } = req.body;
const staff = staffList.find((s) => s.id === id);
if (!staff) { res.json({ code: 40000, message: '教职工不存在' }); return; }
Object.assign(staff, { name, phone, maskedPhone: maskPhone(phone), college, dept, className });
res.json({ code: 0, data: staff, message: '编辑成功' });
},
'POST /api/admin/staff/toggle-status': (req: any, res: any) => {
const { id } = req.body;
const staff = staffList.find((s) => s.id === id);
if (staff) { staff.status = staff.status === 'normal' ? 'disabled' : 'normal'; res.json({ code: 0, message: '操作成功' }); }
else { res.json({ code: 40000, message: '教职工不存在' }); }
},
'POST /api/admin/staff/delete': (req: any, res: any) => {
const { id } = req.body;
const idx = staffList.findIndex((s) => s.id === id);
if (idx > -1) { staffList.splice(idx, 1); res.json({ code: 0, message: '删除成功' }); }
else { res.json({ code: 40000, message: '教职工不存在' }); }
},
'POST /api/admin/staff/batch-delete': (req: any, res: any) => {
const { ids } = req.body;
if (!ids || !Array.isArray(ids) || ids.length === 0) { res.json({ code: 40000, message: '请选择要删除的教职工' }); return; }
let count = 0;
for (let i = staffList.length - 1; i >= 0; i--) { if (ids.includes(staffList[i].id)) { staffList.splice(i, 1); count++; } }
res.json({ code: 0, data: { count }, message: `成功删除${count}名教职工` });
},
'POST /api/admin/staff/batch-import': (_req: any, res: any) => {
const imported: any[] = [];
for (let i = 0; i < 5; i++) {
const college = randomPick(colleges);
const dept = randomPick(deptMap[college] || []);
const phone = `1${randomPick(['39', '56', '78', '38', '59'])}${String(Math.floor(Math.random() * 100000000)).padStart(8, '0')}`;
const s = { id: String(Date.now() + i), name: randomPick(familyNames) + randomPick(givenNames), phone, maskedPhone: maskPhone(phone), status: 'normal' as const, college, dept, className: '' };
staffList.unshift(s);
imported.push(s);
}
res.json({ code: 0, data: { count: imported.length }, message: `成功导入${imported.length}名教职工` });
},
'POST /api/admin/staff/reset-password': (req: any, res: any) => {
const { id } = req.body;
const staff = staffList.find((s) => s.id === id);
if (staff) { res.json({ code: 0, message: '密码已重置为 123456' }); }
else { res.json({ code: 40000, message: '教职工不存在' }); }
},
};

379
mock/student.ts Normal file
View File

@@ -0,0 +1,379 @@
const familyNames = ['赵', '冯', '钱', '刘', '孙', '李', '周', '吴', '郑', '王', '陈', '杨', '黄', '张', '林', '何', '高', '马', '罗', '梁'];
const givenNames = ['玉岚', '艺莲', '若蓉', '思钰', '婷雅', '恩铭', '浩然', '子涵', '欣怡', '梓萱', '宇轩', '雨桐', '佳琪', '思远', '天乐', '晓峰', '文博', '嘉欣', '雅静', '明辉'];
const colleges = ['电气化学院', '管理学院', '设计学院', '信息工程学院', '艺术学院', '外国语学院'];
const deptMap: Record<string, string[]> = {
'电气化学院': ['电力工程系', '电气工程系', '自动化系'],
'管理学院': ['工商管理系', '会计系', '人力资源系'],
'设计学院': ['视觉传达设计系', '环境设计系'],
'信息工程学院': ['计算机系', '电子信息系'],
'艺术学院': ['音乐系', '美术系'],
'外国语学院': ['英语系', '日语系'],
};
const majorMap: Record<string, string[]> = {
'电力工程系': ['电气化', '电力工程'],
'电气工程系': ['电气工程', '智能电网'],
'自动化系': ['自动化', '机器人工程'],
'工商管理系': ['工商管理', '市场营销'],
'会计系': ['财务管理', '审计学'],
'人力资源系': ['人力资源管理'],
'视觉传达设计系': ['平面设计', '数字媒体设计'],
'环境设计系': ['室内设计', '景观设计'],
'计算机系': ['软件工程', '计算机科学与技术', '人工智能'],
'电子信息系': ['通信工程', '电子信息'],
'音乐系': ['声乐表演', '器乐演奏'],
'美术系': ['国画', '油画'],
'英语系': ['英语', '翻译'],
'日语系': ['日语'],
};
const statuses: Array<'normal' | 'disabled'> = ['normal', 'normal', 'normal', 'normal', 'disabled'];
const roles = ['学生'];
const grades = ['2023级', '2024级', '2025级'];
const classes = ['1班', '2班', '3班', '4班', '5班'];
// 年级管理数据
const gradeList: any[] = [
{ id: '1', name: '2025级', createTime: '2025.09.01 16:01:30', graduated: false },
{ id: '2', name: '2026级', createTime: '2025.09.01 16:01:30', graduated: false },
{ id: '3', name: '2024级', createTime: '2025.09.01 16:01:30', graduated: false },
{ id: '4', name: '2023级', createTime: '2025.09.01 16:01:30', graduated: false },
{ id: '5', name: '2022级', createTime: '2025.09.01 16:01:30', graduated: false },
{ id: '6', name: '2021级', createTime: '2025.09.01 16:01:30', graduated: false },
{ id: '7', name: '2020级', createTime: '2025.09.01 16:01:30', graduated: false },
{ id: '8', name: '2025级', createTime: '2025.09.01 16:01:30', graduated: false },
{ id: '9', name: '2025级', createTime: '2025.09.01 16:01:30', graduated: false },
{ id: '10', name: '2025级', createTime: '2025.09.01 16:01:30', graduated: false },
];
// 设置一条数据为已毕业状态,用于"还原"操作演示
gradeList[1].graduated = true;
function randomPick<T>(arr: T[]): T {
return arr[Math.floor(Math.random() * arr.length)];
}
function maskPhone(phone: string) {
return phone.substring(0, 3) + '****' + phone.substring(7);
}
// 生成100条学生数据
const students: any[] = [];
for (let i = 0; i < 100; i++) {
const college = randomPick(colleges);
const depts = deptMap[college] || [];
const dept = randomPick(depts);
const majors = majorMap[dept] || [];
const major = randomPick(majors);
const status = randomPick(statuses);
const phone = `1${randomPick(['39', '56', '78', '38', '59'])}${String(Math.floor(Math.random() * 100000000)).padStart(8, '0')}`;
students.push({
id: String(i + 1),
name: randomPick(familyNames) + randomPick(givenNames),
studentNo: String(178000 + Math.floor(Math.random() * 1000)),
college,
dept,
major,
className: `${Math.ceil(Math.random() * 3)}`,
grade: randomPick(grades),
phone,
maskedPhone: maskPhone(phone),
role: randomPick(roles),
updateDate: '2025.09.01',
status,
graduated: false,
});
}
// 生成100条已毕业学生数据
const graduatedStudents: any[] = [];
for (let i = 0; i < 100; i++) {
const college = randomPick(colleges);
const depts = deptMap[college] || [];
const dept = randomPick(depts);
const majors = majorMap[dept] || [];
const major = randomPick(majors);
const status = randomPick(statuses);
const phone = `1${randomPick(['39', '56', '78', '38', '59'])}${String(Math.floor(Math.random() * 100000000)).padStart(8, '0')}`;
graduatedStudents.push({
id: String(i + 2000),
name: randomPick(familyNames) + randomPick(givenNames),
studentNo: String(178000 + Math.floor(Math.random() * 1000)),
college,
dept,
major,
className: `${Math.ceil(Math.random() * 3)}`,
grade: randomPick(grades),
phone,
maskedPhone: maskPhone(phone),
role: randomPick(roles),
updateDate: '2025.09.01',
status,
graduated: true,
});
}
export default {
'GET /api/admin/student': (req: any, res: any) => {
const {
page = '1',
pageSize = '10',
graduated,
studentNo,
name,
college,
dept,
major,
className,
status,
} = req.query;
const source = graduated === 'true' ? graduatedStudents : students;
let filtered = [...source];
if (studentNo) {
filtered = filtered.filter((s) => s.studentNo.includes(studentNo));
}
if (name) {
filtered = filtered.filter((s) => s.name.includes(name));
}
if (college) {
filtered = filtered.filter((s) => s.college === college);
}
if (dept) {
filtered = filtered.filter((s) => s.dept === dept);
}
if (major) {
filtered = filtered.filter((s) => s.major === major);
}
if (className) {
filtered = filtered.filter((s) => s.className === className);
}
if (status) {
filtered = filtered.filter((s) => s.status === status);
}
const p = Number(page);
const ps = Number(pageSize);
const start = (p - 1) * ps;
const list = filtered.slice(start, start + ps);
res.json({
code: 0,
data: {
list,
total: filtered.length,
page: p,
pageSize: ps,
},
message: 'ok',
});
},
'POST /api/admin/student/toggle-status': (req: any, res: any) => {
const { id } = req.body;
const student = students.find((s) => s.id === id) || graduatedStudents.find((s) => s.id === id);
if (student) {
student.status = student.status === 'normal' ? 'disabled' : 'normal';
res.json({ code: 0, data: null, message: '操作成功' });
} else {
res.json({ code: 40000, data: null, message: '学生不存在' });
}
},
'POST /api/admin/student/delete': (req: any, res: any) => {
const { id } = req.body;
let idx = students.findIndex((s) => s.id === id);
if (idx > -1) {
students.splice(idx, 1);
res.json({ code: 0, data: null, message: '删除成功' });
return;
}
idx = graduatedStudents.findIndex((s) => s.id === id);
if (idx > -1) {
graduatedStudents.splice(idx, 1);
res.json({ code: 0, data: null, message: '删除成功' });
return;
}
res.json({ code: 40000, data: null, message: '学生不存在' });
},
'POST /api/admin/student/batch-delete': (req: any, res: any) => {
const { ids, graduated } = req.body;
if (!ids || !Array.isArray(ids) || ids.length === 0) {
res.json({ code: 40000, data: null, message: '请选择要删除的学生' });
return;
}
const source = graduated ? graduatedStudents : students;
let count = 0;
for (let i = source.length - 1; i >= 0; i--) {
if (ids.includes(source[i].id)) {
source.splice(i, 1);
count++;
}
}
res.json({ code: 0, data: { count }, message: `成功删除${count}名学生` });
},
'POST /api/admin/student/batch-import': (req: any, res: any) => {
// 模拟批量导入5名学生
const imported: any[] = [];
for (let i = 0; i < 5; i++) {
const college = randomPick(colleges);
const depts = deptMap[college] || [];
const dept = randomPick(depts);
const majors = majorMap[dept] || [];
const major = randomPick(majors);
const phone = `1${randomPick(['39', '56', '78', '38', '59'])}${String(Math.floor(Math.random() * 100000000)).padStart(8, '0')}`;
const newStudent = {
id: String(Date.now() + i),
name: randomPick(familyNames) + randomPick(givenNames),
studentNo: String(178000 + Math.floor(Math.random() * 1000)),
college,
dept,
major,
className: `${Math.ceil(Math.random() * 3)}`,
grade: randomPick(grades),
phone,
maskedPhone: maskPhone(phone),
role: '学生',
updateDate: new Date().toISOString().slice(0, 10).replace(/-/g, '.'),
status: 'normal' as const,
graduated: false,
};
students.unshift(newStudent);
imported.push(newStudent);
}
res.json({ code: 0, data: { count: imported.length }, message: `成功导入${imported.length}名学生` });
},
'POST /api/admin/student/reset-password': (req: any, res: any) => {
const { id } = req.body;
const student = students.find((s) => s.id === id) || graduatedStudents.find((s) => s.id === id);
if (student) {
console.log(`[Mock] 重置学生 ${student.name} 密码`);
res.json({ code: 0, data: null, message: '密码已重置为 123456' });
} else {
res.json({ code: 40000, data: null, message: '学生不存在' });
}
},
'POST /api/admin/student/edit': (req: any, res: any) => {
const { id, name, studentNo, role, college, dept, major, className, grade, phone } = req.body;
const student = students.find((s) => s.id === id) || graduatedStudents.find((s) => s.id === id);
if (!student) {
res.json({ code: 40000, data: null, message: '学生不存在' });
return;
}
if (!name || !studentNo || !role || !college || !dept || !major || !className || !grade) {
res.json({ code: 40000, data: null, message: '请填写完整信息' });
return;
}
Object.assign(student, {
name, studentNo, role, college, dept, major, className, grade,
phone: phone || '',
maskedPhone: phone ? phone.substring(0, 3) + '****' + phone.substring(7) : '',
updateDate: new Date().toISOString().slice(0, 10).replace(/-/g, '.'),
});
res.json({ code: 0, data: student, message: '编辑成功' });
},
'POST /api/admin/student/add': (req: any, res: any) => {
const { name, studentNo, role, college, dept, major, className, grade, phone } = req.body;
if (!name || !studentNo || !role || !college || !dept || !major || !className || !grade) {
res.json({ code: 40000, data: null, message: '请填写完整信息' });
return;
}
const newStudent = {
id: String(students.length + 1000),
name,
studentNo,
college,
dept,
major,
className,
grade,
phone: phone || '',
maskedPhone: phone ? phone.substring(0, 3) + '****' + phone.substring(7) : '',
role,
updateDate: new Date().toISOString().slice(0, 10).replace(/-/g, '.'),
status: 'normal' as const,
};
students.unshift(newStudent);
res.json({ code: 0, data: newStudent, message: '新增成功' });
},
'GET /api/admin/student/colleges': (_req: any, res: any) => {
res.json({
code: 0,
data: {
colleges,
deptMap,
majorMap,
classes,
grades,
roles,
},
message: 'ok',
});
},
// 年级管理接口
'GET /api/admin/grade': (req: any, res: any) => {
const { page = '1', pageSize = '10', name } = req.query;
let filtered = [...gradeList];
if (name) {
filtered = filtered.filter((g: any) => g.name.includes(name));
}
const p = Number(page);
const ps = Number(pageSize);
const start = (p - 1) * ps;
const list = filtered.slice(start, start + ps);
res.json({ code: 0, data: { list, total: filtered.length, page: p, pageSize: ps }, message: 'ok' });
},
'POST /api/admin/grade/add': (req: any, res: any) => {
const { name } = req.body;
const newGrade = {
id: String(gradeList.length + 1),
name,
createTime: new Date().toISOString().replace('T', ' ').slice(0, 19),
};
gradeList.unshift(newGrade);
res.json({ code: 0, data: newGrade, message: '新增成功' });
},
'POST /api/admin/grade/edit': (req: any, res: any) => {
const { id, name } = req.body;
const item = gradeList.find((g: any) => g.id === id);
if (item) {
item.name = name;
res.json({ code: 0, data: item, message: '编辑成功' });
} else {
res.json({ code: 1, message: '未找到该年级' });
}
},
'POST /api/admin/grade/graduate': (req: any, res: any) => {
const { id } = req.body;
const idx = gradeList.findIndex((g: any) => g.id === id);
if (idx >= 0) {
gradeList[idx].graduated = true;
res.json({ code: 0, message: '转毕业成功' });
} else {
res.json({ code: 1, message: '未找到该年级' });
}
},
'POST /api/admin/grade/restore': (req: any, res: any) => {
const { id } = req.body;
const idx = gradeList.findIndex((g: any) => g.id === id);
if (idx >= 0) {
gradeList[idx].graduated = false;
res.json({ code: 0, message: '还原成功' });
} else {
res.json({ code: 1, message: '未找到该年级' });
}
},
};

156
mock/user.ts Normal file
View File

@@ -0,0 +1,156 @@
/**
* 用户相关 Mock
*
* 认证接口(登录、验证码登录、发送验证码)已接入真实后端 192.168.0.55:8080
* 通过 .umirc.ts 的 proxy 代理转发,不再 mock。
*
* 以下仅保留尚未对接真实接口的 mock。
*/
const mockUsers = [
{
userId: 1,
username: 'admin',
realName: '管理员',
phone: '13800138000',
avatar: null,
userType: 1,
password: '123456',
},
{
userId: 2,
username: 'teacher001',
realName: '李老师',
phone: '13800138002',
avatar: null,
userType: 2,
password: '123456',
},
{
userId: 3,
username: 'student001',
realName: '张三',
phone: '13800138001',
avatar: null,
userType: 3,
password: '123456',
},
];
let nextUserId = 4;
const generateToken = () =>
'eyJhbGciOiJIUzI1NiJ9.mock-' + Date.now() + '-' + Math.random().toString(36).slice(2);
const toUserData = (user: (typeof mockUsers)[0], token: string) => ({
userId: user.userId,
username: user.username,
realName: user.realName,
phone: user.phone,
avatar: user.avatar,
userType: user.userType,
token,
});
export default {
// 验证重置密码验证码(暂无真实接口)
'POST /api/auth/verifyResetCode': (req: any, res: any) => {
const { phone, code } = req.body;
if (!phone || !code) {
return res.json({ code: 40000, data: null, message: '请求参数错误' });
}
if (!/^1[3-9]\d{9}$/.test(phone)) {
return res.json({ code: 40000, data: null, message: '手机号格式不正确' });
}
const user = mockUsers.find((u) => u.phone === phone);
if (!user) {
return res.json({ code: 40000, data: null, message: '该手机号未注册' });
}
if (code !== '123456') {
return res.json({ code: 40000, data: null, message: '验证码错误或已过期' });
}
console.log(`[Mock] 验证码验证通过: ${phone}`);
res.json({ code: 0, data: true, message: 'ok' });
},
// 重置密码(暂无真实接口)
'POST /api/auth/resetPassword': (req: any, res: any) => {
const { phone, code, newPassword } = req.body;
if (!phone || !code || !newPassword) {
return res.json({ code: 40000, data: null, message: '请求参数错误' });
}
if (code !== '123456') {
return res.json({ code: 40000, data: null, message: '验证码错误或已过期' });
}
const user = mockUsers.find((u) => u.phone === phone);
if (!user) {
return res.json({ code: 40000, data: null, message: '该手机号未注册' });
}
if (newPassword.length < 6 || newPassword.length > 20) {
return res.json({ code: 40000, data: null, message: '密码长度需为6-20位' });
}
user.password = newPassword;
console.log(`[Mock] 用户 ${user.username} 密码已重置`);
res.json({ code: 0, data: true, message: 'ok' });
},
// 用户注册(暂无真实接口)
'POST /api/auth/register': (req: any, res: any) => {
const { username, password, confirmPassword, realName, phone, email, userType } = req.body;
if (!username || !password || !confirmPassword || !realName || !phone) {
return res.json({ code: 40000, data: null, message: '请求参数错误' });
}
if (!/^[a-zA-Z0-9_]{4,20}$/.test(username)) {
return res.json({ code: 40000, data: null, message: '用户名只能包含字母、数字、下划线' });
}
if (password.length < 6 || password.length > 20) {
return res.json({ code: 40000, data: null, message: '密码长度需为6-20位' });
}
if (password !== confirmPassword) {
return res.json({ code: 40000, data: null, message: '两次密码不一致' });
}
if (!/^1[3-9]\d{9}$/.test(phone)) {
return res.json({ code: 40000, data: null, message: '手机号格式不正确' });
}
if (mockUsers.find((u) => u.username === username)) {
return res.json({ code: 40000, data: null, message: '用户名已存在' });
}
if (mockUsers.find((u) => u.phone === phone)) {
return res.json({ code: 40000, data: null, message: '手机号已注册' });
}
const newUser = {
userId: nextUserId++,
username,
realName,
phone,
avatar: null,
userType: userType || 3,
password,
};
mockUsers.push(newUser);
res.json({
code: 0,
data: toUserData(newUser, generateToken()),
message: 'ok',
});
},
};