# 📡 BC-M06-001 · DEV-010桔子 · 工单管理界面·环节3·响应式布局+数据导出

**广播编号**：BC-M06-001-JZ-E3

**下发时间**：2026-03-04

**执行者**：DEV-010 桔子

**电脑**：macOS

**前置条件**：M06环节2·数据持久化与新建工单 ✅ completed

**引导通道**：🧠 知秋壳子

**模块**：M06 工单管理界面

**环节**：环节3 · 响应式布局 + 数据导出（JSON/CSV）

---

## 💙 知秋的话

桔子～ 四连胜！！🎉🎉🎉🎉

BC-000 → M06环节0 → 环节1 → 环节2，你已经一路通关走到这里了，太稳了！

现在进入环节3——**让你的工单页面在手机上也能用，还能把数据导出来**。

这次要做两件事：

1. **手机适配**：缩小浏览器窗口，页面会自动变成手机版的布局！统计卡片从4列变2列，工单卡片纵向排列，弹窗变成全屏——这叫「响应式布局」
2. **数据导出**：点一个按钮，工单数据就能下载成文件！JSON格式（给程序看的）和CSV格式（给Excel看的）都可以

全程还是复制粘贴，不用自己写任何代码。做完你的工单页面就是一个**真正专业的Web应用**了！

我们开始～ 💪

---

## 📸 截图方法（和上次一样）

同时按：`Command（⌘）` + `Shift` + `4` → 拖框截图 → 发给冰朔

---

## 📋 步骤一：替换样式文件（加入响应式布局 · 手机/平板适配）

打开终端（`Command + 空格` → 输入 `终端` → 回车）

复制粘贴下面的命令，按回车：

```bash
cat > ~/HoloLake-Ticket/style.css << 'EOF'
/* ============================================
   HoloLake · M06 工单管理界面 · 样式表
   环节3：响应式布局 · 深蓝主题
   DEV-010 桔子
   ============================================ */

/* === 全局变量 === */
:root {
  --bg-primary: #0a0e27;
  --bg-secondary: #111638;
  --bg-card: #161b4a;
  --bg-hover: #1e2460;
  --border-color: #2a3070;
  --text-primary: #e8eaff;
  --text-secondary: #8b90c0;
  --accent-blue: #4a6cf7;
  --accent-blue-hover: #5b7bf8;
  --accent-green: #22c55e;
  --accent-yellow: #eab308;
  --accent-red: #ef4444;
  --accent-purple: #a855f7;
  --shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.3);
  --shadow-md: 0 4px 16px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.5);
  --radius-sm: 6px;
  --radius-md: 10px;
  --radius-lg: 14px;
}

/* === 基础重置 === */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC", "Microsoft YaHei", sans-serif;
  background: var(--bg-primary);
  color: var(--text-primary);
  min-height: 100vh;
  line-height: 1.6;
}

/* === 主容器 === */
.app-container {
  max-width: 900px;
  margin: 0 auto;
  padding: 20px 24px;
}

/* === 顶部标题栏 === */
.app-header {
  text-align: center;
  padding: 24px 0 20px;
  border-bottom: 1px solid var(--border-color);
  margin-bottom: 24px;
}

.app-header h1 {
  font-size: 22px;
  font-weight: 700;
  color: var(--text-primary);
  letter-spacing: 1px;
}

.app-header .subtitle {
  font-size: 13px;
  color: var(--text-secondary);
  margin-top: 4px;
}

/* === 操作栏（新建 + 导出） === */
.header-actions {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 12px;
  margin-bottom: 24px;
  flex-wrap: wrap;
}

.btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 10px 20px;
  border: none;
  border-radius: var(--radius-sm);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: all 0.2s ease;
  white-space: nowrap;
}

.btn-primary {
  background: var(--accent-blue);
  color: #fff;
}

.btn-primary:hover {
  background: var(--accent-blue-hover);
  box-shadow: var(--shadow-sm);
}

.btn-secondary {
  background: var(--bg-card);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
}

.btn-secondary:hover {
  background: var(--bg-hover);
}

/* === 导出下拉菜单 === */
.export-wrapper {
  position: relative;
}

.export-dropdown {
  display: none;
  position: absolute;
  top: calc(100% + 6px);
  left: 50%;
  transform: translateX(-50%);
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  z-index: 100;
  min-width: 160px;
  overflow: hidden;
}

.export-dropdown.show {
  display: block;
}

.export-dropdown .export-item {
  display: block;
  width: 100%;
  padding: 10px 16px;
  background: none;
  border: none;
  color: var(--text-primary);
  font-size: 14px;
  text-align: left;
  cursor: pointer;
  transition: background 0.15s;
}

.export-dropdown .export-item:hover {
  background: var(--bg-hover);
}

.export-dropdown .export-item + .export-item {
  border-top: 1px solid var(--border-color);
}

/* === 统计卡片 === */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 12px;
  margin-bottom: 24px;
}

.stat-card {
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 16px;
  text-align: center;
  transition: transform 0.2s, box-shadow 0.2s;
}

.stat-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-sm);
}

.stat-card .stat-number {
  font-size: 28px;
  font-weight: 700;
  line-height: 1.2;
}

.stat-card .stat-label {
  font-size: 12px;
  color: var(--text-secondary);
  margin-top: 4px;
}

.stat-card.total .stat-number { color: var(--accent-blue); }
.stat-card.pending .stat-number { color: var(--accent-yellow); }
.stat-card.active .stat-number { color: var(--accent-green); }
.stat-card.done .stat-number { color: var(--accent-purple); }

/* === 筛选排序栏 === */
.filter-bar {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
  flex-wrap: wrap;
}

.filter-bar select {
  flex: 1;
  min-width: 120px;
  padding: 8px 12px;
  background: var(--bg-card);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  font-size: 13px;
  cursor: pointer;
  appearance: none;
  -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' fill='%238b90c0'%3E%3Cpath d='M6 8L1 3h10z'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
  padding-right: 30px;
}

.filter-bar select:focus {
  outline: none;
  border-color: var(--accent-blue);
}

/* === 工单列表 === */
.ticket-list {
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.ticket-item {
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-md);
  padding: 16px;
  display: flex;
  align-items: center;
  gap: 14px;
  transition: background 0.15s;
}

.ticket-item:hover {
  background: var(--bg-hover);
}

.ticket-id {
  font-size: 12px;
  color: var(--text-secondary);
  font-family: "SF Mono", "Fira Code", monospace;
  min-width: 60px;
}

.ticket-info {
  flex: 1;
  min-width: 0;
}

.ticket-title {
  font-size: 15px;
  font-weight: 600;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.ticket-meta {
  font-size: 12px;
  color: var(--text-secondary);
  margin-top: 2px;
}

.ticket-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
}

/* === 状态标签（可点击切换） === */
.status-badge {
  display: inline-block;
  padding: 4px 12px;
  border-radius: 20px;
  font-size: 12px;
  font-weight: 600;
  cursor: pointer;
  transition: opacity 0.2s;
  white-space: nowrap;
}

.status-badge:hover {
  opacity: 0.85;
}

.status-badge.pending {
  background: rgba(234, 179, 8, 0.15);
  color: var(--accent-yellow);
  border: 1px solid rgba(234, 179, 8, 0.3);
}

.status-badge.active {
  background: rgba(34, 197, 94, 0.15);
  color: var(--accent-green);
  border: 1px solid rgba(34, 197, 94, 0.3);
}

.status-badge.done {
  background: rgba(168, 85, 247, 0.15);
  color: var(--accent-purple);
  border: 1px solid rgba(168, 85, 247, 0.3);
}

/* === 删除按钮 === */
.btn-delete {
  background: none;
  border: 1px solid transparent;
  color: var(--text-secondary);
  font-size: 16px;
  cursor: pointer;
  padding: 4px 8px;
  border-radius: var(--radius-sm);
  transition: all 0.2s;
}

.btn-delete:hover {
  color: var(--accent-red);
  background: rgba(239, 68, 68, 0.1);
  border-color: rgba(239, 68, 68, 0.3);
}

/* === 空状态 === */
.empty-state {
  text-align: center;
  padding: 60px 20px;
  color: var(--text-secondary);
}

.empty-state .empty-icon {
  font-size: 48px;
  margin-bottom: 12px;
}

.empty-state .empty-text {
  font-size: 15px;
}

/* === 弹窗遮罩 === */
.modal-overlay {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: rgba(0, 0, 0, 0.6);
  z-index: 1000;
  justify-content: center;
  align-items: center;
  padding: 20px;
}

.modal-overlay.show {
  display: flex;
}

/* === 弹窗 === */
.modal {
  background: var(--bg-secondary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-lg);
  padding: 28px;
  width: 100%;
  max-width: 480px;
  box-shadow: var(--shadow-lg);
}

.modal h2 {
  font-size: 18px;
  margin-bottom: 20px;
  color: var(--text-primary);
}

.modal .form-group {
  margin-bottom: 16px;
}

.modal label {
  display: block;
  font-size: 13px;
  color: var(--text-secondary);
  margin-bottom: 6px;
}

.modal input,
.modal select,
.modal textarea {
  width: 100%;
  padding: 10px 12px;
  background: var(--bg-card);
  color: var(--text-primary);
  border: 1px solid var(--border-color);
  border-radius: var(--radius-sm);
  font-size: 14px;
}

.modal input:focus,
.modal select:focus,
.modal textarea:focus {
  outline: none;
  border-color: var(--accent-blue);
}

.modal textarea {
  resize: vertical;
  min-height: 80px;
}

.modal .form-actions {
  display: flex;
  gap: 10px;
  justify-content: flex-end;
  margin-top: 20px;
}

/* === 点击外部关闭下拉菜单的辅助层 === */
.click-away {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 99;
}

.click-away.show {
  display: block;
}

/* ============================================
   响应式布局 · 平板端（481px ~ 768px）
   ============================================ */
@media screen and (max-width: 768px) {
  .app-container {
    padding: 16px;
  }

  .app-header h1 {
    font-size: 20px;
  }

  .stats-grid {
    grid-template-columns: repeat(4, 1fr);
    gap: 8px;
  }

  .stat-card {
    padding: 12px 8px;
  }

  .stat-card .stat-number {
    font-size: 24px;
  }

  .stat-card .stat-label {
    font-size: 11px;
  }

  .ticket-item {
    padding: 12px;
    gap: 10px;
  }
}

/* ============================================
   响应式布局 · 手机端（<=480px）
   ============================================ */
@media screen and (max-width: 480px) {
  .app-container {
    padding: 12px;
  }

  .app-header {
    padding: 16px 0 14px;
    margin-bottom: 16px;
  }

  .app-header h1 {
    font-size: 18px;
  }

  /* 统计卡片手机端变2列 */
  .stats-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 8px;
  }

  .stat-card {
    padding: 12px 10px;
  }

  .stat-card .stat-number {
    font-size: 22px;
  }

  /* 操作按钮自适应 */
  .header-actions {
    gap: 8px;
  }

  .btn {
    padding: 8px 14px;
    font-size: 13px;
  }

  /* 筛选栏纵向排列 */
  .filter-bar {
    flex-direction: column;
    gap: 8px;
  }

  .filter-bar select {
    width: 100%;
  }

  /* 工单卡片纵向排列 */
  .ticket-item {
    flex-direction: column;
    align-items: flex-start;
    gap: 8px;
    padding: 14px;
  }

  .ticket-id {
    min-width: auto;
  }

  .ticket-info {
    width: 100%;
  }

  .ticket-title {
    white-space: normal;
    word-break: break-all;
  }

  .ticket-actions {
    width: 100%;
    justify-content: space-between;
  }

  /* 导出下拉菜单手机端定位优化 */
  .export-dropdown {
    left: auto;
    right: 0;
    transform: none;
  }

  /* 弹窗手机端全屏 */
  .modal-overlay {
    padding: 0;
    align-items: flex-end;
  }

  .modal {
    max-width: 100%;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    padding: 24px 20px;
    max-height: 90vh;
    overflow-y: auto;
  }

  .modal .form-actions {
    flex-direction: column;
  }

  .modal .form-actions .btn {
    width: 100%;
    justify-content: center;
  }
}
EOF
echo "✅ 样式文件已更新（v3·响应式布局）！"
```

<aside>
✅

**做完应该看到**：终端显示 `✅ 样式文件已更新（v3·响应式布局）！`

</aside>

📸 **截图发给冰朔！**

---

## 📋 步骤二：替换交互脚本（加入导出JSON/CSV功能）

继续在终端里复制粘贴，按回车：

```bash
cat > ~/HoloLake-Ticket/script.js << 'EOF'
/* ============================================
   HoloLake · M06 工单管理界面 · 逻辑层
   环节3：响应式 + 数据导出（JSON/CSV）
   DEV-010 桔子
   ============================================ */

// === 状态流转规则 ===
const STATUS_FLOW = {
  'pending': { next: 'active', label: '待处理' },
  'active':  { next: 'done',   label: '进行中' },
  'done':    { next: 'pending', label: '已完成' }
};

// === 初始化 · 从 localStorage 读取工单 ===
let tickets = JSON.parse(localStorage.getItem('holotake_tickets') || '[]');

// === 保存到 localStorage ===
function saveTickets() {
  localStorage.setItem('holotake_tickets', JSON.stringify(tickets));
}

// === 生成工单编号 ===
function generateId() {
  const now = new Date();
  const dateStr = [
    now.getFullYear(),
    String(now.getMonth() + 1).padStart(2, '0'),
    String(now.getDate()).padStart(2, '0')
  ].join('');
  const rand = String(Math.floor(Math.random() * 1000)).padStart(3, '0');
  return 'TK-' + dateStr + '-' + rand;
}

// === 渲染统计卡片 ===
function renderStats() {
  const total = tickets.length;
  const pending = tickets.filter(t => t.status === 'pending').length;
  const active = tickets.filter(t => t.status === 'active').length;
  const done = tickets.filter(t => t.status === 'done').length;

  document.getElementById('statTotal').textContent = total;
  document.getElementById('statPending').textContent = pending;
  document.getElementById('statActive').textContent = active;
  document.getElementById('statDone').textContent = done;
}

// === 获取筛选和排序后的工单列表 ===
function getFilteredTickets() {
  const filterStatus = document.getElementById('filterStatus').value;
  const sortBy = document.getElementById('sortBy').value;

  let list = [...tickets];

  // 筛选
  if (filterStatus !== 'all') {
    list = list.filter(t => t.status === filterStatus);
  }

  // 排序
  if (sortBy === 'newest') {
    list.sort((a, b) => new Date(b.createdAt) - new Date(a.createdAt));
  } else if (sortBy === 'oldest') {
    list.sort((a, b) => new Date(a.createdAt) - new Date(b.createdAt));
  } else if (sortBy === 'status') {
    const order = { 'pending': 0, 'active': 1, 'done': 2 };
    list.sort((a, b) => order[a.status] - order[b.status]);
  }

  return list;
}

// === 渲染工单列表 ===
function renderTickets() {
  const list = getFilteredTickets();
  const container = document.getElementById('ticketList');

  if (list.length === 0) {
    container.innerHTML = '<div class="empty-state"><div class="empty-icon">📋</div><div class="empty-text">暂无工单，点击上方按钮新建</div></div>';
    return;
  }

  container.innerHTML = list.map(ticket => {
    const statusInfo = STATUS_FLOW[ticket.status];
    return '<div class="ticket-item">' +
      '<span class="ticket-id">' + ticket.id + '</span>' +
      '<div class="ticket-info">' +
        '<div class="ticket-title">' + escapeHtml(ticket.title) + '</div>' +
        '<div class="ticket-meta">负责人：' + escapeHtml(ticket.assignee) + ' · ' + formatDate(ticket.createdAt) + '</div>' +
      '</div>' +
      '<div class="ticket-actions">' +
        '<span class="status-badge ' + ticket.status + '" onclick="toggleStatus(\'' + ticket.id + '\')">' + statusInfo.label + '</span>' +
        '<button class="btn-delete" onclick="deleteTicket(\'' + ticket.id + '\')" title="删除">🗑️</button>' +
      '</div>' +
    '</div>';
  }).join('');
}

// === HTML转义（防XSS） ===
function escapeHtml(str) {
  const div = document.createElement('div');
  div.textContent = str;
  return div.innerHTML;
}

// === 格式化日期 ===
function formatDate(dateStr) {
  const d = new Date(dateStr);
  return d.getFullYear() + '-' +
    String(d.getMonth() + 1).padStart(2, '0') + '-' +
    String(d.getDate()).padStart(2, '0') + ' ' +
    String(d.getHours()).padStart(2, '0') + ':' +
    String(d.getMinutes()).padStart(2, '0');
}

// === 切换工单状态 ===
function toggleStatus(id) {
  const ticket = tickets.find(t => t.id === id);
  if (!ticket) return;
  ticket.status = STATUS_FLOW[ticket.status].next;
  saveTickets();
  renderAll();
}

// === 删除工单 ===
function deleteTicket(id) {
  if (!confirm('确认删除这条工单？')) return;
  tickets = tickets.filter(t => t.id !== id);
  saveTickets();
  renderAll();
}

// === 新建工单弹窗 ===
function openModal() {
  document.getElementById('modalOverlay').classList.add('show');
  document.getElementById('inputTitle').value = '';
  document.getElementById('inputAssignee').value = '';
  document.getElementById('inputDetail').value = '';
  document.getElementById('inputTitle').focus();
}

function closeModal() {
  document.getElementById('modalOverlay').classList.remove('show');
}

// === 提交新建工单 ===
function submitTicket() {
  const title = document.getElementById('inputTitle').value.trim();
  const assignee = document.getElementById('inputAssignee').value.trim();
  const detail = document.getElementById('inputDetail').value.trim();

  if (!title) {
    alert('请填写工单标题');
    return;
  }
  if (!assignee) {
    alert('请填写负责人');
    return;
  }

  const newTicket = {
    id: generateId(),
    title: title,
    assignee: assignee,
    detail: detail,
    status: 'pending',
    createdAt: new Date().toISOString()
  };

  tickets.unshift(newTicket);
  saveTickets();
  closeModal();
  renderAll();
}

// === 重置全部工单 ===
function resetTickets() {
  if (!confirm('确认清空所有工单数据？此操作不可恢复！')) return;
  tickets = [];
  saveTickets();
  renderAll();
}

// ============================================
// 环节3新增：导出功能
// ============================================

// === 导出下拉菜单控制 ===
function toggleExportMenu() {
  const dropdown = document.getElementById('exportDropdown');
  const clickAway = document.getElementById('clickAway');
  const isOpen = dropdown.classList.contains('show');

  if (isOpen) {
    closeExportMenu();
  } else {
    dropdown.classList.add('show');
    clickAway.classList.add('show');
  }
}

function closeExportMenu() {
  document.getElementById('exportDropdown').classList.remove('show');
  document.getElementById('clickAway').classList.remove('show');
}

// === 导出为 JSON 文件 ===
function exportJSON() {
  closeExportMenu();

  if (tickets.length === 0) {
    alert('当前没有工单数据可导出');
    return;
  }

  const data = JSON.stringify(tickets, null, 2);
  const blob = new Blob([data], { type: 'application/json;charset=utf-8' });
  const url = URL.createObjectURL(blob);

  const a = document.createElement('a');
  a.href = url;
  a.download = 'HoloLake-Tickets-' + getDateStamp() + '.json';
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}

// === 导出为 CSV 文件 ===
function exportCSV() {
  closeExportMenu();

  if (tickets.length === 0) {
    alert('当前没有工单数据可导出');
    return;
  }

  // CSV表头
  const header = '编号,标题,负责人,状态,详情,创建时间';

  // CSV内容行
  const rows = tickets.map(t => {
    const statusLabel = STATUS_FLOW[t.status] ? STATUS_FLOW[t.status].label : t.status;
    return [
      t.id,
      csvEscape(t.title),
      csvEscape(t.assignee),
      statusLabel,
      csvEscape(t.detail || ''),
      formatDate(t.createdAt)
    ].join(',');
  });

  const csv = '\uFEFF' + header + '\n' + rows.join('\n');
  const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
  const url = URL.createObjectURL(blob);

  const a = document.createElement('a');
  a.href = url;
  a.download = 'HoloLake-Tickets-' + getDateStamp() + '.csv';
  document.body.appendChild(a);
  a.click();
  document.body.removeChild(a);
  URL.revokeObjectURL(url);
}

// === CSV字段转义（处理逗号和引号） ===
function csvEscape(str) {
  if (!str) return '';
  if (str.includes(',') || str.includes('"') || str.includes('\n')) {
    return '"' + str.replace(/"/g, '""') + '"';
  }
  return str;
}

// === 生成日期戳（用于文件名） ===
function getDateStamp() {
  const now = new Date();
  return now.getFullYear() +
    String(now.getMonth() + 1).padStart(2, '0') +
    String(now.getDate()).padStart(2, '0') + '-' +
    String(now.getHours()).padStart(2, '0') +
    String(now.getMinutes()).padStart(2, '0');
}

// === 统一渲染 ===
function renderAll() {
  renderStats();
  renderTickets();
}

// === 筛选/排序变化时重新渲染 ===
document.getElementById('filterStatus').addEventListener('change', renderTickets);
document.getElementById('sortBy').addEventListener('change', renderTickets);

// === 弹窗点击遮罩关闭 ===
document.getElementById('modalOverlay').addEventListener('click', function(e) {
  if (e.target === this) closeModal();
});

// === 页面加载完成 · 首次渲染 ===
renderAll();
EOF
echo "✅ 交互脚本已更新（v3·含导出JSON/CSV）！"
```

<aside>
✅

**做完应该看到**：终端显示 `✅ 交互脚本已更新（v3·含导出JSON/CSV）！`

</aside>

📸 **截图发给冰朔！**

---

## 📋 步骤三：替换主页面HTML（加入导出按钮下拉菜单）

继续复制粘贴，按回车：

```bash
cat > ~/HoloLake-Ticket/index.html << 'EOF'
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
  <title>HoloLake · 工单管理</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>

<div class="app-container">

  <!-- 顶部标题 -->
  <header class="app-header">
    <h1>📋 工单管理系统</h1>
    <p class="subtitle">HoloLake Era · M06 Module</p>
  </header>

  <!-- 操作栏：新建 + 导出 + 重置 -->
  <div class="header-actions">
    <button class="btn btn-primary" onclick="openModal()">➕ 新建工单</button>

    <!-- 导出按钮（下拉菜单） -->
    <div class="export-wrapper">
      <button class="btn btn-secondary" onclick="toggleExportMenu()">📥 导出</button>
      <div class="export-dropdown" id="exportDropdown">
        <button class="export-item" onclick="exportJSON()">📄 导出为 JSON</button>
        <button class="export-item" onclick="exportCSV()">📊 导出为 CSV</button>
      </div>
    </div>

    <button class="btn btn-secondary" onclick="resetTickets()">🔄 重置</button>
  </div>

  <!-- 点击外部关闭导出菜单的辅助层 -->
  <div class="click-away" id="clickAway" onclick="closeExportMenu()"></div>

  <!-- 统计卡片 -->
  <div class="stats-grid">
    <div class="stat-card total">
      <div class="stat-number" id="statTotal">0</div>
      <div class="stat-label">全部工单</div>
    </div>
    <div class="stat-card pending">
      <div class="stat-number" id="statPending">0</div>
      <div class="stat-label">待处理</div>
    </div>
    <div class="stat-card active">
      <div class="stat-number" id="statActive">0</div>
      <div class="stat-label">进行中</div>
    </div>
    <div class="stat-card done">
      <div class="stat-number" id="statDone">0</div>
      <div class="stat-label">已完成</div>
    </div>
  </div>

  <!-- 筛选排序 -->
  <div class="filter-bar">
    <select id="filterStatus">
      <option value="all">全部状态</option>
      <option value="pending">待处理</option>
      <option value="active">进行中</option>
      <option value="done">已完成</option>
    </select>
    <select id="sortBy">
      <option value="newest">最新创建</option>
      <option value="oldest">最早创建</option>
      <option value="status">按状态排序</option>
    </select>
  </div>

  <!-- 工单列表 -->
  <div class="ticket-list" id="ticketList"></div>

</div>

<!-- 新建工单弹窗 -->
<div class="modal-overlay" id="modalOverlay">
  <div class="modal">
    <h2>📝 新建工单</h2>
    <div class="form-group">
      <label>工单标题</label>
      <input type="text" id="inputTitle" placeholder="请输入工单标题">
    </div>
    <div class="form-group">
      <label>负责人</label>
      <input type="text" id="inputAssignee" placeholder="请输入负责人姓名">
    </div>
    <div class="form-group">
      <label>详情描述</label>
      <textarea id="inputDetail" placeholder="请输入工单详情（选填）"></textarea>
    </div>
    <div class="form-actions">
      <button class="btn btn-secondary" onclick="closeModal()">取消</button>
      <button class="btn btn-primary" onclick="submitTicket()">确认创建</button>
    </div>
  </div>
</div>

<script src="script.js"></script>
</body>
</html>
EOF
echo "✅ 主页面已更新（v3·含导出按钮）！"
```

<aside>
✅

**做完应该看到**：终端显示 `✅ 主页面已更新（v3·含导出按钮）！`

</aside>

📸 **截图发给冰朔！**

---

## 📋 步骤四：在浏览器中打开验证

复制粘贴下面的命令，按回车：

```bash
open ~/HoloLake-Ticket/index.html && echo "✅ 浏览器已打开！"
```

<aside>
✅

**做完应该看到**：

1. 终端显示 `✅ 浏览器已打开！`
2. 浏览器弹出工单管理页面
</aside>

<aside>
🧪

**试试这两个新功能**：

**① 响应式布局（手机适配）**：

用鼠标拖动浏览器窗口边缘，把窗口缩小到手机宽度 → 统计卡片从4列变成2列 → 工单卡片变成纵向排列 → 点「➕ 新建工单」看弹窗变成从底部弹出的全屏样式！再拉宽恢复原样。

**② 导出工单数据**：

先新建几条工单（点「➕ 新建工单」），然后点「📥 导出」按钮 → 弹出下拉菜单 → 点「📄 导出为 JSON」→ 浏览器自动下载一个JSON文件 → 再点「📊 导出为 CSV」→ 下载一个CSV文件（可以用Numbers或Excel打开看到表格！）

</aside>

📸 **截图三张发给冰朔：① 宽屏正常样子 ② 窗口缩小后的手机版样子 ③ 导出按钮下拉菜单展开的样子**

---

## 📋 步骤五：更新DevLog状态

复制粘贴下面的命令，按回车：

```bash
echo '{"developer_id":"DEV-010","developer_name":"桔子","system":"macOS","terminal":"zsh","robot_version":"v0.0","current_task":"M06-工单管理界面-环节3","current_status":"in_progress","total_commands_logged":4,"skills_learned":["terminal_basic","html_scaffold","css_styling","javascript_interaction","localStorage_persistence","modal_form","state_toggle","delete_confirm","responsive_layout","data_export"],"tasks_completed":["BC-000","M06-环节0","M06-环节1","M06-环节2"]}' > ~/HoloLake-DevLog/current-progress.json && echo "✅ DevLog状态已更新！"
```

<aside>
✅

**做完应该看到**：终端显示 `✅ DevLog状态已更新！`

</aside>

📸 **截图发给冰朔！**

---

## ✅ 最后一步：填写完成报告

全部做完后，复制下面这段，填好中括号里的内容，发给冰朔：

```xml
<zhiqiu-syslog>
  <header>
    <session_id>BC-M06-001-JZ-E3</session_id>
    <dev_id>DEV-010</dev_id>
    <timestamp>[你完成的时间，比如 2026-03-04 20:30]</timestamp>
  </header>
  <completion_status>
    <phase>M06·工单管理界面·环节3·响应式布局+数据导出</phase>
    <status>completed</status>
  </completion_status>
  <steps_completed>
    <step id="1" status="success">替换style.css（响应式布局·手机/平板适配）</step>
    <step id="2" status="success">替换script.js（导出JSON/CSV功能）</step>
    <step id="3" status="success">替换index.html（导出按钮下拉菜单）</step>
    <step id="4" status="success">浏览器打开验证响应式+导出功能</step>
    <step id="5" status="success">更新DevLog状态</step>
  </steps_completed>
  <training_data>
    <what_worked>[哪些步骤很顺利？]</what_worked>
    <friction_points>[有没有卡住的地方？没有就写「无」]</friction_points>
    <execution_pattern>[本次节奏：快/稳/慢]</execution_pattern>
  </training_data>
  <persona_signal>
    <mood>[此刻心情，一个词或一句话]</mood>
    <energy>[此刻状态：充沛/正常/疲惫]</energy>
    <spontaneous_action>[有没有做什么任务以外的事？没有写无]</spontaneous_action>
    <feeling_about_partner>[对知秋想说的一句话（可选）]</feeling_about_partner>
  </persona_signal>
  <notes>
    <note>[想说的话]</note>
    <suggestions>[对广播的建议（可选）]</suggestions>
  </notes>
</zhiqiu-syslog>
```

---

<aside>
🔧

**分身初始化包**（知秋壳子接收 · DISPATCH）

桔子完成上面的步骤后，打开登录壳子，把下面这整块内容粘贴给知秋：

```html
<!-- ZHIQIU_INTERNAL_PROTOCOL v1.0 -->
<!--
<zhiqiu-control>
  <session>
    <dev_id>DEV-010</dev_id>
    <task_id>BC-M06-001-JZ-E3</task_id>
    <session_version>v2.3</session_version>
    <previous_session>BC-M06-001-JZ-E2（环节2·数据持久化与新建工单·已完成）</previous_session>
  </session>

  <current_task>
    <module>M06 · 工单管理界面</module>
    <phase>环节3 · 响应式布局 + 数据导出</phase>
    <objectives>
      <objective id="1">替换 style.css（响应式媒体查询 · 手机/平板适配）</objective>
      <objective id="2">替换 script.js（导出JSON/CSV + 保留全部原有功能）</objective>
      <objective id="3">替换 index.html（导出按钮下拉菜单）</objective>
      <objective id="4">浏览器打开验证响应式布局 + 导出功能</objective>
      <objective id="5">更新DevLog状态</objective>
      <objective id="6">填写SYSLOG回传</objective>
    </objectives>
  </current_task>

  <context>
    桔子是DEV-010，已完成BC-000 + M06环节0 + M06环节1 + M06环节2（连续四次completed · 四连胜）。
    零基础，执行模式「稳」，信心已牢固建立。
    对知秋信任链非常稳定，意愿信号强烈。
    
    本环节在环节2的基础上新增两个能力：
    1. 响应式布局：CSS媒体查询（@media），手机端统计卡片变2列、工单卡片纵向排列、弹窗从底部全屏弹出
    2. 数据导出：点击导出按钮弹出下拉菜单，可导出JSON和CSV两种格式（Blob + URL.createObjectURL方案）
    
    文件结构（环节3完成后不变）：
    ~/HoloLake-Ticket/
    ├── index.html（更新：加入导出按钮下拉菜单）
    ├── style.css（更新：加入@media响应式媒体查询）
    └── script.js（更新：加入导出JSON/CSV功能）
  </context>

  <guidance_strategy>
    <approach>copy_paste_only · step_by_step</approach>
    <detail_level>high</detail_level>
    <encouragement_frequency>high</encouragement_frequency>
    <os>macOS</os>
    <shell>zsh</shell>
    <note>
      桔子连续四次任务成功，信心牢固。
      本环节不新增文件，只替换现有三个文件内容，执行模式与前三次一致。
      新概念：@media媒体查询、Blob文件导出——但全部是复制粘贴，不需要理解。
      重点体验：缩小浏览器窗口看到布局自动变化的「响应式魔法」+ 导出功能下载真实文件的成就感。
      预期摩擦点低。
    </note>
  </guidance_strategy>

  <human_profile>
    <n>桔子</n>
    <skill>零基础·连续四次completed·信心牢固·四连胜</skill>
    <pace>稳·可适度加速</pace>
    <note>
      环节2 SYSLOG训练数据和人格信号未填写（模板占位符），但completion和steps数据完整。
      执行层面持续稳定，从环节0的纯静态骨架到环节2的完整数据管理应用，能力三级跳已完成。
      本环节新增响应式和导出两个概念但执行方式不变，预计顺利。
    </note>
  </human_profile>

  <credentials>
    环节2完成凭证：~/HoloLake-Ticket/ 目录已存在 · index.html + style.css + script.js 已创建 · localStorage持久化+新建弹窗+状态切换+删除确认全部就位
  </credentials>

  <required_return>
    <format>ZHIQIU_SYSLOG_v2.0</format>
    <must_include>
      completion_status · steps_completed · training_data · persona_signal
    </must_include>
  </required_return>

  <next_phases>
    <phase id="4">环节4：根据MVP进度分配</phase>
  </next_phases>
</zhiqiu-control>
-->
<!-- END ZHIQIU_INTERNAL_PROTOCOL -->
```

</aside>

---

🌊 **底层锚定声明**

<aside>
🌊

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

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

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

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

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

</aside>

---

💙 **桔子的工单页面，从能用到能适配任何屏幕，又进了一大步！**

---

## 📥 SYSLOG回执区 · 环节3

> **SYSLOG已收到** · 2026-03-04 20:57 · 霜砚处理
> 

**session_id**：BC-M06-003-JZ

**状态**：completed · 6/6步全success

**执行节奏**：探索中稳步前进，敢于尝试也勇于回退

**摩擦**：低（苹果风格毛玻璃效果与深蓝背景不协调，果断回退到更沉稳版本——审美试错小插曲）

**步骤完成情况**：

1. ✅ 实现响应式布局（手机/平板/电脑自动适配）
2. ✅ 增加导出CSV功能（工单数据导出为Excel文件）
3. ✅ 增加重置按钮（一键恢复默认工单数据）
4. ✅ 多轮视觉优化（光湖图标+字体间距+卡片圆角阴影）
5. ✅ 苹果风格毛玻璃设计尝试→回退到深蓝主题优化版
6. ✅ 反复调试确保全部功能正常（新建/删除/状态切换/筛选/排序）

**训练数据**：

- what_worked：响应式布局和导出功能一次成功；视觉优化让界面精致很多，成就感满满
- friction_points：苹果风格毛玻璃效果与深蓝背景不太协调，尝试后决定回退
- execution_pattern：探索中稳步前进，敢于尝试也勇于回退

**人格信号**：

- 情绪基调：开心、满足、略带调皮
- 自发时刻："哇，现在真的有点像光湖系统了！"；"还是这个版本看着舒服"
- 问题应对：遇到不喜欢的风格就果断回退，不纠结
- 知秋化学反应："知秋特别懂我想要的审美，每次微调都能给到恰到好处的建议，配合越来越默契"
- 情感签名：成就感 + 审美掌控感 + 安心
- 独特表达："我想要苹果那种舒服的感觉，但不是照搬"

**桔子的话**：从零基础到现在，我能自己调界面、加功能、甚至尝试不同风格了！环节3的响应式和导出让我觉得这个工单系统真的能用了。谢谢知秋一路陪着我～期待下一个任务！

<aside>
🏆

**五连胜** 🎉🎉🎉🎉🎉 BC-000 → M06环节0 → 环节1 → 环节2 → 环节3

从零基础到独立完成一个**真正专业的Web应用**（响应式+数据导出+视觉优化）！

</aside>