feat: implement QR code scan login system with admin control

- Add scan login service with Redis-based token management
- Add scan login API routes for token generation and validation
- Add QRCodeLogin component for PC-side QR code display
- Add EntryQRCode component for mass login scenarios
- Add ScanLoginView for mobile-side login form
- Add localStorage persistence for user identity
- Add logout functionality to mobile client
- Add auto-redirect for logged-in users
- Add admin console control for QR code display on big screen
- Add socket event forwarding from admin to screen display

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
empty
2026-01-25 21:44:52 +08:00
parent 75570af8bc
commit f4736b6ebd
26 changed files with 1925 additions and 119 deletions

View File

@@ -34,6 +34,15 @@ export const SOCKET_EVENTS = {
ADMIN_EMERGENCY_RESET: 'admin:emergency_reset',
ADMIN_STATE_SYNC: 'admin:state_sync',
ADMIN_MUSIC_CONTROL: 'admin:music_control',
// Scan login events
SCAN_SUBSCRIBE: 'scan:subscribe',
SCAN_UNSUBSCRIBE: 'scan:unsubscribe',
SCAN_STATUS_UPDATE: 'scan:status_update',
// QR code display control events
DISPLAY_SHOW_ENTRY_QR: 'display:show_entry_qr',
DISPLAY_HIDE_QR: 'display:hide_qr',
} as const;
export const SOCKET_ROOMS = {

View File

@@ -3,3 +3,4 @@ export * from './socket.types';
export * from './vote.types';
export * from './draw.types';
export * from './admin.types';
export * from './scan-login.types';

View File

@@ -0,0 +1,66 @@
// Scan login types
export type ScanLoginStatus = 'pending' | 'scanned' | 'confirmed' | 'expired';
export interface ScanTokenData {
scanToken: string;
pcSocketId: string;
status: ScanLoginStatus;
createdAt: number;
expiresAt: number;
userInfo?: {
userId: string;
userName: string;
department: string;
};
}
export interface GenerateScanTokenResponse {
success: boolean;
data?: {
scanToken: string;
qrCodeUrl: string;
expiresAt: number;
};
error?: string;
}
export interface ValidateTokenResponse {
success: boolean;
data?: {
valid: boolean;
status: ScanLoginStatus;
expiresAt: number;
};
error?: string;
}
export interface ScanConfirmPayload {
scanToken: string;
userName: string;
department: string;
}
export interface ScanConfirmResponse {
success: boolean;
data?: {
sessionToken: string;
userId: string;
};
error?: string;
}
export interface ScanStatusUpdatePayload {
scanToken: string;
status: ScanLoginStatus;
userInfo?: {
userId: string;
userName: string;
department: string;
sessionToken: string;
};
}
export interface ScanSubscribePayload {
scanToken: string;
}