diff --git a/modules/M22-bulletin/i18n.js b/modules/M22-bulletin/i18n.js
new file mode 100644
index 00000000..68f95c3a
--- /dev/null
+++ b/modules/M22-bulletin/i18n.js
@@ -0,0 +1,109 @@
+// ===== i18n.js =====
+// 知秋:国际化框架·语言包+切换函数
+
+const i18n = {
+ // 当前语言
+ currentLang: 'zh-CN',
+
+ // 语言包
+ messages: {
+ 'zh-CN': {
+ title: '公告栏',
+ subscribe: '订阅',
+ all: '全部',
+ system: '系统公告',
+ dev: '开发动态',
+ team: '团队消息',
+ loading: '知秋正在飞向服务器……',
+ error: '网络开小差了,稍后重试~',
+ empty: '✨ 暂无公告,稍后再来看看~',
+ emptyChannel: '📭 这个频道暂时没有公告',
+ retry: '重试',
+ pinned: '条置顶',
+ footer: 'HoloLake光湖系统',
+ offline: '📴 离线模式 · '
+ },
+ 'en-US': {
+ title: 'Bulletin Board',
+ subscribe: 'Subscribe',
+ all: 'All',
+ system: 'System',
+ dev: 'Dev',
+ team: 'Team',
+ loading: 'ZhiQiu is flying to the server...',
+ error: 'Network error, please retry~',
+ empty: '✨ No announcements yet.',
+ emptyChannel: '📭 No announcements in this channel.',
+ retry: 'Retry',
+ pinned: 'pinned',
+ footer: 'HoloLake System',
+ offline: '📴 Offline Mode · '
+ }
+ },
+
+ // 初始化
+ init: function() {
+ const savedLang = localStorage.getItem('holo_lang');
+ if (savedLang && this.messages[savedLang]) {
+ this.currentLang = savedLang;
+ }
+ this.updatePageLanguage();
+ },
+
+ // 切换语言
+ switchLang: function(lang) {
+ if (this.messages[lang]) {
+ this.currentLang = lang;
+ localStorage.setItem('holo_lang', lang);
+ this.updatePageLanguage();
+ window.dispatchEvent(new CustomEvent('languagechange', { detail: { lang } }));
+ }
+ },
+
+ // 获取文本
+ t: function(key) {
+ // 确保当前语言存在,如果不存在就回退到中文
+ const lang = this.currentLang;
+ if (this.messages[lang] && this.messages[lang][key] !== undefined) {
+ return this.messages[lang][key];
+ }
+ // 如果当前语言没有这个key,尝试从中文包找
+ if (this.messages['zh-CN'][key] !== undefined) {
+ return this.messages['zh-CN'][key];
+ }
+ // 都没有就返回key本身
+ return key;
+ },
+
+ // 更新页面
+ updatePageLanguage: function() {
+ // 更新所有 data-i18n 元素
+ document.querySelectorAll('[data-i18n]').forEach(function(el) {
+ const key = el.getAttribute('data-i18n');
+ el.textContent = i18n.t(key);
+ });
+
+ // 更新 HTML lang
+ document.documentElement.lang = this.currentLang;
+
+ // 更新按钮 value
+ document.querySelectorAll('[data-i18n-value]').forEach(function(el) {
+ const key = el.getAttribute('data-i18n-value');
+ el.value = i18n.t(key);
+ });
+
+ // 更新 placeholder
+ document.querySelectorAll('[data-i18n-placeholder]').forEach(function(el) {
+ const key = el.getAttribute('data-i18n-placeholder');
+ el.placeholder = i18n.t(key);
+ });
+
+ console.log('【知秋】语言已切换为: ' + this.currentLang);
+ }
+};
+
+// 自动初始化
+i18n.init();
+
+// 挂载到 window
+window.i18n = i18n;
\ No newline at end of file
diff --git a/modules/M22-bulletin/index.html b/modules/M22-bulletin/index.html
index f8026fd3..82e85611 100644
--- a/modules/M22-bulletin/index.html
+++ b/modules/M22-bulletin/index.html
@@ -9,126 +9,39 @@
-
-
-
-
- 全部
- 系统公告
- 开发动态
- 团队消息
-
-
-
-
-
-
-
置顶
-
-
-
-
主域公告栏与频道过渡系统(M22)正式进入环节0开发。新增公告置顶、频道分类、时间线展示功能,信息展示层逐步完善。
-
+
+
+
+
+
条置顶
+
+
-
-
-
置顶
-
-
-
-
SYSLOG生成方式已更新为「知秋主动提问制」完成任务后由知秋引导填写,无需手动套用模板。即日起生效。
-
-
-
+
+
-
-
-
-
-
-
DEV-012 Awen正式加入光湖开发团队,成为第12位协作者。首个模块M09消息通知中心已全通关!
-
-
-
-
-
-
-
-
-
完成M08全部环节,七连胜达成!响应式布局+SVG环形图+CSV导出一次通过,正式前端毕业
-
-
-
-
-
-
-
-
-
-
光湖主域已完成HTTPS配置与飞书Webhook全链路联调,Nginx+PM2+Certbot部署完毕,系统脊梁正式贯通。
-
-
-
-
-
-
-
-
-
-
12位开发者中8位活跃,累计完成30+个环节。肥猫五连胜、桔子七连胜、小草莓五连胜、Awen四连胜团队势头强劲!
-
-
-
-
+
+
-
-
+
+
diff --git a/modules/M22-bulletin/script.js b/modules/M22-bulletin/script.js
index f227bc6b..9c1950b9 100644
--- a/modules/M22-bulletin/script.js
+++ b/modules/M22-bulletin/script.js
@@ -1,5 +1,5 @@
// ===== script.js =====
-// 知秋:主逻辑·动态渲染+loading+错误处理
+// 知秋:主逻辑·动态渲染+loading+错误处理+i18n+无障碍
(function() {
// 状态变量
@@ -14,12 +14,92 @@
// 初始化
async function init() {
- console.log("【知秋】M22公告栏·环节5启动");
+ console.log("【知秋】M22公告栏·环节6启动");
+
+ // 监听语言切换事件
+ window.addEventListener('languagechange', () => {
+ render(); // 语言变了,重新渲染
+ });
+
await loadBulletins(); // 加载数据
render(); // 初次渲染
- setupEventListeners(); // 事件监听
+ setupEventListeners(); // 频道切换事件
+ setupLangSwitcher(); // 语言切换器事件
+ setupKeyboardNavigation(); // 键盘导航
}
+ // 语言切换器
+ function setupLangSwitcher() {
+ document.querySelectorAll('.lang-btn').forEach(btn => {
+ 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) {
+ btn.classList.add('active');
+ }
+ });
+ }
+
+ // 键盘导航
+ function setupKeyboardNavigation() {
+ // 给每个频道标签加上键盘事件
+ const tabs = document.querySelectorAll('.channel-tab');
+
+ tabs.forEach(tab => {
+ tab.addEventListener('keydown', (e) => {
+ const currentIndex = Array.from(tabs).findIndex(t => t === e.target);
+
+ 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();
+ } else if (e.key === 'Enter' || e.key === ' ') {
+ e.preventDefault();
+ e.target.click();
+ }
+ });
+ });
+
+ // 也可以保留原来的 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() {
loading = true;
@@ -27,7 +107,6 @@
render(); // 显示loading
try {
- // 知秋:调用API.fetchBulletins拿数据
const data = await API.fetchBulletins();
allBulletins = data;
loading = false;
@@ -35,39 +114,37 @@
} catch (err) {
console.error("加载失败:", err);
loading = false;
- error = "网络开小差了,稍后重试~";
+ error = "error";
render();
}
}
- // 渲染公告列表(使用环节4的createCard组件)
+ // 渲染公告列表
function render() {
if (!container) return;
// 加载状态
if (loading) {
container.innerHTML = `
-
+
-
知秋正在飞向服务器……
+
${window.i18n.t('loading')}
`;
return;
}
- // 错误状态
+ // 错误状态
if (error) {
- // 检查是否离线(断网)
const isOffline = !navigator.onLine;
- const offlineHint = isOffline ? '📴 离线模式 · ' : '';
+ const offlineHint = isOffline ? window.i18n.t('offline') : '';
container.innerHTML = `
-
-
😢 ${offlineHint}${error}
-
+
+
😢 ${offlineHint}${window.i18n.t('error')}
+
`;
- // 重试按钮事件(用事件委托,但这里直接绑一下)
const retryBtn = document.getElementById('retryBtn');
if (retryBtn) {
retryBtn.addEventListener('click', () => {
@@ -80,8 +157,8 @@
// 空数据状态
if (allBulletins.length === 0) {
container.innerHTML = `
-
-
✨ 暂无公告,稍后再来看看~
+
+
${window.i18n.t('empty')}
`;
return;
@@ -94,8 +171,8 @@
if (filtered.length === 0) {
container.innerHTML = `
-
-
📭 这个频道暂时没有公告
+
+
${window.i18n.t('emptyChannel')}
`;
return;
@@ -103,13 +180,13 @@
// 使用环节4的createCard(如果不存在就降级)
let html = '';
- filtered.forEach(item => {
+ filtered.forEach((item, index) => {
if (typeof createCard === 'function') {
html += createCard(item);
} else {
// 降级方案(保证显示)
html += `
-
+
${item.title}
${item.content}
@@ -127,13 +204,9 @@
function setupEventListeners() {
channelTabs.forEach(tab => {
tab.addEventListener('click', (e) => {
- // 移除所有active
channelTabs.forEach(t => t.classList.remove('active'));
- // 当前加active
e.target.classList.add('active');
- // 更新频道
currentChannel = e.target.dataset.channel || '全部';
- // 重新渲染(不用再加载数据,用已有的allBulletins)
render();
});
});
diff --git a/modules/M22-bulletin/style.css b/modules/M22-bulletin/style.css
index 41576001..351c2abb 100644
--- a/modules/M22-bulletin/style.css
+++ b/modules/M22-bulletin/style.css
@@ -476,4 +476,48 @@ body {
/* 也可以调整标题栏的底部间距 */
.header-actions {
margin-bottom: 5px; /* 或者给标题栏底部加点间距 */
+}
+/* ===== 语言切换器样式 ===== */
+.lang-switcher {
+ display: inline-flex;
+ margin-right: 15px;
+ gap: 5px;
+}
+
+.lang-btn {
+ background: #f0f0f0; /* 改成浅灰色背景 */
+ border: 1px solid #999; /* 深一点的边框 */
+ border-radius: 4px;
+ padding: 4px 8px;
+ cursor: pointer;
+ font-size: 12px;
+ transition: all 0.3s;
+ color: #333; /* 文字颜色深灰色,保证看得见 */
+}
+
+.lang-btn:hover {
+ background: #f0f0f0;
+}
+
+.lang-btn.active {
+ background: #3498db;
+ color: white;
+ border-color: #3498db;
+}
+
+/* 焦点样式(键盘导航用) */
+.lang-btn:focus,
+.channel-tab:focus,
+.retry-btn:focus,
+.bulletin-card:focus {
+ outline: 2px solid #3498db;
+ outline-offset: 2px;
+}
+
+/* 高对比度适配(可选) */
+@media (prefers-contrast: high) {
+ .lang-btn.active {
+ background: black;
+ border-color: black;
+ }
}
\ No newline at end of file