Compare commits

..

4 Commits

Author SHA1 Message Date
ee797ebb14 Merge remote-tracking branch 'origin/dev'
# Conflicts:
#	src/views/ChengduGamePage.vue
2026-03-24 09:13:16 +08:00
1308ca5a2c Update vite.config.ts 2026-03-24 09:03:11 +08:00
fba407c1bf chore(config): 更新开发服务器代理配置
- 将 API 代理目标端口从 8080 更改为 18080
- 将 WebSocket 代理目标端口从 8080 更改为 18080
- 保持了原有的 changeOrigin 和 ws 配置选项
2026-03-20 17:27:13 +08:00
0fa14ca407 feat(game): 实现成都麻将游戏界面和核心功能
- 添加麻将桌面背景和完整的UI布局设计
- 实现玩家座位渲染、牌面显示和游戏状态管理
- 集成定缺选择、碰牌操作和结算功能
- 添加计时器、网络状态和实时消息显示
- 创建麻将牌面图片资源和动态加载机制
- 实现游戏流程控制和玩家交互逻辑
2026-03-18 17:26:20 +08:00
12 changed files with 1541 additions and 676 deletions

1
.env.dev Normal file
View File

@@ -0,0 +1 @@
VITE_API_BASE_URL=http://localhost:8080/api/v1

View File

@@ -1,4 +0,0 @@
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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 MiB

View File

@@ -39,15 +39,6 @@ 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)

View File

@@ -47,15 +47,6 @@ 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(/\/$/, '')

View File

@@ -423,78 +423,36 @@ 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 {
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;
flex: 0 0 auto;
}
.game-table-panel {
flex: 1 1 auto;
display: flex;
align-items: center;
min-height: 60px;
padding: 10px 14px;
overflow: hidden;
flex-direction: column;
min-height: 0;
}
.room-brief {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
margin-bottom: 0;
padding: 4px 2px;
margin-bottom: 10px;
padding: 8px 10px;
border-radius: 8px;
border: 0;
background: transparent;
overflow: hidden;
border: 1px solid rgba(194, 226, 208, 0.2);
background: rgba(7, 28, 20, 0.55);
}
.room-brief-title {
@@ -537,345 +495,6 @@ 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;
@@ -995,17 +614,11 @@ button:disabled {
}
.ws-panel {
grid-column: 2;
grid-row: 1;
display: flex;
flex-direction: column;
min-height: 0;
margin-top: 0;
margin-top: 10px;
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 {
@@ -1049,8 +662,7 @@ button:disabled {
.ws-log {
margin-top: 8px;
flex: 1 1 auto;
min-height: 0;
max-height: 140px;
overflow: auto;
padding: 8px;
border-radius: 8px;
@@ -1190,31 +802,6 @@ 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;
@@ -1237,9 +824,6 @@ 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 {

File diff suppressed because it is too large Load Diff

View File

@@ -1,26 +1,20 @@
import { defineConfig, loadEnv } from 'vite'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd(), '')
const apiProxyTarget = (env.VITE_API_PROXY_TARGET || 'http://127.0.0.1:19000').replace(/\/$/, '')
const wsProxyTarget = (env.VITE_WS_PROXY_TARGET || apiProxyTarget).replace(/\/$/, '')
return {
plugins: [vue()],
server: {
proxy: {
'/api/v1/ws': {
target: wsProxyTarget,
changeOrigin: true,
ws: true,
rewriteWsOrigin: true,
},
'/api/v1': {
target: apiProxyTarget,
changeOrigin: true,
},
export default defineConfig({
plugins: [vue()],
server: {
host: '127.0.0.1',
port: 3000,
proxy: {
'/api/v1': {
target: 'http://127.0.0.1:19000',
changeOrigin: true,
},
},
'/api/v1/ws': {
target: 'ws://127.0.0.1:19000',
ws: true,
}
}
}
})