fix: 时间显示/3D线置顶强调/子标签页重构

1. fix(HeroSection): period 为 {zh,en} 对象却直接渲染导致显示 [object Object],
   改为字符串 '2022'
2. feat(timeline): 3D滚球(最终形态)线移到最前并 featured 强调(渐变背景+★+实心3D徽章),
   其后用分隔标记提示「早期并行演进:2D线」
3. refactor(MobileGame): 团队+排行榜/迭代记录/实验 改为平级子标签页(SubTabs),
   默认显示团队+排行榜;新增 SubTabs.vue 通用子标签组件
   headless 验证:默认页(团队+排行榜) -> 迭代记录(时间线) -> 实验(视频) -> 切回 全通过,
   中英文子标签页文案正常
This commit is contained in:
Zikai
2026-07-12 15:34:30 +00:00
parent 0adb320a47
commit 8e08e619fc
5 changed files with 163 additions and 60 deletions

View File

@@ -7,7 +7,7 @@ const { locale } = useI18n({ useScope: 'global' })
const meta = {
course: { zh: '迈阿密大学 · 毕设 448 / 449', en: 'Miami University · Capstone 448 / 449' },
stack: { zh: 'Unity · C# · WebGL · Android · PHP', en: 'Unity · C# · WebGL · Android · PHP' },
period: { zh: '2022', en: '2022' },
period: '2022',
role: { zh: '主开发', en: 'Lead Developer' }
}
</script>

View File

@@ -0,0 +1,66 @@
<script setup>
import { useI18n } from 'vue-i18n'
const props = defineProps({
tabs: { type: Array, required: true }, // [{ id, label: {zh,en} }]
modelValue: { type: String, required: true }
})
const emit = defineEmits(['update:modelValue'])
const { locale } = useI18n({ useScope: 'global' })
const L = (obj) => (locale.value === 'zh-CN' ? obj.zh : obj.en)
</script>
<template>
<div class="subtabs">
<button
v-for="t in tabs"
:key="t.id"
type="button"
class="subtabs__btn"
:class="{ 'is-active': modelValue === t.id }"
@click="emit('update:modelValue', t.id)"
>
{{ L(t.label) }}
</button>
</div>
</template>
<style scoped>
.subtabs {
display: flex;
gap: var(--space-xs);
border-bottom: 1px solid var(--border);
margin-bottom: var(--space-lg);
overflow-x: auto;
scrollbar-width: none;
}
.subtabs::-webkit-scrollbar {
display: none;
}
.subtabs__btn {
position: relative;
padding: var(--space-sm) var(--space-md);
font-size: 0.92rem;
font-weight: 500;
color: var(--text-secondary);
white-space: nowrap;
transition: color var(--transition);
}
.subtabs__btn:hover {
color: var(--text-primary);
}
.subtabs__btn.is-active {
color: var(--accent);
}
.subtabs__btn.is-active::after {
content: '';
position: absolute;
left: var(--space-md);
right: var(--space-md);
bottom: -1px;
height: 2px;
background: var(--accent);
border-radius: 2px;
}
</style>

View File

@@ -6,7 +6,7 @@ import { versions, tracks } from '../data/versions.js'
const { locale } = useI18n({ useScope: 'global' })
const L = (obj) => (locale.value === 'zh-CN' ? obj.zh : obj.en)
// 按演进线分组;同一线内按 order 排序;线之间按各自最早 order 排序
// 按 tracks 数组顺序分组3D 最终形态在前);同一线内按 order 排序
const grouped = computed(() => {
return tracks
.map((t) => {
@@ -16,11 +16,13 @@ const grouped = computed(() => {
return { track: t, items }
})
.filter((g) => g.items.length > 0)
.sort((a, b) => Math.min(...a.items.map((i) => i.order)) - Math.min(...b.items.map((i) => i.order)))
})
// 标记 3D 转折3D 线之前的最后一条 2D 线结尾,提示形态转变
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)
</script>
<template>
@@ -29,12 +31,16 @@ const is3D = (track) => track.form === '3D'
v-for="(g, gi) in grouped"
:key="g.track.id"
class="timeline__track"
:class="{ 'timeline__track--3d': is3D(g.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">{{ L(g.track.name) }}</span>
<span v-if="isFeatured(g.track)" class="timeline__star"></span>
</div>
<p class="timeline__track-desc">{{ L(g.track.desc) }}</p>
@@ -78,11 +84,11 @@ const is3D = (track) => track.form === '3D'
</div>
</div>
<!-- 2D -> 3D 转折标记仅出现在第一条 3D 线之前 -->
<div v-if="is3D(g.track) && gi > 0" class="timeline__pivot">
<!-- 形态转变标记3D 线之后2D 线之前 -->
<div v-if="showPivotAfter(gi)" class="timeline__pivot">
<span class="timeline__pivot-line"></span>
<span class="timeline__pivot-text">
{{ locale === 'zh-CN' ? '形态转变2D → 3D 滚球' : 'Form shift: 2D → 3D rolling ball' }}
{{ locale === 'zh-CN' ? '早期并行演进2D 线' : 'Earlier parallel lines: 2D' }}
</span>
<span class="timeline__pivot-line"></span>
</div>
@@ -96,8 +102,19 @@ const is3D = (track) => track.form === '3D'
flex-direction: column;
gap: var(--space-xl);
}
.timeline__track--3d {
padding-top: var(--space-sm);
/* 最终形态强调 */
.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 {
@@ -118,14 +135,18 @@ const is3D = (track) => track.form === '3D'
letter-spacing: 0.04em;
}
.timeline__form.is-3d {
background: var(--accent-weak);
color: var(--accent);
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);
@@ -220,7 +241,7 @@ const is3D = (track) => track.form === '3D'
font-size: 0.85rem;
}
/* 2D -> 3D 转折标记 */
/* 形态转变标记 */
.timeline__pivot {
display: flex;
align-items: center;
@@ -235,7 +256,7 @@ const is3D = (track) => track.form === '3D'
.timeline__pivot-text {
font-size: 0.8rem;
font-weight: 600;
color: var(--accent);
color: var(--text-tertiary);
white-space: nowrap;
letter-spacing: 0.02em;
}