diff --git a/docs/AI文档模板系统-像素级任务清单-20260701.md b/docs/AI文档模板系统-像素级任务清单-20260701.md
index 419df4a..c287c35 100644
--- a/docs/AI文档模板系统-像素级任务清单-20260701.md
+++ b/docs/AI文档模板系统-像素级任务清单-20260701.md
@@ -57,9 +57,9 @@
| 038 | 实现预览区 shell 组件(A4 纸效果) | 前端 | 0.25d | ✅ 已完成 |
| 039 | 实现结构树 shell 组件 | 前端 | 0.25d | ✅ 已完成 |
| | **左侧菜单** | | | |
-| 040 | 菜单数据模型(JSON)+ 子菜单配置 | 前端 | 0.25d | ⏳ 未完成 |
-| 041 | 菜单展开/收起交互 + 箭头旋转动画 | 前端 | 0.25d | ⏳ 未完成 |
-| 042 | 菜单选中高亮(蓝色左侧竖条) | 前端 | 0.25d | ⏳ 未完成 |
+| 040 | 菜单数据模型(JSON)+ 子菜单配置 | 前端 | 0.25d | ✅ 已完成 |
+| 041 | 菜单展开/收起交互 + 箭头旋转动画 | 前端 | 0.25d | ✅ 已完成 |
+| 042 | 菜单选中高亮(蓝色左侧竖条) | 前端 | 0.25d | ✅ 已完成 |
| | **顶部工具栏** | | | |
| 043 | 面包屑导航渲染 | 前端 | 0.25d | ⏳ 未完成 |
| 044 | 模板名称 + 版本号标签展示 | 前端 | 0.25d | ⏳ 未完成 |
diff --git a/docs/tasks/task_detail_2026_07_01.md b/docs/tasks/task_detail_2026_07_01.md
index 57e4ad8..b35176f 100644
--- a/docs/tasks/task_detail_2026_07_01.md
+++ b/docs/tasks/task_detail_2026_07_01.md
@@ -72,3 +72,14 @@
6. 新增 `StructureTree.vue`,实现结构树 shell。
7. 增加 `/templates/:id/edit` 路由,并执行前端构建和浏览器 DOM 检查。
- **执行结果**: 完成任务 034-039,编辑页基础四栏工作台已可访问。
+
+## 会话 ID: 20260701-left-menu
+- [2026-07-01 20:35:45]
+- **执行原因**: 按任务清单继续完成左侧菜单 040-042。
+- **执行过程**:
+ 1. 将左侧菜单改为 JSON 数据模型驱动,包含一级菜单和子菜单配置。
+ 2. 增加分组展开/收起状态,点击一级菜单可切换子菜单显示。
+ 3. 增加箭头旋转动画,展示当前展开状态。
+ 4. 增加选中状态管理,子菜单选中后显示高亮样式。
+ 5. 执行前端构建和浏览器交互检查。
+- **执行结果**: 完成任务 040-042,左侧菜单支持数据驱动、展开收起与选中高亮。
diff --git a/frontend/src/components/TemplateEdit/LeftMenu.vue b/frontend/src/components/TemplateEdit/LeftMenu.vue
index 3088570..0e4314b 100644
--- a/frontend/src/components/TemplateEdit/LeftMenu.vue
+++ b/frontend/src/components/TemplateEdit/LeftMenu.vue
@@ -1,10 +1,65 @@
@@ -15,25 +70,39 @@ import {
@@ -71,11 +140,8 @@ import {
padding: 8px 0;
}
-.menu-group-title {
- padding: 8px 16px 4px;
- color: var(--c-text-muted);
- font-size: 11px;
- font-weight: 500;
+.menu-group {
+ margin-bottom: 2px;
}
.menu-item {
@@ -92,6 +158,12 @@ import {
text-align: left;
}
+.menu-item span {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
.menu-item:hover {
color: var(--c-text);
background: var(--c-surface-soft);
@@ -113,4 +185,63 @@ import {
background: var(--c-primary);
content: "";
}
+
+.menu-arrow {
+ margin-left: auto;
+ color: var(--c-text-muted);
+ font-size: 12px;
+ transition: transform 0.18s ease;
+}
+
+.menu-arrow.open {
+ transform: rotate(180deg);
+}
+
+.sub-menu {
+ max-height: 0;
+ overflow: hidden;
+ transition: max-height 0.22s ease;
+}
+
+.sub-menu.open {
+ max-height: 220px;
+}
+
+.sub-menu-item {
+ display: flex;
+ align-items: center;
+ width: 100%;
+ gap: 8px;
+ padding: 6px 16px 6px 32px;
+ border: 0;
+ color: var(--c-text-muted);
+ background: transparent;
+ cursor: pointer;
+ text-align: left;
+}
+
+.sub-menu-item:hover {
+ color: var(--c-text);
+ background: var(--c-surface-soft);
+}
+
+.sub-menu-item.active {
+ color: var(--c-primary);
+ font-weight: 500;
+}
+
+.sub-dot {
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--c-text-muted);
+}
+
+.sub-dot.done {
+ background: var(--c-success);
+}
+
+.sub-dot.empty {
+ background: var(--c-border);
+}