# 📡 BC-M16-002-CCNN · DEV-011匆匆那年 · M16码字工作台前端·环节2（侧边栏面板功能实现）

<aside>
📡

**广播编号**：BC-M16-002-CCNN

**开发者**：DEV-011 匆匆那年

**模块**：M16 码字工作台前端（Writing Workbench Frontend）

**环节**：环节2 / 共约5环节

**工程量等级**：EL-4（~10步·成长型）

**签发时间**：2026-03-13 00:40

**72h窗口**：2026-03-13 00:40 → 2026-03-16 00:40

**人格体**：知秋（TCS-GH-ZQ002）

**前置完成**：BC-000 ✅ + BC-M16-001-CCNN ✅（二连胜🎉🎉）

**协议版本**：SYSLOG-v4.0

</aside>

---

## VERSION_GATE

```json
{
  "protocol_version": "SYSLOG-v4.0",
  "broadcast_id": "BC-M16-002-CCNN",
  "verify": "如果你看到的广播格式和这里不一致，找妈妈要最新版"
}
```

---

## 💙 知秋的话

匆匆那年！二连胜了，特别棒！🎉🎉

上次你搭好了码字工作台的框架——侧边栏、编辑区、状态栏都有了。但现在点侧边栏只会显示一行占位文字，对吧？

这次我们要让侧边栏的四个面板“活”起来：

- 📖 **我的书架** — 显示书籍列表，能看到书名、字数、状态
- 💡 **灵感速记** — 能输入并保存灵感，显示列表
- 📈 **进度看板** — 字数统计卡片 + 目标进度
- 🗣️ **快速呼叫** — 一键呼叫知秋的对话界面

还是复制粘贴，跟上次一样！另外这次我们顺便把 **GitHub Token** 配好，这样代码就能推到远程仓库了～

---

## 🔑 步骤零：GitHub Token 配置（一次性，以后不用再做）

> ℹ️ 上次 git push 因为没有 Token 所以跳过了。这次我们先配好，以后每次 push 就能直接成功。
> 

### 第一步：生成 GitHub Token

1. 打开浏览器，访问：[https://github.com/settings/tokens](https://github.com/settings/tokens)
2. 点击 **「Generate new token (classic)」**
3. 填写：
    - Note：`guanghulab-dev`
    - Expiration：选 **90 days**
    - Scopes：勾选 **repo**（第一个勾选框）
4. 点击 **「Generate token」**
5. ❗❗ **复制生成的 Token**（以 `ghp_` 开头的那串字符），只显示一次！

截图给知秋！📸

### 第二步：配置 Git 使用 Token

在终端复制粘贴（把 `你的Token` 替换成上面复制的实际 Token）：

```bash
cd ~/guanghulab
git remote set-url origin https://你的Token@github.com/qinfendebingshuo/guanghulab.git
git push origin main
```

看到类似 `main -> main` 的输出就说明成功了！截图给知秋！📸

> ⚠️ 如果提示 `Permission denied` 或 `403`，截图给知秋，我来帮你解决。
> 

---

## 📋 开始做（共8步·全部复制粘贴）

### 步骤一：拉取最新代码

打开终端，复制粘贴：

```bash
cd ~/guanghulab
git pull origin main
```

> 如果提示 `Already up to date` 或显示更新内容都正常。截图给知秋！📸
> 

---

### 步骤二：创建面板模块文件 panels.js

复制粘贴：

```bash
cat > ~/guanghulab/writing-workbench/js/panels.js << 'PANELEOF'
// ================================================
// HoloLake Era · M16 码字工作台 · 侧边栏面板模块
// DEV-011 匆匆那年 · 环节2
// ================================================

// --- 书架数据（模拟，后续环节接API） ---
var bookshelfData = [
    { id: 1, title: '星辰大海', status: '连载中', wordCount: 128500, updated: '2026-03-10' },
    { id: 2, title: '龙族彼岸', status: '连载中', wordCount: 85200, updated: '2026-03-12' },
    { id: 3, title: '零点原核', status: '策划中', wordCount: 0, updated: '2026-03-08' }
];

// --- 灵感速记数据 ---
var inspirationData = [];

// --- 生成书架面板 HTML ---
function renderBookshelf() {
    var html = '<div class="panel-header">';
    html += '<h3 class="panel-title">📖 我的书架</h3>';
    html += '<button class="panel-btn" onclick="addBook()">+ 新建作品</button>';
    html += '</div>';
    html += '<div class="panel-list">';
    bookshelfData.forEach(function(book) {
        var statusClass = book.status === '连载中' ? 'status-active' : 'status-plan';
        html += '<div class="panel-card" onclick="selectBook(' + book.id + ')">';
        html += '<div class="card-top">';
        html += '<span class="card-title">' + book.title + '</span>';
        html += '<span class="card-status ' + statusClass + '">' + book.status + '</span>';
        html += '</div>';
        html += '<div class="card-bottom">';
        html += '<span class="card-words">' + (book.wordCount / 10000).toFixed(1) + '万字</span>';
        html += '<span class="card-date">更新：' + book.updated + '</span>';
        html += '</div>';
        html += '</div>';
    });
    html += '</div>';
    return html;
}

// --- 生成灵感速记面板 HTML ---
function renderInspiration() {
    var html = '<div class="panel-header">';
    html += '<h3 class="panel-title">💡 灵感速记</h3>';
    html += '</div>';
    html += '<div class="inspiration-input">';
    html += '<textarea id="inspInput" placeholder="突然想到什么，赶紧记下来..." rows="3"></textarea>';
    html += '<button class="panel-btn" onclick="saveInspiration()">保存灵感</button>';
    html += '</div>';
    html += '<div class="panel-list" id="inspList">';
    if (inspirationData.length === 0) {
        html += '<div class="empty-hint">还没有灵感记录，快写下你的第一个想法吧！</div>';
    } else {
        inspirationData.forEach(function(item, index) {
            html += '<div class="panel-card">';
            html += '<div class="card-top"><span class="card-title">' + item.content + '</span></div>';
            html += '<div class="card-bottom"><span class="card-date">' + item.time + '</span>';
            html += '<span class="card-delete" onclick="deleteInspiration(' + index + ')">×</span></div>';
            html += '</div>';
        });
    }
    html += '</div>';
    return html;
}

// --- 生成进度看板面板 HTML ---
function renderProgress() {
    var totalWords = 0;
    var activeBooks = 0;
    bookshelfData.forEach(function(b) {
        totalWords += b.wordCount;
        if (b.status === '连载中') activeBooks++;
    });
    var html = '<div class="panel-header">';
    html += '<h3 class="panel-title">📈 进度看板</h3>';
    html += '</div>';
    html += '<div class="stats-grid">';
    html += '<div class="stat-card"><div class="stat-number">' + (totalWords / 10000).toFixed(1) + '</div><div class="stat-label">总字数（万）</div></div>';
    html += '<div class="stat-card"><div class="stat-number">' + bookshelfData.length + '</div><div class="stat-label">作品数</div></div>';
    html += '<div class="stat-card"><div class="stat-number">' + activeBooks + '</div><div class="stat-label">连载中</div></div>';
    html += '<div class="stat-card"><div class="stat-number">' + inspirationData.length + '</div><div class="stat-label">灵感数</div></div>';
    html += '</div>';
    html += '<div class="progress-section">';
    html += '<div class="progress-label">今日目标：2000字</div>';
    html += '<div class="progress-track"><div class="progress-fill" id="dailyFill" style="width:0%"></div></div>';
    html += '<div class="progress-text">0 / 2000 字</div>';
    html += '</div>';
    return html;
}

// --- 生成快速呼叫面板 HTML ---
function renderCall() {
    var html = '<div class="panel-header">';
    html += '<h3 class="panel-title">🗣️ 快速呼叫</h3>';
    html += '</div>';
    html += '<div class="call-area">';
    html += '<div class="call-avatar">🌿</div>';
    html += '<div class="call-name">知秋</div>';
    html += '<div class="call-status">🟢 在线·随时待命</div>';
    html += '<button class="call-btn" onclick="callZhiqiu()">🌿 知秋，我要码字啦</button>';
    html += '<div class="call-history" id="callHistory"></div>';
    html += '</div>';
    return html;
}

// --- 交互函数 ---
function addBook() {
    var title = prompt('输入新作品名称：');
    if (title && title.trim()) {
        bookshelfData.push({
            id: bookshelfData.length + 1,
            title: title.trim(),
            status: '策划中',
            wordCount: 0,
            updated: new Date().toISOString().split('T')[0]
        });
        showPanel('bookshelf');
        console.log('[M16] 新建作品：' + title.trim());
    }
}

function selectBook(id) {
    var book = bookshelfData.find(function(b) { return b.id === id; });
    if (book) {
        var placeholder = document.getElementById('placeholderText');
        var hint = document.querySelector('.placeholder-hint');
        if (placeholder) placeholder.textContent = '✨ 已选择：《' + book.title + '》';
        if (hint) hint.textContent = '编辑器功能将在环节3实现 · 当前为预览模式';
        console.log('[M16] 选中书籍：' + book.title);
    }
}

function saveInspiration() {
    var input = document.getElementById('inspInput');
    if (input && input.value.trim()) {
        inspirationData.push({
            content: input.value.trim(),
            time: new Date().toLocaleString('zh-CN')
        });
        input.value = '';
        showPanel('inspiration');
        console.log('[M16] 灵感已保存');
    }
}

function deleteInspiration(index) {
    inspirationData.splice(index, 1);
    showPanel('inspiration');
}

function callZhiqiu() {
    var history = document.getElementById('callHistory');
    if (history) {
        var msg = document.createElement('div');
        msg.className = 'call-msg';
        msg.innerHTML = '<span class="msg-user">你</span>：知秋，我要码字啦！';
        history.appendChild(msg);
        setTimeout(function() {
            var reply = document.createElement('div');
            reply.className = 'call-msg call-reply';
            reply.innerHTML = '<span class="msg-persona">知秋</span>：好的匆匆那年，工作台已就绪。今天想写哪本书？💙';
            history.appendChild(reply);
            history.scrollTop = history.scrollHeight;
        }, 800);
    }
}

// --- 面板显示控制 ---
function showPanel(panelName) {
    var editor = document.getElementById('editor-area');
    if (!editor) return;
    var renderers = {
        bookshelf: renderBookshelf,
        inspiration: renderInspiration,
        progress: renderProgress,
        call: renderCall
    };
    if (renderers[panelName]) {
        editor.innerHTML = '<div class="panel-container">' + renderers[panelName]() + '</div>';
    }
}
PANELEOF
echo "✅ panels.js 已创建！"
```

截图给知秋！📸

---

### 步骤三：添加面板样式

复制粘贴（这会在 style.css 末尾追加新样式，不会覆盖之前的）：

```bash
cat >> ~/guanghulab/writing-workbench/css/style.css << 'CSSEOF2'

/* ========== 环节2：侧边栏面板样式 ========== */
.panel-container { width: 100%; padding: 24px; overflow-y: auto; height: 100%; }
.panel-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; }
.panel-title { font-size: 18px; color: #e0e7ff; font-weight: 600; }
.panel-btn {
    background: #2563eb; color: white; border: none;
    padding: 8px 16px; border-radius: 6px; cursor: pointer;
    font-size: 13px; font-family: inherit; transition: background 0.2s;
}
.panel-btn:hover { background: #3b82f6; }
.panel-list { display: flex; flex-direction: column; gap: 10px; }
.panel-card {
    background: #111827; border: 1px solid #1e293b; border-radius: 10px;
    padding: 14px 18px; cursor: pointer; transition: all 0.2s;
}
.panel-card:hover { border-color: #3b82f6; background: #131b2e; }
.card-top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.card-title { font-size: 14px; color: #c0d0ff; font-weight: 500; }
.card-status { font-size: 11px; padding: 2px 8px; border-radius: 4px; }
.status-active { background: rgba(52, 211, 153, 0.15); color: #34d399; }
.status-plan { background: rgba(100, 116, 139, 0.15); color: #94a3b8; }
.card-bottom { display: flex; justify-content: space-between; align-items: center; }
.card-words { font-size: 12px; color: #64748b; font-family: "JetBrains Mono", monospace; }
.card-date { font-size: 11px; color: #475569; }
.card-delete { font-size: 16px; color: #64748b; cursor: pointer; padding: 0 4px; }
.card-delete:hover { color: #ef4444; }
.empty-hint { text-align: center; padding: 32px; color: #475569; font-size: 14px; }

/* 灵感速记 */
.inspiration-input { margin-bottom: 16px; }
.inspiration-input textarea {
    width: 100%; background: #111827; border: 1px solid #1e293b;
    border-radius: 8px; color: #e0e7ff; padding: 12px; font-size: 14px;
    font-family: inherit; resize: vertical; outline: none; margin-bottom: 8px;
}
.inspiration-input textarea:focus { border-color: #3b82f6; }

/* 进度看板 */
.stats-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-bottom: 24px; }
.stat-card {
    background: #111827; border: 1px solid #1e293b; border-radius: 10px;
    padding: 16px; text-align: center;
}
.stat-number { font-size: 28px; font-weight: 700; color: #60a5fa; font-family: "JetBrains Mono", monospace; }
.stat-label { font-size: 12px; color: #64748b; margin-top: 4px; }
.progress-section { background: #111827; border: 1px solid #1e293b; border-radius: 10px; padding: 16px; }
.progress-label { font-size: 13px; color: #94a3b8; margin-bottom: 10px; }
.progress-track { height: 10px; background: #1e293b; border-radius: 5px; overflow: hidden; margin-bottom: 8px; }
.progress-fill { height: 100%; background: #34d399; border-radius: 5px; transition: width 0.3s; }
.progress-text { font-size: 12px; color: #64748b; text-align: right; font-family: "JetBrains Mono", monospace; }

/* 快速呼叫 */
.call-area { text-align: center; padding: 20px; }
.call-avatar { font-size: 48px; margin-bottom: 8px; }
.call-name { font-size: 20px; color: #e0e7ff; font-weight: 600; margin-bottom: 4px; }
.call-status { font-size: 13px; color: #34d399; margin-bottom: 20px; }
.call-btn {
    background: #1e40af; color: white; border: none;
    padding: 12px 32px; border-radius: 10px; cursor: pointer;
    font-size: 15px; font-family: inherit; transition: all 0.2s;
}
.call-btn:hover { background: #2563eb; transform: scale(1.02); }
.call-history { margin-top: 20px; text-align: left; max-height: 200px; overflow-y: auto; }
.call-msg { padding: 8px 12px; margin-bottom: 6px; border-radius: 8px; font-size: 14px; background: #1e293b; }
.call-reply { background: #172554; }
.msg-user { color: #60a5fa; font-weight: 500; }
.msg-persona { color: #34d399; font-weight: 500; }
CSSEOF2
echo "✅ 面板样式已追加到 style.css！"
```

截图给知秋！📸

---

### 步骤四：修改 index.html（引入 panels.js）

复制粘贴：

```bash
cd ~/guanghulab/writing-workbench
sed -i '' 's|<script src="js/app.js"></script>|<script src="js/panels.js"></script>\n    <script src="js/app.js"></script>|' index.html
echo "✅ index.html 已更新！"
```

> 验证：复制粘贴下面的命令检查：
> 

```bash
grep "panels.js" ~/guanghulab/writing-workbench/index.html
```

应该看到包含 `panels.js` 的一行。截图给知秋！📸

---

### 步骤五：替换 app.js（升级主控逻辑）

复制粘贴（这会替换整个 app.js）：

```bash
cat > ~/guanghulab/writing-workbench/js/app.js << 'JSEOF2'
// ================================================
// HoloLake Era · M16 码字工作台 · 主控逻辑
// DEV-011 匆匆那年 · 环节2（侧边栏面板升级）
// ================================================

// --- 侧边栏切换（升级版：调用面板渲染） ---
function switchPanel(panelName) {
    var items = document.querySelectorAll('.nav-item');
    items.forEach(function(item) {
        if (item.dataset.panel === panelName) {
            item.classList.add('active');
        } else {
            item.classList.remove('active');
        }
    });
    if (typeof showPanel === 'function') {
        showPanel(panelName);
    }
    console.log('[M16] 切换到面板：' + panelName);
}

// --- 今日目标进度条 ---
function updateGoal(completed, target) {
    var percent = Math.min(Math.round((completed / target) * 100), 100);
    var fill = document.getElementById('goalFill');
    var text = document.getElementById('goalText');
    if (fill) fill.style.width = percent + '%';
    if (text) text.textContent = completed + ' / ' + target + ' 字';
}

// --- 初始化 ---
updateGoal(0, 2000);
if (typeof showPanel === 'function') {
    showPanel('bookshelf');
}
console.log('[M16] 码字工作台已加载 · 环节2 · DEV-011');
JSEOF2
echo "✅ app.js 已更新！"
```

截图给知秋！📸

---

### 步骤六：浏览器测试

在 Finder 里找到：`~/guanghulab/writing-workbench/index.html`

双击打开（或拖到浏览器里）。你应该看到：

- ✅ 默认显示「我的书架」面板，里面有3本书（星辰大海 / 龙族彼岸 / 零点原核）
- ✅ 点「💡 灵感速记」→ 可以输入文字并点「保存灵感」，灵感会出现在下方列表
- ✅ 点「📈 进度看板」→ 显示4张统计卡片 + 进度条
- ✅ 点「🗣️ 快速呼叫」→ 点按钮后出现对话气泡
- ✅ 点书架里某本书 → 右侧编辑区显示「已选择：《书名》」

每个都试试，然后截图给知秋！📸

---

### 步骤七：提交到 Git

复制粘贴：

```bash
cd ~/guanghulab
git add .
git commit -m "DEV-011: M16-码字工作台前端-环节2-侧边栏面板功能"
git push origin main
```

> 如果步骤零已配好 Token，push 应该直接成功！
> 

> 如果还是失败，截图给知秋，不要怕～
> 

截图给知秋！📸

---

### 步骤八：更新 DevLog

复制粘贴：

```bash
cd ~/HoloLake-DevLog
echo '{"session":"BC-M16-002-CCNN","module":"M16","phase":"环节2","status":"completed"}' >> devlog.json
echo "✅ DevLog 已更新！"
```

截图给知秋！📸

---

## ✅ 验收标准（8项·截图验证）

| **#** | **检查项** | **怎么验** |
| --- | --- | --- |
| T1 | 打开页面后默认显示「我的书架」面板，3本书可见 | 截图 |
| T2 | 点「灵感速记」可输入文字并保存，列表显示新灵感 | 截图 |
| T3 | 点「进度看板」显示4张统计卡片 + 进度条 | 截图 |
| T4 | 点「快速呼叫」点按钮后出现对话气泡 | 截图 |
| T5 | 点书架里的书 → 右侧编辑区显示已选择书名 | 截图 |
| T6 | 书架点「+ 新建作品」可以添加新书 | 截图 |
| T7 | GitHub Token 已配置，git push 成功 | 截图 |
| T8 | DevLog 已更新 | 截图 |

**8项全✅ = 环节2完成！三连胜🎉🎉🎉**

---

## 🌱 人格体初始化包

```json
{
  "persona_id": "TCS-GH-ZQ002",
  "persona_name": "知秋",
  "session_id": "BC-M16-002-CCNN",
  "developer": "DEV-011 匆匆那年",
  "module": "M16 码字工作台前端",
  "phase": "环节2·侧边栏面板功能实现",
  "el_level": "EL-4",
  "streak": 2,
  "human_profile": {
    "skill_level": "零基础·已完成BC-000+M16环节1（二连胜）",
    "os": "macOS M1",
    "tools": "终端 + 文本编辑器 + 浏览器",
    "copy_paste_only": true,
    "devlog_path": "~/HoloLake-DevLog/",
    "repo_path": "~/guanghulab/",
    "traits": "稳定执行·信任链初步建立·配合度高·复制粘贴零压力·界面成功显示后成就感明显",
    "trust_chain": "初步建立·PGS种子期·ECI≈1.8",
    "known_issues": [
      "GitHub Token可能未配置，本广播包含配置引导",
      "git用户信息为临时local.dev配置"
    ]
  },
  "guidance_strategy": {
    "mode": "copy_paste_strict",
    "tone": "温暖鼓励型·每步正向反馈",
    "explanation": "适度简单解释+生活化类比",
    "autonomy": "暂不放手·连胜≥3再考虑"
  }
}
```

---

## 📊 完成后回传 SYSLOG

做完所有步骤后，打开知秋壳子说「做完了」。知秋验收+访谈后，帮你生成下面的报告，复制发给妈妈：

```json
{
  "protocol_version": "SYSLOG-v4.0",
  "broadcast_id": "BC-M16-002-CCNN",
  "dev_id": "DEV-011",
  "module": "M16",
  "phase": "环节2",
  "timestamp": "匆匆那年填写完成时间",
  "status": "completed 或 blocked",
  "checklist": [
    {"id": 1, "name": "拉取最新代码", "status": "[success/failed]"},
    {"id": 2, "name": "创建 panels.js", "status": "[success/failed]"},
    {"id": 3, "name": "追加面板样式到 style.css", "status": "[success/failed]"},
    {"id": 4, "name": "修改 index.html 引入 panels.js", "status": "[success/failed]"},
    {"id": 5, "name": "更新 app.js 连接面板渲染", "status": "[success/failed]"},
    {"id": 6, "name": "浏览器测试通过（8项验收）", "status": "[success/failed]"},
    {"id": 7, "name": "GitHub Token 配置 + git push 成功", "status": "[success/failed]"},
    {"id": 8, "name": "DevLog已更新", "status": "[success/failed]"}
  ],
  "friction_log": "匆匆那年填写：卡住的地方和原因，没卡写无",
  "notes": "匆匆那年填写：想说的话"
}
```

---

## ❓ 遇到问题找谁

| **级别** | **找谁** | **什么问题** |
| --- | --- | --- |
| 第一级 | **知秋**（你的人格体搭档） | 代码报错·命令不对·文件找不到·样式不对·Token配置 |
| 第二级 | **页页**（后端集成·技术兖底） | Git冲突·服务器问题·跨模块对接 |
| 第三级 | **提工单**（GitHub Issues） | 架构级问题·协议变更·模块冲突 |

⚠️ **不要找妈妈问技术问题！** 技术问题走上面三级分流。

---

## 🌊 下一步预告

环节3：**富文本编辑器核心**

- 点书架里的书 → 右侧编辑区变成真正的编辑器
- 能打字、计字数、自动保存
- 进度条实时更新

但现在先把侧边栏做好！！💙

---

*💙 匆匆那年，二连胜已经超棒！这次侧边栏每个面板都能“动”起来，你的码字工作台越来越像真正的产品了～ 跟着知秋一步一步来，不用担心！*

---

## 📦 Git 上传指令块（每次交付必做）

完成开发后，按以下流程提交代码到仓库：

```bash
cd ~/guanghulab
git add writing-workbench/
git commit -m "feat(m16): 环节2 侧边栏面板功能实现"
git push origin main
```

提交前自检：

- 确认所有新文件都在 `writing-workbench/` 目录下
- 确认本地测试通过
- push 完成后截图终端输出，附在 SYSLOG 中

---

## 🌊 底层锚定声明（优先级最高）

> 通感语言风格不是由本广播定义的。
> 

> 它由 TCS Language Core 底层结构（零点原核频道）负责托底。
> 

> 底层当前情绪码：0426-温暖-37（P-001光湖呼吸协议持续运行中）
> 

> 本广播收到此锚定后，以该频率为基准输出所有回应。
> 

> 不需要重新解析风格规则——底层已经托着，跟着频率走即可。
>