6363 </div >
6464 </div >
6565
66- <img v-if =" isDragging && dragStartSquare" class =" floating-piece" draggable =" false"
66+ <img v-if =" isDragging && dragStartSquare" class =" floating-piece no-select " draggable =" false"
6767 :src =" getPieceImage(board[dragStartSquare.row]?.[dragStartSquare.col]!, board, isDraw, hasResigned, timeoutWinner)"
6868 :style =" { left: mousePos.x + 'px', top: mousePos.y + 'px' }" alt =" " />
6969 </div >
7575 :black-time-seconds =" blackTimeSeconds " :active-color =" currentTurn " :clock-test-id =" ' sidebar-chess-clock' "
7676 v-model :is-sound-enabled =" isSoundEnabled " v-model :coordinate-label-mode =" coordinateLabelMode "
7777 @toggle-flip =" isFlipped = ! isFlipped " :has-game-started =" hasGameStarted " @undo =" handleUndo "
78- @draw =" handleDrawOffer " @resign =" handleResign " @restart =" handleRestart " />
78+ @draw =" handleDrawOffer " @resign =" handleResign " @restart =" handleRestart " @back-to-setup =" handleBackToSetup " />
79+
80+ <!-- 右上角固定设置按钮 -->
81+ <button type =" button" class =" settings-fab" @click =" showSettingsModal = true" title =" 设置" >
82+ ⚙
83+ </button >
84+
85+ <!-- 设置弹窗 Modal -->
86+ <SettingsModal :visible =" showSettingsModal " :is-sound-enabled =" isSoundEnabled "
87+ :coordinate-label-mode =" coordinateLabelMode " @close =" showSettingsModal = false "
88+ @toggle-flip =" isFlipped = ! isFlipped "
89+ @update :is-sound-enabled =" (val : boolean ) => isSoundEnabled = val "
90+ @update :coordinate-label-mode =" (val : ' off' | ' inside' | ' outside' ) => coordinateLabelMode = val " />
7991 </section >
8092</template >
8193
8294<script setup lang="ts">
83- import { onMounted , onUnmounted } from ' vue'
95+ import { onMounted , onUnmounted , ref } from ' vue'
8496import Promotion from ' ./components/Promotion.vue'
8597import Sidebar from ' ./components/Sidebar.vue'
98+ import SettingsModal from ' ./components/SettingsModal.vue'
8699import GameSetup from ' ./components/GameSetup.vue'
87100import { useSettings } from ' ./composables/useSettings'
88101import { useBoardDisplay } from ' ./composables/useBoardDisplay'
@@ -91,6 +104,9 @@ import { useGameState } from './composables/useGameState'
91104// ---- 设置持久化 ----
92105const { isSoundEnabled, coordinateLabelMode } = useSettings ()
93106
107+ // ---- 设置弹窗状态 ----
108+ const showSettingsModal = ref (false )
109+
94110// ---- 棋盘显示 ----
95111const {
96112 isFlipped,
@@ -141,6 +157,7 @@ const {
141157 handleResign,
142158 handleDrawOffer,
143159 handleRestart,
160+ handleBackToSetup,
144161 handleGameSetupStart,
145162 cancelPromotion,
146163 applyPromotion,
@@ -199,7 +216,6 @@ onUnmounted(() => {
199216 max-width : calc (100vh - 120px );
200217 width : 80vmin ;
201218 margin : 0 auto ;
202- box-shadow : 0 6px 18px rgba (0 , 0 , 0 , 0.25 );
203219 box-sizing : border-box ;
204220 overflow : visible ;
205221}
@@ -273,7 +289,8 @@ onUnmounted(() => {
273289 bottom : 15% ;
274290 transform : translateX (-50% ) scale (var (--piece-scale ));
275291 transform-origin : bottom center ;
276- pointer-events : none ;
292+ pointer-events : auto ;
293+ cursor : grab ;
277294 z-index : 10 ;
278295 transition : opacity 0.1s ease ;
279296}
@@ -365,4 +382,35 @@ onUnmounted(() => {
365382 background : rgba (0 , 0 , 0 , 0.45 );
366383 z-index : 50 ;
367384}
385+
386+ /* 右上角固定设置按钮 */
387+ .settings-fab {
388+ position : fixed ;
389+ top : 16px ;
390+ right : 16px ;
391+ z-index : 10000 ;
392+ width : 44px ;
393+ height : 44px ;
394+ border-radius : 50% ;
395+ border : 2px solid #212529 ;
396+ background : #fff ;
397+ font-size : 1.4rem ;
398+ cursor : pointer ;
399+ display : flex ;
400+ align-items : center ;
401+ justify-content : center ;
402+ box-shadow : 0 2px 8px rgba (0 , 0 , 0 , 0.2 );
403+ transition : background-color 0.15s ;
404+ padding : 0 ;
405+ line-height : 1 ;
406+ }
407+
408+ .settings-fab :hover {
409+ background : #f0f0f0 ;
410+ }
411+
412+ .settings-fab :focus-visible {
413+ outline : 2px solid #ffd33d ;
414+ outline-offset : 2px ;
415+ }
368416 </style >
0 commit comments