diff --git a/backend/src/dao/UserDAO.ts b/backend/src/dao/UserDAO.ts index e056709..6b27724 100644 --- a/backend/src/dao/UserDAO.ts +++ b/backend/src/dao/UserDAO.ts @@ -28,3 +28,35 @@ export async function findSubordinates(managerId: number): Promise { ); return rows as UserRow[]; } + +export interface CreateUserInput { + username: string; + password: string; + name: string; + role?: UserRole; + department: string; + position: string; + manager_id?: number | null; + status?: 'active' | 'inactive'; +} + +export async function createUser(userData: CreateUserInput): Promise { + const { + username, + password, + name, + role = 'employee', + department, + position, + manager_id = null, + status = 'active' + } = userData; + + const [result] = await pool.query( + `INSERT INTO user (username, password, name, role, department, position, manager_id, status) + VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, + [username, password, name, role, department, position, manager_id, status] + ); + + return result.insertId; +} diff --git a/backend/src/routes/auth.ts b/backend/src/routes/auth.ts index b19bf7a..0815652 100644 --- a/backend/src/routes/auth.ts +++ b/backend/src/routes/auth.ts @@ -1,5 +1,5 @@ import { Router, Request, Response } from 'express'; -import { login } from '../services/AuthService'; +import { login, register } from '../services/AuthService'; const router = Router(); @@ -24,4 +24,25 @@ router.post('/login', async (req: Request, res: Response) => { } }); +// POST /api/user/register +router.post('/register', async (req: Request, res: Response) => { + console.log('收到注册请求:', req.body); + const { username, password, name, department, position, role } = req.body; + + if (!username || !password || !name || !department || !position) { + console.log('参数验证失败'); + return res.status(400).json({ code: 400, message: '用户名、密码、姓名、部门和岗位均为必填' }); + } + + try { + console.log('调用注册服务...'); + const result = await register({ username, password, name, department, position, role }); + console.log('注册成功:', result.userInfo); + return res.json({ code: 200, message: '注册成功', data: result }); + } catch (err: any) { + console.error('注册失败:', err.message); + return res.status(400).json({ code: 400, message: err.message || '注册失败' }); + } +}); + export default router; diff --git a/backend/src/services/AuthService.ts b/backend/src/services/AuthService.ts index 96d19a5..c56956f 100644 --- a/backend/src/services/AuthService.ts +++ b/backend/src/services/AuthService.ts @@ -1,7 +1,7 @@ // 注意:此版本使用明文密码验证,所有用户密码均为123456(仅用于测试环境) // 生产环境必须使用加密密码存储和验证 import jwt from 'jsonwebtoken'; -import { findByUsername } from '../dao/UserDAO'; +import { findByUsername, createUser, CreateUserInput } from '../dao/UserDAO'; import { JWT_SECRET, JWT_EXPIRES_IN } from '../config/jwt'; import { LoginResult, UserInfo, UserRole } from '../types'; @@ -38,3 +38,53 @@ export async function login( return { token, userInfo }; } + +export interface RegisterInput { + username: string; + password: string; + name: string; + department: string; + position: string; + role?: UserRole; +} + +export async function register(userData: RegisterInput): Promise { + const { username, password, name, department, position, role = 'employee' } = userData; + + // 检查必填字段 + if (!username || !password || !name || !department || !position) { + throw new Error('用户名、密码、姓名、部门和岗位均为必填'); + } + + // 检查用户名是否已存在 + const existingUser = await findByUsername(username); + if (existingUser) { + throw new Error('用户名已存在'); + } + + // 创建用户 - 所有用户密码固定为123456(明文存储) + const userId = await createUser({ + username, + password: '123456', // 固定密码,忽略用户输入的密码 + name, + role, + department, + position, + manager_id: null, // 新注册用户没有直属领导 + status: 'active' + }); + + // 注册成功后自动登录,返回token和用户信息 + const userInfo: UserInfo = { + userId, + name, + role, + department, + position, + managerId: null + }; + + const token = jwt.sign(userInfo, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN }); + + return { token, userInfo }; +} diff --git a/backend/tmpclaude-2026-cwd b/backend/tmpclaude-2026-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-2026-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/backend/tmpclaude-4785-cwd b/backend/tmpclaude-4785-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-4785-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/backend/tmpclaude-83c4-cwd b/backend/tmpclaude-83c4-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-83c4-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/backend/tmpclaude-b1f0-cwd b/backend/tmpclaude-b1f0-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-b1f0-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/backend/tmpclaude-c361-cwd b/backend/tmpclaude-c361-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-c361-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/backend/tmpclaude-dc44-cwd b/backend/tmpclaude-dc44-cwd new file mode 100644 index 0000000..c189ea7 --- /dev/null +++ b/backend/tmpclaude-dc44-cwd @@ -0,0 +1 @@ +/c/Users/99095/Desktop/优一科技/performance-evaluation-system/backend diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts index e549edc..2fcdf17 100644 --- a/frontend/src/api/auth.ts +++ b/frontend/src/api/auth.ts @@ -12,6 +12,15 @@ interface ApiResponse { data: T; } +export interface RegisterRequest { + username: string; + password: string; + name: string; + department: string; + position: string; + role?: string; +} + export const authApi = { login: async ( username: string, @@ -25,4 +34,9 @@ export const authApi = { }); return data.data; // 返回 data.data,因为后端包装了一层 }, + + register: async (userData: RegisterRequest): Promise => { + const { data } = await http.post>('/api/user/register', userData); + return data.data; + }, }; diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 3b7c5f1..dba4d73 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -1,7 +1,7 @@ import React, { useState } from 'react'; import { Form, Input, Button, Select, Alert, Typography } from 'antd'; import { UserOutlined, LockOutlined, TeamOutlined } from '@ant-design/icons'; -import { useNavigate } from 'react-router-dom'; +import { useNavigate, Link } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import { authApi } from '../api/auth'; import logo from '../img/logo2.png'; @@ -126,7 +126,12 @@ const LoginPage: React.FC = () => {
- 优一科技 © 2026 + + 新用户?注册新账户 + +
+ 优一科技 © 2026 +
diff --git a/frontend/src/pages/Register.tsx b/frontend/src/pages/Register.tsx new file mode 100644 index 0000000..ad2b11a --- /dev/null +++ b/frontend/src/pages/Register.tsx @@ -0,0 +1,267 @@ +import React, { useState } from 'react'; +import { Form, Input, Button, Alert, Typography } from 'antd'; +import { UserOutlined, LockOutlined, IdcardOutlined, ApartmentOutlined, UsergroupAddOutlined } from '@ant-design/icons'; +import { useNavigate, Link } from 'react-router-dom'; +import { useAuth } from '../context/AuthContext'; +import { authApi } from '../api/auth'; +import logo from '../img/logo2.png'; + +const { Text } = Typography; + +interface RegisterFormValues { + username: string; + password: string; + confirmPassword: string; + name: string; + department: string; + position: string; +} + +const RegisterPage: React.FC = () => { + const { login } = useAuth(); + const navigate = useNavigate(); + const [loading, setLoading] = useState(false); + const [alertInfo, setAlertInfo] = useState<{ type: 'success' | 'error'; msg: string } | null>(null); + + const onFinish = async (values: RegisterFormValues) => { + setLoading(true); + setAlertInfo(null); + try { + // 调用注册API + const { token, userInfo } = await authApi.register({ + username: values.username, + password: values.password, + name: values.name, + department: values.department, + position: values.position, + role: 'employee', // 新注册用户默认为员工角色 + }); + + // 注册成功后自动登录 + login(token, userInfo); + setAlertInfo({ type: 'success', msg: `注册成功!欢迎 ${userInfo.name},正在跳转到员工页面...` }); + setTimeout(() => navigate('/employee', { replace: true }), 1500); + } catch (err: any) { + setAlertInfo({ type: 'error', msg: err?.response?.data?.message || '注册失败,请检查输入信息' }); + } finally { + setLoading(false); + } + }; + + return ( +
+ {/* 光晕装饰 */} +
+
+
+ + {/* 双层纹理叠层:点阵 + 斜线 */} +
+ +
+ {/* Logo + 标题 */} +
+ logo +
员工月度绩效考核系统
+
新用户注册
+
+ + {/* 提示信息 */} + {alertInfo && ( + + )} + + + + + name="register" + onFinish={onFinish} + size="large" + > + + } + placeholder="用户名(工号)" + style={styles.input} + /> + + + + } + placeholder="密码" + style={styles.input} + /> + + + ({ + validator(_, value) { + if (!value || getFieldValue('password') === value) { + return Promise.resolve(); + } + return Promise.reject(new Error('两次输入的密码不一致')); + }, + }), + ]} + > + } + placeholder="确认密码" + style={styles.input} + /> + + + + } + placeholder="姓名" + style={styles.input} + /> + + + + } + placeholder="部门" + style={styles.input} + /> + + + + } + placeholder="岗位" + style={styles.input} + /> + + + + + + + +
+ + 已有账户?返回登录 + +
+ 优一科技 © 2026 +
+
+
+
+ ); +}; + +const styles: Record = { + bg: { + minHeight: '100vh', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + background: 'linear-gradient(135deg, #f0f7ff 0%, #e8f0fe 35%, #fce4ec 70%, #f3e5f5 100%)', + position: 'relative', + overflow: 'hidden', + padding: 16, + }, + circle: { + position: 'absolute', + borderRadius: '50%', + background: 'radial-gradient(circle, rgba(99,102,241,0.12) 0%, transparent 70%)', + filter: 'blur(40px)', + }, + card: { + width: '100%', + maxWidth: 420, + background: '#ffffff', + borderRadius: 20, + padding: '36px 32px 28px', + boxShadow: '0 4px 24px rgba(99,102,241,0.12), 0 1px 4px rgba(0,0,0,0.06)', + position: 'relative', + zIndex: 1, + border: '1px solid rgba(99,102,241,0.08)', + }, + input: { + borderRadius: 8, + height: 44, + }, + btn: { + height: 46, + borderRadius: 10, + fontSize: 16, + fontWeight: 600, + background: 'linear-gradient(90deg, #6366f1, #8b5cf6)', + border: 'none', + letterSpacing: 2, + }, +}; + +export default RegisterPage; \ No newline at end of file diff --git a/frontend/src/router/index.tsx b/frontend/src/router/index.tsx index b5f8d6b..c54c90d 100644 --- a/frontend/src/router/index.tsx +++ b/frontend/src/router/index.tsx @@ -7,6 +7,7 @@ const EmployeeDashboard = React.lazy(() => import('../pages/employee/Dashboard') const ManagerDashboard = React.lazy(() => import('../pages/manager/Dashboard')); const GMDashboard = React.lazy(() => import('../pages/gm/Dashboard')); const LoginPage = React.lazy(() => import('../pages/Login')); +const RegisterPage = React.lazy(() => import('../pages/Register')); interface ProtectedRouteProps { children: React.ReactNode; @@ -43,6 +44,7 @@ const AppRouter: React.FC = () => { 加载中...
}> } /> + } /> {/* 员工路由 */}