Compare commits
8 Commits
623ee94b04
...
dev-claude
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b3e3fdb90 | |||
| e435fa9d96 | |||
| 941d878931 | |||
| 100d950eb8 | |||
| 06b25bde62 | |||
| 2625baf266 | |||
| 43439cb09d | |||
| be9bd8c76d |
@@ -31,6 +31,7 @@ HTTP 接口:
|
|||||||
{
|
{
|
||||||
"name": "房间名",
|
"name": "房间名",
|
||||||
"game_type": "chengdu",
|
"game_type": "chengdu",
|
||||||
|
"total_rounds": 8,
|
||||||
"max_players": 4
|
"max_players": 4
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ const ROOM_JOIN_PATH = import.meta.env.VITE_ROOM_JOIN_PATH ?? '/api/v1/game/mahj
|
|||||||
|
|
||||||
export async function createRoom(
|
export async function createRoom(
|
||||||
auth: AuthSession,
|
auth: AuthSession,
|
||||||
input: { name: string; gameType: string; maxPlayers: number },
|
input: { name: string; gameType: string; totalRounds: number; maxPlayers: number },
|
||||||
onAuthUpdated?: (next: AuthSession) => void,
|
onAuthUpdated?: (next: AuthSession) => void,
|
||||||
): Promise<RoomItem> {
|
): Promise<RoomItem> {
|
||||||
return authedRequest<RoomItem>({
|
return authedRequest<RoomItem>({
|
||||||
@@ -44,6 +44,7 @@ export async function createRoom(
|
|||||||
body: {
|
body: {
|
||||||
name: input.name,
|
name: input.name,
|
||||||
game_type: input.gameType,
|
game_type: input.gameType,
|
||||||
|
total_rounds: input.totalRounds,
|
||||||
max_players: input.maxPlayers,
|
max_players: input.maxPlayers,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB |
BIN
src/assets/images/desk/desk_01_1920_945.png
Normal file
BIN
src/assets/images/desk/desk_01_1920_945.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1006 KiB |
@@ -1,8 +1,7 @@
|
|||||||
.picture-scene {
|
.picture-scene {
|
||||||
|
|
||||||
min-height: 100vh;
|
min-height: 100vh;
|
||||||
min-height: 100dvh;
|
min-height: 100dvh;
|
||||||
padding: 18px;
|
padding: 0;
|
||||||
background:
|
background:
|
||||||
radial-gradient(circle at top, rgba(116, 58, 41, 0.28), transparent 20%),
|
radial-gradient(circle at top, rgba(116, 58, 41, 0.28), transparent 20%),
|
||||||
linear-gradient(180deg, #3f2119 0%, #27140f 100%);
|
linear-gradient(180deg, #3f2119 0%, #27140f 100%);
|
||||||
@@ -10,44 +9,51 @@
|
|||||||
|
|
||||||
.picture-layout {
|
.picture-layout {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: minmax(0, 1fr) 320px;
|
grid-template-columns: minmax(0, 1fr);
|
||||||
gap: 18px;
|
gap: 0;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
min-height: calc(100vh - 36px);
|
min-height: 100vh;
|
||||||
|
min-height: 100dvh;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-stage {
|
.picture-scene .table-stage {
|
||||||
position: relative;
|
position: relative;
|
||||||
display: grid;
|
display: grid;
|
||||||
place-items: center;
|
place-items: center;
|
||||||
align-content: start;
|
align-content: stretch;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-height: calc(100vh - 36px);
|
min-height: 100vh;
|
||||||
|
min-height: 100dvh;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-desk,
|
.picture-scene .table-desk {
|
||||||
.table-felt {
|
position: absolute;
|
||||||
width: 100%;
|
inset: 0;
|
||||||
max-width: 100%;
|
|
||||||
max-height: calc(100dvh - 72px);
|
|
||||||
aspect-ratio: 16 / 9;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-desk {
|
|
||||||
grid-area: 1 / 1;
|
|
||||||
display: block;
|
display: block;
|
||||||
margin-top: 18px;
|
width: 100%;
|
||||||
border-radius: 26px;
|
height: 100%;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
border-radius: 0;
|
||||||
box-shadow: 0 24px 44px rgba(0, 0, 0, 0.34);
|
box-shadow: 0 24px 44px rgba(0, 0, 0, 0.34);
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-felt {
|
.picture-scene .table-felt {
|
||||||
grid-area: 1 / 1;
|
position: absolute;
|
||||||
position: relative;
|
inset: 0;
|
||||||
margin-top: 18px;
|
width: 100%;
|
||||||
border-radius: 26px;
|
height: 100%;
|
||||||
|
max-width: none;
|
||||||
|
max-height: none;
|
||||||
|
min-height: 100vh;
|
||||||
|
min-height: 100dvh;
|
||||||
|
aspect-ratio: auto;
|
||||||
|
margin-top: 0;
|
||||||
|
border-radius: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
justify-self: stretch;
|
||||||
|
align-self: stretch;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-surface {
|
.table-surface {
|
||||||
@@ -312,10 +318,57 @@
|
|||||||
z-index: 5;
|
z-index: 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action-countdown {
|
.room-status-panel {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 92px;
|
top: 92px;
|
||||||
right: 40px;
|
right: 40px;
|
||||||
|
width: min(320px, calc(100% - 80px));
|
||||||
|
padding: 12px;
|
||||||
|
border: 1px solid rgba(255, 226, 175, 0.12);
|
||||||
|
border-radius: 14px;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(45, 24, 18, 0.82), rgba(26, 14, 11, 0.88)),
|
||||||
|
radial-gradient(circle at top, rgba(255, 219, 154, 0.05), transparent 44%);
|
||||||
|
box-shadow: 0 14px 26px rgba(0, 0, 0, 0.22);
|
||||||
|
z-index: 5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-status-grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-status-item {
|
||||||
|
padding: 10px 12px;
|
||||||
|
border-radius: 12px;
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-status-item span {
|
||||||
|
display: block;
|
||||||
|
font-size: 11px;
|
||||||
|
color: rgba(244, 233, 208, 0.62);
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-status-item strong {
|
||||||
|
display: block;
|
||||||
|
margin-top: 6px;
|
||||||
|
color: #fff0c2;
|
||||||
|
font-size: 15px;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-status-error {
|
||||||
|
margin-top: 10px;
|
||||||
|
color: #ffc1c1;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-countdown {
|
||||||
|
position: absolute;
|
||||||
|
top: 210px;
|
||||||
|
right: 40px;
|
||||||
min-width: 188px;
|
min-width: 188px;
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border: 1px solid rgba(255, 219, 131, 0.22);
|
border: 1px solid rgba(255, 219, 131, 0.22);
|
||||||
@@ -625,12 +678,12 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wall-right {
|
.wall-right {
|
||||||
right: 110px;
|
right: 140px;
|
||||||
gap: 0;
|
gap: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wall-left {
|
.wall-left {
|
||||||
left: 110px;
|
left: 140px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wall-left img,
|
.wall-left img,
|
||||||
@@ -707,11 +760,32 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.wall-live-tile-button {
|
.wall-live-tile-button {
|
||||||
|
position: relative;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
border: 0;
|
border: 0;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
appearance: none;
|
appearance: none;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
transform: translateY(0);
|
||||||
|
transition: transform 150ms cubic-bezier(0.22, 0.82, 0.32, 1), filter 150ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wall-live-tile-lack-tag {
|
||||||
|
position: absolute;
|
||||||
|
top: 21px;
|
||||||
|
left: 5px;
|
||||||
|
min-width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
padding: 0 4px;
|
||||||
|
border-radius: 6px;
|
||||||
|
color: #fff8e8;
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 16px;
|
||||||
|
font-weight: 800;
|
||||||
|
background: linear-gradient(180deg, rgba(200, 56, 41, 0.95), rgba(137, 25, 14, 0.96));
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wall-live-tile-button:disabled {
|
.wall-live-tile-button:disabled {
|
||||||
@@ -719,6 +793,21 @@
|
|||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wall-live-tile-button:not(:disabled):hover {
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wall-live-tile-button.is-selected {
|
||||||
|
transform: translateY(-18px);
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wall-live-tile-button.is-selected .wall-live-tile {
|
||||||
|
filter:
|
||||||
|
drop-shadow(0 14px 18px rgba(0, 0, 0, 0.24))
|
||||||
|
drop-shadow(0 0 10px rgba(255, 214, 111, 0.42));
|
||||||
|
}
|
||||||
|
|
||||||
.wall-live-tile-button:disabled .wall-live-tile {
|
.wall-live-tile-button:disabled .wall-live-tile {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
filter: drop-shadow(0 6px 12px rgba(0, 0, 0, 0.18));
|
filter: drop-shadow(0 6px 12px rgba(0, 0, 0, 0.18));
|
||||||
@@ -772,6 +861,116 @@
|
|||||||
left: 110px;
|
left: 110px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.desk-zone {
|
||||||
|
position: absolute;
|
||||||
|
display: flex;
|
||||||
|
gap: 0;
|
||||||
|
max-width: 280px;
|
||||||
|
max-height: 220px;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 2;
|
||||||
|
filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.2));
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-top,
|
||||||
|
.desk-zone-bottom {
|
||||||
|
left: 50%;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-top {
|
||||||
|
top: 208px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-bottom {
|
||||||
|
bottom: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-left,
|
||||||
|
.desk-zone-right {
|
||||||
|
top: 50%;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-left {
|
||||||
|
left: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-right {
|
||||||
|
right: 240px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-tile {
|
||||||
|
display: block;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-top .desk-tile,
|
||||||
|
.desk-zone-bottom .desk-tile {
|
||||||
|
width: 30px;
|
||||||
|
height: 44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-left .desk-tile,
|
||||||
|
.desk-zone-right .desk-tile {
|
||||||
|
width: 44px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-top .desk-tile + .desk-tile {
|
||||||
|
margin-left: -0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-bottom .desk-tile + .desk-tile {
|
||||||
|
margin-left: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-left .desk-tile + .desk-tile,
|
||||||
|
.desk-zone-right .desk-tile + .desk-tile {
|
||||||
|
margin-top: -14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-top .desk-tile.is-group-start,
|
||||||
|
.desk-zone-bottom .desk-tile.is-group-start {
|
||||||
|
margin-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-bottom .desk-tile.is-group-start {
|
||||||
|
margin-left: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-left .desk-tile.is-group-start,
|
||||||
|
.desk-zone-right .desk-tile.is-group-start {
|
||||||
|
margin-top: 7px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-tile.is-covered {
|
||||||
|
opacity: 0.95;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-hu-flag {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
margin-left: 6px;
|
||||||
|
padding: 0 7px;
|
||||||
|
border-radius: 999px;
|
||||||
|
color: #fff3da;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 800;
|
||||||
|
background: linear-gradient(180deg, rgba(219, 81, 56, 0.92), rgba(146, 32, 20, 0.96));
|
||||||
|
box-shadow: 0 4px 10px rgba(0, 0, 0, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-left .desk-hu-flag,
|
||||||
|
.desk-zone-right .desk-hu-flag {
|
||||||
|
margin-top: 6px;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.center-wind-square {
|
.center-wind-square {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
left: 50%;
|
left: 50%;
|
||||||
@@ -1013,6 +1212,18 @@
|
|||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.discard-confirm-button {
|
||||||
|
min-width: 168px;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(110, 32, 20, 0.94), rgba(72, 16, 9, 0.98)),
|
||||||
|
radial-gradient(circle at 20% 24%, rgba(255, 214, 153, 0.14), transparent 34%);
|
||||||
|
border-color: rgba(255, 184, 112, 0.34);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 232, 205, 0.14),
|
||||||
|
inset 0 -1px 0 rgba(0, 0, 0, 0.28),
|
||||||
|
0 12px 22px rgba(0, 0, 0, 0.26);
|
||||||
|
}
|
||||||
|
|
||||||
.hand-action-bar {
|
.hand-action-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
@@ -1228,126 +1439,9 @@
|
|||||||
background: radial-gradient(circle at 35% 28%, #fff6c2 0%, #ffe16c 42%, #e3aa23 100%);
|
background: radial-gradient(circle at 35% 28%, #fff6c2 0%, #ffe16c 42%, #e3aa23 100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.ws-sidebar {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
height: calc(100vh - 36px);
|
|
||||||
min-height: calc(100vh - 36px);
|
|
||||||
padding: 16px;
|
|
||||||
border-radius: 18px;
|
|
||||||
border: 1px solid rgba(255, 226, 175, 0.12);
|
|
||||||
background:
|
|
||||||
linear-gradient(180deg, rgba(45, 24, 18, 0.94), rgba(26, 14, 11, 0.96)),
|
|
||||||
radial-gradient(circle at top, rgba(255, 219, 154, 0.06), transparent 40%);
|
|
||||||
box-shadow: 0 16px 28px rgba(0, 0, 0, 0.22);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-head {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 12px;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-title {
|
|
||||||
font-size: 18px;
|
|
||||||
font-weight: 800;
|
|
||||||
color: #ffe2a0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-head small {
|
|
||||||
color: rgba(248, 233, 199, 0.68);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-btn {
|
|
||||||
min-width: 76px;
|
|
||||||
height: 38px;
|
|
||||||
border: 1px solid rgba(255, 223, 164, 0.16);
|
|
||||||
border-radius: 999px;
|
|
||||||
color: #ffe9b7;
|
|
||||||
background: rgba(0, 0, 0, 0.18);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-stats {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-stat {
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 14px;
|
|
||||||
background: rgba(255, 255, 255, 0.04);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-stat span {
|
|
||||||
display: block;
|
|
||||||
font-size: 11px;
|
|
||||||
color: rgba(244, 233, 208, 0.62);
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-stat strong {
|
|
||||||
display: block;
|
|
||||||
margin-top: 6px;
|
|
||||||
color: #fff0c2;
|
|
||||||
font-size: 15px;
|
|
||||||
word-break: break-word;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-error {
|
|
||||||
margin-top: 14px;
|
|
||||||
color: #ffc1c1;
|
|
||||||
font-size: 13px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-log {
|
|
||||||
flex: 1 1 auto;
|
|
||||||
margin-top: 14px;
|
|
||||||
padding: 12px;
|
|
||||||
border-radius: 14px;
|
|
||||||
background: rgba(9, 12, 19, 0.34);
|
|
||||||
overflow: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-empty,
|
|
||||||
.sidebar-line {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #e6eef8;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sidebar-line + .sidebar-line {
|
|
||||||
margin-top: 8px;
|
|
||||||
padding-top: 8px;
|
|
||||||
border-top: 1px solid rgba(255, 255, 255, 0.06);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1280px) {
|
|
||||||
.picture-layout {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-desk,
|
|
||||||
.table-felt {
|
|
||||||
width: min(100%, calc((100dvh - 290px) * 16 / 9));
|
|
||||||
}
|
|
||||||
|
|
||||||
.ws-sidebar {
|
|
||||||
height: auto;
|
|
||||||
min-height: 240px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 980px) {
|
@media (max-width: 980px) {
|
||||||
.picture-scene {
|
.picture-scene {
|
||||||
padding: 10px;
|
padding: 0;
|
||||||
}
|
|
||||||
|
|
||||||
.table-desk,
|
|
||||||
.table-felt {
|
|
||||||
width: 100%;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.wall-right {
|
.wall-right {
|
||||||
@@ -1358,6 +1452,22 @@
|
|||||||
left: 88px;
|
left: 88px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.desk-zone-top {
|
||||||
|
top: 196px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-bottom {
|
||||||
|
bottom: 208px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-left {
|
||||||
|
left: 186px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-right {
|
||||||
|
right: 186px;
|
||||||
|
}
|
||||||
|
|
||||||
.inner-outline.mid {
|
.inner-outline.mid {
|
||||||
inset: 70px 72px 120px;
|
inset: 70px 72px 120px;
|
||||||
}
|
}
|
||||||
@@ -1384,14 +1494,15 @@
|
|||||||
right: 20px;
|
right: 20px;
|
||||||
min-width: 164px;
|
min-width: 164px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.room-status-panel {
|
||||||
|
top: 92px;
|
||||||
|
right: 20px;
|
||||||
|
width: min(300px, calc(100% - 40px));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
.table-desk,
|
|
||||||
.table-felt {
|
|
||||||
aspect-ratio: 9 / 16;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inner-outline.mid {
|
.inner-outline.mid {
|
||||||
inset: 92px 34px 190px;
|
inset: 92px 34px 190px;
|
||||||
}
|
}
|
||||||
@@ -1406,6 +1517,11 @@
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.desk-zone-top,
|
||||||
|
.desk-zone-bottom {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.wall-left {
|
.wall-left {
|
||||||
left: 32px;
|
left: 32px;
|
||||||
}
|
}
|
||||||
@@ -1414,6 +1530,14 @@
|
|||||||
right: 32px;
|
right: 32px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.desk-zone-left {
|
||||||
|
left: 84px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desk-zone-right {
|
||||||
|
right: 84px;
|
||||||
|
}
|
||||||
|
|
||||||
.floating-status.left,
|
.floating-status.left,
|
||||||
.floating-status.right {
|
.floating-status.right {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -1438,8 +1562,23 @@
|
|||||||
padding: 7px 10px;
|
padding: 7px 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.room-status-panel {
|
||||||
|
top: 58px;
|
||||||
|
right: 16px;
|
||||||
|
width: calc(100% - 32px);
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-status-grid {
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.room-status-item {
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.action-countdown {
|
.action-countdown {
|
||||||
top: 62px;
|
top: 176px;
|
||||||
right: 16px;
|
right: 16px;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
width: calc(100% - 32px);
|
width: calc(100% - 32px);
|
||||||
@@ -1477,3 +1616,157 @@
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ── Settlement Overlay ── */
|
||||||
|
.settlement-overlay {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
z-index: 100;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(0, 0, 0, 0.65);
|
||||||
|
backdrop-filter: blur(4px);
|
||||||
|
animation: settlement-fade-in 300ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes settlement-fade-in {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-panel {
|
||||||
|
width: 380px;
|
||||||
|
max-width: 90vw;
|
||||||
|
padding: 28px 24px 20px;
|
||||||
|
border: 1px solid rgba(220, 191, 118, 0.3);
|
||||||
|
border-radius: 16px;
|
||||||
|
background:
|
||||||
|
linear-gradient(180deg, rgba(14, 55, 40, 0.96), rgba(8, 36, 27, 0.98)),
|
||||||
|
radial-gradient(circle at 20% 24%, rgba(237, 214, 157, 0.06), transparent 34%);
|
||||||
|
box-shadow:
|
||||||
|
inset 0 1px 0 rgba(255, 244, 214, 0.08),
|
||||||
|
0 24px 48px rgba(0, 0, 0, 0.4);
|
||||||
|
animation: settlement-panel-pop 300ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes settlement-panel-pop {
|
||||||
|
from { opacity: 0; transform: scale(0.92) translateY(12px); }
|
||||||
|
to { opacity: 1; transform: scale(1) translateY(0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-title {
|
||||||
|
margin: 0 0 4px;
|
||||||
|
color: #e5c472;
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 800;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: 1px;
|
||||||
|
text-shadow:
|
||||||
|
-1px 0 rgba(0, 0, 0, 0.38),
|
||||||
|
0 1px rgba(0, 0, 0, 0.38),
|
||||||
|
1px 0 rgba(0, 0, 0, 0.38),
|
||||||
|
0 -1px rgba(0, 0, 0, 0.38);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-round-info {
|
||||||
|
margin: 0 0 16px;
|
||||||
|
color: rgba(229, 196, 114, 0.6);
|
||||||
|
font-size: 13px;
|
||||||
|
text-align: center;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-row {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 10px 14px;
|
||||||
|
border-radius: 10px;
|
||||||
|
background: rgba(255, 255, 255, 0.04);
|
||||||
|
transition: background 150ms;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-row.is-winner {
|
||||||
|
background: rgba(229, 196, 114, 0.1);
|
||||||
|
border: 1px solid rgba(220, 191, 118, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-row.is-self {
|
||||||
|
box-shadow: inset 0 0 0 1px rgba(229, 196, 114, 0.18);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-rank {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: rgba(255, 255, 255, 0.08);
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-row.is-winner .settlement-rank {
|
||||||
|
background: rgba(229, 196, 114, 0.2);
|
||||||
|
color: #e5c472;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-name {
|
||||||
|
flex: 1;
|
||||||
|
color: rgba(255, 255, 255, 0.88);
|
||||||
|
font-size: 15px;
|
||||||
|
font-weight: 600;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-winner-badge {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 6px;
|
||||||
|
padding: 1px 6px;
|
||||||
|
border-radius: 4px;
|
||||||
|
background: rgba(229, 196, 114, 0.22);
|
||||||
|
color: #e5c472;
|
||||||
|
font-size: 11px;
|
||||||
|
font-weight: 800;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-score {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: rgba(255, 255, 255, 0.7);
|
||||||
|
flex-shrink: 0;
|
||||||
|
min-width: 48px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-score.is-positive {
|
||||||
|
color: #5dda6e;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-score.is-negative {
|
||||||
|
color: #e85d5d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settlement-btn {
|
||||||
|
min-width: 160px;
|
||||||
|
}
|
||||||
|
|||||||
151
src/assets/styles/windowSquare.css
Normal file
151
src/assets/styles/windowSquare.css
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
.wind-square {
|
||||||
|
position: relative;
|
||||||
|
width: 96px;
|
||||||
|
height: 96px;
|
||||||
|
border-radius: 22px;
|
||||||
|
overflow: hidden;
|
||||||
|
box-shadow: 0 10px 18px rgba(0, 0, 0, 0.28),
|
||||||
|
inset 0 0 0 1px rgba(255, 240, 196, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.square-base {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
filter: sepia(1) hue-rotate(92deg) saturate(3.3) brightness(0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind-square::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background: radial-gradient(circle at 28% 22%, rgba(255, 238, 191, 0.08), transparent 42%),
|
||||||
|
linear-gradient(145deg, rgba(5, 33, 24, 0.34), rgba(0, 0, 0, 0.16));
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ===== 四个三角形区域 ===== */
|
||||||
|
.quadrant {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
opacity: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 1;
|
||||||
|
transition: opacity 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 上三角 */
|
||||||
|
.quadrant-top {
|
||||||
|
clip-path: polygon(50% 50%, 0 0, 100% 0);
|
||||||
|
background: radial-gradient(circle at 50% 38%, rgba(255, 225, 180, 0.30), transparent 68%),
|
||||||
|
linear-gradient(to bottom, rgba(180, 95, 55, 0.28), rgba(80, 35, 20, 0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 右三角 */
|
||||||
|
.quadrant-right {
|
||||||
|
clip-path: polygon(50% 50%, 100% 0, 100% 100%);
|
||||||
|
background: radial-gradient(circle at 62% 50%, rgba(255, 225, 180, 0.30), transparent 68%),
|
||||||
|
linear-gradient(to left, rgba(180, 95, 55, 0.28), rgba(80, 35, 20, 0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 下三角 */
|
||||||
|
.quadrant-bottom {
|
||||||
|
clip-path: polygon(50% 50%, 0 100%, 100% 100%);
|
||||||
|
background: radial-gradient(circle at 50% 62%, rgba(255, 225, 180, 0.30), transparent 68%),
|
||||||
|
linear-gradient(to top, rgba(180, 95, 55, 0.28), rgba(80, 35, 20, 0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 左三角 */
|
||||||
|
.quadrant-left {
|
||||||
|
clip-path: polygon(50% 50%, 0 0, 0 100%);
|
||||||
|
background: radial-gradient(circle at 38% 50%, rgba(255, 225, 180, 0.30), transparent 68%),
|
||||||
|
linear-gradient(to right, rgba(180, 95, 55, 0.28), rgba(80, 35, 20, 0.12));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 激活时闪烁 */
|
||||||
|
.quadrant.active {
|
||||||
|
opacity: 1;
|
||||||
|
animation: quadrant-pulse 1.2s ease-in-out infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes quadrant-pulse {
|
||||||
|
0% {
|
||||||
|
opacity: 0.22;
|
||||||
|
filter: brightness(0.95);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 0.72;
|
||||||
|
filter: brightness(1.18);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
opacity: 0.22;
|
||||||
|
filter: brightness(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagonal {
|
||||||
|
position: absolute;
|
||||||
|
left: 50%;
|
||||||
|
top: 50%;
|
||||||
|
width: 160%;
|
||||||
|
height: 2px;
|
||||||
|
border-radius: 999px;
|
||||||
|
background: linear-gradient(
|
||||||
|
90deg,
|
||||||
|
rgba(0, 0, 0, 0) 0%,
|
||||||
|
rgba(80, 35, 20, 0.6) 25%,
|
||||||
|
rgba(160, 85, 50, 0.9) 50%,
|
||||||
|
rgba(80, 35, 20, 0.6) 75%,
|
||||||
|
rgba(0, 0, 0, 0) 100%
|
||||||
|
);
|
||||||
|
transform-origin: center;
|
||||||
|
z-index: 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagonal-a {
|
||||||
|
transform: translate(-50%, -50%) rotate(45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.diagonal-b {
|
||||||
|
transform: translate(-50%, -50%) rotate(-45deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind-slot {
|
||||||
|
position: absolute;
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind-icon {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: contain;
|
||||||
|
filter: brightness(0) invert(1) drop-shadow(0 0 2px rgba(255, 220, 180, 0.8)) drop-shadow(0 0 4px rgba(120, 60, 30, 0.6));
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind-top {
|
||||||
|
top: 5px;
|
||||||
|
left: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind-right {
|
||||||
|
top: 34px;
|
||||||
|
right: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind-bottom {
|
||||||
|
bottom: 5px;
|
||||||
|
left: 34px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wind-left {
|
||||||
|
top: 34px;
|
||||||
|
left: 5px;
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import squareIcon from '../../assets/images/icons/square.svg'
|
import squareIcon from '../../assets/images/icons/square.svg'
|
||||||
|
import '@src/assets/styles/windowSquare.css'
|
||||||
|
|
||||||
defineProps<{
|
defineProps<{
|
||||||
seatWinds: {
|
seatWinds: {
|
||||||
@@ -8,12 +9,20 @@ defineProps<{
|
|||||||
bottom: string
|
bottom: string
|
||||||
left: string
|
left: string
|
||||||
}
|
}
|
||||||
|
activePosition?: 'top' | 'right' | 'bottom' | 'left' | ''
|
||||||
}>()
|
}>()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="wind-square">
|
<div class="wind-square">
|
||||||
<img class="square-base" :src="squareIcon" alt="" />
|
<img class="square-base" :src="squareIcon" alt="" />
|
||||||
|
|
||||||
|
<!-- 四个三角形高亮区域 -->
|
||||||
|
<div class="quadrant quadrant-top" :class="{ active: activePosition === 'top' }"></div>
|
||||||
|
<div class="quadrant quadrant-right" :class="{ active: activePosition === 'right' }"></div>
|
||||||
|
<div class="quadrant quadrant-bottom" :class="{ active: activePosition === 'bottom' }"></div>
|
||||||
|
<div class="quadrant quadrant-left" :class="{ active: activePosition === 'left' }"></div>
|
||||||
|
|
||||||
<div class="diagonal diagonal-a"></div>
|
<div class="diagonal diagonal-a"></div>
|
||||||
<div class="diagonal diagonal-b"></div>
|
<div class="diagonal diagonal-b"></div>
|
||||||
|
|
||||||
@@ -30,99 +39,4 @@ defineProps<{
|
|||||||
<img class="wind-icon" :src="seatWinds.left" alt="左方位风" />
|
<img class="wind-icon" :src="seatWinds.left" alt="左方位风" />
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.wind-square {
|
|
||||||
position: relative;
|
|
||||||
width: 96px;
|
|
||||||
height: 96px;
|
|
||||||
border-radius: 22px;
|
|
||||||
overflow: hidden;
|
|
||||||
box-shadow:
|
|
||||||
0 10px 18px rgba(0, 0, 0, 0.28),
|
|
||||||
inset 0 0 0 1px rgba(255, 240, 196, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.square-base {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: cover;
|
|
||||||
filter:
|
|
||||||
sepia(1)
|
|
||||||
hue-rotate(92deg)
|
|
||||||
saturate(3.3)
|
|
||||||
brightness(0.22);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wind-square::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
background:
|
|
||||||
radial-gradient(circle at 28% 22%, rgba(255, 238, 191, 0.08), transparent 42%),
|
|
||||||
linear-gradient(145deg, rgba(5, 33, 24, 0.34), rgba(0, 0, 0, 0.16));
|
|
||||||
pointer-events: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagonal {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
width: 150px;
|
|
||||||
height: 1px;
|
|
||||||
border-radius: 999px;
|
|
||||||
background: linear-gradient(90deg, rgba(16, 40, 31, 0.22), rgba(25, 55, 42, 0.72), rgba(16, 40, 31, 0.22));
|
|
||||||
box-shadow:
|
|
||||||
0 0 4px rgba(0, 0, 0, 0.08);
|
|
||||||
transform-origin: center;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagonal-a {
|
|
||||||
transform: translate(-50%, -50%) rotate(45deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagonal-b {
|
|
||||||
transform: translate(-50%, -50%) rotate(-45deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wind-slot {
|
|
||||||
position: absolute;
|
|
||||||
width: 28px;
|
|
||||||
height: 28px;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wind-icon {
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
object-fit: contain;
|
|
||||||
filter: brightness(0) invert(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.wind-top {
|
|
||||||
top: 10px;
|
|
||||||
left: 34px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wind-right {
|
|
||||||
top: 34px;
|
|
||||||
right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wind-bottom {
|
|
||||||
bottom: 10px;
|
|
||||||
left: 34px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.wind-left {
|
|
||||||
top: 34px;
|
|
||||||
left: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
24
src/config/deskImageMap.ts
Normal file
24
src/config/deskImageMap.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
// src/config/deskImageMap.ts
|
||||||
|
|
||||||
|
export interface DeskAsset {
|
||||||
|
width: number
|
||||||
|
height: number
|
||||||
|
ratio: number
|
||||||
|
src: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// 所有桌面资源
|
||||||
|
export const DESK_ASSETS: DeskAsset[] = [
|
||||||
|
{
|
||||||
|
width: 1920,
|
||||||
|
height: 1080,
|
||||||
|
ratio: 1920 / 1080,
|
||||||
|
src: new URL('@/assets/images/desk/desk_01_1920_1080.png', import.meta.url).href,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
width: 1920,
|
||||||
|
height: 945,
|
||||||
|
ratio: 1920 / 945,
|
||||||
|
src: new URL('@/assets/images/desk/desk_01_1920_945.png', import.meta.url).href,
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -29,6 +29,20 @@ export interface RoomTrusteePayload {
|
|||||||
reason?: string
|
reason?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface PlayerTurnPayload {
|
||||||
|
player_id?: string
|
||||||
|
playerId?: string
|
||||||
|
PlayerID?: string
|
||||||
|
timeout?: number
|
||||||
|
Timeout?: number
|
||||||
|
start_at?: number
|
||||||
|
startAt?: number
|
||||||
|
StartAt?: number
|
||||||
|
allow_actions?: string[]
|
||||||
|
allowActions?: string[]
|
||||||
|
AllowActions?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 游戏动作定义(只描述“发生了什么”)
|
* 游戏动作定义(只描述“发生了什么”)
|
||||||
@@ -92,3 +106,8 @@ export type GameAction =
|
|||||||
type: 'ROOM_TRUSTEE'
|
type: 'ROOM_TRUSTEE'
|
||||||
payload: RoomTrusteePayload
|
payload: RoomTrusteePayload
|
||||||
}
|
}
|
||||||
|
|
||||||
|
| {
|
||||||
|
type: 'PLAYER_TURN'
|
||||||
|
payload: PlayerTurnPayload
|
||||||
|
}
|
||||||
|
|||||||
@@ -38,6 +38,10 @@ export function dispatchGameAction(action: GameAction) {
|
|||||||
store.onRoomTrustee(action.payload)
|
store.onRoomTrustee(action.payload)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
case 'PLAYER_TURN':
|
||||||
|
store.onPlayerTurn(action.payload)
|
||||||
|
break
|
||||||
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error('Invalid game action')
|
throw new Error('Invalid game action')
|
||||||
|
|||||||
@@ -4,10 +4,35 @@ import {
|
|||||||
type GameState,
|
type GameState,
|
||||||
type PendingClaimState,
|
type PendingClaimState,
|
||||||
} from '../types/state'
|
} from '../types/state'
|
||||||
import type { RoomPlayerUpdatePayload, RoomTrusteePayload } from '../game/actions'
|
import type { PlayerTurnPayload, RoomPlayerUpdatePayload, RoomTrusteePayload } from '../game/actions'
|
||||||
|
import { readStoredAuth } from '../utils/auth-storage'
|
||||||
|
|
||||||
import type { Tile } from '../types/tile'
|
import type { Tile } from '../types/tile'
|
||||||
|
|
||||||
|
function parseBooleanish(value: unknown): boolean | null {
|
||||||
|
if (typeof value === 'boolean') {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
if (value === 1) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (value === 0) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (typeof value === 'string') {
|
||||||
|
const normalized = value.trim().toLowerCase()
|
||||||
|
if (normalized === 'true' || normalized === '1') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (normalized === 'false' || normalized === '0') {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
export const useGameStore = defineStore('game', {
|
export const useGameStore = defineStore('game', {
|
||||||
state: (): GameState => ({
|
state: (): GameState => ({
|
||||||
roomId: '',
|
roomId: '',
|
||||||
@@ -16,6 +41,7 @@ export const useGameStore = defineStore('game', {
|
|||||||
|
|
||||||
dealerIndex: 0,
|
dealerIndex: 0,
|
||||||
currentTurn: 0,
|
currentTurn: 0,
|
||||||
|
currentPlayerId: '',
|
||||||
needDraw: false,
|
needDraw: false,
|
||||||
|
|
||||||
players: {},
|
players: {},
|
||||||
@@ -27,6 +53,10 @@ export const useGameStore = defineStore('game', {
|
|||||||
winners: [],
|
winners: [],
|
||||||
|
|
||||||
scores: {},
|
scores: {},
|
||||||
|
|
||||||
|
currentRound: 0,
|
||||||
|
|
||||||
|
totalRounds: 0,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
@@ -34,7 +64,7 @@ export const useGameStore = defineStore('game', {
|
|||||||
this.$reset()
|
this.$reset()
|
||||||
},
|
},
|
||||||
|
|
||||||
// 初始化
|
// 初始<EFBFBD>?
|
||||||
initGame(data: GameState) {
|
initGame(data: GameState) {
|
||||||
Object.assign(this, data)
|
Object.assign(this, data)
|
||||||
},
|
},
|
||||||
@@ -53,8 +83,9 @@ export const useGameStore = defineStore('game', {
|
|||||||
// 剩余牌数减少
|
// 剩余牌数减少
|
||||||
this.remainingTiles = Math.max(0, this.remainingTiles - 1)
|
this.remainingTiles = Math.max(0, this.remainingTiles - 1)
|
||||||
|
|
||||||
// 更新回合(seatIndex)
|
// 更新回合(seatIndex<EFBFBD>?
|
||||||
this.currentTurn = player.seatIndex
|
this.currentTurn = player.seatIndex
|
||||||
|
this.currentPlayerId = player.playerId
|
||||||
|
|
||||||
// 清除操作窗口
|
// 清除操作窗口
|
||||||
this.pendingClaim = undefined
|
this.pendingClaim = undefined
|
||||||
@@ -84,7 +115,7 @@ export const useGameStore = defineStore('game', {
|
|||||||
}
|
}
|
||||||
player.handCount = Math.max(0, player.handCount - 1)
|
player.handCount = Math.max(0, player.handCount - 1)
|
||||||
|
|
||||||
// 加入出牌区
|
// 加入出牌<EFBFBD>?
|
||||||
player.discardTiles.push(data.tile)
|
player.discardTiles.push(data.tile)
|
||||||
|
|
||||||
// 更新回合
|
// 更新回合
|
||||||
@@ -95,7 +126,7 @@ export const useGameStore = defineStore('game', {
|
|||||||
this.phase = GAME_PHASE.ACTION
|
this.phase = GAME_PHASE.ACTION
|
||||||
},
|
},
|
||||||
|
|
||||||
// 触发操作窗口(碰/杠/胡)
|
// 触发操作窗口(碰/<EFBFBD>?胡)
|
||||||
onPendingClaim(data: PendingClaimState) {
|
onPendingClaim(data: PendingClaimState) {
|
||||||
this.pendingClaim = data
|
this.pendingClaim = data
|
||||||
this.needDraw = false
|
this.needDraw = false
|
||||||
@@ -142,6 +173,7 @@ export const useGameStore = defineStore('game', {
|
|||||||
const seatIndex =
|
const seatIndex =
|
||||||
typeof seatRaw === 'number' && Number.isFinite(seatRaw) ? seatRaw : index
|
typeof seatRaw === 'number' && Number.isFinite(seatRaw) ? seatRaw : index
|
||||||
const readyRaw = raw.Ready ?? raw.ready
|
const readyRaw = raw.Ready ?? raw.ready
|
||||||
|
const ready = parseBooleanish(readyRaw)
|
||||||
const displayNameRaw = raw.PlayerName ?? raw.player_name
|
const displayNameRaw = raw.PlayerName ?? raw.player_name
|
||||||
const avatarUrlRaw = raw.AvatarUrl ?? raw.avatar_url
|
const avatarUrlRaw = raw.AvatarUrl ?? raw.avatar_url
|
||||||
const missingSuitRaw = raw.MissingSuit ?? raw.missing_suit
|
const missingSuitRaw = raw.MissingSuit ?? raw.missing_suit
|
||||||
@@ -169,8 +201,8 @@ export const useGameStore = defineStore('game', {
|
|||||||
hasHu: previous?.hasHu ?? false,
|
hasHu: previous?.hasHu ?? false,
|
||||||
score: previous?.score ?? 0,
|
score: previous?.score ?? 0,
|
||||||
isReady:
|
isReady:
|
||||||
typeof readyRaw === 'boolean'
|
ready !== null
|
||||||
? readyRaw
|
? ready
|
||||||
: (previous?.isReady ?? false),
|
: (previous?.isReady ?? false),
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@@ -220,14 +252,49 @@ export const useGameStore = defineStore('game', {
|
|||||||
},
|
},
|
||||||
|
|
||||||
// 清理操作窗口
|
// 清理操作窗口
|
||||||
|
onPlayerTurn(payload: PlayerTurnPayload) {
|
||||||
|
const playerId =
|
||||||
|
(typeof payload.player_id === 'string' && payload.player_id) ||
|
||||||
|
(typeof payload.playerId === 'string' && payload.playerId) ||
|
||||||
|
(typeof payload.PlayerID === 'string' && payload.PlayerID) ||
|
||||||
|
''
|
||||||
|
if (!playerId) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const player = this.players[playerId]
|
||||||
|
if (player) {
|
||||||
|
this.currentTurn = player.seatIndex
|
||||||
|
}
|
||||||
|
this.currentPlayerId = playerId
|
||||||
|
|
||||||
|
this.needDraw = false
|
||||||
|
this.pendingClaim = undefined
|
||||||
|
this.phase = GAME_PHASE.PLAYING
|
||||||
|
},
|
||||||
|
|
||||||
clearPendingClaim() {
|
clearPendingClaim() {
|
||||||
this.pendingClaim = undefined
|
this.pendingClaim = undefined
|
||||||
this.phase = GAME_PHASE.PLAYING
|
this.phase = GAME_PHASE.PLAYING
|
||||||
},
|
},
|
||||||
|
|
||||||
// 获取当前玩家ID(后续建议放到 userStore)
|
// 获取当前玩家ID(后续建议放<EFBFBD>?userStore<EFBFBD>?
|
||||||
getMyPlayerId(): string {
|
getMyPlayerId(): string {
|
||||||
return Object.keys(this.players)[0] || ''
|
const auth = readStoredAuth()
|
||||||
|
const source = auth?.user as Record<string, unknown> | undefined
|
||||||
|
const rawId =
|
||||||
|
source?.id ??
|
||||||
|
source?.userID ??
|
||||||
|
source?.user_id
|
||||||
|
if (typeof rawId === 'string' && rawId.trim()) {
|
||||||
|
return rawId
|
||||||
|
}
|
||||||
|
if (typeof rawId === 'number') {
|
||||||
|
return String(rawId)
|
||||||
|
}
|
||||||
|
return ''
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,8 @@ export interface GameState {
|
|||||||
|
|
||||||
// 当前操作玩家(座位)
|
// 当前操作玩家(座位)
|
||||||
currentTurn: number
|
currentTurn: number
|
||||||
|
// 当前操作玩家ID
|
||||||
|
currentPlayerId: string
|
||||||
|
|
||||||
// 当前回合是否需要先摸牌
|
// 当前回合是否需要先摸牌
|
||||||
needDraw: boolean
|
needDraw: boolean
|
||||||
@@ -31,4 +33,10 @@ export interface GameState {
|
|||||||
|
|
||||||
// 分数(playerId -> score)
|
// 分数(playerId -> score)
|
||||||
scores: Record<string, number>
|
scores: Record<string, number>
|
||||||
|
|
||||||
|
// 当前第几局
|
||||||
|
currentRound: number
|
||||||
|
|
||||||
|
// 总局数
|
||||||
|
totalRounds: number
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,27 +1,36 @@
|
|||||||
import { defineConfig, loadEnv } from 'vite'
|
import {defineConfig, loadEnv} from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
import path from 'path'
|
||||||
|
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({mode}) => {
|
||||||
const env = loadEnv(mode, process.cwd(), '')
|
const env = loadEnv(mode, process.cwd(), '')
|
||||||
const apiProxyTarget = (env.VITE_API_PROXY_TARGET || 'http://127.0.0.1:19000').replace(/\/$/, '')
|
const apiProxyTarget = (env.VITE_API_PROXY_TARGET || 'http://127.0.0.1:19000').replace(/\/$/, '')
|
||||||
const wsProxyTarget = (env.VITE_WS_PROXY_TARGET || apiProxyTarget).replace(/\/$/, '')
|
const wsProxyTarget = (env.VITE_WS_PROXY_TARGET || apiProxyTarget).replace(/\/$/, '')
|
||||||
|
|
||||||
return {
|
return {
|
||||||
plugins: [vue()],
|
resolve: {
|
||||||
server: {
|
alias: {
|
||||||
host: '0.0.0.0',
|
'@': path.resolve(__dirname, 'src'),
|
||||||
proxy: {
|
'@src': path.resolve(__dirname, 'src'),
|
||||||
'/ws': {
|
},
|
||||||
target: wsProxyTarget,
|
|
||||||
changeOrigin: true,
|
|
||||||
ws: true,
|
|
||||||
rewriteWsOrigin: true,
|
|
||||||
},
|
},
|
||||||
'/api/v1': {
|
|
||||||
target: apiProxyTarget,
|
plugins: [vue()],
|
||||||
changeOrigin: true,
|
server: {
|
||||||
|
host: '0.0.0.0',
|
||||||
|
port: 8080,
|
||||||
|
proxy: {
|
||||||
|
'/ws': {
|
||||||
|
target: wsProxyTarget,
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
rewriteWsOrigin: true,
|
||||||
|
},
|
||||||
|
'/api/v1': {
|
||||||
|
target: apiProxyTarget,
|
||||||
|
changeOrigin: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
},
|
},
|
||||||
},
|
}
|
||||||
},
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user