init: 移动游戏项目独立化(便携式 App.vue + 独立 i18n,可嵌入 mainPage)
This commit is contained in:
128
src/components/Leaderboard.vue
Normal file
128
src/components/Leaderboard.vue
Normal file
@@ -0,0 +1,128 @@
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { useI18n } from 'vue-i18n'
|
||||
import { leaderboard } from '../config.js'
|
||||
|
||||
const { t } = useI18n({ useScope: 'global' })
|
||||
|
||||
const rows = ref([])
|
||||
const state = ref('loading') // loading | ok | error
|
||||
const errorMsg = ref('')
|
||||
|
||||
// 复用 449 现有后端:POST endpoint {num} 取分页 JSON
|
||||
async function load() {
|
||||
state.value = 'loading'
|
||||
try {
|
||||
const all = []
|
||||
for (let page = 0; page < leaderboard.pages; page++) {
|
||||
const res = await fetch(leaderboard.endpoint, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: `num=${page * leaderboard.perPage}`
|
||||
})
|
||||
if (!res.ok) throw new Error(`HTTP ${res.status}`)
|
||||
const data = await res.json()
|
||||
// data 形如 { "1": ["name", score], ... }
|
||||
Object.keys(data).forEach((k) => {
|
||||
const entry = data[k]
|
||||
all.push({ rank: all.length + 1, name: entry[0], score: entry[1] })
|
||||
})
|
||||
}
|
||||
rows.value = all
|
||||
state.value = all.length ? 'ok' : 'error'
|
||||
if (!all.length) errorMsg.value = t('mobileGame.leaderboard.noScores')
|
||||
} catch (e) {
|
||||
state.value = 'error'
|
||||
errorMsg.value = t('mobileGame.leaderboard.error')
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(load)
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="leaderboard">
|
||||
<div v-if="state === 'loading'" class="leaderboard__state">
|
||||
{{ t('common.loading') }}
|
||||
</div>
|
||||
|
||||
<div v-else-if="state === 'error'" class="leaderboard__state leaderboard__state--error">
|
||||
{{ errorMsg }}
|
||||
</div>
|
||||
|
||||
<table v-else class="leaderboard__table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>{{ t('mobileGame.leaderboard.colRank') }}</th>
|
||||
<th>{{ t('mobileGame.leaderboard.colName') }}</th>
|
||||
<th>{{ t('mobileGame.leaderboard.colScore') }}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="r in rows" :key="r.rank" :class="{ 'is-top': r.rank <= 3 }">
|
||||
<td class="leaderboard__rank">{{ r.rank }}</td>
|
||||
<td class="leaderboard__name">{{ r.name }}</td>
|
||||
<td class="leaderboard__score">{{ r.score }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.leaderboard {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
background: var(--bg);
|
||||
}
|
||||
.leaderboard__state {
|
||||
padding: var(--space-xl);
|
||||
text-align: center;
|
||||
color: var(--text-secondary);
|
||||
}
|
||||
.leaderboard__state--error {
|
||||
color: var(--text-tertiary);
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.leaderboard__table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
.leaderboard__table th {
|
||||
text-align: left;
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
background: var(--surface);
|
||||
border-bottom: 1px solid var(--border);
|
||||
font-weight: 600;
|
||||
color: var(--text-secondary);
|
||||
font-size: 0.82rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.leaderboard__table td {
|
||||
padding: var(--space-sm) var(--space-md);
|
||||
border-bottom: 1px solid var(--border);
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.leaderboard__table tbody tr:last-child td {
|
||||
border-bottom: none;
|
||||
}
|
||||
.leaderboard__table tbody tr:hover {
|
||||
background: var(--surface);
|
||||
}
|
||||
.leaderboard__rank {
|
||||
width: 50px;
|
||||
font-family: var(--font-mono);
|
||||
color: var(--text-tertiary);
|
||||
}
|
||||
tr.is-top .leaderboard__rank {
|
||||
color: var(--accent);
|
||||
font-weight: 600;
|
||||
}
|
||||
.leaderboard__score {
|
||||
font-family: var(--font-mono);
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user