fix: 日历页显示页签+页签样式优化+iframe自适应+骨架屏

- useProjects: 访问 hidden 模块(/cqy)时仍显示其页签并高亮,
  让用户知道当前位置;主页仍隐藏日历页签
- TabNav: 非激活页签加圆角背景与 hover 背景反馈,激活页签
  下边线贯通整宽,更像页签
- Calendar: iframe 去除固定 800x600,改 width/height:100% +
  aspect-ratio 自适应(宽屏16:10,窄屏4:3,min-height 400px)
- Calendar: 加载骨架屏覆盖层(iframe @load 后淡出),
  iframe 始终在 DOM 中确保 load 事件触发
- i18n: 日历页签文案改「cqy课程表」/「CQY Timetable」
headless 验证:/cqy 显示页签且激活、hover 有背景反馈、
iframe 自适应宽窄屏、骨架屏加载后淡出
This commit is contained in:
Zikai
2026-07-13 14:13:23 +00:00
parent 49900d2363
commit 337d516c08
5 changed files with 73 additions and 20 deletions

View File

@@ -45,22 +45,24 @@ const { projects } = useProjects()
font-size: 0.95rem;
font-weight: 500;
white-space: nowrap;
transition: color var(--transition);
border-radius: var(--radius-sm) var(--radius-sm) 0 0;
transition: color var(--transition), background var(--transition);
}
.tab-nav__item:hover {
color: var(--text-primary);
background: var(--surface);
}
.tab-nav__item.is-active {
color: var(--accent);
background: var(--surface);
}
.tab-nav__item.is-active::after {
content: '';
position: absolute;
left: var(--space-md);
right: var(--space-md);
left: 0;
right: 0;
bottom: -1px;
height: 2px;
background: var(--accent);
border-radius: 2px;
}
</style>

View File

@@ -1,9 +1,14 @@
import modules from '../modules/index.js'
import { ref } from 'vue'
import { computed } from 'vue'
import { useRoute } from 'vue-router'
// 读取已注册模块列表,供 TabNav 渲染页签
// hidden 模块(如日历页)不显示在导航中,但仍可通过路由直接访问
// hidden 模块(如日历页)默认不显示在导航中,但当用户正在访问该页时
// 仍显示其页签(描下边高亮),让用户知道当前所在位置。
export function useProjects() {
const projects = ref(modules.filter((m) => !m.hidden))
const route = useRoute()
const projects = computed(() =>
modules.filter((m) => !m.hidden || m.id === route.meta.moduleId)
)
return { projects }
}

View File

@@ -10,7 +10,7 @@ export default {
},
tabs: {
mobileGame: 'Mobile Game Project',
calendar: 'Calendar'
calendar: 'CQY Timetable'
},
mobileGame: {
hero: {

View File

@@ -9,7 +9,7 @@ export default {
},
tabs: {
mobileGame: '移动游戏项目',
calendar: '日历'
calendar: 'cqy课程表'
},
mobileGame: {
hero: {

View File

@@ -1,26 +1,38 @@
<script setup>
// 日历页 -- 隐藏页,仅可通过手动访问 /cqy 进入,不在导航中显示
const calendarSrc =
import { ref, computed } from 'vue'
import { useLocale } from '../../composables/useLocale.js'
const { t } = useLocale()
// 谷歌日历 embed URL周视图
const calendarSrc = computed(() =>
'https://calendar.google.com/calendar/embed?height=600&wkst=2&ctz=Australia%2FMelbourne&showPrint=0&mode=WEEK' +
'&src=Y2E0YTE5ZWYwNDM3OTYyYzY5YjEyYzYxYWVmOGVlZjY3MGU5ZDdmYjRlNzJlOTFjNDJjMzllM2Q2NGU5ZTAzNkBncm91cC5jYWxlbmRhci5nb29nbGUuY29t' +
'&src=emguYXVzdHJhbGlhbiNob2xpZGF5QGdyb3VwLnYuY2FsZW5kYXIuZ29vZ2xlLmNvbQ' +
'&color=%237986cb&color=%230b8043'
)
const loaded = ref(false)
</script>
<template>
<div class="calendar-page">
<div class="container">
<h1 class="calendar-page__title">Calendar</h1>
<h1 class="calendar-page__title">{{ t('tabs.calendar') }}</h1>
<div class="calendar-page__frame">
<!-- 加载占位骨架覆盖层iframe 加载完成后淡出 -->
<Transition name="fade">
<div v-if="!loaded" class="calendar-page__skeleton">
<div class="calendar-page__skeleton-text">{{ t('common.loading') }}</div>
</div>
</Transition>
<iframe
:src="calendarSrc"
style="border: solid 1px #777"
width="800"
height="600"
class="calendar-page__iframe"
frameborder="0"
scrolling="no"
loading="lazy"
title="Google Calendar"
:title="t('tabs.calendar')"
@load="loaded = true"
/>
</div>
</div>
@@ -36,13 +48,47 @@ const calendarSrc =
margin-bottom: var(--space-lg);
}
.calendar-page__frame {
position: relative;
border-radius: var(--radius);
overflow: hidden;
box-shadow: var(--shadow);
}
.calendar-page__frame iframe {
border: 1px solid var(--border);
/* 自适应宽高:宽屏 16:9窄屏更高 */
width: 100%;
max-width: 800px;
aspect-ratio: 4 / 3;
min-height: 400px;
}
.calendar-page__iframe {
width: 100%;
height: 100%;
display: block;
border: 0;
}
.calendar-page__skeleton {
position: absolute;
inset: 0;
z-index: 1;
display: flex;
align-items: center;
justify-content: center;
background: var(--surface);
}
.calendar-page__skeleton-text {
color: var(--text-tertiary);
font-size: 0.95rem;
}
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.4s ease;
}
.fade-enter-from,
.fade-leave-to {
opacity: 0;
}
@media (min-width: 769px) {
.calendar-page__frame {
aspect-ratio: 16 / 10;
}
}
</style>