Skip to content

Commit 8d574fd

Browse files
committed
前端优化 - 优化加载关卡和生成结果的等待图标
1 parent d486ca6 commit 8d574fd

File tree

2 files changed

+168
-28
lines changed

2 files changed

+168
-28
lines changed
24 KB
Loading

coder-test-frontend/src/views/Challenge.vue

Lines changed: 168 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,23 @@
1313
<el-button
1414
type="primary"
1515
size="large"
16-
:loading="generating"
16+
:disabled="generating"
1717
@click="generateLevel"
1818
>
1919
<el-icon><MagicStick /></el-icon>
2020
生成关卡
2121
</el-button>
22+
23+
<!-- 自定义加载区域 -->
24+
<div v-if="generating" class="custom-loading-area">
25+
<img
26+
:src="loadingIcon"
27+
alt="生成中..."
28+
class="custom-loading-icon"
29+
:style="{ left: loadingPosition.x + 'px', top: loadingPosition.y + 'px' }"
30+
/>
31+
<div class="loading-text">正在生成关卡中...</div>
32+
</div>
2233
</div>
2334
</el-card>
2435
</div>
@@ -94,24 +105,37 @@
94105
</div>
95106

96107
<div class="submit-area">
97-
<el-button
98-
type="primary"
99-
size="large"
100-
:loading="submitting"
101-
:disabled="selectedOptions.length === 0"
102-
@click="submitAnswer"
103-
>
104-
<el-icon><Check /></el-icon>
105-
提交答案
106-
</el-button>
108+
<div class="button-row">
109+
<el-button
110+
type="primary"
111+
size="large"
112+
:disabled="submitting || selectedOptions.length === 0"
113+
@click="submitAnswer"
114+
>
115+
<el-icon><Check /></el-icon>
116+
提交答案
117+
</el-button>
118+
119+
<el-button
120+
size="large"
121+
:disabled="submitting"
122+
@click="resetLevel"
123+
>
124+
<el-icon><Refresh /></el-icon>
125+
重新生成
126+
</el-button>
127+
</div>
107128

108-
<el-button
109-
size="large"
110-
@click="resetLevel"
111-
>
112-
<el-icon><Refresh /></el-icon>
113-
重新生成
114-
</el-button>
129+
<!-- 提交答案加载区域 -->
130+
<div v-if="submitting" class="custom-loading-area submit-loading">
131+
<img
132+
:src="loadingIcon"
133+
alt="提交中..."
134+
class="custom-loading-icon"
135+
:style="{ left: submitLoadingPosition.x + 'px', top: submitLoadingPosition.y + 'px' }"
136+
/>
137+
<div class="loading-text">正在提交答案...</div>
138+
</div>
115139
</div>
116140
</div>
117141
</div>
@@ -121,7 +145,7 @@
121145
</template>
122146

123147
<script setup>
124-
import { ref, computed, onMounted } from 'vue'
148+
import { ref, computed, onMounted, watch, onUnmounted } from 'vue'
125149
import { useRouter } from 'vue-router'
126150
import { useUserStore } from '../stores/user'
127151
import { generateLevel as generateLevelAPI } from '../api/level'
@@ -134,6 +158,7 @@ import {
134158
Close
135159
} from '@element-plus/icons-vue'
136160
import GlobalNavbar from '../components/GlobalNavbar.vue'
161+
import loadingIcon from '../assets/loading.png'
137162
138163
const router = useRouter()
139164
const userStore = useUserStore()
@@ -145,6 +170,48 @@ const currentLevel = ref(null)
145170
const selectedOptions = ref([])
146171
const draggedOption = ref(null)
147172
173+
// 加载图标随机移动相关
174+
const loadingPosition = ref({ x: 0, y: 0 })
175+
const submitLoadingPosition = ref({ x: 0, y: 0 })
176+
let loadingInterval = null
177+
let submitLoadingInterval = null
178+
179+
// 随机移动函数
180+
const getRandomPosition = (containerWidth = 300, containerHeight = 80, iconSize = 40) => {
181+
return {
182+
x: Math.random() * (containerWidth - iconSize),
183+
y: Math.random() * (containerHeight - iconSize)
184+
}
185+
}
186+
187+
// 开始随机移动
188+
const startRandomMovement = (positionRef, intervalRef) => {
189+
if (intervalRef) {
190+
clearInterval(intervalRef)
191+
}
192+
193+
// 根据屏幕大小调整容器尺寸
194+
const isMobile = window.innerWidth <= 768
195+
const containerWidth = isMobile ? 210 : 260
196+
const containerHeight = isMobile ? 30 : 40
197+
const iconSize = isMobile ? 35 : 40
198+
199+
// 初始位置
200+
positionRef.value = getRandomPosition(containerWidth, containerHeight, iconSize)
201+
202+
// 每800ms移动一次(稍微慢一点,更优雅)
203+
return setInterval(() => {
204+
positionRef.value = getRandomPosition(containerWidth, containerHeight, iconSize)
205+
}, 800)
206+
}
207+
208+
// 停止随机移动
209+
const stopRandomMovement = (intervalRef) => {
210+
if (intervalRef) {
211+
clearInterval(intervalRef)
212+
}
213+
}
214+
148215
// 计算可用选项(排除已选择的)
149216
const availableOptions = computed(() => {
150217
if (!currentLevel.value?.options) return []
@@ -302,12 +369,38 @@ const resetLevel = () => {
302369
selectedOptions.value = []
303370
}
304371
372+
// 监听生成状态变化
373+
watch(generating, (newVal) => {
374+
if (newVal) {
375+
loadingInterval = startRandomMovement(loadingPosition, loadingInterval)
376+
} else {
377+
stopRandomMovement(loadingInterval)
378+
loadingInterval = null
379+
}
380+
})
381+
382+
// 监听提交状态变化
383+
watch(submitting, (newVal) => {
384+
if (newVal) {
385+
submitLoadingInterval = startRandomMovement(submitLoadingPosition, submitLoadingInterval)
386+
} else {
387+
stopRandomMovement(submitLoadingInterval)
388+
submitLoadingInterval = null
389+
}
390+
})
391+
305392
onMounted(() => {
306393
// 页面加载时检查用户登录状态
307394
if (!user.value) {
308395
router.push('/login')
309396
}
310397
})
398+
399+
onUnmounted(() => {
400+
// 组件销毁时清理定时器
401+
stopRandomMovement(loadingInterval)
402+
stopRandomMovement(submitLoadingInterval)
403+
})
311404
</script>
312405
313406
<style scoped>
@@ -496,20 +589,56 @@ onMounted(() => {
496589
497590
.submit-area {
498591
margin-top: 30px;
592+
display: flex;
593+
flex-direction: column;
594+
align-items: center;
595+
}
596+
597+
.submit-area .button-row {
499598
display: flex;
500599
gap: 15px;
501600
justify-content: center;
601+
margin-bottom: 10px;
602+
}
603+
604+
/* 自定义加载区域 */
605+
.custom-loading-area {
606+
position: relative;
607+
width: 300px;
608+
height: 80px;
609+
margin: 20px auto;
610+
border: 2px dashed var(--border-medium);
611+
border-radius: 12px;
612+
background: var(--bg-secondary);
613+
overflow: hidden;
614+
}
615+
616+
.custom-loading-icon {
617+
position: absolute;
618+
width: 40px;
619+
height: 40px;
620+
transition: all 0.3s ease-in-out;
621+
z-index: 2;
622+
}
623+
624+
.loading-text {
625+
position: absolute;
626+
bottom: 5px;
627+
left: 50%;
628+
transform: translateX(-50%);
629+
color: var(--text-secondary);
630+
font-size: 14px;
631+
font-weight: 500;
632+
z-index: 1;
633+
}
634+
635+
.submit-loading {
636+
margin-top: 15px;
502637
}
503638
504639
@media (max-width: 768px) {
505-
.header-content {
506-
flex-direction: column;
507-
gap: 15px;
508-
}
509-
510-
.user-info {
511-
flex-direction: column;
512-
gap: 10px;
640+
.main-content {
641+
padding: 30px 20px;
513642
}
514643
515644
.answer-section {
@@ -520,8 +649,19 @@ onMounted(() => {
520649
grid-template-columns: 1fr;
521650
}
522651
523-
.submit-area {
652+
.submit-area .button-row {
524653
flex-direction: column;
654+
gap: 10px;
655+
}
656+
657+
.custom-loading-area {
658+
width: 250px;
659+
height: 70px;
660+
}
661+
662+
.custom-loading-icon {
663+
width: 35px;
664+
height: 35px;
525665
}
526666
}
527667
</style>

0 commit comments

Comments
 (0)