feat: update login flow and add firework effects
- Update mobile HomeView to show WeChat scan login instructions - Remove manual name/department input form from mobile client - Add firework particle effects to big screen background - Remove department field from login flow types Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ import { SOCKET_EVENTS } from '@gala/shared/constants';
|
||||
import type { ScanStatusUpdatePayload, ScanLoginStatus } from '@gala/shared/types';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'login-success', data: { userId: string; userName: string; department: string; sessionToken: string }): void;
|
||||
(e: 'login-success', data: { userId: string; userName: string; sessionToken: string }): void;
|
||||
(e: 'login-cancel'): void;
|
||||
}>();
|
||||
|
||||
@@ -125,7 +125,6 @@ function handleStatusUpdate(data: ScanStatusUpdatePayload) {
|
||||
emit('login-success', {
|
||||
userId: data.userInfo.userId,
|
||||
userName: data.userInfo.userName,
|
||||
department: data.userInfo.department,
|
||||
sessionToken: data.userInfo.sessionToken,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -6,8 +6,22 @@ const COLORS = {
|
||||
gold: 0xf0c239,
|
||||
goldDark: 0xd4a84b,
|
||||
goldLight: 0xffd700,
|
||||
// Firework colors
|
||||
fireworkRed: 0xff4444,
|
||||
fireworkOrange: 0xff8844,
|
||||
fireworkYellow: 0xffdd44,
|
||||
fireworkPink: 0xff66aa,
|
||||
fireworkWhite: 0xffffee,
|
||||
};
|
||||
|
||||
// Firework color palettes
|
||||
const FIREWORK_PALETTES = [
|
||||
[COLORS.gold, COLORS.goldLight, COLORS.fireworkYellow],
|
||||
[COLORS.fireworkRed, COLORS.fireworkOrange, COLORS.fireworkYellow],
|
||||
[COLORS.fireworkPink, COLORS.fireworkWhite, COLORS.gold],
|
||||
[COLORS.goldDark, COLORS.gold, COLORS.fireworkWhite],
|
||||
];
|
||||
|
||||
interface Particle {
|
||||
x: number;
|
||||
y: number;
|
||||
@@ -17,9 +31,11 @@ interface Particle {
|
||||
alpha: number;
|
||||
rotation: number;
|
||||
rotationSpeed: number;
|
||||
type: 'dust' | 'symbol' | 'streak';
|
||||
type: 'dust' | 'symbol' | 'streak' | 'firework-trail' | 'firework-spark';
|
||||
life: number;
|
||||
maxLife: number;
|
||||
color?: number;
|
||||
gravity?: number;
|
||||
}
|
||||
|
||||
export class BackgroundEffect {
|
||||
@@ -29,6 +45,7 @@ export class BackgroundEffect {
|
||||
private dustLayer: Graphics;
|
||||
private symbolLayer: Graphics;
|
||||
private streakLayer: Graphics;
|
||||
private fireworkLayer: Graphics;
|
||||
private time = 0;
|
||||
private windOffset = 0;
|
||||
private isDestroyed = false;
|
||||
@@ -37,6 +54,7 @@ export class BackgroundEffect {
|
||||
private readonly DUST_COUNT = 120;
|
||||
private readonly SYMBOL_COUNT = 25;
|
||||
private readonly STREAK_INTERVAL = 3000; // ms between streaks
|
||||
private readonly FIREWORK_INTERVAL = 4000; // ms between fireworks
|
||||
|
||||
constructor() {
|
||||
this.app = new Application();
|
||||
@@ -44,6 +62,7 @@ export class BackgroundEffect {
|
||||
this.dustLayer = new Graphics();
|
||||
this.symbolLayer = new Graphics();
|
||||
this.streakLayer = new Graphics();
|
||||
this.fireworkLayer = new Graphics();
|
||||
}
|
||||
|
||||
async init(canvas: HTMLCanvasElement): Promise<void> {
|
||||
@@ -61,6 +80,7 @@ export class BackgroundEffect {
|
||||
this.container.addChild(this.dustLayer);
|
||||
this.container.addChild(this.symbolLayer);
|
||||
this.container.addChild(this.streakLayer);
|
||||
this.container.addChild(this.fireworkLayer);
|
||||
this.app.stage.addChild(this.container);
|
||||
|
||||
// Draw vignette overlay
|
||||
@@ -76,6 +96,9 @@ export class BackgroundEffect {
|
||||
// Spawn streaks periodically
|
||||
this.spawnStreakLoop();
|
||||
|
||||
// Spawn fireworks periodically
|
||||
this.spawnFireworkLoop();
|
||||
|
||||
// Handle resize
|
||||
window.addEventListener('resize', this.handleResize.bind(this));
|
||||
}
|
||||
@@ -157,6 +180,78 @@ export class BackgroundEffect {
|
||||
setTimeout(() => this.spawnStreakLoop(), this.STREAK_INTERVAL + Math.random() * 2000);
|
||||
}
|
||||
|
||||
private spawnFireworkLoop(): void {
|
||||
if (this.isDestroyed) return;
|
||||
|
||||
this.launchFirework();
|
||||
setTimeout(() => this.spawnFireworkLoop(), this.FIREWORK_INTERVAL + Math.random() * 3000);
|
||||
}
|
||||
|
||||
private launchFirework(): void {
|
||||
if (this.isDestroyed) return;
|
||||
|
||||
const w = this.app.screen.width;
|
||||
const h = this.app.screen.height;
|
||||
|
||||
// Random launch position from bottom
|
||||
const startX = w * 0.2 + Math.random() * w * 0.6;
|
||||
const startY = h;
|
||||
|
||||
// Target explosion point
|
||||
const targetY = h * 0.2 + Math.random() * h * 0.3;
|
||||
|
||||
// Calculate velocity to reach target
|
||||
const flightTime = 60 + Math.random() * 30; // frames
|
||||
const vy = -(startY - targetY) / flightTime - 0.5 * 0.15 * flightTime;
|
||||
|
||||
// Select color palette for this firework
|
||||
const palette = FIREWORK_PALETTES[Math.floor(Math.random() * FIREWORK_PALETTES.length)];
|
||||
|
||||
// Create trail particle (the rising firework)
|
||||
this.particles.push({
|
||||
x: startX,
|
||||
y: startY,
|
||||
vx: (Math.random() - 0.5) * 0.5,
|
||||
vy: vy,
|
||||
size: 3,
|
||||
alpha: 1,
|
||||
rotation: 0,
|
||||
rotationSpeed: 0,
|
||||
type: 'firework-trail',
|
||||
life: 0,
|
||||
maxLife: flightTime,
|
||||
color: palette[0],
|
||||
gravity: 0.15,
|
||||
});
|
||||
}
|
||||
|
||||
private explodeFirework(x: number, y: number): void {
|
||||
const palette = FIREWORK_PALETTES[Math.floor(Math.random() * FIREWORK_PALETTES.length)];
|
||||
const sparkCount = 40 + Math.floor(Math.random() * 30);
|
||||
|
||||
for (let i = 0; i < sparkCount; i++) {
|
||||
const angle = (Math.PI * 2 * i) / sparkCount + (Math.random() - 0.5) * 0.3;
|
||||
const speed = 3 + Math.random() * 4;
|
||||
const color = palette[Math.floor(Math.random() * palette.length)];
|
||||
|
||||
this.particles.push({
|
||||
x: x,
|
||||
y: y,
|
||||
vx: Math.cos(angle) * speed,
|
||||
vy: Math.sin(angle) * speed,
|
||||
size: 2 + Math.random() * 2,
|
||||
alpha: 1,
|
||||
rotation: 0,
|
||||
rotationSpeed: 0,
|
||||
type: 'firework-spark',
|
||||
life: 0,
|
||||
maxLife: 50 + Math.random() * 30,
|
||||
color: color,
|
||||
gravity: 0.08,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private update(ticker: Ticker): void {
|
||||
if (this.isDestroyed) return;
|
||||
|
||||
@@ -167,6 +262,7 @@ export class BackgroundEffect {
|
||||
this.dustLayer.clear();
|
||||
this.symbolLayer.clear();
|
||||
this.streakLayer.clear();
|
||||
this.fireworkLayer.clear();
|
||||
|
||||
const w = this.app.screen.width;
|
||||
const h = this.app.screen.height;
|
||||
@@ -185,6 +281,25 @@ export class BackgroundEffect {
|
||||
return false;
|
||||
}
|
||||
p.alpha = 0.9 * (1 - p.life / p.maxLife);
|
||||
} else if (p.type === 'firework-trail') {
|
||||
// Apply gravity
|
||||
p.vy += p.gravity || 0.15;
|
||||
if (p.life >= p.maxLife) {
|
||||
// Explode at the end
|
||||
this.explodeFirework(p.x, p.y);
|
||||
return false;
|
||||
}
|
||||
p.alpha = 1;
|
||||
} else if (p.type === 'firework-spark') {
|
||||
// Apply gravity and friction
|
||||
p.vy += p.gravity || 0.08;
|
||||
p.vx *= 0.98;
|
||||
p.vy *= 0.98;
|
||||
if (p.life >= p.maxLife) {
|
||||
return false;
|
||||
}
|
||||
p.alpha = 1 - (p.life / p.maxLife);
|
||||
p.size *= 0.995;
|
||||
} else if (p.type === 'symbol') {
|
||||
// Reset symbols that go off top
|
||||
if (p.y < -20) {
|
||||
@@ -210,7 +325,9 @@ export class BackgroundEffect {
|
||||
? this.dustLayer
|
||||
: p.type === 'symbol'
|
||||
? this.symbolLayer
|
||||
: this.streakLayer;
|
||||
: (p.type === 'firework-trail' || p.type === 'firework-spark')
|
||||
? this.fireworkLayer
|
||||
: this.streakLayer;
|
||||
|
||||
if (p.type === 'dust') {
|
||||
// Simple gold circle
|
||||
@@ -250,6 +367,35 @@ export class BackgroundEffect {
|
||||
// Draw head
|
||||
layer.circle(p.x, p.y, p.size);
|
||||
layer.fill({ color: COLORS.goldLight, alpha: p.alpha });
|
||||
} else if (p.type === 'firework-trail') {
|
||||
// Rising firework with sparkle trail
|
||||
const trailLength = 30;
|
||||
for (let i = 0; i < 8; i++) {
|
||||
const ratio = i / 8;
|
||||
const tx = p.x + (Math.random() - 0.5) * 3;
|
||||
const ty = p.y + ratio * trailLength;
|
||||
const ta = p.alpha * (1 - ratio) * 0.6;
|
||||
const ts = p.size * (1 - ratio * 0.5);
|
||||
layer.circle(tx, ty, ts);
|
||||
layer.fill({ color: p.color || COLORS.gold, alpha: ta });
|
||||
}
|
||||
// Bright head
|
||||
layer.circle(p.x, p.y, p.size + 1);
|
||||
layer.fill({ color: COLORS.fireworkWhite, alpha: p.alpha });
|
||||
} else if (p.type === 'firework-spark') {
|
||||
// Exploding spark with small trail
|
||||
const trailLen = 5;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const ratio = i / 3;
|
||||
const tx = p.x - p.vx * ratio * trailLen;
|
||||
const ty = p.y - p.vy * ratio * trailLen;
|
||||
const ta = p.alpha * (1 - ratio) * 0.5;
|
||||
layer.circle(tx, ty, p.size * (1 - ratio * 0.3));
|
||||
layer.fill({ color: p.color || COLORS.gold, alpha: ta });
|
||||
}
|
||||
// Spark head
|
||||
layer.circle(p.x, p.y, p.size);
|
||||
layer.fill({ color: p.color || COLORS.gold, alpha: p.alpha });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ const showQRLogin = ref(false);
|
||||
// Mobile URL for entry QR code - use environment variable or fallback to current origin
|
||||
const mobileUrl = import.meta.env.VITE_MOBILE_URL || window.location.origin;
|
||||
|
||||
function handleLoginSuccess(data: { userId: string; userName: string; department: string; sessionToken: string }) {
|
||||
function handleLoginSuccess(data: { userId: string; userName: string; sessionToken: string }) {
|
||||
console.log('Login success:', data);
|
||||
showQRLogin.value = false;
|
||||
// 可以在这里处理登录成功后的逻辑
|
||||
|
||||
Reference in New Issue
Block a user