Files
nl-game/src/components/LevelSelect.vue
李琦 cd1ab90013 初始化
需要注意的是饥荒不够完善:动作、手持工具、细节交互、系统逻辑
2026-08-15 07:24:34 +08:00

171 lines
5.3 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
// 关卡选择:带关卡游戏的开局面板(连连看/消消乐/马里奥通用)
// 展示:关卡宫格(已过⭐/当前📍/未解锁🔒)、每关正在挑战的好友头像、好友进度名单、继续按钮
import { computed } from 'vue'
import PixelAvatar from './PixelAvatar.vue'
const props = defineProps({
total: { type: Number, required: true }, // 总关卡数
names: { type: Array, default: null }, // 每关小字标签(可选,如模式名/世界号)
myLevel: { type: Number, default: 1 }, // 我解锁到的关卡(通关全部后为 total+1
friends: { type: Array, default: () => [] }, // 好友进度 [{user_id,nickname,avatar,level}]
})
const emit = defineEmits(['pick'])
const allCleared = computed(() => props.myLevel > props.total) // 是否已全通关
const continueLevel = computed(() => Math.min(props.myLevel, props.total)) // 继续按钮对应的关
// 好友挂在哪一格显示(超过总关数的通关好友挂在最后一格)
function friendsAt(lv) {
return props.friends.filter((f) => Math.min(f.level, props.total) === lv)
}
// 好友进度文案
function friendText(f) {
return f.level > props.total ? '已通关' : `${f.level}`
}
</script>
<template>
<div class="level-select">
<p v-if="allCleared" class="ls-sub">🏆 已全部通关可以重温任意关卡</p>
<p v-else-if="myLevel > 1" class="ls-sub">上次闯到<b class="text-primary"> {{ myLevel }} </b>从这里继续吧</p>
<p v-else class="ls-sub text-dim">从第 1 关开始你的闯关之旅</p>
<button class="btn btn-lg ls-continue" @click="emit('pick', continueLevel)">
{{ allCleared ? `🔄 重玩第 ${total} ` : myLevel > 1 ? `▶ 继续第 ${myLevel}` : '▶ 开始闯关' }}
</button>
<!-- 关卡宫格 -->
<div class="ls-grid">
<button
v-for="i in total"
:key="i"
class="ls-cell"
:class="{ cleared: i < myLevel, current: i === myLevel && !allCleared, locked: i > myLevel }"
:disabled="i > myLevel"
:title="i > myLevel ? '通关前面的关卡后解锁' : `进入第 ${i} 关`"
@click="emit('pick', i)"
>
<span class="ls-state">{{ i < myLevel ? '⭐' : i === myLevel && !allCleared ? '📍' : i > myLevel ? '🔒' : '⭐' }}</span>
<span class="ls-num">{{ i }}</span>
<span v-if="names && names[i - 1]" class="ls-name">{{ names[i - 1] }}</span>
<!-- 该关的好友头像最多 3 + 溢出数 -->
<span v-if="friendsAt(i).length" class="ls-favatars" :title="friendsAt(i).map((f) => f.nickname).join('、')">
<span v-for="f in friendsAt(i).slice(0, 3)" :key="f.user_id" class="ls-fa"><PixelAvatar :code="f.avatar" :size="14" /></span>
<span v-if="friendsAt(i).length > 3" class="ls-fmore">+{{ friendsAt(i).length - 3 }}</span>
</span>
</button>
</div>
<!-- 好友进度名单 -->
<div v-if="friends.length" class="ls-friends">
<span class="text-dim">好友进度</span>
<span v-for="f in friends" :key="f.user_id" class="ls-fitem">
<PixelAvatar :code="f.avatar" :size="16" /> {{ f.nickname }} <b>{{ friendText(f) }}</b>
</span>
</div>
<p v-else class="ls-friends text-dim" style="font-size: 12px">还没有好友玩过这个游戏喊朋友一起来闯关吧</p>
</div>
</template>
<style scoped>
.level-select {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
width: min(560px, 100%);
}
.ls-sub {
font-size: 14px;
}
.ls-continue {
min-width: 220px;
}
.ls-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(76px, 1fr));
gap: 8px;
width: 100%;
}
.ls-cell {
position: relative;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 2px;
aspect-ratio: 1;
border-radius: calc(var(--radius) / 1.4);
border: 1px solid var(--border);
background: var(--bg-card);
color: var(--text);
cursor: pointer;
transition: all 0.15s;
padding: 4px;
}
.ls-cell:not(.locked):hover {
border-color: var(--primary);
box-shadow: var(--glow);
transform: translateY(-2px);
}
.ls-cell.cleared {
border-color: color-mix(in srgb, var(--accent) 55%, var(--border));
}
.ls-cell.current {
border-color: var(--primary);
background: linear-gradient(135deg, color-mix(in srgb, var(--primary) 25%, var(--bg-card)), var(--bg-card));
box-shadow: var(--glow);
}
.ls-cell.locked {
opacity: 0.45;
cursor: not-allowed;
}
.ls-state {
font-size: 14px;
line-height: 1;
}
.ls-num {
font-weight: 800;
font-size: 17px;
line-height: 1;
}
.ls-name {
font-size: 10px;
color: var(--text-dim);
line-height: 1;
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.ls-favatars {
position: absolute;
top: 3px;
right: 3px;
display: flex;
align-items: center;
background: color-mix(in srgb, var(--bg) 70%, transparent);
border-radius: 999px;
padding: 1px 4px;
font-size: 11px;
line-height: 1.2;
}
.ls-fa {
margin-left: -2px;
}
.ls-fmore {
font-size: 9px;
color: var(--text-dim);
margin-left: 1px;
}
.ls-friends {
display: flex;
flex-wrap: wrap;
gap: 6px 14px;
justify-content: center;
font-size: 12px;
color: var(--text);
}
.ls-fitem b {
color: var(--primary-2);
}
</style>