DEV-012: M22环节7- 性能优化+骨架屏+首屏加速+预加载

This commit is contained in:
Awen 2026-03-11 23:36:22 +08:00
parent 57478c0721
commit c969a7ac56
3 changed files with 108 additions and 63 deletions

View File

@ -4,6 +4,10 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>HoloLake公告栏</title>
<!-- 关键资源预加载 -->
<link rel="preload" href="style.css" as="style">
<link rel="preload" href="i18n.js" as="script">
<link rel="preload" href="script.js" as="script">
<link rel="stylesheet" href="style.css">
</head>
<body>
@ -22,13 +26,32 @@
</div>
</header>
<!-- 频道标签栏(用 nav 包裹) -->
<!-- 频道标签栏 -->
<nav class="channel-bar" role="tablist">
<button class="channel-tab active" data-channel="全部" role="tab" data-i18n="all">全部</button>
<button class="channel-tab" data-channel="系统" role="tab" data-i18n="system">系统公告</button>
<button class="channel-tab" data-channel="知秋" role="tab" data-i18n="dev">开发动态</button>
<button class="channel-tab" data-channel="技术" role="tab" data-i18n="team">团队消息</button>
</nav>
<button class="channel-tab active" data-channel="全部" role="tab" data-i18n="all">全部</button>
<button class="channel-tab" data-channel="系统" role="tab" data-i18n="system">系统公告</button>
<button class="channel-tab" data-channel="知秋" role="tab" data-i18n="dev">开发动态</button>
<button class="channel-tab" data-channel="技术" role="tab" data-i18n="team">团队消息</button>
</nav>
<!-- 骨架屏容器(独立于公告容器) -->
<div id="skeleton-container" class="skeleton-wrapper">
<div class="skeleton-card">
<div class="skeleton skeleton-title"></div>
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text short"></div>
</div>
<div class="skeleton-card">
<div class="skeleton skeleton-title"></div>
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text short"></div>
</div>
<div class="skeleton-card">
<div class="skeleton skeleton-title"></div>
<div class="skeleton skeleton-text"></div>
<div class="skeleton skeleton-text short"></div>
</div>
</div>
<!-- 公告主内容区 -->
<main class="bulletin-container" role="list"></main>
@ -40,7 +63,7 @@
</footer>
</div>
<!-- 先加载 i18n再加载其他 JS -->
<!-- JS 文件 -->
<script src="i18n.js"></script>
<script src="api.js"></script>
<script src="storage.js"></script>

View File

@ -1,5 +1,5 @@
// ===== script.js =====
// 知秋:主逻辑·动态渲染+loading+错误处理+i18n+无障碍
// 知秋:主逻辑·动态渲染+错误处理+i18n+无障碍+骨架屏
(function() {
// 状态变量
@ -11,21 +11,39 @@
// DOM元素
const container = document.querySelector('.bulletin-container');
const channelTabs = document.querySelectorAll('.channel-tab');
const skeletonContainer = document.getElementById('skeleton-container');
// ========== 骨架屏控制 ==========
function showSkeleton() {
if (skeletonContainer) {
skeletonContainer.style.display = 'block';
}
}
function hideSkeleton() {
if (skeletonContainer) {
skeletonContainer.style.opacity = '0';
skeletonContainer.style.transition = 'opacity 0.3s ease';
setTimeout(() => {
skeletonContainer.style.display = 'none';
}, 300);
}
}
// 初始化
async function init() {
console.log("【知秋】M22公告栏·环节6启动");
console.log("【知秋】M22公告栏·环节7启动");
// 监听语言切换事件
window.addEventListener('languagechange', () => {
render(); // 语言变了,重新渲染
render();
});
await loadBulletins(); // 加载数据
render(); // 初次渲染
setupEventListeners(); // 频道切换事件
setupLangSwitcher(); // 语言切换器事件
setupKeyboardNavigation(); // 键盘导航
await loadBulletins();
render();
setupEventListeners();
setupLangSwitcher();
setupKeyboardNavigation();
}
// 语言切换器
@ -34,12 +52,10 @@
btn.addEventListener('click', () => {
const lang = btn.dataset.lang;
window.i18n.switchLang(lang);
// 更新按钮激活状态
document.querySelectorAll('.lang-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
});
});
// 设置当前语言按钮激活状态
const currentLang = window.i18n.currentLang;
document.querySelectorAll('.lang-btn').forEach(btn => {
if (btn.dataset.lang === currentLang) {
@ -48,9 +64,8 @@
});
}
// 键盘导航
// 键盘导航
function setupKeyboardNavigation() {
// 给每个频道标签加上键盘事件
const tabs = document.querySelectorAll('.channel-tab');
tabs.forEach(tab => {
@ -61,13 +76,11 @@
e.preventDefault();
const nextIndex = (currentIndex + 1) % tabs.length;
tabs[nextIndex].focus();
// 自动激活聚焦的标签
tabs[nextIndex].click();
} else if (e.key === 'ArrowLeft') {
e.preventDefault();
const prevIndex = (currentIndex - 1 + tabs.length) % tabs.length;
tabs[prevIndex].focus();
// 自动激活聚焦的标签
tabs[prevIndex].click();
} else if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
@ -75,47 +88,26 @@
}
});
});
// 也可以保留原来的 channel-bar 监听作为备用
const channelBar = document.querySelector('.channel-bar');
if (channelBar) {
channelBar.addEventListener('keydown', (e) => {
// 防止重复处理
if (e.target.classList.contains('channel-tab')) return;
const tabs = Array.from(document.querySelectorAll('.channel-tab'));
const currentIndex = tabs.findIndex(tab => tab === document.activeElement);
if (e.key === 'ArrowRight') {
e.preventDefault();
const nextIndex = (currentIndex + 1) % tabs.length;
tabs[nextIndex].focus();
tabs[nextIndex].click();
} else if (e.key === 'ArrowLeft') {
e.preventDefault();
const prevIndex = (currentIndex - 1 + tabs.length) % tabs.length;
tabs[prevIndex].focus();
tabs[prevIndex].click();
}
});
}
}
// 加载公告(核心!)
// 加载公告
async function loadBulletins() {
showSkeleton(); // 显示骨架屏
loading = true;
error = null;
render(); // 显示loading
try {
const data = await API.fetchBulletins();
allBulletins = data;
hideSkeleton(); // 数据到手,隐藏骨架屏
loading = false;
render();
render(); // 渲染真实内容
} catch (err) {
console.error("加载失败:", err);
hideSkeleton(); // 出错也要隐藏骨架屏
loading = false;
error = "error";
render();
render(); // 显示错误状态
}
}
@ -123,17 +115,6 @@
function render() {
if (!container) return;
// 加载状态
if (loading) {
container.innerHTML = `
<div class="loading-state" role="status" aria-live="polite">
<div class="spinner"></div>
<p>${window.i18n.t('loading')}</p>
</div>
`;
return;
}
// 错误状态
if (error) {
const isOffline = !navigator.onLine;
@ -178,13 +159,12 @@
return;
}
// 使用环节4的createCard如果不存在就降级
// 渲染公告卡片
let html = '';
filtered.forEach((item, index) => {
if (typeof createCard === 'function') {
html += createCard(item);
} else {
// 降级方案(保证显示)
html += `
<div class="bulletin-card" role="listitem" tabindex="0" aria-label="${item.title}">
<h3>${item.title}</h3>
@ -200,7 +180,7 @@
container.innerHTML = html;
}
// 事件监听(频道切换)
// 频道切换事件
function setupEventListeners() {
channelTabs.forEach(tab => {
tab.addEventListener('click', (e) => {

View File

@ -520,4 +520,46 @@ body {
background: black;
border-color: black;
}
}
/* ========== 骨架屏样式 ========== */
.skeleton {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
border-radius: 4px;
}
@keyframes skeleton-loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.skeleton-text {
height: 16px;
margin-bottom: 8px;
width: 80%;
}
.skeleton-text.short {
width: 40%;
}
.skeleton-title {
height: 24px;
margin-bottom: 12px;
width: 60%;
}
.skeleton-card {
padding: 16px;
border-radius: 8px;
background: var(--card-bg, #fff);
margin-bottom: 12px;
}
/* 暗色主题适配 */
[data-theme="dark"] .skeleton {
background: linear-gradient(90deg, #2a2a2a 25%, #333 50%, #2a2a2a 75%);
background-size: 200% 100%;
animation: skeleton-loading 1.5s infinite;
}