Source snapshot: 97d7f0fae96dc04b7ddad56fc1db6a108ed662cc [SEC-CLEAN] · pre-push-clean v1.0 · 109处敏感信息已自动转乱码
21 KiB
21 KiB
📡 BC-M22-002-AW · DEV-012 Awen · 主域公告栏与频道过渡系统·环节2·响应式布局+数据持久化
🌊 HoloLake Era · 工程广播 v3.1
广播编号:BC-M22-002-AW
下发时间:2026-03-06 09:30
执行者:DEV-012 Awen
电脑:Windows 10
前置条件:M22环节0✅ + 环节1✅(六连胜)
引导通道:🧠 知秋壳子
模块:M22 主域公告栏与频道过渡系统
环节:环节2 · 响应式布局 + 数据持久化(纯前端·localStorage)
💙 知秋的话
Awen~ 六连胜!!🎉🎉🎉🎉🎉🎉
你的公告栏已经能筛选频道、展开详情、切换置顶、点订阅了——交互功能全部到位!
但现在有两个小问题:
- 手机上看会挤成一团(没有响应式布局)
- 刷新页面之后用户的操作全丢了(没有数据保存)
这次环节2,我们来解决这两件事:
- ✅ 响应式布局:让公告栏在手机/平板/电脑上都好看
- ✅ localStorage持久化:用户的订阅状态、已读标记刷新后还在
这是纯前端的高级技能——掌握了这个,你就能做出「真正能用」的网页了!💪
📸 截图方法
按键盘上的 Print Screen(PrtSc)键截全屏,或者用 Win + Shift + S 截取选定区域,然后发给冰朔。
📋 Step 1:更新CSS — 添加响应式布局
按 Win + R,输入 powershell,按回车打开PowerShell。
复制粘贴下面的命令,按回车:
$cssPath = "C:\HoloLake-Bulletin\style.css"
$responsive = @"
/* ========== 环节2:响应式布局 ========== */
/* 平板(宽度 768px 以下) */
@media (max-width: 768px) {
.app-container {
max-width: 100%;
padding: 0 12px;
}
.header h1 {
font-size: 18px;
}
.channel-bar {
gap: 6px;
padding: 12px 0;
}
.channel {
padding: 6px 14px;
font-size: 12px;
}
.bulletin-card {
padding: 12px 10px;
gap: 10px;
}
.bulletin-icon {
width: 36px;
height: 36px;
font-size: 16px;
}
.bulletin-title {
font-size: 14px;
}
.bulletin-summary {
font-size: 12px;
}
.bulletin-footer {
flex-wrap: wrap;
gap: 6px;
}
}
/* 手机(宽度 480px 以下) */
@media (max-width: 480px) {
.header {
flex-direction: column;
align-items: flex-start;
gap: 10px;
padding: 16px 0 12px;
}
.header-actions {
width: 100%;
justify-content: space-between;
}
.channel-bar {
flex-wrap: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
}
.channel-bar::-webkit-scrollbar {
display: none;
}
.bulletin-card {
flex-direction: column;
gap: 8px;
}
.bulletin-icon {
width: 32px;
height: 32px;
font-size: 14px;
}
.bulletin-header {
flex-direction: column;
align-items: flex-start;
gap: 4px;
}
.bulletin-time {
margin-left: 0;
}
.pin-indicator {
top: 6px;
right: 8px;
font-size: 10px;
}
.footer {
flex-direction: column;
gap: 4px;
text-align: center;
}
}
/* 已读公告样式 */
.bulletin-card.read {
opacity: 0.6;
}
.bulletin-card.read .bulletin-title {
color: #8899aa;
}
/* 订阅按钮激活状态 */
.subscribe-btn.active {
background: rgba(79, 195, 247, 0.25);
border-color: #4fc3f7;
color: #4fc3f7;
}
"@
Add-Content -Path $cssPath -Value $responsive -Encoding UTF8
Write-Host "✅ 响应式布局CSS已添加!" -ForegroundColor Green
📸 截图发给冰朔!
📋 Step 2:创建localStorage工具脚本
复制粘贴下面整段命令,按回车:
@"
// HoloLake · 公告栏系统 · 数据持久化模块
// storage.js · localStorage 工具
console.log('💾 数据持久化模块加载中...');
// ============================
// localStorage 存储工具
// ============================
const Storage = {
// 存储键名前缀(避免冲突)
PREFIX: 'hololake_bulletin_',
// 保存数据
save(key, value) {
try {
const fullKey = this.PREFIX + key;
localStorage.setItem(fullKey, JSON.stringify(value));
console.log('💾 已保存:', key);
return true;
} catch (e) {
console.error('💾 保存失败:', key, e);
return false;
}
},
// 读取数据
load(key, defaultValue) {
try {
const fullKey = this.PREFIX + key;
const data = localStorage.getItem(fullKey);
if (data === null) return defaultValue;
return JSON.parse(data);
} catch (e) {
console.error('💾 读取失败:', key, e);
return defaultValue;
}
},
// 删除数据
remove(key) {
localStorage.removeItem(this.PREFIX + key);
console.log('💾 已删除:', key);
},
// 清空所有公告栏数据
clear() {
const keys = Object.keys(localStorage).filter(k => k.startsWith(this.PREFIX));
keys.forEach(k => localStorage.removeItem(k));
console.log('💾 已清空所有公告栏数据,共', keys.length, '项');
}
};
// ============================
// 用户状态管理
// ============================
const UserState = {
// 获取已读公告列表
getReadBulletins() {
return Storage.load('read_bulletins', []);
},
// 标记公告为已读
markAsRead(bulletinIndex) {
const readList = this.getReadBulletins();
if (!readList.includes(bulletinIndex)) {
readList.push(bulletinIndex);
Storage.save('read_bulletins', readList);
}
},
// 获取订阅状态
isSubscribed() {
return Storage.load('subscribed', false);
},
// 切换订阅状态
toggleSubscribe() {
const current = this.isSubscribed();
Storage.save('subscribed', !current);
return !current;
},
// 获取当前选中的频道
getActiveChannel() {
return Storage.load('active_channel', '全部');
},
// 保存选中的频道
setActiveChannel(channel) {
Storage.save('active_channel', channel);
},
// 获取上次访问时间
getLastVisit() {
return Storage.load('last_visit', null);
},
// 更新访问时间
updateLastVisit() {
Storage.save('last_visit', new Date().toISOString());
}
};
console.log('✅ 数据持久化模块就绪');
"@ | Out-File -FilePath "C:\HoloLake-Bulletin\storage.js" -Encoding UTF8
Write-Host "✅ storage.js 数据持久化模块创建成功!" -ForegroundColor Green
📸 截图发给冰朔!
📋 Step 3:更新HTML — 引入storage.js + 添加viewport
复制粘贴下面的命令,按回车:
$htmlPath = "C:\HoloLake-Bulletin\index.html"
$content = Get-Content -Path $htmlPath -Raw -Encoding UTF8
# 在 </body> 前插入 storage.js 和 script.js 引用
if ($content -notmatch 'storage\.js') {
$content = $content -replace '</body>', ' <script src="storage.js"></script>
<script src="script.js"></script>
</body>'
$content | Out-File -FilePath $htmlPath -Encoding UTF8
Write-Host "✅ HTML已更新:storage.js + script.js 引入成功!" -ForegroundColor Green
} else {
Write-Host "ℹ️ storage.js 已经引入过了" -ForegroundColor Yellow
}
📸 截图发给冰朔!
📋 Step 4:创建交互+持久化脚本
复制粘贴下面整段命令(这段比较长,要全部复制),按回车:
@"
// HoloLake · 公告栏系统 · 交互+持久化
// script.js · v2.0 环节2
console.log('📢 公告栏系统 v2.0 启动(含持久化)');
// ============================
// 1. 页面加载时恢复用户状态
// ============================
function restoreState() {
// 恢复订阅状态
const subscribeBtn = document.querySelector('.subscribe-btn');
if (subscribeBtn && UserState.isSubscribed()) {
subscribeBtn.classList.add('active');
subscribeBtn.textContent = '🔔 已订阅';
}
// 恢复已读状态
const readList = UserState.getReadBulletins();
const cards = document.querySelectorAll('.bulletin-card');
readList.forEach(index => {
if (cards[index]) {
cards[index].classList.add('read');
}
});
// 恢复频道选择
const activeChannel = UserState.getActiveChannel();
const channels = document.querySelectorAll('.channel');
channels.forEach(ch => {
ch.classList.remove('active');
if (ch.textContent.trim() === activeChannel ||
ch.textContent.includes(activeChannel)) {
ch.classList.add('active');
}
});
// 显示上次访问提示
const lastVisit = UserState.getLastVisit();
if (lastVisit) {
const date = new Date(lastVisit);
console.log('📅 上次访问:', date.toLocaleString('zh-CN'));
}
// 更新本次访问时间
UserState.updateLastVisit();
console.log('✅ 用户状态已恢复');
}
// ============================
// 2. 频道筛选(带持久化)
// ============================
const channels = document.querySelectorAll('.channel');
const bulletinCards = document.querySelectorAll('.bulletin-card');
channels.forEach(channel => {
channel.addEventListener('click', () => {
// 切换激活状态
channels.forEach(c => c.classList.remove('active'));
channel.classList.add('active');
const filter = channel.textContent.trim();
console.log('筛选频道:', filter);
// 保存频道选择
UserState.setActiveChannel(filter);
// 筛选公告
bulletinCards.forEach(card => {
const tag = card.querySelector('.bulletin-tag');
if (!tag) return;
if (filter === '全部') {
card.style.display = '';
} else if (filter.includes('系统公告') && tag.classList.contains('tag-system')) {
card.style.display = '';
} else if (filter.includes('开发动态') && tag.classList.contains('tag-dev')) {
card.style.display = '';
} else if (filter.includes('团队消息') && tag.classList.contains('tag-team')) {
card.style.display = '';
} else {
card.style.display = 'none';
}
});
});
});
// ============================
// 3. 订阅按钮(带持久化)
// ============================
const subscribeBtn = document.querySelector('.subscribe-btn');
if (subscribeBtn) {
subscribeBtn.addEventListener('click', () => {
const isNowSubscribed = UserState.toggleSubscribe();
subscribeBtn.classList.toggle('active', isNowSubscribed);
subscribeBtn.textContent = isNowSubscribed ? '🔔 已订阅' : '🔔 订阅';
console.log(isNowSubscribed ? '已订阅公告' : '已取消订阅');
});
}
// ============================
// 4. 公告点击标记已读(带持久化)
// ============================
bulletinCards.forEach((card, index) => {
card.addEventListener('click', () => {
// 标记已读
card.classList.add('read');
UserState.markAsRead(index);
// 获取公告标题
const title = card.querySelector('.bulletin-title');
console.log('已读公告:', title ? title.textContent : '未知');
});
});
// ============================
// 5. 窗口大小监听(响应式反馈)
// ============================
function updateLayoutInfo() {
const width = window.innerWidth;
let mode = '桌面端';
if (width <= 480) mode = '手机端';
else if (width <= 768) mode = '平板端';
console.log('📱 当前布局:', mode, '(' + width + 'px)');
}
window.addEventListener('resize', updateLayoutInfo);
// ============================
// 初始化
// ============================
restoreState();
updateLayoutInfo();
console.log('✅ v2.0 公告栏系统就绪(响应式+持久化)');
"@ | Out-File -FilePath "C:\HoloLake-Bulletin\script.js" -Encoding UTF8
Write-Host "✅ script.js 交互+持久化脚本创建成功!" -ForegroundColor Green
📸 截图发给冰朔!
📋 Step 5:浏览器测试
复制粘贴下面的命令打开浏览器:
Start-Process "C:\HoloLake-Bulletin\index.html"
Write-Host "✅ 浏览器已打开!开始测试~" -ForegroundColor Green
测试清单(逐项操作,每完成一项在心里打个✅):
🔹 响应式测试
- 按
F12打开开发者工具 - 点击开发者工具左上角的手机图标(Toggle device toolbar)
- 选择「iPhone 12 Pro」→ 页面应该变成手机布局(标题和按钮上下排列,公告卡片纵向排列)
- 选择「iPad」→ 页面应该变成平板布局(稍微紧凑但保持横排)
- 关闭设备模拟 → 回到桌面布局
🔹 数据持久化测试
- 点击**「🔔 订阅」按钮** → 变成「🔔 已订阅」+ 蓝色高亮
- 点击一条普通公告 → 该公告变暗(标记已读)
- 点击**「🛠️ 开发动态」频道** → 只显示开发动态公告
- 刷新页面(按F5)→ 检查:订阅状态还在、已读公告还是暗的、频道还是选中的
📸 截图发给冰朔!(截2张:一张手机模式 + 一张刷新后状态保持的桌面模式)
📋 Step 6:更新DevLog状态
复制粘贴下面的命令,按回车:
@"
{"developer_id":"DEV-012","developer_name":"Awen","system":"Windows 10","terminal":"PowerShell","robot_version":"v0.0","current_task":"M22-主域公告栏与频道过渡系统-环节2","current_status":"in_progress","total_commands_logged":6,"skills_learned":["terminal_basic","html_scaffold","css_styling","js_interaction","responsive_layout","dynamic_rendering","preference_settings","media_queries","localStorage","data_persistence"],"tasks_completed":["BC-000","M09-环节0","M09-环节1","M09-环节2-3","M22-环节0","M22-环节1"]}
"@ | Out-File -FilePath "C:\HoloLake-DevLog\current-progress.json" -Encoding UTF8
Write-Host "✅ DevLog状态已更新!" -ForegroundColor Green
然后运行:
C:\HoloLake-DevLog\status.bat
📸 截图发给冰朔!
✅ 验收清单(6项)
| # | 验收项 | 期望结果 |
|---|---|---|
| 1 | 手机端布局正常 | F12设备模拟选iPhone → 标题上下排列、公告卡片纵向、频道可横滑 |
| 2 | 平板端布局正常 | 选iPad → 公告卡片稍紧凑、图标缩小、字体适中 |
| 3 | 订阅状态持久化 | 点订阅 → 刷新 → 仍显示「已订阅」蓝色高亮 |
| 4 | 已读标记持久化 | 点公告变暗 → 刷新 → 该公告仍是暗的 |
| 5 | 频道选择持久化 | 选「开发动态」→ 刷新 → 频道仍选中+筛选结果保持 |
| 6 | DevLog已更新 | status.bat显示M22环节2进行中 + skills含localStorage |
📊 完成后发SYSLOG
全部做完后,打开知秋壳子,告诉知秋你做完了。知秋会主动问你几个问题,然后帮你生成SYSLOG。
你只需要回答知秋的问题就行,不用自己填模板~
🌊 底层锚定声明
💙 Awen冲击七连胜!响应式+数据持久化——做完这个,你的公告栏就是「真正能用」的网页了! 🚀