From a5c833c76912003a27bc131a8a2664d9c7967c16 Mon Sep 17 00:00:00 2001 From: wsy182 <2392948297@qq.com> Date: Tue, 24 Mar 2026 13:44:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(game):=20=E5=AE=8C=E5=96=84=E6=88=90?= =?UTF-8?q?=E9=83=BD=E9=BA=BB=E5=B0=86=E6=B8=B8=E6=88=8F=E9=A1=B5=E9=9D=A2?= =?UTF-8?q?=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加WebSocket URL构建逻辑和认证令牌刷新功能 - 实现游戏状态显示包括阶段、网络状态、时钟等信息 - 添加游戏桌面背景图片和玩家座位装饰组件 - 重构CSS样式为网格布局提升响应式体验 - 配置环境变量支持API和WebSocket代理目标设置 - 优化WebSocket连接管理增加错误处理机制 - 添加游戏桌墙体和中心计数器等UI元素 - 修复多处字符串国际化和路径处理问题 --- .env.dev | 1 - .env.development | 4 + src/api/auth.ts | 9 + src/api/authed-request.ts | 9 + src/assets/styles/global.css | 436 +++++++++++++++++++++++++++++++++- src/views/ChengduGamePage.vue | 204 ++++++++++++++-- vite.config.ts | 36 +-- 7 files changed, 657 insertions(+), 42 deletions(-) delete mode 100644 .env.dev create mode 100644 .env.development diff --git a/.env.dev b/.env.dev deleted file mode 100644 index 5345049..0000000 --- a/.env.dev +++ /dev/null @@ -1 +0,0 @@ -VITE_API_BASE_URL=http://localhost:8080/api/v1 \ No newline at end of file diff --git a/.env.development b/.env.development new file mode 100644 index 0000000..cc48875 --- /dev/null +++ b/.env.development @@ -0,0 +1,4 @@ +VITE_API_BASE_URL=/api/v1 +VITE_GAME_WS_URL=/api/v1/ws +VITE_API_PROXY_TARGET=http://127.0.0.1:19000 +VITE_WS_PROXY_TARGET=http://127.0.0.1:19000 diff --git a/src/api/auth.ts b/src/api/auth.ts index eb5c1d6..ae70331 100644 --- a/src/api/auth.ts +++ b/src/api/auth.ts @@ -39,6 +39,15 @@ function buildUrl(path: string): string { return normalizedPath } + if (API_BASE_URL.startsWith('/')) { + const basePath = API_BASE_URL.startsWith('/') ? API_BASE_URL : `/${API_BASE_URL}` + if (normalizedPath === basePath || normalizedPath.startsWith(`${basePath}/`)) { + return normalizedPath + } + + return `${basePath}${normalizedPath}` + } + // Avoid duplicated API prefix, e.g. base: /api/v1 + path: /api/v1/auth/login try { const baseUrl = new URL(API_BASE_URL) diff --git a/src/api/authed-request.ts b/src/api/authed-request.ts index 1f6ec34..7c2d9c1 100644 --- a/src/api/authed-request.ts +++ b/src/api/authed-request.ts @@ -47,6 +47,15 @@ function buildUrl(path: string): string { return normalizedPath } + if (API_BASE_URL.startsWith('/')) { + const basePath = API_BASE_URL.startsWith('/') ? API_BASE_URL : `/${API_BASE_URL}` + if (normalizedPath === basePath || normalizedPath.startsWith(`${basePath}/`)) { + return normalizedPath + } + + return `${basePath}${normalizedPath}` + } + try { const baseUrl = new URL(API_BASE_URL) const basePath = baseUrl.pathname.replace(/\/$/, '') diff --git a/src/assets/styles/global.css b/src/assets/styles/global.css index a0e19c4..a629333 100644 --- a/src/assets/styles/global.css +++ b/src/assets/styles/global.css @@ -423,36 +423,78 @@ button:disabled { } .game-page { + display: grid; + grid-template-rows: auto auto minmax(0, 1fr); + gap: 12px; width: 100%; max-width: none; + height: 100vh; min-height: 100vh; margin: 0; padding-top: max(12px, env(safe-area-inset-top)); padding-right: max(12px, env(safe-area-inset-right)); padding-bottom: max(12px, env(safe-area-inset-bottom)); padding-left: max(12px, env(safe-area-inset-left)); + overflow: hidden; } .game-header { - flex: 0 0 auto; + display: grid; + grid-template-columns: minmax(260px, 1fr) minmax(320px, auto) minmax(280px, 1fr); + align-items: center; + min-height: 96px; + padding: 14px 18px; + border-radius: 22px; + border: 1px solid rgba(233, 199, 108, 0.16); + background: + linear-gradient(180deg, rgba(20, 47, 35, 0.86), rgba(8, 24, 18, 0.82)), + radial-gradient(circle at top, rgba(255, 219, 123, 0.08), transparent 38%); + backdrop-filter: blur(10px); + box-shadow: + inset 0 1px 0 rgba(255, 255, 255, 0.06), + 0 16px 36px rgba(0, 0, 0, 0.28); +} + +.game-header > div:first-child { + min-width: 0; +} + +.game-header h1 { + font-size: 28px; + font-weight: 800; + letter-spacing: 1px; + color: #f7e4b0; +} + +.game-header .sub-title { + margin-top: 6px; + color: #d7eadf; + font-size: 13px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; } .game-table-panel { - flex: 1 1 auto; display: flex; - flex-direction: column; - min-height: 0; + align-items: center; + min-height: 60px; + padding: 10px 14px; + overflow: hidden; } .room-brief { + width: 100%; display: flex; align-items: center; + justify-content: space-between; gap: 10px; - margin-bottom: 10px; - padding: 8px 10px; + margin-bottom: 0; + padding: 4px 2px; border-radius: 8px; - border: 1px solid rgba(194, 226, 208, 0.2); - background: rgba(7, 28, 20, 0.55); + border: 0; + background: transparent; + overflow: hidden; } .room-brief-title { @@ -495,6 +537,345 @@ button:disabled { text-overflow: ellipsis; } +.topbar-center { + display: flex; + justify-content: center; + min-width: 0; +} + +.title-stack { + padding: 10px 18px; + border-radius: 18px; + text-align: center; + border: 1px solid rgba(255, 255, 255, 0.08); + background: rgba(7, 24, 17, 0.36); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.04); +} + +.game-title { + font-size: 30px; + font-weight: 700; + line-height: 1.1; + letter-spacing: 2px; + color: #f6edd5; +} + +.game-subtitle { + margin-top: 6px; + color: #c4ddd0; + font-size: 13px; +} + +.topbar-right { + display: flex; + justify-content: flex-end; + align-items: center; + gap: 10px; +} + +.status-chip { + display: inline-flex; + align-items: center; + gap: 8px; + height: 36px; + padding: 0 12px; + border-radius: 999px; + border: 1px solid rgba(198, 223, 209, 0.18); + background: rgba(5, 24, 17, 0.42); + font-size: 13px; +} + +.wifi-dot { + width: 10px; + height: 10px; + border-radius: 50%; + background: #9a6b6b; + box-shadow: 0 0 0 4px rgba(255, 255, 255, 0.06); +} + +.wifi-dot.is-connected { + background: #62d78f; +} + +.wifi-dot.is-connecting { + background: #f0c46b; +} + +.wifi-dot.is-disconnected { + background: #d86f6f; +} + +.header-btn { + height: 36px; +} + +.table-shell { + display: grid; + grid-template-columns: minmax(0, 1fr) 320px; + gap: 12px; + min-height: 0; + overflow: hidden; + align-items: stretch; +} + +.table-desk { + display: block; + grid-column: 1; + grid-row: 1; + width: 100%; + height: 100%; + border-radius: 28px; + object-fit: cover; + object-position: center; + box-shadow: + inset 0 0 0 1px rgba(255, 255, 255, 0.04), + 0 20px 42px rgba(0, 0, 0, 0.32); +} + +.table-felt { + grid-column: 1; + grid-row: 1; + position: relative; + min-height: 0; + height: 100%; + border-radius: 28px; + border: 1px solid rgba(255, 255, 255, 0.06); + background: + radial-gradient(circle at center, rgba(30, 126, 70, 0.12), transparent 42%), + linear-gradient(180deg, rgba(0, 0, 0, 0.03), rgba(0, 0, 0, 0.12)); + overflow: hidden; + box-shadow: + inset 0 0 0 1px rgba(255, 255, 255, 0.04); +} + +.table-felt::before { + content: ''; + position: absolute; + inset: 22px; + border-radius: 24px; + background: radial-gradient(circle at center, rgba(35, 121, 68, 0.14), transparent 55%); + pointer-events: none; +} + +.felt-frame { + position: absolute; + inset: 20px; + border-radius: 24px; + border: 1px solid rgba(255, 255, 255, 0.08); + pointer-events: none; +} + +.felt-frame.inner { + inset: 38px; + border-color: rgba(255, 255, 255, 0.06); + border-style: solid; +} + +.table-watermark { + position: absolute; + left: 50%; + top: 24px; + transform: translateX(-50%); + display: flex; + flex-direction: column; + align-items: center; + gap: 2px; + color: rgba(244, 240, 220, 0.82); + text-align: center; + pointer-events: none; +} + +.table-watermark span { + font-size: 12px; + color: #f7e4b0; +} + +.table-watermark strong { + font-size: 26px; + letter-spacing: 2px; + text-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); +} + +.table-watermark small { + font-size: 12px; + color: #bdd8ca; +} + +.player-badge { + position: absolute; + display: flex; + align-items: center; + gap: 10px; + min-width: 148px; + padding: 8px 12px; + border-radius: 14px; + border: 1px solid rgba(244, 222, 163, 0.24); + background: rgba(8, 27, 20, 0.72); + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.2); +} + +.player-badge.seat-top { + top: 76px; + left: 50%; + transform: translateX(-50%); +} + +.player-badge.seat-right { + right: 24px; + top: 50%; + transform: translateY(-50%); +} + +.player-badge.seat-bottom { + bottom: 90px; + left: 50%; + transform: translateX(-50%); +} + +.player-badge.seat-left { + left: 24px; + top: 50%; + transform: translateY(-50%); +} + +.player-badge.is-turn { + border-color: rgba(244, 222, 163, 0.72); +} + +.player-badge.offline { + opacity: 0.55; +} + +.avatar-card { + display: grid; + place-items: center; + width: 42px; + height: 42px; + border-radius: 12px; + background: linear-gradient(145deg, #ecd995, #d3b767); + color: #1c2d23; + font-weight: 800; +} + +.player-meta p { + font-size: 14px; + font-weight: 700; +} + +.player-meta strong { + font-size: 13px; + color: #f7e4b0; +} + +.dealer-mark, +.missing-mark { + display: inline-flex; + align-items: center; + justify-content: center; + min-width: 24px; + height: 24px; + padding: 0 6px; + border-radius: 999px; + font-size: 12px; +} + +.dealer-mark { + background: rgba(236, 188, 84, 0.88); + color: #1c2d23; +} + +.missing-mark { + margin-left: auto; + background: rgba(255, 255, 255, 0.08); + color: #d6eadf; +} + +.wall { + position: absolute; + display: flex; + gap: 2px; + filter: drop-shadow(0 6px 8px rgba(0, 0, 0, 0.22)); +} + +.wall img { + display: block; + object-fit: contain; +} + +.wall-top, +.wall-bottom { + left: 50%; + transform: translateX(-50%); +} + +.wall-left, +.wall-right { + top: 50%; + transform: translateY(-50%); + flex-direction: column; +} + +.wall-top { + top: 154px; +} + +.wall-top img, +.wall-bottom img { + width: 24px; + height: 36px; +} + +.wall-right { + right: 132px; +} + +.wall-left { + left: 132px; +} + +.wall-left img, +.wall-right img { + width: 36px; + height: 24px; +} + +.wall-bottom { + bottom: 176px; +} + +.center-deck { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + display: grid; + grid-template-columns: repeat(2, 42px); + gap: 6px; + align-items: center; + justify-items: center; + padding: 12px 16px; + border-radius: 18px; + background: rgba(8, 27, 20, 0.82); + border: 1px solid rgba(244, 222, 163, 0.28); + box-shadow: 0 12px 28px rgba(0, 0, 0, 0.18); +} + +.center-deck strong { + grid-column: 1 / -1; + font-size: 16px; + color: #f7e4b0; +} + +.wind { + display: grid; + place-items: center; + width: 36px; + height: 36px; + border-radius: 10px; + background: rgba(255, 255, 255, 0.08); + font-weight: 700; +} + .table-tip { margin-top: 4px; color: #c1dfcf; @@ -614,11 +995,17 @@ button:disabled { } .ws-panel { - margin-top: 10px; + grid-column: 2; + grid-row: 1; + display: flex; + flex-direction: column; + min-height: 0; + margin-top: 0; padding: 10px; border-radius: 8px; border: 1px solid rgba(176, 216, 194, 0.22); background: rgba(5, 24, 17, 0.58); + overflow: hidden; } .ws-panel-head { @@ -662,7 +1049,8 @@ button:disabled { .ws-log { margin-top: 8px; - max-height: 140px; + flex: 1 1 auto; + min-height: 0; overflow: auto; padding: 8px; border-radius: 8px; @@ -802,6 +1190,31 @@ button:disabled { align-items: flex-start; } + .game-header { + grid-template-columns: 1fr; + justify-items: stretch; + } + + .topbar-center, + .topbar-right { + justify-content: flex-start; + } + + .table-shell { + grid-template-columns: 1fr; + } + + .table-desk, + .table-felt, + .ws-panel { + grid-column: auto; + grid-row: auto; + } + + .ws-panel { + min-height: 180px; + } + .header-actions { width: 100%; display: grid; @@ -824,6 +1237,9 @@ button:disabled { padding-right: max(8px, env(safe-area-inset-right)); padding-bottom: max(8px, env(safe-area-inset-bottom)); padding-left: max(8px, env(safe-area-inset-left)); + height: auto; + min-height: 100vh; + overflow: visible; } .game-mahjong-table { diff --git a/src/views/ChengduGamePage.vue b/src/views/ChengduGamePage.vue index bb46208..761b0d6 100644 --- a/src/views/ChengduGamePage.vue +++ b/src/views/ChengduGamePage.vue @@ -1,8 +1,13 @@ -