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:
@@ -14,6 +14,31 @@ import { CONFIG } from '@gala/shared/constants';
|
||||
|
||||
type GalaSocket = Socket<ServerToClientEvents, ClientToServerEvents>;
|
||||
|
||||
// LocalStorage keys
|
||||
const STORAGE_KEYS = {
|
||||
USER_ID: 'gala_user_id',
|
||||
USER_NAME: 'gala_user_name',
|
||||
DEPARTMENT: 'gala_department',
|
||||
};
|
||||
|
||||
// Helper functions for localStorage
|
||||
function loadFromStorage<T>(key: string, defaultValue: T): T {
|
||||
try {
|
||||
const stored = localStorage.getItem(key);
|
||||
return stored ? JSON.parse(stored) : defaultValue;
|
||||
} catch {
|
||||
return defaultValue;
|
||||
}
|
||||
}
|
||||
|
||||
function saveToStorage(key: string, value: unknown): void {
|
||||
try {
|
||||
localStorage.setItem(key, JSON.stringify(value));
|
||||
} catch (e) {
|
||||
console.warn('[Storage] Failed to save:', e);
|
||||
}
|
||||
}
|
||||
|
||||
export const useConnectionStore = defineStore('connection', () => {
|
||||
// State - use shallowRef for socket to avoid deep reactivity issues
|
||||
const socket = shallowRef<GalaSocket | null>(null);
|
||||
@@ -23,9 +48,9 @@ export const useConnectionStore = defineStore('connection', () => {
|
||||
const lastPingTime = ref<number>(0);
|
||||
const latency = ref<number>(0);
|
||||
const reconnectAttempts = ref(0);
|
||||
const userId = ref<string | null>(null);
|
||||
const userName = ref<string | null>(null);
|
||||
const department = ref<string | null>(null);
|
||||
const userId = ref<string | null>(loadFromStorage(STORAGE_KEYS.USER_ID, null));
|
||||
const userName = ref<string | null>(loadFromStorage(STORAGE_KEYS.USER_NAME, null));
|
||||
const department = ref<string | null>(loadFromStorage(STORAGE_KEYS.DEPARTMENT, null));
|
||||
const votedCategories = ref<VoteCategory[]>([]);
|
||||
|
||||
// Computed
|
||||
@@ -69,10 +94,12 @@ export const useConnectionStore = defineStore('connection', () => {
|
||||
isConnecting.value = false;
|
||||
reconnectAttempts.value = 0;
|
||||
|
||||
// Auto-generate userId if not set
|
||||
// Auto-generate userId if not set (and save to localStorage)
|
||||
if (!userId.value) {
|
||||
userId.value = `user_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
||||
userName.value = '访客';
|
||||
saveToStorage(STORAGE_KEYS.USER_ID, userId.value);
|
||||
saveToStorage(STORAGE_KEYS.USER_NAME, userName.value);
|
||||
}
|
||||
|
||||
// Join with user info
|
||||
@@ -217,19 +244,43 @@ export const useConnectionStore = defineStore('connection', () => {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set user info
|
||||
* Set user info (and persist to localStorage)
|
||||
*/
|
||||
function setUser(id: string, name: string, dept: string) {
|
||||
userId.value = id;
|
||||
userName.value = name;
|
||||
department.value = dept;
|
||||
|
||||
// Persist to localStorage
|
||||
saveToStorage(STORAGE_KEYS.USER_ID, id);
|
||||
saveToStorage(STORAGE_KEYS.USER_NAME, name);
|
||||
saveToStorage(STORAGE_KEYS.DEPARTMENT, dept);
|
||||
|
||||
// Rejoin if already connected
|
||||
if (socket.value?.connected) {
|
||||
joinRoom();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Logout and clear stored user info
|
||||
*/
|
||||
function logout() {
|
||||
// Clear state
|
||||
userId.value = null;
|
||||
userName.value = null;
|
||||
department.value = null;
|
||||
votedCategories.value = [];
|
||||
|
||||
// Clear localStorage
|
||||
localStorage.removeItem(STORAGE_KEYS.USER_ID);
|
||||
localStorage.removeItem(STORAGE_KEYS.USER_NAME);
|
||||
localStorage.removeItem(STORAGE_KEYS.DEPARTMENT);
|
||||
|
||||
// Disconnect socket
|
||||
disconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Add voted category
|
||||
*/
|
||||
@@ -274,6 +325,7 @@ export const useConnectionStore = defineStore('connection', () => {
|
||||
// Actions
|
||||
connect,
|
||||
disconnect,
|
||||
logout,
|
||||
setUser,
|
||||
addVotedCategory,
|
||||
requestSync,
|
||||
|
||||
Reference in New Issue
Block a user