feat: 完成多多畅职就业服务平台核心功能开发
主要更新: - ✅ 完成主题配色从暗色到亮蓝白配色的全面转换 - ✅ 实现高薪岗位页面及后端API集成 - ✅ 完成登录注册页面及认证系统 - ✅ 实现预招录确认功能 - ✅ 添加数据库管理和维护工具脚本 - ✅ 优化错误处理和用户体验 核心功能: 1. 首页 (index.html) - 3D地球、专业分类、过渡岗位 2. 高薪岗位页面 (high.html) - 岗位详情、预招录确认、成功案例 3. 登录注册 (auth.html) - 用户认证、专业分类选择 4. 后端API - RESTful接口,JWT认证,MySQL数据库 技术栈: - 前端:Three.js, GSAP, 原生JavaScript - 后端:Node.js, Express, MySQL - 认证:JWT, bcrypt - 样式:自定义CSS,响应式设计 数据库工具: - kill-by-ids.js - 批量终止MySQL进程 - unlock-all-tables.js - 解锁数据库表 - init-db.js - 初始化数据库 - 其他管理脚本 🤖 Generated with Claude Code Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
272
api/controllers/applicationController.js
Normal file
272
api/controllers/applicationController.js
Normal file
@@ -0,0 +1,272 @@
|
||||
/**
|
||||
* 投递记录控制器
|
||||
*/
|
||||
|
||||
const db = require('../../config/database');
|
||||
|
||||
/**
|
||||
* 创建投递记录
|
||||
*/
|
||||
exports.createApplication = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const {
|
||||
job_type, job_name, company_name, company_short_name,
|
||||
city, province, segment_name, application_data
|
||||
} = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
if (!job_type || !job_name || !company_name) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '岗位类型、岗位名称和企业名称不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证job_type
|
||||
if (!['transition', 'referral'].includes(job_type)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '无效的岗位类型'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否已投递过相同岗位
|
||||
const [existing] = await db.query(`
|
||||
SELECT id FROM job_applications
|
||||
WHERE user_id = ? AND job_name = ? AND company_name = ? AND status != 'withdrawn'
|
||||
`, [userId, job_name, company_name]);
|
||||
|
||||
if (existing.length > 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '您已经投递过该岗位'
|
||||
});
|
||||
}
|
||||
|
||||
// 插入投递记录
|
||||
const [result] = await db.query(`
|
||||
INSERT INTO job_applications (
|
||||
user_id, job_type, job_name, company_name, company_short_name,
|
||||
city, province, segment_name, status, application_data
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 'pending', ?)
|
||||
`, [
|
||||
userId, job_type, job_name, company_name, company_short_name || null,
|
||||
city || null, province || null, segment_name || null,
|
||||
application_data ? JSON.stringify(application_data) : null
|
||||
]);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '投递成功',
|
||||
data: {
|
||||
id: result.insertId
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('创建投递记录错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取投递记录列表
|
||||
*/
|
||||
exports.getApplications = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const { job_type, status, page = 1, limit = 20 } = req.query;
|
||||
|
||||
// 构建查询条件
|
||||
let whereClause = 'user_id = ?';
|
||||
const params = [userId];
|
||||
|
||||
if (job_type) {
|
||||
whereClause += ' AND job_type = ?';
|
||||
params.push(job_type);
|
||||
}
|
||||
|
||||
if (status) {
|
||||
whereClause += ' AND status = ?';
|
||||
params.push(status);
|
||||
}
|
||||
|
||||
// 计算分页
|
||||
const offset = (page - 1) * limit;
|
||||
|
||||
// 查询总数
|
||||
const [countResult] = await db.query(
|
||||
`SELECT COUNT(*) as total FROM job_applications WHERE ${whereClause}`,
|
||||
params
|
||||
);
|
||||
const total = countResult[0].total;
|
||||
|
||||
// 查询列表
|
||||
const [applications] = await db.query(`
|
||||
SELECT
|
||||
id, job_type, job_name, company_name, company_short_name,
|
||||
city, province, segment_name, status, applied_at, updated_at, notes
|
||||
FROM job_applications
|
||||
WHERE ${whereClause}
|
||||
ORDER BY applied_at DESC
|
||||
LIMIT ? OFFSET ?
|
||||
`, [...params, parseInt(limit), parseInt(offset)]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
list: applications,
|
||||
pagination: {
|
||||
page: parseInt(page),
|
||||
limit: parseInt(limit),
|
||||
total,
|
||||
totalPages: Math.ceil(total / limit)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取投递记录错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取单个投递记录详情
|
||||
*/
|
||||
exports.getApplicationDetail = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const { id } = req.params;
|
||||
|
||||
const [applications] = await db.query(`
|
||||
SELECT * FROM job_applications
|
||||
WHERE id = ? AND user_id = ?
|
||||
`, [id, userId]);
|
||||
|
||||
if (applications.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '投递记录不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const application = applications[0];
|
||||
|
||||
// 解析JSON字段
|
||||
if (application.application_data) {
|
||||
try {
|
||||
application.application_data = JSON.parse(application.application_data);
|
||||
} catch (e) {
|
||||
application.application_data = null;
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: application
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取投递详情错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 撤回投递
|
||||
*/
|
||||
exports.withdrawApplication = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const { id } = req.params;
|
||||
|
||||
// 检查投递记录是否存在且属于当前用户
|
||||
const [applications] = await db.query(`
|
||||
SELECT id, status FROM job_applications
|
||||
WHERE id = ? AND user_id = ?
|
||||
`, [id, userId]);
|
||||
|
||||
if (applications.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '投递记录不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const application = applications[0];
|
||||
|
||||
// 检查状态是否允许撤回
|
||||
if (['offered', 'rejected', 'withdrawn'].includes(application.status)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '该投递记录无法撤回'
|
||||
});
|
||||
}
|
||||
|
||||
// 更新状态为撤回
|
||||
await db.query(`
|
||||
UPDATE job_applications
|
||||
SET status = 'withdrawn', updated_at = NOW()
|
||||
WHERE id = ?
|
||||
`, [id]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '撤回成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('撤回投递错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取投递统计
|
||||
*/
|
||||
exports.getStatistics = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
|
||||
// 统计各状态的投递数量
|
||||
const [stats] = await db.query(`
|
||||
SELECT
|
||||
COUNT(*) as total,
|
||||
SUM(CASE WHEN job_type = 'transition' THEN 1 ELSE 0 END) as transition_count,
|
||||
SUM(CASE WHEN job_type = 'referral' THEN 1 ELSE 0 END) as referral_count,
|
||||
SUM(CASE WHEN status = 'pending' THEN 1 ELSE 0 END) as pending_count,
|
||||
SUM(CASE WHEN status = 'reviewing' THEN 1 ELSE 0 END) as reviewing_count,
|
||||
SUM(CASE WHEN status = 'interviewed' THEN 1 ELSE 0 END) as interviewed_count,
|
||||
SUM(CASE WHEN status = 'offered' THEN 1 ELSE 0 END) as offered_count,
|
||||
SUM(CASE WHEN status = 'rejected' THEN 1 ELSE 0 END) as rejected_count
|
||||
FROM job_applications
|
||||
WHERE user_id = ?
|
||||
`, [userId]);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: stats[0]
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取统计错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
337
api/controllers/authController.js
Normal file
337
api/controllers/authController.js
Normal file
@@ -0,0 +1,337 @@
|
||||
/**
|
||||
* 用户认证控制器
|
||||
*/
|
||||
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const db = require('../../config/database');
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'duoduo-career-jwt-secret-key-2024';
|
||||
const JWT_EXPIRE = process.env.JWT_EXPIRE || '7d';
|
||||
|
||||
/**
|
||||
* 用户注册
|
||||
*/
|
||||
exports.register = async (req, res) => {
|
||||
try {
|
||||
const { username, password, email, phone, professional_category_code, professional_category } = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '真实姓名和密码不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证真实姓名长度
|
||||
if (username.length < 2 || username.length > 10) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '姓名长度必须在2-10个字符之间'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证密码长度
|
||||
if (password.length < 6) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '密码长度至少为6个字符'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证手机号必填
|
||||
if (!phone) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '手机号码不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查手机号是否已被使用(手机号不允许重复)
|
||||
const [existingPhones] = await db.query(
|
||||
'SELECT id FROM users WHERE phone = ?',
|
||||
[phone]
|
||||
);
|
||||
if (existingPhones.length > 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '该手机号已被注册'
|
||||
});
|
||||
}
|
||||
|
||||
// 如果提供了邮箱,检查是否已被使用
|
||||
if (email) {
|
||||
const [existingEmails] = await db.query(
|
||||
'SELECT id FROM users WHERE email = ?',
|
||||
[email]
|
||||
);
|
||||
if (existingEmails.length > 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '邮箱已被使用'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 加密密码
|
||||
const passwordHash = await bcrypt.hash(password, 10);
|
||||
|
||||
// 插入用户
|
||||
const [result] = await db.query(
|
||||
'INSERT INTO users (username, password_hash, email, phone, role, status) VALUES (?, ?, ?, ?, ?, ?)',
|
||||
[username, passwordHash, email || null, phone || null, 'user', 'active']
|
||||
);
|
||||
|
||||
// 创建用户资料记录
|
||||
await db.query(
|
||||
`INSERT INTO user_profiles (
|
||||
user_id,
|
||||
professional_category_code,
|
||||
professional_category
|
||||
) VALUES (?, ?, ?)`,
|
||||
[
|
||||
result.insertId,
|
||||
professional_category_code || null,
|
||||
professional_category || null
|
||||
]
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '注册成功',
|
||||
data: {
|
||||
id: result.insertId,
|
||||
username
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('注册错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误,请稍后重试'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 用户登录
|
||||
*/
|
||||
exports.login = async (req, res) => {
|
||||
try {
|
||||
const { username, password } = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
if (!username || !password) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '用户名和密码不能为空'
|
||||
});
|
||||
}
|
||||
|
||||
// 查询用户
|
||||
const [users] = await db.query(`
|
||||
SELECT
|
||||
u.id, u.username, u.password_hash, u.email, u.phone, u.role, u.status,
|
||||
p.professional_category_code,
|
||||
p.professional_category
|
||||
FROM users u
|
||||
LEFT JOIN user_profiles p ON u.id = p.user_id
|
||||
WHERE u.username = ?
|
||||
`, [username]);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
});
|
||||
}
|
||||
|
||||
const user = users[0];
|
||||
|
||||
// 检查账号状态
|
||||
if (user.status === 'banned') {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '账号已被封禁'
|
||||
});
|
||||
}
|
||||
|
||||
if (user.status === 'inactive') {
|
||||
return res.status(403).json({
|
||||
success: false,
|
||||
message: '账号未激活'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证密码
|
||||
const isPasswordValid = await bcrypt.compare(password, user.password_hash);
|
||||
|
||||
if (!isPasswordValid) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: '用户名或密码错误'
|
||||
});
|
||||
}
|
||||
|
||||
// 尝试更新最后登录时间(非阻塞)
|
||||
try {
|
||||
await db.query(
|
||||
'UPDATE users SET last_login = NOW() WHERE id = ?',
|
||||
[user.id]
|
||||
);
|
||||
} catch (updateError) {
|
||||
// 如果更新失败(如数据库锁定),记录错误但不影响登录
|
||||
console.warn('更新登录时间失败:', updateError.message);
|
||||
}
|
||||
|
||||
// 生成JWT
|
||||
const token = jwt.sign(
|
||||
{
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
role: user.role
|
||||
},
|
||||
JWT_SECRET,
|
||||
{ expiresIn: JWT_EXPIRE }
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '登录成功',
|
||||
data: {
|
||||
token,
|
||||
user: {
|
||||
id: user.id,
|
||||
username: user.username,
|
||||
email: user.email,
|
||||
phone: user.phone,
|
||||
role: user.role,
|
||||
professional_category_code: user.professional_category_code,
|
||||
professional_category: user.professional_category
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('登录错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误,请稍后重试'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
*/
|
||||
exports.getCurrentUser = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
|
||||
// 查询用户基本信息和资料
|
||||
const [users] = await db.query(`
|
||||
SELECT
|
||||
u.id, u.username, u.email, u.phone, u.role, u.status, u.created_at, u.last_login,
|
||||
p.real_name, p.gender, p.birth_date, p.education, p.major, p.school,
|
||||
p.graduation_year, p.city, p.avatar_url, p.self_intro, p.skills,
|
||||
p.professional_category_code,
|
||||
p.professional_category
|
||||
FROM users u
|
||||
LEFT JOIN user_profiles p ON u.id = p.user_id
|
||||
WHERE u.id = ?
|
||||
`, [userId]);
|
||||
|
||||
if (users.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '用户不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const user = users[0];
|
||||
|
||||
// 解析JSON字段
|
||||
if (user.skills) {
|
||||
try {
|
||||
user.skills = JSON.parse(user.skills);
|
||||
} catch (e) {
|
||||
user.skills = [];
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: user
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('获取用户信息错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新用户资料
|
||||
*/
|
||||
exports.updateProfile = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const {
|
||||
real_name, gender, birth_date, education, major, school,
|
||||
graduation_year, city, address, avatar_url, self_intro, skills,
|
||||
professional_category_code,
|
||||
professional_category
|
||||
} = req.body;
|
||||
|
||||
// 构建更新字段
|
||||
const updates = {};
|
||||
if (real_name !== undefined) updates.real_name = real_name;
|
||||
if (gender !== undefined) updates.gender = gender;
|
||||
if (birth_date !== undefined) updates.birth_date = birth_date;
|
||||
if (education !== undefined) updates.education = education;
|
||||
if (major !== undefined) updates.major = major;
|
||||
if (school !== undefined) updates.school = school;
|
||||
if (graduation_year !== undefined) updates.graduation_year = graduation_year;
|
||||
if (city !== undefined) updates.city = city;
|
||||
if (address !== undefined) updates.address = address;
|
||||
if (avatar_url !== undefined) updates.avatar_url = avatar_url;
|
||||
if (self_intro !== undefined) updates.self_intro = self_intro;
|
||||
if (skills !== undefined) updates.skills = JSON.stringify(skills);
|
||||
if (professional_category_code !== undefined) updates.professional_category_code = professional_category_code;
|
||||
if (professional_category !== undefined) updates.professional_category = professional_category;
|
||||
|
||||
if (Object.keys(updates).length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '没有要更新的字段'
|
||||
});
|
||||
}
|
||||
|
||||
// 构建SQL
|
||||
const fields = Object.keys(updates).map(key => `${key} = ?`).join(', ');
|
||||
const values = Object.values(updates);
|
||||
values.push(userId);
|
||||
|
||||
await db.query(
|
||||
`UPDATE user_profiles SET ${fields}, updated_at = NOW() WHERE user_id = ?`,
|
||||
values
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '资料更新成功'
|
||||
});
|
||||
|
||||
} catch (error) {
|
||||
console.error('更新资料错误:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '服务器错误'
|
||||
});
|
||||
}
|
||||
};
|
||||
251
api/controllers/highSalaryJobController.js
Normal file
251
api/controllers/highSalaryJobController.js
Normal file
@@ -0,0 +1,251 @@
|
||||
/**
|
||||
* 高薪岗位控制器
|
||||
* 基于high.html的最新数据结构
|
||||
*/
|
||||
|
||||
const db = require('../../config/database');
|
||||
|
||||
/**
|
||||
* 获取岗位列表
|
||||
* GET /api/high-salary-jobs
|
||||
* 支持分页、筛选、搜索
|
||||
*/
|
||||
exports.getList = async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 20;
|
||||
const offset = (page - 1) * limit;
|
||||
const location = req.query.location;
|
||||
const keyword = req.query.keyword;
|
||||
|
||||
// 构建查询条件
|
||||
let whereClause = 'WHERE is_active = 1';
|
||||
const params = [];
|
||||
|
||||
if (location) {
|
||||
whereClause += ' AND location = ?';
|
||||
params.push(location);
|
||||
}
|
||||
|
||||
if (keyword) {
|
||||
whereClause += ' AND (title LIKE ? OR company LIKE ?)';
|
||||
params.push(`%${keyword}%`, `%${keyword}%`);
|
||||
}
|
||||
|
||||
// 查询总数
|
||||
const [countResult] = await db.query(
|
||||
`SELECT COUNT(*) as total FROM high_salary_jobs ${whereClause}`,
|
||||
params
|
||||
);
|
||||
const total = countResult[0].total;
|
||||
|
||||
// 查询岗位列表
|
||||
const [jobs] = await db.query(
|
||||
`SELECT * FROM high_salary_jobs ${whereClause} ORDER BY display_order ASC, id DESC LIMIT ? OFFSET ?`,
|
||||
[...params, limit, offset]
|
||||
);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
jobs,
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取岗位列表失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '获取岗位列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取岗位详情
|
||||
* GET /api/high-salary-jobs/:id
|
||||
*/
|
||||
exports.getById = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const [jobs] = await db.query(
|
||||
'SELECT * FROM high_salary_jobs WHERE id = ? AND is_active = 1',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (jobs.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '岗位不存在'
|
||||
});
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: jobs[0]
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取岗位详情失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '获取岗位详情失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建岗位(管理员)
|
||||
* POST /api/high-salary-jobs
|
||||
*/
|
||||
exports.create = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
title,
|
||||
salary,
|
||||
company,
|
||||
location,
|
||||
quota,
|
||||
tags,
|
||||
requirements,
|
||||
description,
|
||||
icon,
|
||||
display_order
|
||||
} = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
if (!title || !salary || !company || !location || quota === undefined) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '缺少必填字段'
|
||||
});
|
||||
}
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO high_salary_jobs
|
||||
(title, salary, company, location, quota, tags, requirements, description, icon, display_order)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
title,
|
||||
salary,
|
||||
company,
|
||||
location,
|
||||
quota,
|
||||
JSON.stringify(tags || []),
|
||||
requirements,
|
||||
description,
|
||||
icon || 'fa-microchip',
|
||||
display_order || 0
|
||||
]
|
||||
);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
message: '岗位创建成功',
|
||||
data: { id: result.insertId }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建岗位失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '创建岗位失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新岗位(管理员)
|
||||
* PUT /api/high-salary-jobs/:id
|
||||
*/
|
||||
exports.update = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updateData = req.body;
|
||||
|
||||
// 检查岗位是否存在
|
||||
const [existing] = await db.query('SELECT id FROM high_salary_jobs WHERE id = ?', [id]);
|
||||
if (existing.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '岗位不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const updates = [];
|
||||
const params = [];
|
||||
|
||||
const allowedFields = ['title', 'salary', 'company', 'location', 'quota', 'tags', 'requirements', 'description', 'icon', 'display_order', 'is_active'];
|
||||
|
||||
for (const field of allowedFields) {
|
||||
if (updateData[field] !== undefined) {
|
||||
updates.push(`${field} = ?`);
|
||||
params.push(field === 'tags' ? JSON.stringify(updateData[field]) : updateData[field]);
|
||||
}
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '没有要更新的字段'
|
||||
});
|
||||
}
|
||||
|
||||
params.push(id);
|
||||
await db.query(
|
||||
`UPDATE high_salary_jobs SET ${updates.join(', ')} WHERE id = ?`,
|
||||
params
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '岗位更新成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新岗位失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '更新岗位失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除岗位(软删除)
|
||||
* DELETE /api/high-salary-jobs/:id
|
||||
*/
|
||||
exports.delete = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const [result] = await db.query(
|
||||
'UPDATE high_salary_jobs SET is_active = 0 WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '岗位不存在'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '岗位删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除岗位失败:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '删除岗位失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
224
api/controllers/successStoryController.js
Normal file
224
api/controllers/successStoryController.js
Normal file
@@ -0,0 +1,224 @@
|
||||
/**
|
||||
* 成功案例控制器
|
||||
* 基于high.html的最新数据结构 (三轨道弹幕系统)
|
||||
*/
|
||||
|
||||
const db = require('../../config/database');
|
||||
|
||||
/**
|
||||
* 获取成功案例列表 (按轨道分组)
|
||||
* GET /api/success-stories
|
||||
* 支持可选的track_type筛选
|
||||
*/
|
||||
exports.getList = async (req, res) => {
|
||||
try {
|
||||
const trackType = req.query.track_type;
|
||||
|
||||
// 构建查询条件
|
||||
let whereClause = 'WHERE is_active = 1';
|
||||
const params = [];
|
||||
|
||||
if (trackType && ['fast', 'slow', 'medium'].includes(trackType)) {
|
||||
whereClause += ' AND track_type = ?';
|
||||
params.push(trackType);
|
||||
}
|
||||
|
||||
const [stories] = await db.query(
|
||||
`SELECT
|
||||
id,
|
||||
name,
|
||||
job,
|
||||
company,
|
||||
salary,
|
||||
avatar_color,
|
||||
track_type
|
||||
FROM success_stories
|
||||
${whereClause}
|
||||
ORDER BY display_order ASC, id ASC`,
|
||||
params
|
||||
);
|
||||
|
||||
// 按轨道分组
|
||||
const grouped = {
|
||||
fast: stories.filter(s => s.track_type === 'fast'),
|
||||
slow: stories.filter(s => s.track_type === 'slow'),
|
||||
medium: stories.filter(s => s.track_type === 'medium')
|
||||
};
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: grouped
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取成功案例失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '获取成功案例失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建成功案例 (管理员)
|
||||
* POST /api/success-stories
|
||||
*/
|
||||
exports.create = async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
name,
|
||||
job,
|
||||
company,
|
||||
salary,
|
||||
avatar_color,
|
||||
track_type,
|
||||
display_order
|
||||
} = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
if (!name || !job || !company || !salary) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '缺少必填字段 (name, job, company, salary)'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证track_type
|
||||
if (track_type && !['fast', 'slow', 'medium'].includes(track_type)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'track_type 必须是 fast, slow 或 medium'
|
||||
});
|
||||
}
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO success_stories
|
||||
(name, job, company, salary, avatar_color, track_type, display_order)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)`,
|
||||
[
|
||||
name,
|
||||
job,
|
||||
company,
|
||||
salary,
|
||||
avatar_color || '#2563eb',
|
||||
track_type || 'fast',
|
||||
display_order || 0
|
||||
]
|
||||
);
|
||||
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
message: '成功案例创建成功',
|
||||
data: { id: result.insertId }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建成功案例失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '创建成功案例失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新成功案例 (管理员)
|
||||
* PUT /api/success-stories/:id
|
||||
*/
|
||||
exports.update = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const updateData = req.body;
|
||||
|
||||
// 检查案例是否存在
|
||||
const [existing] = await db.query(
|
||||
'SELECT id FROM success_stories WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (existing.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '成功案例不存在'
|
||||
});
|
||||
}
|
||||
|
||||
const updates = [];
|
||||
const params = [];
|
||||
|
||||
const allowedFields = ['name', 'job', 'company', 'salary', 'avatar_color', 'track_type', 'display_order', 'is_active'];
|
||||
|
||||
for (const field of allowedFields) {
|
||||
if (updateData[field] !== undefined) {
|
||||
// 验证track_type
|
||||
if (field === 'track_type' && !['fast', 'slow', 'medium'].includes(updateData[field])) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'track_type 必须是 fast, slow 或 medium'
|
||||
});
|
||||
}
|
||||
updates.push(`${field} = ?`);
|
||||
params.push(updateData[field]);
|
||||
}
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '没有要更新的字段'
|
||||
});
|
||||
}
|
||||
|
||||
params.push(id);
|
||||
await db.query(
|
||||
`UPDATE success_stories SET ${updates.join(', ')} WHERE id = ?`,
|
||||
params
|
||||
);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '成功案例更新成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新成功案例失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '更新成功案例失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除成功案例 (软删除)
|
||||
* DELETE /api/success-stories/:id
|
||||
*/
|
||||
exports.delete = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const [result] = await db.query(
|
||||
'UPDATE success_stories SET is_active = 0 WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '成功案例不存在'
|
||||
});
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '成功案例删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除成功案例失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '删除成功案例失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
229
api/controllers/trainingUnitController.js
Normal file
229
api/controllers/trainingUnitController.js
Normal file
@@ -0,0 +1,229 @@
|
||||
/**
|
||||
* 培训单元控制器
|
||||
* 基于high.html的最新数据结构
|
||||
*/
|
||||
|
||||
const db = require('../../config/database');
|
||||
|
||||
/**
|
||||
* 获取所有培训单元
|
||||
* GET /api/training-units
|
||||
* 支持optionalAuth
|
||||
*/
|
||||
exports.getList = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user ? req.user.id : null;
|
||||
|
||||
let query, params;
|
||||
|
||||
if (userId) {
|
||||
// 已登录:查询确认状态
|
||||
query = `
|
||||
SELECT
|
||||
tu.*,
|
||||
tc.id as confirmation_id,
|
||||
tc.confirmed_at
|
||||
FROM training_units tu
|
||||
LEFT JOIN training_confirmations tc ON tu.id = tc.training_unit_id AND tc.user_id = ?
|
||||
ORDER BY tu.display_order ASC, tu.start_date ASC
|
||||
`;
|
||||
params = [userId];
|
||||
} else {
|
||||
// 未登录:仅返回单元列表
|
||||
query = `
|
||||
SELECT * FROM training_units
|
||||
ORDER BY display_order ASC, start_date ASC
|
||||
`;
|
||||
params = [];
|
||||
}
|
||||
|
||||
const [units] = await db.query(query, params);
|
||||
|
||||
// 格式化数据
|
||||
const formattedUnits = units.map(unit => ({
|
||||
id: unit.id,
|
||||
title: unit.title,
|
||||
start_date: unit.start_date,
|
||||
related_positions: unit.related_positions,
|
||||
related_companies: unit.related_companies,
|
||||
status: unit.status,
|
||||
is_confirmed: !!unit.confirmation_id,
|
||||
confirmed_at: unit.confirmed_at || null
|
||||
}));
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: formattedUnits
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取培训单元失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '获取培训单元失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 确认预招录
|
||||
* POST /api/training-units/:id/confirm
|
||||
* 需要登录
|
||||
*/
|
||||
exports.confirm = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
// 检查单元是否存在
|
||||
const [units] = await db.query(
|
||||
'SELECT id, title FROM training_units WHERE id = ?',
|
||||
[id]
|
||||
);
|
||||
|
||||
if (units.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '培训单元不存在'
|
||||
});
|
||||
}
|
||||
|
||||
// 检查是否已确认
|
||||
const [existing] = await db.query(
|
||||
'SELECT id FROM training_confirmations WHERE user_id = ? AND training_unit_id = ?',
|
||||
[userId, id]
|
||||
);
|
||||
|
||||
if (existing.length > 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '您已确认过该单元'
|
||||
});
|
||||
}
|
||||
|
||||
// 插入确认记录(设置较短的超时时间)
|
||||
try {
|
||||
await db.query(
|
||||
'INSERT INTO training_confirmations (user_id, training_unit_id) VALUES (?, ?)',
|
||||
[userId, id]
|
||||
);
|
||||
} catch (insertError) {
|
||||
// 特殊处理数据库锁定错误
|
||||
if (insertError.code === 'ER_LOCK_WAIT_TIMEOUT') {
|
||||
return res.status(503).json({
|
||||
success: false,
|
||||
message: '系统繁忙,请稍后再试',
|
||||
error: '数据库暂时无法处理请求'
|
||||
});
|
||||
}
|
||||
throw insertError;
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '确认成功'
|
||||
});
|
||||
} catch (error) {
|
||||
// 处理唯一键冲突
|
||||
if (error.code === 'ER_DUP_ENTRY') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '您已确认过该单元'
|
||||
});
|
||||
}
|
||||
|
||||
// 处理数据库锁定错误
|
||||
if (error.code === 'ER_LOCK_WAIT_TIMEOUT') {
|
||||
console.error('数据库锁定:', error.message);
|
||||
return res.status(503).json({
|
||||
success: false,
|
||||
message: '系统繁忙,请稍后再试',
|
||||
error: '数据库暂时无法处理请求'
|
||||
});
|
||||
}
|
||||
|
||||
console.error('确认预招录失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '确认失败,请稍后重试'
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 取消预招录
|
||||
* DELETE /api/training-units/:id/confirm
|
||||
* 需要登录
|
||||
*/
|
||||
exports.cancelConfirm = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
const [result] = await db.query(
|
||||
'DELETE FROM training_confirmations WHERE user_id = ? AND training_unit_id = ?',
|
||||
[userId, id]
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '未找到确认记录'
|
||||
});
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '取消成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('取消预招录失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '取消预招录失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取我的确认列表
|
||||
* GET /api/training-units/my-confirmations
|
||||
* 需要登录
|
||||
*/
|
||||
exports.getMyConfirmations = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
|
||||
const [confirmations] = await db.query(`
|
||||
SELECT
|
||||
tu.*,
|
||||
tc.confirmed_at
|
||||
FROM training_confirmations tc
|
||||
JOIN training_units tu ON tc.training_unit_id = tu.id
|
||||
WHERE tc.user_id = ?
|
||||
ORDER BY tc.confirmed_at DESC
|
||||
`, [userId]);
|
||||
|
||||
const formattedData = confirmations.map(c => ({
|
||||
id: c.id,
|
||||
title: c.title,
|
||||
start_date: c.start_date,
|
||||
related_positions: c.related_positions,
|
||||
related_companies: c.related_companies,
|
||||
confirmed_at: c.confirmed_at
|
||||
}));
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: formattedData
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取确认列表失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '获取确认列表失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
270
api/controllers/transitionProgressController.js
Normal file
270
api/controllers/transitionProgressController.js
Normal file
@@ -0,0 +1,270 @@
|
||||
/**
|
||||
* 面试进度控制器
|
||||
* 基于high.html的最新数据结构
|
||||
*/
|
||||
|
||||
const db = require('../../config/database');
|
||||
|
||||
/**
|
||||
* 获取我的面试进度列表
|
||||
* GET /api/transition-progress
|
||||
* 需要登录
|
||||
*/
|
||||
exports.getMyProgress = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
|
||||
const [records] = await db.query(
|
||||
`SELECT
|
||||
id,
|
||||
company,
|
||||
job,
|
||||
status,
|
||||
status_type,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM transition_progress
|
||||
WHERE user_id = ?
|
||||
ORDER BY created_at DESC`,
|
||||
[userId]
|
||||
);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: records
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取面试进度失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '获取面试进度失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 创建面试进度记录
|
||||
* POST /api/transition-progress
|
||||
* 需要登录
|
||||
*/
|
||||
exports.create = async (req, res) => {
|
||||
try {
|
||||
const userId = req.user.id;
|
||||
const {
|
||||
company,
|
||||
job,
|
||||
status,
|
||||
status_type
|
||||
} = req.body;
|
||||
|
||||
// 验证必填字段
|
||||
if (!company || !job || !status) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '缺少必填字段 (company, job, status)'
|
||||
});
|
||||
}
|
||||
|
||||
// 验证status_type
|
||||
if (status_type && !['offer', 'hr', 'ing', 'pending'].includes(status_type)) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'status_type 必须是 offer, hr, ing 或 pending'
|
||||
});
|
||||
}
|
||||
|
||||
const [result] = await db.query(
|
||||
`INSERT INTO transition_progress
|
||||
(user_id, company, job, status, status_type)
|
||||
VALUES (?, ?, ?, ?, ?)`,
|
||||
[
|
||||
userId,
|
||||
company,
|
||||
job,
|
||||
status,
|
||||
status_type || 'pending'
|
||||
]
|
||||
);
|
||||
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
message: '面试进度记录创建成功',
|
||||
data: { id: result.insertId }
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('创建面试进度失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '创建面试进度失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 更新面试进度
|
||||
* PUT /api/transition-progress/:id
|
||||
* 需要登录
|
||||
*/
|
||||
exports.update = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const userId = req.user.id;
|
||||
const updateData = req.body;
|
||||
|
||||
// 检查记录是否存在且属于当前用户
|
||||
const [existing] = await db.query(
|
||||
'SELECT id FROM transition_progress WHERE id = ? AND user_id = ?',
|
||||
[id, userId]
|
||||
);
|
||||
|
||||
if (existing.length === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '面试进度记录不存在或无权访问'
|
||||
});
|
||||
}
|
||||
|
||||
const updates = [];
|
||||
const params = [];
|
||||
|
||||
const allowedFields = ['company', 'job', 'status', 'status_type'];
|
||||
|
||||
for (const field of allowedFields) {
|
||||
if (updateData[field] !== undefined) {
|
||||
// 验证status_type
|
||||
if (field === 'status_type' && !['offer', 'hr', 'ing', 'pending'].includes(updateData[field])) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'status_type 必须是 offer, hr, ing 或 pending'
|
||||
});
|
||||
}
|
||||
updates.push(`${field} = ?`);
|
||||
params.push(updateData[field]);
|
||||
}
|
||||
}
|
||||
|
||||
if (updates.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '没有要更新的字段'
|
||||
});
|
||||
}
|
||||
|
||||
params.push(id);
|
||||
await db.query(
|
||||
`UPDATE transition_progress SET ${updates.join(', ')} WHERE id = ?`,
|
||||
params
|
||||
);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '面试进度更新成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('更新面试进度失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '更新面试进度失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 删除面试进度记录
|
||||
* DELETE /api/transition-progress/:id
|
||||
* 需要登录
|
||||
*/
|
||||
exports.delete = async (req, res) => {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const userId = req.user.id;
|
||||
|
||||
const [result] = await db.query(
|
||||
'DELETE FROM transition_progress WHERE id = ? AND user_id = ?',
|
||||
[id, userId]
|
||||
);
|
||||
|
||||
if (result.affectedRows === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '面试进度记录不存在或无权访问'
|
||||
});
|
||||
}
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: '面试进度删除成功'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('删除面试进度失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '删除面试进度失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 获取所有面试进度记录 (管理员)
|
||||
* GET /api/transition-progress/all
|
||||
* 需要管理员权限
|
||||
*/
|
||||
exports.getAll = async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 20;
|
||||
const offset = (page - 1) * limit;
|
||||
const statusType = req.query.status_type;
|
||||
|
||||
// 构建查询条件
|
||||
let whereClause = '';
|
||||
const params = [];
|
||||
|
||||
if (statusType && ['offer', 'hr', 'ing', 'pending'].includes(statusType)) {
|
||||
whereClause = 'WHERE status_type = ?';
|
||||
params.push(statusType);
|
||||
}
|
||||
|
||||
// 查询总数
|
||||
const [countResult] = await db.query(
|
||||
`SELECT COUNT(*) as total FROM transition_progress ${whereClause}`,
|
||||
params
|
||||
);
|
||||
const total = countResult[0].total;
|
||||
|
||||
// 查询记录列表
|
||||
const [records] = await db.query(
|
||||
`SELECT
|
||||
tp.*,
|
||||
u.username
|
||||
FROM transition_progress tp
|
||||
LEFT JOIN users u ON tp.user_id = u.id
|
||||
${whereClause}
|
||||
ORDER BY tp.created_at DESC
|
||||
LIMIT ? OFFSET ?`,
|
||||
[...params, limit, offset]
|
||||
);
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
records,
|
||||
total,
|
||||
page,
|
||||
limit,
|
||||
totalPages: Math.ceil(total / limit)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('获取所有面试进度失败:', error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: '获取所有面试进度失败',
|
||||
error: error.message
|
||||
});
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user