feat(HUD): 添加HUD界面资产与组件

- 新增HUD图标、面板和状态指示器资产
- 实现HUD图标组件和错误边界组件
- 重构顶部状态栏和底部导航栏
- 更新路线面板样式和交互
- 添加HUD资产清单和切片脚本
- 移除未使用的资产文件
- 调整API安全配置和生产环境设置
This commit is contained in:
2026-04-29 01:24:37 +08:00
parent 6234a50bdb
commit 5c41eb410b
71 changed files with 2492 additions and 1028 deletions
+1
View File
@@ -15,6 +15,7 @@
"dependencies": {
"@fastify/cookie": "^11.0.2",
"@fastify/cors": "^11.1.0",
"@fastify/rate-limit": "^10.3.0",
"@fastify/static": "^9.1.3",
"@tinywaste/content": "workspace:*",
"@tinywaste/game-core": "workspace:*",
+2 -2
View File
@@ -8,13 +8,13 @@ export const registerAuthRoutes = (app: FastifyInstance, authService: AuthServic
return { user };
});
app.post('/api/auth/register', async (request, reply) => {
app.post('/api/auth/register', { config: { rateLimit: { max: 5, timeWindow: '1 minute' } } }, async (request, reply) => {
const input = registerSchema.parse(request.body);
const user = await authService.register(input, reply);
return { user };
});
app.post('/api/auth/login', async (request, reply) => {
app.post('/api/auth/login', { config: { rateLimit: { max: 10, timeWindow: '1 minute' } } }, async (request, reply) => {
const input = loginSchema.parse(request.body);
const user = await authService.login(input, reply);
return { user };
+1 -1
View File
@@ -48,7 +48,7 @@ export class AuthService {
sameSite: 'lax',
path: '/',
maxAge: SESSION_TTL_MS / 1000,
secure: false,
secure: env.isProduction,
});
}
@@ -13,11 +13,15 @@ export class GameRepository {
});
}
async upsertMainSlot(userId: string, stateJson: string, label: string) {
async upsertMainSlot(userId: string, stateJson: string, label: string, expectedRevision?: number) {
const existing = await this.getMainSlot(userId);
const now = Date.now();
if (existing) {
if (expectedRevision !== undefined && existing.revision !== expectedRevision) {
return null;
}
this.db
.update(saveSlotsTable)
.set({
+4 -1
View File
@@ -43,7 +43,10 @@ export class GameService {
const state = JSON.parse(slot.stateJson) as GameState;
const result = applyGameAction(state, gameContent, action);
await this.repository.upsertMainSlot(userId, JSON.stringify(result.state), slot.label);
const saved = await this.repository.upsertMainSlot(userId, JSON.stringify(result.state), slot.label, slot.revision);
if (!saved) {
throw new AppError(409, 'SAVE_CONFLICT', '存档已被其他请求修改,请重试。');
}
return buildGameView(result.state, gameContent);
}
}
+6
View File
@@ -2,6 +2,7 @@ import { existsSync } from 'node:fs';
import Fastify from 'fastify';
import cookie from '@fastify/cookie';
import cors from '@fastify/cors';
import rateLimit from '@fastify/rate-limit';
import fastifyStatic from '@fastify/static';
import { ZodError } from 'zod';
import { db } from './shared/database/client';
@@ -25,6 +26,11 @@ await app.register(cors, {
credentials: true,
});
await app.register(rateLimit, {
max: env.isProduction ? 100 : 1000,
timeWindow: '1 minute',
});
const authRepository = new AuthRepository(db);
const authService = new AuthService(authRepository);
const gameRepository = new GameRepository(db);
+3
View File
@@ -8,6 +8,7 @@ loadDotenv({
});
const envSchema = z.object({
NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),
API_PORT: z.coerce.number().int().positive().default(3001),
WEB_ORIGIN: z.string().url().default('http://localhost:5173'),
WEB_DIST_DIR: z.string().min(1).default('../web/dist'),
@@ -26,6 +27,8 @@ const webDistPath = resolve(process.cwd(), parsed.data.WEB_DIST_DIR);
mkdirSync(dirname(databasePath), { recursive: true });
export const env = {
nodeEnv: parsed.data.NODE_ENV,
isProduction: parsed.data.NODE_ENV === 'production',
port: parsed.data.API_PORT,
webOrigin: parsed.data.WEB_ORIGIN,
webDistPath,