Skip to content

Commit 9c088f0

Browse files
committed
update
1 parent d224d95 commit 9c088f0

9 files changed

Lines changed: 323 additions & 221 deletions

File tree

src/App.vue

Lines changed: 53 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@
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>
@@ -75,14 +75,27 @@
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'
8496
import Promotion from './components/Promotion.vue'
8597
import Sidebar from './components/Sidebar.vue'
98+
import SettingsModal from './components/SettingsModal.vue'
8699
import GameSetup from './components/GameSetup.vue'
87100
import { useSettings } from './composables/useSettings'
88101
import { useBoardDisplay } from './composables/useBoardDisplay'
@@ -91,6 +104,9 @@ import { useGameState } from './composables/useGameState'
91104
// ---- 设置持久化 ----
92105
const { isSoundEnabled, coordinateLabelMode } = useSettings()
93106
107+
// ---- 设置弹窗状态 ----
108+
const showSettingsModal = ref(false)
109+
94110
// ---- 棋盘显示 ----
95111
const {
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>

src/assets/styles/global.css

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
.card {
2+
border: 2px solid #212529;
3+
background-color: #fff;
4+
padding: 1rem;
5+
box-shadow: 2px 2px 0 #909294;
6+
}
7+
8+
.card.with-title {
9+
padding-top: 0;
10+
}
11+
12+
.btn {
13+
font-family: 'Unifont', system-ui, -apple-system, sans-serif;
14+
font-size: 0.7rem;
15+
white-space: nowrap;
16+
border: 2px solid #212529;
17+
background-color: #e7e7e7;
18+
color: #212529;
19+
cursor: pointer;
20+
text-align: center;
21+
box-shadow: 2px 2px 0 #909294;
22+
}
23+
24+
.btn-primary {
25+
background-color: #209cee;
26+
color: #fff;
27+
}
28+
29+
.btn-danger {
30+
background-color: #e76e55;
31+
color: #fff;
32+
}
33+
34+
.btn-warning {
35+
background-color: #f7d51d;
36+
color: #212529;
37+
}
38+
39+
.btn-success {
40+
background-color: #92cc41;
41+
color: #212529;
42+
}
43+
44+
.btn:disabled {
45+
opacity: 0.5;
46+
cursor: not-allowed;
47+
}
48+
49+
.btn:not(:disabled):hover {
50+
transform: translateY(1px);
51+
box-shadow: 1px 1px 0 #909294;
52+
}
53+
54+
.btn:focus {
55+
outline: none;
56+
}
57+
58+
.no-select {
59+
-webkit-user-select: none;
60+
user-select: none;
61+
}

src/components/ChessClock.vue

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<template>
2-
<div v-if="isClockEnabled" class="clock-grid" :data-testid="testId">
2+
<div v-if="isClockEnabled" class="clock-grid no-select" :data-testid="testId">
33
<div class="clock-side side-white" :class="{ 'is-active': activeColor === 'white' }">
44
<span class="clock-time">{{ formattedWhiteTime }}</span>
55
</div>
@@ -63,6 +63,7 @@ const formattedBlackTime = computed(() => formatTime(props.blackTimeSeconds))
6363
justify-content: center;
6464
align-items: center;
6565
border: 2px solid #adafbc;
66+
box-shadow: 2px 2px 0 #909294;
6667
font-family: 'Unifont', monospace;
6768
transition: all 0.2s ease;
6869
}

src/components/GameSetup.vue

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
</div>
1616

1717
<input v-if="boardMode === 'custom'" v-model="fenInput" type="text" class="fen-input"
18-
placeholder="请输入 FEN,例如:rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1" />
18+
placeholder="在此处粘贴 FEN 文本" />
1919
</div>
2020

2121
<div class="setup-section">
2222
<h3>棋钟</h3>
2323
<label class="slider-row">
2424
<span>限时(分钟)</span>
2525
<input v-model.number="timeMinutes" type="range" min="0" max="180" step="1" />
26-
<strong>{{ timeMinutes === 0 ? '不限时' : `${timeMinutes} 分钟` }}</strong>
26+
<strong>{{ timeMinutes === 0 ? '无限制' : `${timeMinutes} 分钟` }}</strong>
2727
</label>
2828

2929
<label v-if="timeMinutes > 0" class="slider-row">
@@ -34,7 +34,7 @@
3434
</div>
3535

3636
<div class="setup-section">
37-
<h3>先手方</h3>
37+
<h3>执棋方</h3>
3838
<div class="option-group">
3939
<label class="option-card">
4040
<input v-model="starter" type="radio" value="black" />
@@ -118,7 +118,7 @@ const handleStart = () => {
118118
justify-content: center;
119119
align-items: center;
120120
padding: 20px;
121-
background: rgba(16, 16, 16, 0.7);
121+
background-color: #f0f0f0;
122122
}
123123
124124
.setup-panel {

src/components/Promotion.vue

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ const computedStyle = computed(() => ({
4444
justify-content: flex-start;
4545
align-items: stretch;
4646
background: #ffffff;
47-
box-shadow: 0 6px 18px rgba(0, 0, 0, 0.25);
4847
z-index: 60;
4948
overflow: hidden;
5049

src/components/SettingsModal.vue

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
<template>
2+
<div v-if="visible" class="modal-backdrop">
3+
<div class="card dialog-box settings-dialog">
4+
<p class="dialog-title">游戏设置</p>
5+
6+
<div class="settings-list">
7+
<!-- 翻转棋盘(按钮形式) -->
8+
<div class="setting-item">
9+
<span class="setting-label">棋盘方向</span>
10+
<button type="button" class="btn flip-btn" @click="$emit('toggle-flip')">
11+
翻转棋盘
12+
</button>
13+
</div>
14+
15+
<!-- 音效开关 -->
16+
<div class="setting-item">
17+
<span class="setting-label">音效</span>
18+
<label class="checkbox-label">
19+
<input type="checkbox" class="custom-checkbox" :checked="isSoundEnabled" @change="handleSoundChange" />
20+
</label>
21+
</div>
22+
23+
<!-- 棋盘坐标标记 -->
24+
<div class="setting-item">
25+
<span class="setting-label">棋盘标志</span>
26+
<div class="select-wrapper">
27+
<select :value="coordinateLabelMode" @change="handleCoordinateChange" class="custom-select">
28+
<option value="off">关闭</option>
29+
<option value="inside">内侧</option>
30+
<option value="outside">外侧</option>
31+
</select>
32+
</div>
33+
</div>
34+
</div>
35+
36+
<div class="dialog-buttons">
37+
<button type="button" class="btn" @click="$emit('close')">完成</button>
38+
</div>
39+
</div>
40+
</div>
41+
</template>
42+
43+
<script setup lang="ts">
44+
interface Props {
45+
visible: boolean
46+
isSoundEnabled: boolean
47+
coordinateLabelMode: 'off' | 'inside' | 'outside'
48+
}
49+
50+
defineProps<Props>()
51+
52+
const emit = defineEmits<{
53+
close: []
54+
'toggle-flip': []
55+
'update:isSoundEnabled': [value: boolean]
56+
'update:coordinateLabelMode': [value: 'off' | 'inside' | 'outside']
57+
}>()
58+
59+
const handleSoundChange = (e: Event) => {
60+
const checked = (e.target as HTMLInputElement).checked
61+
emit('update:isSoundEnabled', checked)
62+
}
63+
64+
const handleCoordinateChange = (e: Event) => {
65+
const value = (e.target as HTMLSelectElement).value as 'off' | 'inside' | 'outside'
66+
emit('update:coordinateLabelMode', value)
67+
}
68+
</script>
69+
70+
<style scoped>
71+
@import url('https://fonts.cdnfonts.com/css/unifont');
72+
73+
.modal-backdrop {
74+
position: fixed;
75+
inset: 0;
76+
background-color: rgba(0, 0, 0, 0.5);
77+
display: flex;
78+
justify-content: center;
79+
align-items: center;
80+
z-index: 1000;
81+
}
82+
83+
.settings-dialog {
84+
background: white;
85+
padding: 1.5rem;
86+
max-width: 280px;
87+
width: 90%;
88+
text-align: center;
89+
}
90+
91+
.dialog-title {
92+
font-weight: bold;
93+
margin-bottom: 0.75rem;
94+
}
95+
96+
.dialog-buttons {
97+
display: flex;
98+
justify-content: space-around;
99+
gap: 0.5rem;
100+
}
101+
102+
.dialog-buttons .btn {
103+
flex: 1;
104+
font-size: 0.8rem;
105+
padding: 0.25rem 0.5rem;
106+
}
107+
108+
.settings-list {
109+
display: flex;
110+
flex-direction: column;
111+
gap: 0.75rem;
112+
margin-bottom: 1rem;
113+
text-align: left;
114+
}
115+
116+
.setting-item {
117+
display: flex;
118+
justify-content: space-between;
119+
align-items: center;
120+
font-size: 0.8rem;
121+
}
122+
123+
.setting-label {
124+
font-weight: bold;
125+
}
126+
127+
.flip-btn {
128+
font-size: 0.7rem;
129+
padding: 2px 8px;
130+
}
131+
132+
.custom-checkbox {
133+
width: 1rem;
134+
height: 1rem;
135+
cursor: pointer;
136+
}
137+
138+
.select-wrapper {
139+
width: auto;
140+
}
141+
142+
.custom-select {
143+
cursor: pointer;
144+
font-family: 'Unifont', system-ui;
145+
font-size: 0.75rem;
146+
padding: 2px 8px;
147+
border: 2px solid #212529;
148+
background-color: #fff;
149+
}
150+
</style>

0 commit comments

Comments
 (0)