init: 移动游戏项目独立化(便携式 App.vue + 独立 i18n,可嵌入 mainPage)

This commit is contained in:
root
2026-07-23 06:57:03 +00:00
commit a5d4c05392
28 changed files with 2844 additions and 0 deletions

View File

@@ -0,0 +1,266 @@
<script setup>
import { computed } from 'vue'
import { useI18n } from 'vue-i18n'
import { versions, tracks } from '../data/versions.js'
const { t } = useI18n({ useScope: 'global' })
// 按 tracks 数组顺序分组3D 最终形态在前);同一线内按 order 排序
const grouped = computed(() => {
return tracks
.map((tr) => {
const items = versions
.filter((v) => v.track === tr.id)
.sort((a, b) => a.order - b.order)
return { track: tr, items }
})
.filter((g) => g.items.length > 0)
})
const is3D = (track) => track.form === '3D'
const isFeatured = (track) => track.featured
// 形态转变标记:出现在 3D 线首位之后、2D 线之前
const showPivotAfter = (gi) => gi === 0 && grouped.value.length > 1 && is3D(grouped.value[0].track)
// i18n 取值辅助track/version 的文案在 locale 的 mobileGame.tracks / mobileGame.versions 下
const trackName = (trackId) => t(`mobileGame.tracks.${trackId}.name`)
const trackDesc = (trackId) => t(`mobileGame.tracks.${trackId}.desc`)
const vControl = (versionId) => t(`mobileGame.versions.${versionId}.control`)
const vNote = (versionId) => t(`mobileGame.versions.${versionId}.note`)
</script>
<template>
<div class="timeline">
<div
v-for="(g, gi) in grouped"
:key="g.track.id"
class="timeline__track"
:class="{
'timeline__track--3d': is3D(g.track),
'timeline__track--featured': isFeatured(g.track)
}"
>
<!-- 线标题 -->
<div class="timeline__track-head">
<span class="timeline__form" :class="{ 'is-3d': is3D(g.track) }">{{ g.track.form }}</span>
<span class="timeline__track-name">{{ trackName(g.track.id) }}</span>
<span v-if="isFeatured(g.track)" class="timeline__star"></span>
</div>
<p class="timeline__track-desc">{{ trackDesc(g.track.id) }}</p>
<!-- 该线下的版本 -->
<div class="timeline__items">
<div
v-for="(v, i) in g.items"
:key="v.id"
class="timeline__item"
>
<div class="timeline__marker">
<span class="timeline__dot"></span>
<span v-if="i < g.items.length - 1" class="timeline__line"></span>
</div>
<div class="timeline__body">
<div class="timeline__head">
<span class="timeline__version">v{{ v.version }}</span>
<span class="timeline__product">{{ v.product }}</span>
</div>
<div class="timeline__row">
<span class="timeline__label">{{ t('mobileGame.iterations.colControls') }}</span>
<span>{{ vControl(v.id) }}</span>
</div>
<div class="timeline__row">
<span class="timeline__label">{{ t('mobileGame.iterations.colNote') }}</span>
<span>{{ vNote(v.id) }}</span>
</div>
<div class="timeline__row">
<span class="timeline__label">{{ t('mobileGame.iterations.colUnity') }}</span>
<span class="timeline__mono">{{ v.unity }}</span>
</div>
<div class="timeline__links">
<a v-if="v.webgl" :href="v.webgl" target="_blank" rel="noopener">
{{ t('mobileGame.iterations.playWebgl') }}
</a>
<a v-if="v.apk" :href="v.apk" target="_blank" rel="noopener">
{{ t('mobileGame.iterations.downloadApk') }}
</a>
</div>
</div>
</div>
</div>
<!-- 形态转变标记3D 线之后2D 线之前 -->
<div v-if="showPivotAfter(gi)" class="timeline__pivot">
<span class="timeline__pivot-line"></span>
<span class="timeline__pivot-text">{{ t('mobileGame.iterations.pivotText') }}</span>
<span class="timeline__pivot-line"></span>
</div>
</div>
</div>
</template>
<style scoped>
.timeline {
display: flex;
flex-direction: column;
gap: var(--space-xl);
}
/* 最终形态强调 */
.timeline__track--featured {
padding: var(--space-lg);
background: linear-gradient(180deg, var(--accent-weak) 0%, transparent 100%);
border: 1px solid var(--accent-weak);
border-radius: var(--radius);
}
.timeline__track--featured .timeline__track-name {
font-size: 1.2rem;
}
.timeline__track--featured .timeline__track-desc {
color: var(--text-primary);
}
.timeline__track-head {
display: flex;
align-items: center;
gap: var(--space-sm);
margin-bottom: var(--space-xs);
}
.timeline__form {
display: inline-flex;
align-items: center;
padding: 2px 10px;
font-size: 0.74rem;
font-weight: 600;
border-radius: 999px;
background: var(--surface-2);
color: var(--text-secondary);
letter-spacing: 0.04em;
}
.timeline__form.is-3d {
background: var(--accent);
color: #fff;
}
.timeline__track-name {
font-weight: 600;
font-size: 1.05rem;
color: var(--text-primary);
}
.timeline__star {
color: var(--accent);
font-size: 0.9rem;
}
.timeline__track-desc {
font-size: 0.9rem;
color: var(--text-secondary);
margin: 0 0 var(--space-md);
max-width: 640px;
}
.timeline__items {
padding-left: 4px;
}
.timeline__item {
display: flex;
gap: var(--space-md);
padding-bottom: var(--space-md);
}
.timeline__marker {
position: relative;
flex: 0 0 14px;
display: flex;
flex-direction: column;
align-items: center;
padding-top: 6px;
}
.timeline__dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--bg);
border: 2px solid var(--accent);
flex: 0 0 10px;
}
.timeline__track--3d .timeline__dot {
background: var(--accent);
}
.timeline__line {
flex: 1;
width: 2px;
background: var(--border);
margin-top: 4px;
min-height: 20px;
}
.timeline__body {
flex: 1;
padding: var(--space-sm) var(--space-md);
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
box-shadow: var(--shadow-sm);
transition: box-shadow var(--transition);
}
.timeline__body:hover {
box-shadow: var(--shadow);
}
.timeline__head {
display: flex;
align-items: baseline;
gap: var(--space-sm);
margin-bottom: var(--space-xs);
}
.timeline__version {
font-family: var(--font-mono);
font-size: 0.82rem;
color: var(--accent);
background: var(--accent-weak);
padding: 1px 8px;
border-radius: 6px;
font-weight: 600;
}
.timeline__product {
font-size: 0.82rem;
color: var(--text-tertiary);
}
.timeline__row {
display: flex;
gap: var(--space-sm);
font-size: 0.88rem;
color: var(--text-secondary);
padding: 1px 0;
}
.timeline__label {
flex: 0 0 52px;
color: var(--text-tertiary);
font-size: 0.8rem;
}
.timeline__mono {
font-family: var(--font-mono);
}
.timeline__links {
display: flex;
gap: var(--space-md);
margin-top: var(--space-xs);
font-size: 0.85rem;
}
/* 形态转变标记 */
.timeline__pivot {
display: flex;
align-items: center;
gap: var(--space-md);
margin: var(--space-md) 0 0;
}
.timeline__pivot-line {
flex: 1;
height: 1px;
background: linear-gradient(90deg, transparent, var(--border-strong), transparent);
}
.timeline__pivot-text {
font-size: 0.8rem;
font-weight: 600;
color: var(--text-tertiary);
white-space: nowrap;
letter-spacing: 0.02em;
}
</style>