style(game): 优化成都麻将游戏页面样式和代码结构

- 将CSS样式提取到独立的room.css文件中
- 移除组件中的内联样式定义
- 调整了顶部工具栏位置参数
- 更新了计数器指示灯的样式
- 精简了导入语句的空格格式
- 移除了调试用的编辑按钮
- 更新游戏标题为"指尖四川麻将"
- 移除了冗余的import类型声明顺序
- 优化了数组创建语法格式
- 统一了图片标签的alt属性格式
- 调整了循环渲染元素的缩进格式
This commit is contained in:
2026-03-24 17:01:02 +08:00
parent ceba41fb08
commit 716bc2b106
3 changed files with 752 additions and 755 deletions

712
src/assets/styles/room.css Normal file
View File

@@ -0,0 +1,712 @@
.picture-scene {
min-height: 100vh;
min-height: 100dvh;
padding: 18px;
background:
radial-gradient(circle at top, rgba(116, 58, 41, 0.28), transparent 20%),
linear-gradient(180deg, #3f2119 0%, #27140f 100%);
}
.picture-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 320px;
gap: 18px;
align-items: stretch;
min-height: calc(100vh - 36px);
}
.table-stage {
position: relative;
display: grid;
place-items: center;
align-content: start;
width: 100%;
min-height: calc(100vh - 36px);
}
.table-desk,
.table-felt {
width: 100%;
max-width: 100%;
max-height: calc(100dvh - 72px);
aspect-ratio: 16 / 9;
}
.table-desk {
grid-area: 1 / 1;
display: block;
margin-top: 18px;
border-radius: 26px;
object-fit: cover;
box-shadow: 0 24px 44px rgba(0, 0, 0, 0.34);
}
.table-felt {
grid-area: 1 / 1;
position: relative;
margin-top: 18px;
border-radius: 26px;
overflow: hidden;
}
.table-surface {
position: absolute;
inset: 18px;
border-radius: 20px;
background:
radial-gradient(circle at center, rgba(255, 255, 255, 0.04), transparent 34%),
linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(0, 0, 0, 0.04) 52%, rgba(0, 0, 0, 0.08));
}
.table-surface::before {
content: '';
position: absolute;
inset: 0;
border-radius: 20px;
background:
radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.03), transparent 40%),
linear-gradient(180deg, rgba(255, 255, 255, 0.01), rgba(0, 0, 0, 0.06));
}
.inner-outline {
position: absolute;
pointer-events: none;
}
.inner-outline.outer {
inset: 22px;
border-radius: 20px;
border: 1px solid rgba(24, 63, 35, 0.82);
box-shadow:
inset 0 0 0 2px rgba(177, 112, 69, 0.3),
inset 0 0 0 6px rgba(8, 36, 18, 0.18);
}
.inner-outline.mid {
inset: 74px 92px 122px;
border-radius: 20px;
border: 2px solid rgba(180, 224, 187, 0.12);
}
.inner-outline.diamond {
left: 50%;
top: 50%;
width: 280px;
height: 280px;
border: 2px solid rgba(143, 199, 155, 0.08);
transform: translate(-50%, -50%) rotate(45deg);
}
.top-left-tools {
position: absolute;
top: 28px;
left: 28px;
display: flex;
align-items: center;
gap: 14px;
z-index: 5;
}
.metal-circle {
width: 50px;
height: 50px;
border: 1px solid rgba(255, 255, 255, 0.16);
border-radius: 50%;
color: #d7f0ff;
font-size: 26px;
background:
linear-gradient(180deg, rgba(156, 171, 191, 0.82), rgba(79, 94, 114, 0.86)),
radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.28), transparent 42%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.22),
0 8px 16px rgba(0, 0, 0, 0.22);
}
.left-counter {
display: inline-flex;
align-items: center;
gap: 10px;
height: 42px;
padding: 0 16px;
border-radius: 8px;
color: #fff4cf;
background: rgba(13, 15, 20, 0.78);
}
.counter-light {
width: 16px;
height: 24px;
border-radius: 3px;
background: linear-gradient(180deg, #54e061 0%, #179928 100%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.36),
0 0 8px rgba(84, 224, 97, 0.35);
}
.top-right-clock {
position: absolute;
top: 14px;
right: 16px;
display: inline-flex;
align-items: center;
gap: 12px;
padding: 8px 14px;
border-radius: 10px;
color: #f1f6ff;
background: rgba(14, 16, 22, 0.46);
z-index: 5;
}
.signal-chip {
display: inline-flex;
align-items: center;
gap: 8px;
}
.scene-watermark {
position: absolute;
left: 50%;
bottom: 38%;
transform: translateX(-50%);
text-align: center;
color: rgba(7, 42, 19, 0.18);
pointer-events: none;
}
.scene-watermark strong {
display: block;
font-size: clamp(30px, 4vw, 52px);
line-height: 1;
font-weight: 800;
}
.scene-watermark span,
.scene-watermark small {
display: block;
margin-top: 6px;
color: rgba(7, 42, 19, 0.24);
}
.wall {
position: absolute;
display: flex;
gap: 2px;
filter: drop-shadow(0 8px 8px rgba(0, 0, 0, 0.2));
}
.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: 54px;
}
.wall-top img,
.wall-bottom img {
width: 24px;
height: 36px;
}
.wall-right {
right: 110px;
}
.wall-left {
left: 110px;
}
.wall-left img,
.wall-right img {
width: 36px;
height: 24px;
}
.wall-bottom {
bottom: 126px;
}
.center-desk {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: grid;
grid-template-columns: repeat(2, 46px);
grid-template-areas:
'north west'
'count count'
'south east';
gap: 8px;
padding: 14px;
border-radius: 18px;
border: 2px solid rgba(69, 55, 38, 0.72);
background:
linear-gradient(135deg, rgba(107, 51, 41, 0.94), rgba(33, 35, 32, 0.96) 50%, rgba(26, 69, 36, 0.92)),
radial-gradient(circle at center, rgba(255, 255, 255, 0.06), transparent 52%);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.22);
}
.center-desk strong {
grid-area: count;
text-align: center;
font-size: 32px;
color: #3ec37f;
letter-spacing: 3px;
}
.wind {
display: grid;
place-items: center;
width: 42px;
height: 42px;
border-radius: 10px;
background: rgba(0, 0, 0, 0.18);
color: rgba(255, 255, 255, 0.68);
font-weight: 700;
}
.wind.north {
grid-area: north;
}
.wind.west {
grid-area: west;
}
.wind.south {
grid-area: south;
}
.wind.east {
grid-area: east;
}
.floating-status {
position: absolute;
display: inline-flex;
align-items: center;
gap: 8px;
color: #ffcf4d;
font-size: clamp(24px, 2.6vw, 42px);
font-weight: 800;
text-shadow:
0 0 12px rgba(255, 195, 0, 0.32),
0 3px 0 rgba(130, 74, 0, 0.4);
pointer-events: none;
}
.floating-status img {
width: 40px;
height: 40px;
object-fit: contain;
filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.2));
}
.floating-status.top {
top: 48px;
left: 50%;
transform: translateX(-50%);
}
.floating-status.left {
left: 24%;
top: 50%;
transform: translate(-50%, -50%);
}
.floating-status.right {
right: 24%;
top: 50%;
transform: translate(50%, -50%);
}
.claim-banner {
position: absolute;
left: 50%;
top: 58%;
transform: translate(-50%, -50%);
min-width: 380px;
padding: 14px 20px;
border-radius: 10px;
text-align: center;
color: #eef7ef;
background: rgba(13, 31, 17, 0.24);
}
.claim-banner span {
display: block;
font-size: 14px;
opacity: 0.8;
}
.claim-banner strong {
display: block;
margin-top: 2px;
font-size: 18px;
}
.bottom-control-panel {
position: absolute;
left: 50%;
bottom: 8px;
transform: translateX(-50%);
width: min(100% - 120px, 1180px);
padding: 8px 14px 12px;
}
.control-copy {
margin-bottom: 10px;
text-align: center;
color: rgba(239, 247, 237, 0.84);
}
.control-copy p {
font-size: 14px;
font-weight: 700;
}
.control-copy small {
font-size: 12px;
}
.action-orbs {
position: absolute;
right: 38px;
top: -66px;
display: flex;
align-items: center;
gap: 12px;
}
.orb-button {
width: 76px;
height: 76px;
border: 0;
border-radius: 50%;
font-size: 26px;
font-weight: 800;
cursor: pointer;
box-shadow:
inset 0 3px 8px rgba(255, 255, 255, 0.18),
0 10px 20px rgba(0, 0, 0, 0.22);
}
.orb-button.theme-gold {
color: #8a4e00;
background: radial-gradient(circle at 35% 28%, #fff7bf 0%, #ffd85d 42%, #d89a19 100%);
}
.orb-button.theme-jade {
color: #efffff;
background: radial-gradient(circle at 35% 28%, #c4fff2 0%, #3ad8b4 42%, #00876e 100%);
}
.orb-button.theme-blue {
color: #effff2;
background: radial-gradient(circle at 35% 28%, #bff2c8 0%, #6bc77c 42%, #2e7a43 100%);
}
.orb-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.player-hand {
display: flex;
align-items: flex-end;
justify-content: center;
gap: 4px;
overflow-x: auto;
padding-bottom: 2px;
}
.tile-chip {
min-width: 90px;
height: 126px;
border: 1px solid rgba(70, 80, 92, 0.18);
border-radius: 8px;
color: #14181d;
font-size: 32px;
font-weight: 700;
background:
linear-gradient(180deg, #ffffff 0%, #f8fafc 68%, #dfe6ed 100%);
box-shadow:
inset 0 -4px 0 #1ea328,
inset 0 1px 0 rgba(255, 255, 255, 0.9),
0 6px 12px rgba(0, 0, 0, 0.18);
cursor: pointer;
}
.tile-chip.selected {
transform: translateY(-18px);
}
.empty-hand {
text-align: center;
color: rgba(237, 244, 253, 0.82);
font-size: 13px;
}
.table-side-buttons {
position: absolute;
right: 18px;
top: 42%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
gap: 12px;
}
.side-round {
width: 56px;
height: 56px;
border: 0;
border-radius: 50%;
color: #edf8ef;
font-size: 22px;
font-weight: 700;
background:
linear-gradient(180deg, rgba(126, 140, 122, 0.82), rgba(59, 72, 57, 0.86)),
radial-gradient(circle at 35% 28%, rgba(255, 255, 255, 0.22), transparent 40%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.2),
0 8px 14px rgba(0, 0, 0, 0.22);
}
.side-round.gold {
color: #7a4600;
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) {
.picture-scene {
padding: 10px;
}
.table-desk,
.table-felt {
width: 100%;
margin-top: 8px;
}
.wall-right {
right: 88px;
}
.wall-left {
left: 88px;
}
.inner-outline.mid {
inset: 70px 72px 120px;
}
.tile-chip {
min-width: 70px;
height: 102px;
font-size: 24px;
}
.action-orbs {
right: 18px;
top: -54px;
}
.orb-button {
width: 62px;
height: 62px;
font-size: 22px;
}
}
@media (max-width: 640px) {
.table-desk,
.table-felt {
aspect-ratio: 9 / 16;
}
.inner-outline.mid {
inset: 92px 34px 190px;
}
.inner-outline.diamond {
width: 180px;
height: 180px;
}
.wall-top,
.wall-bottom {
display: none;
}
.wall-left {
left: 32px;
}
.wall-right {
right: 32px;
}
.floating-status.left,
.floating-status.right {
display: none;
}
.claim-banner {
min-width: 0;
width: calc(100% - 40px);
}
.bottom-control-panel {
width: calc(100% - 20px);
}
.action-orbs {
position: static;
justify-content: center;
margin-bottom: 10px;
}
.tile-chip {
min-width: 48px;
height: 76px;
font-size: 16px;
}
.table-side-buttons {
right: 10px;
gap: 8px;
}
.side-round {
width: 46px;
height: 46px;
font-size: 18px;
}
}

View File

@@ -54,9 +54,6 @@ function humanizeSuit(value: string): string {
W: '万', W: '万',
B: '筒', B: '筒',
T: '条', T: '条',
wan: '万',
tong: '筒',
tiao: '条',
} }
return suitMap[value] ?? value return suitMap[value] ?? value

View File

@@ -1,10 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { computed, onBeforeUnmount, onMounted, ref } from 'vue' import {computed, onBeforeUnmount, onMounted, ref} from 'vue'
import { useRoute, useRouter } from 'vue-router' import {useRoute, useRouter} from 'vue-router'
import deskImage from '../assets/images/desk/desk_01.png' import deskImage from '../assets/images/desk/desk_01.png'
import wanIcon from '../assets/images/flowerClolor/wan.png' import wanIcon from '../assets/images/flowerClolor/wan.png'
import tongIcon from '../assets/images/flowerClolor/tong.png' import tongIcon from '../assets/images/flowerClolor/tong.png'
import tiaoIcon from '../assets/images/flowerClolor/tiao.png' import tiaoIcon from '../assets/images/flowerClolor/tiao.png'
import '../assets/styles/room.css'
import topBackImage from '../assets/images/tiles/top/tbgs_2.png' import topBackImage from '../assets/images/tiles/top/tbgs_2.png'
import rightBackImage from '../assets/images/tiles/right/tbgs_1.png' import rightBackImage from '../assets/images/tiles/right/tbgs_1.png'
import bottomBackImage from '../assets/images/tiles/bottom/tdbgs_4.png' import bottomBackImage from '../assets/images/tiles/bottom/tdbgs_4.png'
@@ -13,8 +14,8 @@ import TopPlayerCard from '../components/game/TopPlayerCard.vue'
import RightPlayerCard from '../components/game/RightPlayerCard.vue' import RightPlayerCard from '../components/game/RightPlayerCard.vue'
import BottomPlayerCard from '../components/game/BottomPlayerCard.vue' import BottomPlayerCard from '../components/game/BottomPlayerCard.vue'
import LeftPlayerCard from '../components/game/LeftPlayerCard.vue' import LeftPlayerCard from '../components/game/LeftPlayerCard.vue'
import type { SeatPlayerCardModel } from '../components/game/seat-player-card' import type {SeatPlayerCardModel} from '../components/game/seat-player-card'
import { useChengduGameRoom, type SeatKey } from '../features/chengdu-game/useChengduGameRoom' import {type SeatKey, useChengduGameRoom} from '../features/chengdu-game/useChengduGameRoom'
const route = useRoute() const route = useRoute()
const router = useRouter() const router = useRouter()
@@ -94,10 +95,10 @@ const wallBacks = computed<Record<SeatKey, string[]>>(() => {
const perSide = Math.max(6, Math.ceil((wallSize || 48) / 4 / 2)) const perSide = Math.max(6, Math.ceil((wallSize || 48) / 4 / 2))
return { return {
top: Array.from({ length: perSide }, (_, index) => `top-${index}`), top: Array.from({length: perSide}, (_, index) => `top-${index}`),
right: Array.from({ length: perSide }, (_, index) => `right-${index}`), right: Array.from({length: perSide}, (_, index) => `right-${index}`),
bottom: Array.from({ length: perSide }, (_, index) => `bottom-${index}`), bottom: Array.from({length: perSide}, (_, index) => `bottom-${index}`),
left: Array.from({ length: perSide }, (_, index) => `left-${index}`), left: Array.from({length: perSide}, (_, index) => `left-${index}`),
} }
}) })
@@ -237,7 +238,7 @@ onBeforeUnmount(() => {
<section class="picture-scene"> <section class="picture-scene">
<div class="picture-layout"> <div class="picture-layout">
<section class="table-stage"> <section class="table-stage">
<img class="table-desk" :src="deskImage" alt="" /> <img class="table-desk" :src="deskImage" alt=""/>
<div class="table-felt"> <div class="table-felt">
<div class="table-surface"></div> <div class="table-surface"></div>
@@ -251,7 +252,6 @@ onBeforeUnmount(() => {
<span class="counter-light"></span> <span class="counter-light"></span>
<strong>{{ roomState.game?.state?.wall.length ?? 48 }}</strong> <strong>{{ roomState.game?.state?.wall.length ?? 48 }}</strong>
</div> </div>
<button class="metal-circle" type="button" @click="connectWs"></button>
</div> </div>
<div class="top-right-clock"> <div class="top-right-clock">
@@ -263,27 +263,27 @@ onBeforeUnmount(() => {
</div> </div>
<div class="scene-watermark"> <div class="scene-watermark">
<strong>四川麻将</strong> <strong>指尖四川麻将</strong>
<span>{{ roomState.name || roomName || '成都麻将房' }}</span> <span>{{ roomState.name || roomName || '成都麻将房' }}</span>
<small>底注 6 亿 · 封顶 32 </small> <small>底注 6 亿 · 封顶 32 </small>
</div> </div>
<TopPlayerCard :player="seatDecor.top" /> <TopPlayerCard :player="seatDecor.top"/>
<RightPlayerCard :player="seatDecor.right" /> <RightPlayerCard :player="seatDecor.right"/>
<BottomPlayerCard :player="seatDecor.bottom" /> <BottomPlayerCard :player="seatDecor.bottom"/>
<LeftPlayerCard :player="seatDecor.left" /> <LeftPlayerCard :player="seatDecor.left"/>
<div class="wall wall-top"> <div class="wall wall-top">
<img v-for="key in wallBacks.top" :key="key" :src="getBackImage('top')" alt="" /> <img v-for="key in wallBacks.top" :key="key" :src="getBackImage('top')" alt=""/>
</div> </div>
<div class="wall wall-right"> <div class="wall wall-right">
<img v-for="key in wallBacks.right" :key="key" :src="getBackImage('right')" alt="" /> <img v-for="key in wallBacks.right" :key="key" :src="getBackImage('right')" alt=""/>
</div> </div>
<div class="wall wall-bottom"> <div class="wall wall-bottom">
<img v-for="key in wallBacks.bottom" :key="key" :src="getBackImage('bottom')" alt="" /> <img v-for="key in wallBacks.bottom" :key="key" :src="getBackImage('bottom')" alt=""/>
</div> </div>
<div class="wall wall-left"> <div class="wall wall-left">
<img v-for="key in wallBacks.left" :key="key" :src="getBackImage('left')" alt="" /> <img v-for="key in wallBacks.left" :key="key" :src="getBackImage('left')" alt=""/>
</div> </div>
<div class="center-desk"> <div class="center-desk">
@@ -295,15 +295,15 @@ onBeforeUnmount(() => {
</div> </div>
<div class="floating-status top"> <div class="floating-status top">
<img v-if="floatingMissingSuit.top" :src="floatingMissingSuit.top" alt="" /> <img v-if="floatingMissingSuit.top" :src="floatingMissingSuit.top" alt=""/>
<span>{{ seatDecor.top.missingSuitLabel }}</span> <span>{{ seatDecor.top.missingSuitLabel }}</span>
</div> </div>
<div class="floating-status left"> <div class="floating-status left">
<img v-if="floatingMissingSuit.left" :src="floatingMissingSuit.left" alt="" /> <img v-if="floatingMissingSuit.left" :src="floatingMissingSuit.left" alt=""/>
<span>{{ seatDecor.left.missingSuitLabel }}</span> <span>{{ seatDecor.left.missingSuitLabel }}</span>
</div> </div>
<div class="floating-status right"> <div class="floating-status right">
<img v-if="floatingMissingSuit.right" :src="floatingMissingSuit.right" alt="" /> <img v-if="floatingMissingSuit.right" :src="floatingMissingSuit.right" alt=""/>
<span>{{ seatDecor.right.missingSuitLabel }}</span> <span>{{ seatDecor.right.missingSuitLabel }}</span>
</div> </div>
@@ -320,13 +320,13 @@ onBeforeUnmount(() => {
<div class="action-orbs"> <div class="action-orbs">
<button <button
v-for="action in actionButtons" v-for="action in actionButtons"
:key="action.type" :key="action.type"
class="orb-button" class="orb-button"
:class="`theme-${actionTheme(action.type)}`" :class="`theme-${actionTheme(action.type)}`"
type="button" type="button"
:disabled="action.disabled" :disabled="action.disabled"
@click="sendGameAction(action.type)" @click="sendGameAction(action.type)"
> >
{{ action.label }} {{ action.label }}
</button> </button>
@@ -334,12 +334,12 @@ onBeforeUnmount(() => {
<div class="player-hand" v-if="roomState.myHand.length > 0"> <div class="player-hand" v-if="roomState.myHand.length > 0">
<button <button
v-for="(tile, index) in roomState.myHand" v-for="(tile, index) in roomState.myHand"
:key="`${tile}-${index}`" :key="`${tile}-${index}`"
class="tile-chip" class="tile-chip"
:class="{ selected: selectedTile === tile }" :class="{ selected: selectedTile === tile }"
type="button" type="button"
@click="selectTile(tile)" @click="selectTile(tile)"
> >
{{ tile }} {{ tile }}
</button> </button>
@@ -351,10 +351,10 @@ onBeforeUnmount(() => {
<button class="side-round" type="button" @click="connectWs"></button> <button class="side-round" type="button" @click="connectWs"></button>
<button class="side-round" type="button"></button> <button class="side-round" type="button"></button>
<button <button
class="side-round gold" class="side-round gold"
type="button" type="button"
:disabled="!canStartGame || startGamePending" :disabled="!canStartGame || startGamePending"
@click="sendStartGame" @click="sendStartGame"
> >
{{ startGamePending ? '开局中' : '开局' }} {{ startGamePending ? '开局中' : '开局' }}
</button> </button>
@@ -400,715 +400,3 @@ onBeforeUnmount(() => {
</div> </div>
</section> </section>
</template> </template>
<style scoped>
.picture-scene {
min-height: 100vh;
min-height: 100dvh;
padding: 18px;
background:
radial-gradient(circle at top, rgba(116, 58, 41, 0.28), transparent 20%),
linear-gradient(180deg, #3f2119 0%, #27140f 100%);
}
.picture-layout {
display: grid;
grid-template-columns: minmax(0, 1fr) 320px;
gap: 18px;
align-items: stretch;
min-height: calc(100vh - 36px);
}
.table-stage {
position: relative;
display: grid;
place-items: center;
align-content: start;
width: 100%;
min-height: calc(100vh - 36px);
}
.table-desk,
.table-felt {
width: 100%;
max-width: 100%;
max-height: calc(100dvh - 72px);
aspect-ratio: 16 / 9;
}
.table-desk {
grid-area: 1 / 1;
display: block;
margin-top: 18px;
border-radius: 26px;
object-fit: cover;
box-shadow: 0 24px 44px rgba(0, 0, 0, 0.34);
}
.table-felt {
grid-area: 1 / 1;
position: relative;
margin-top: 18px;
border-radius: 26px;
overflow: hidden;
}
.table-surface {
position: absolute;
inset: 18px;
border-radius: 20px;
background:
radial-gradient(circle at center, rgba(255, 255, 255, 0.04), transparent 34%),
linear-gradient(180deg, rgba(255, 255, 255, 0.02), rgba(0, 0, 0, 0.04) 52%, rgba(0, 0, 0, 0.08));
}
.table-surface::before {
content: '';
position: absolute;
inset: 0;
border-radius: 20px;
background:
radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.03), transparent 40%),
linear-gradient(180deg, rgba(255, 255, 255, 0.01), rgba(0, 0, 0, 0.06));
}
.inner-outline {
position: absolute;
pointer-events: none;
}
.inner-outline.outer {
inset: 22px;
border-radius: 20px;
border: 1px solid rgba(24, 63, 35, 0.82);
box-shadow:
inset 0 0 0 2px rgba(177, 112, 69, 0.3),
inset 0 0 0 6px rgba(8, 36, 18, 0.18);
}
.inner-outline.mid {
inset: 74px 92px 122px;
border-radius: 20px;
border: 2px solid rgba(180, 224, 187, 0.12);
}
.inner-outline.diamond {
left: 50%;
top: 50%;
width: 280px;
height: 280px;
border: 2px solid rgba(143, 199, 155, 0.08);
transform: translate(-50%, -50%) rotate(45deg);
}
.top-left-tools {
position: absolute;
top: 14px;
left: 14px;
display: flex;
align-items: center;
gap: 14px;
z-index: 5;
}
.metal-circle {
width: 50px;
height: 50px;
border: 1px solid rgba(255, 255, 255, 0.16);
border-radius: 50%;
color: #d7f0ff;
font-size: 26px;
background:
linear-gradient(180deg, rgba(156, 171, 191, 0.82), rgba(79, 94, 114, 0.86)),
radial-gradient(circle at 30% 25%, rgba(255, 255, 255, 0.28), transparent 42%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.22),
0 8px 16px rgba(0, 0, 0, 0.22);
}
.left-counter {
display: inline-flex;
align-items: center;
gap: 10px;
height: 42px;
padding: 0 16px;
border-radius: 8px;
color: #fff4cf;
background: rgba(13, 15, 20, 0.78);
}
.counter-light {
width: 18px;
height: 18px;
border-radius: 3px;
background: linear-gradient(180deg, #43d34b 0%, #159d22 100%);
}
.top-right-clock {
position: absolute;
top: 14px;
right: 16px;
display: inline-flex;
align-items: center;
gap: 12px;
padding: 8px 14px;
border-radius: 10px;
color: #f1f6ff;
background: rgba(14, 16, 22, 0.46);
z-index: 5;
}
.signal-chip {
display: inline-flex;
align-items: center;
gap: 8px;
}
.scene-watermark {
position: absolute;
left: 50%;
bottom: 38%;
transform: translateX(-50%);
text-align: center;
color: rgba(7, 42, 19, 0.18);
pointer-events: none;
}
.scene-watermark strong {
display: block;
font-size: clamp(30px, 4vw, 52px);
line-height: 1;
font-weight: 800;
}
.scene-watermark span,
.scene-watermark small {
display: block;
margin-top: 6px;
color: rgba(7, 42, 19, 0.24);
}
.wall {
position: absolute;
display: flex;
gap: 2px;
filter: drop-shadow(0 8px 8px rgba(0, 0, 0, 0.2));
}
.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: 54px;
}
.wall-top img,
.wall-bottom img {
width: 24px;
height: 36px;
}
.wall-right {
right: 110px;
}
.wall-left {
left: 110px;
}
.wall-left img,
.wall-right img {
width: 36px;
height: 24px;
}
.wall-bottom {
bottom: 126px;
}
.center-desk {
position: absolute;
left: 50%;
top: 50%;
transform: translate(-50%, -50%);
display: grid;
grid-template-columns: repeat(2, 46px);
grid-template-areas:
'north west'
'count count'
'south east';
gap: 8px;
padding: 14px;
border-radius: 18px;
border: 2px solid rgba(69, 55, 38, 0.72);
background:
linear-gradient(135deg, rgba(107, 51, 41, 0.94), rgba(33, 35, 32, 0.96) 50%, rgba(26, 69, 36, 0.92)),
radial-gradient(circle at center, rgba(255, 255, 255, 0.06), transparent 52%);
box-shadow: 0 12px 24px rgba(0, 0, 0, 0.22);
}
.center-desk strong {
grid-area: count;
text-align: center;
font-size: 32px;
color: #3ec37f;
letter-spacing: 3px;
}
.wind {
display: grid;
place-items: center;
width: 42px;
height: 42px;
border-radius: 10px;
background: rgba(0, 0, 0, 0.18);
color: rgba(255, 255, 255, 0.68);
font-weight: 700;
}
.wind.north {
grid-area: north;
}
.wind.west {
grid-area: west;
}
.wind.south {
grid-area: south;
}
.wind.east {
grid-area: east;
}
.floating-status {
position: absolute;
display: inline-flex;
align-items: center;
gap: 8px;
color: #ffcf4d;
font-size: clamp(24px, 2.6vw, 42px);
font-weight: 800;
text-shadow:
0 0 12px rgba(255, 195, 0, 0.32),
0 3px 0 rgba(130, 74, 0, 0.4);
pointer-events: none;
}
.floating-status img {
width: 40px;
height: 40px;
object-fit: contain;
filter: drop-shadow(0 6px 10px rgba(0, 0, 0, 0.2));
}
.floating-status.top {
top: 48px;
left: 50%;
transform: translateX(-50%);
}
.floating-status.left {
left: 24%;
top: 50%;
transform: translate(-50%, -50%);
}
.floating-status.right {
right: 24%;
top: 50%;
transform: translate(50%, -50%);
}
.claim-banner {
position: absolute;
left: 50%;
top: 58%;
transform: translate(-50%, -50%);
min-width: 380px;
padding: 14px 20px;
border-radius: 10px;
text-align: center;
color: #eef7ef;
background: rgba(13, 31, 17, 0.24);
}
.claim-banner span {
display: block;
font-size: 14px;
opacity: 0.8;
}
.claim-banner strong {
display: block;
margin-top: 2px;
font-size: 18px;
}
.bottom-control-panel {
position: absolute;
left: 50%;
bottom: 8px;
transform: translateX(-50%);
width: min(100% - 120px, 1180px);
padding: 8px 14px 12px;
}
.control-copy {
margin-bottom: 10px;
text-align: center;
color: rgba(239, 247, 237, 0.84);
}
.control-copy p {
font-size: 14px;
font-weight: 700;
}
.control-copy small {
font-size: 12px;
}
.action-orbs {
position: absolute;
right: 38px;
top: -66px;
display: flex;
align-items: center;
gap: 12px;
}
.orb-button {
width: 76px;
height: 76px;
border: 0;
border-radius: 50%;
font-size: 26px;
font-weight: 800;
cursor: pointer;
box-shadow:
inset 0 3px 8px rgba(255, 255, 255, 0.18),
0 10px 20px rgba(0, 0, 0, 0.22);
}
.orb-button.theme-gold {
color: #8a4e00;
background: radial-gradient(circle at 35% 28%, #fff7bf 0%, #ffd85d 42%, #d89a19 100%);
}
.orb-button.theme-jade {
color: #efffff;
background: radial-gradient(circle at 35% 28%, #c4fff2 0%, #3ad8b4 42%, #00876e 100%);
}
.orb-button.theme-blue {
color: #effff2;
background: radial-gradient(circle at 35% 28%, #bff2c8 0%, #6bc77c 42%, #2e7a43 100%);
}
.orb-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
.player-hand {
display: flex;
align-items: flex-end;
justify-content: center;
gap: 4px;
overflow-x: auto;
padding-bottom: 2px;
}
.tile-chip {
min-width: 90px;
height: 126px;
border: 1px solid rgba(70, 80, 92, 0.18);
border-radius: 8px;
color: #14181d;
font-size: 32px;
font-weight: 700;
background:
linear-gradient(180deg, #ffffff 0%, #f8fafc 68%, #dfe6ed 100%);
box-shadow:
inset 0 -4px 0 #1ea328,
inset 0 1px 0 rgba(255, 255, 255, 0.9),
0 6px 12px rgba(0, 0, 0, 0.18);
cursor: pointer;
}
.tile-chip.selected {
transform: translateY(-18px);
}
.empty-hand {
text-align: center;
color: rgba(237, 244, 253, 0.82);
font-size: 13px;
}
.table-side-buttons {
position: absolute;
right: 18px;
top: 42%;
transform: translateY(-50%);
display: flex;
flex-direction: column;
gap: 12px;
}
.side-round {
width: 56px;
height: 56px;
border: 0;
border-radius: 50%;
color: #edf8ef;
font-size: 22px;
font-weight: 700;
background:
linear-gradient(180deg, rgba(126, 140, 122, 0.82), rgba(59, 72, 57, 0.86)),
radial-gradient(circle at 35% 28%, rgba(255, 255, 255, 0.22), transparent 40%);
box-shadow:
inset 0 1px 0 rgba(255, 255, 255, 0.2),
0 8px 14px rgba(0, 0, 0, 0.22);
}
.side-round.gold {
color: #7a4600;
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) {
.picture-scene {
padding: 10px;
}
.table-desk,
.table-felt {
width: 100%;
margin-top: 8px;
}
.wall-right {
right: 88px;
}
.wall-left {
left: 88px;
}
.inner-outline.mid {
inset: 70px 72px 120px;
}
.tile-chip {
min-width: 70px;
height: 102px;
font-size: 24px;
}
.action-orbs {
right: 18px;
top: -54px;
}
.orb-button {
width: 62px;
height: 62px;
font-size: 22px;
}
}
@media (max-width: 640px) {
.table-desk,
.table-felt {
aspect-ratio: 9 / 16;
}
.inner-outline.mid {
inset: 92px 34px 190px;
}
.inner-outline.diamond {
width: 180px;
height: 180px;
}
.wall-top,
.wall-bottom {
display: none;
}
.wall-left {
left: 32px;
}
.wall-right {
right: 32px;
}
.floating-status.left,
.floating-status.right {
display: none;
}
.claim-banner {
min-width: 0;
width: calc(100% - 40px);
}
.bottom-control-panel {
width: calc(100% - 20px);
}
.action-orbs {
position: static;
justify-content: center;
margin-bottom: 10px;
}
.tile-chip {
min-width: 48px;
height: 76px;
font-size: 16px;
}
.table-side-buttons {
right: 10px;
gap: 8px;
}
.side-round {
width: 46px;
height: 46px;
font-size: 18px;
}
}
</style>