Files
wecom_it_smart_desk/docs/02-技术文档/技术方案-REQ-集成-002-管理后台v1.2-IA重构整体.md
T
Simon facc04aa65 chore: docs 结构整改 + compose 双目录对齐(合并重建提交)
本提交为 .git 对象库损坏后的重建提交,内容等价于原先三个本地提交
(5e2fd4c2 / 57a53c98 / 5d7e1873)的累积结果,未做任何额外改动。

一、docs 结构整改(整改 #14)
根因:重构时新结构为 untracked 文件,执行 git stash(未带 -u)未纳入,
随后 git reset 拉回 HEAD 旧 tracked 树,导致旧树复活、新旧两棵目录
树并存于 docs/,共 791 文件、双分类体系冲突。

修复动作:
- b2 同名异主题文件改名迁移保全 9 个
- C 类 39 个孤立文件按主题正确归类
- A/B1 类 222 个重复文件删除(新结构已有内容副本)
- 9 个旧独有空目录删除
- 270 处内部引用按 verified 映射改写
- 整改记录 #14 登记于 04-运维文档/部署运维

结果:docs 791 → 569 文件,顶层仅规范 8 类 + 治理文件,单树恢复。
残留:约 20 处指向从未存在文件的陈旧死链,归入独立文档卫生任务。

二、compose 双目录对齐(消除踩坑 A)
- docker-compose.yml:nginx 前端挂载全部由根目录 frontend-*/dist
  改为 src/frontend-*/dist(h5 / agent / admin / terminal)
- docker-compose.dev.yml:dev 服务 build context 与卷同步改 src/
- 效果:本地 docker compose up 不再把根目录 stale dist 挂回,
  与线上一致,分叉隐患消除(已 docker compose config 校验通过)

防复发铁律:
- 重构须提交;仓库修复须 git stash -u 或先 commit
- 新结构须 git add 并提交,避免再次 untracked 复活
- H5 改动只动 src/frontend-h5/,禁改根目录遗留 frontend-*/
2026-08-07 22:31:32 +08:00

16 KiB
Raw Blame History

技术方案 — 管理后台 v1.2 IA 重构整体

版本: v1.2 | 日期: 2026-07-28 作者: 宋献 · 主理人齐活林(Qi) · 高见远(Gao) 关联 PRD: docs/01-产品文档/08-集成生态/PRD-REQ-集成-002-管理后台-v1.2.md 关联原型: docs/01-产品文档/08-集成生态/原型-REQ-集成-000-管理后台-v1.0.html 任务说明书: docs/07-项目管理/任务说明书/任务说明书-REQ-集成-002-IA重构整体.md 状态: 已实施(生产环境上线 2026-07-28)


1. 目标

把管理后台从 v1.0 的「4 组按优先级」导航重构为 v1.1 的「5 组按用户场景」结构,覆盖完整 27 项功能域(v1.0 漏列 12 项),并通过单一真源 menu.config.ts 消除「Sidebar 与 router 双重维护脱节」的历史包袱。

本次重构由 6 个阶段组成:

阶段 内容 优先级 状态
P0 三 bug 修复(router 重复 / sidebar 漏权限矩阵 / locked-menu-item 可点) P0 已上线
P1 单一真源 menu.config.ts 重构(5 组 27 项 + 子标签 + 角色过滤) P1 已上线
P1-b Dashboard 嵌入会话监控 widget(双视图:Dashboard 概览 + /monitor 详情) P1 已上线
P2-a /quick-rules-audit 顶级路由补全 P2 已上线
P2-c /quick-rules/template 路由 child 补全(复用 QuickReplies.vue P2 已上线
v1.2 分配模式 Tab 收编到坐席管理 P2 🟡 待实施

2. 整体架构

2.1 5 组导航结构(v1.1 替代 v1.0 4 组)

🟦 运营中心(7 项 · 实时作战 + 实时监控)
   运营总览 / 会话监控 / 坐席管理 / 角色管理 / 权限矩阵 / OTP 管理 / 功能开关

🟪 数据与监控(4 项 · 离线分析 + AI 指标)
   会话审计 / 坐席绩效 / 满意度评价 / 自动化指标

🟥 安全审计(5 项 · 合规追溯 + 系统安全)
   配置变更历史 / 运行期日志 / 安全审计日志 / 终端安全 / 快速回复审计日志

🟩 系统与集成(4 项 · 底层能力配置)
   系统集成 / 欢迎与引导 / 自动化场景 / 规则版本

🟪 知识与 AI(7 项 · 配置规则 + 知识沉淀 + 智能建议)
   快速回复规则 / 排查流程图 / 代答排除 / 知识迭代 / RAGFlow / 拓扑预览 / 知识库建议

2.2 单一真源架构

menu.config.ts(单一真源)
  ├─ groups[]      // 5 个分组(顺序、图标、颜色)
  ├─ items[]       // 27 项业务菜单
  ├─ quickRulesSubTabs[]  // 快速回复规则页 5 子 tab
  ├─ lockedItems[]  // 3 个 P2 折叠区占位
  └─ rolesFilter    // 按角色过滤的字段

        ↓ 派生

Sidebar.vue       // v-for 渲染,零硬编码
router/index.ts   // 从 menuConfig 派生路由(meta.menuKey 反查)

收益:新增菜单仅改 menu.ts,自动出现在 Sidebar + router + 角色过滤三处。


3. P0 三 bug 修复

3.1 router 重复注册

症状router/index.ts L87 + L201 重复注册 path: 'knowledge',后注册 Placeholder 覆盖 Knowledge.vue,导致真页面成死代码。

修复router/index.ts):

  {
    path: 'knowledge',
    name: 'Knowledge',
    component: () => import('@/views/Knowledge.vue'),
  },
+ {
+   path: 'knowledge-mgmt',  // 占位路由改名避免冲突
+   name: 'KnowledgeMgmt',
+   component: () => import('@/views/Placeholder.vue'),
+   meta: { title: '知识库管理', priority: 'P2', locked: true },
+ }

3.2 Sidebar 漏权限矩阵

症状/permissions-matrix 路由已注册但 Sidebar 完全没显示(菜单与路由双重维护脱节典型案例)。

修复P1 阶段由 menu.config.ts 自动修复,菜单项已在 menu.ts 声明)。

3.3 locked-menu-item 可点击

症状.locked-menu-item { pointer-events: auto !important } 让"开发中"灰化菜单仍可点击,误导用户。

修复Sidebar.vueglobal.css):

.locked-menu-item {
  pointer-events: none;
  cursor: not-allowed;
}

4. P1 单一真源重构

4.1 menu.config.ts 数据结构(232 行)

export const menuConfig = {
  groups: [
    { key: 'operation', label: '运营中心', icon: 'Operation', color: '#3b82f6', order: 1 },
    { key: 'monitor', label: '数据与监控', icon: 'Monitor', color: '#8b5cf6', order: 2 },
    { key: 'security', label: '安全审计', icon: 'Security', color: '#ef4444', order: 3 },
    { key: 'system', label: '系统与集成', icon: 'System', color: '#10b981', order: 4 },
    { key: 'ai', label: '知识与 AI', icon: 'AI', color: '#ec4899', order: 5 },
  ],
  items: [
    // 27 项业务菜单,每项包含 path/title/icon/groupKey/roles/badge
  ],
  quickRulesSubTabs: [
    { key: 'greeting', label: '打招呼' },
    { key: 'contacts', label: '联系人' },
    { key: 'routing', label: '路由关键词' },
    { key: 'targets', label: '路由目标' },
    { key: 'template', label: '模板', badge: 'v5 收编' },
  ],
  lockedItems: [
    { key: 'themes', label: '主题模板', stage: '阶段二' },
    { key: 'dashboard-board', label: '数据看板', stage: '阶段四' },
    { key: 'knowledge-mgmt', label: '知识库管理', stage: '阶段四' },
  ],
  rolesFilter: ['admin', 'super_admin'],
}

4.2 Sidebar.vue 重构(282 → 222 行)

<template>
  <el-menu :default-active="activeRoute">
    <template v-for="group in menuConfig.groups" :key="group.key">
      <el-sub-menu :index="group.key">
        <template #title>
          <el-icon><component :is="group.icon" /></el-icon>
          <span>{{ group.label }}</span>
        </template>
        <template v-for="item in itemsOfGroup(group.key)" :key="item.path">
          <el-menu-item :index="item.path" :disabled="isLocked(item)">
            <el-icon><component :is="item.icon" /></el-icon>
            <span>{{ item.title }}</span>
            <el-badge v-if="item.badge" :value="item.badge" class="menu-badge" />
          </el-menu-item>
        </template>
      </el-sub-menu>
    </template>
    <!-- 折叠区占位 -->
    <el-sub-menu v-for="locked in lockedItems" :key="locked.key" class="locked-menu-item">
      ...
    </el-sub-menu>
  </el-menu>
</template>

4.3 图标替换诚实记录

原计划 实际使用 原因
Shield(终端安全) Warning 与原 sidebar 一致
Bulb(知识库建议) Sunny element-plus/icons-vue@2.3.0 不存在 Bulb

5. P1-b Dashboard 嵌入会话监控 widget

5.1 双视图设计(Datadog/Grafana 同款)

Dashboard 概览(/admin/dashboard
  ├─ 第一行 4 张核心 KPI(在线坐席/今日会话/平均响应/AI 命中率)
  ├─ 第二行 3 张副卡(进行中/等待中/异常告警)
  ├─ 🆕 实时会话 Top 5 widget
  ├─ 待处理事项
  └─ 系统健康

会话监控详情(/admin/monitor)
  └─ 完整会话表格 + 详情

5.2 Dashboard.vue 单文件实现

<template>
  <!-- 原有 KPI + 待处理 + 系统健康 -->
  
  <!-- 🆕 实时会话监控 widget -->
  <div class="monitor-widget">
    <div class="widget-header">
      <h3>实时会话监控</h3>
      <el-button text @click="$router.push('/admin/monitor')">
        查看全部 
      </el-button>
    </div>
    
    <div class="monitor-stats">
      <StatCard label="进行中" :value="monitorStats.in_progress" color="green" />
      <StatCard label="等待中" :value="monitorStats.queued" color="yellow" />
      <StatCard label="今日已结" :value="monitorStats.resolved_today" color="blue" />
      <StatCard label="异常告警" :value="monitorStats.alerts" color="red" />
    </div>
    
    <el-table :data="monitorSessions.slice(0, 6)" stripe>
      <el-table-column prop="session_id" label="会话 ID" />
      <el-table-column prop="employee_name" label="员工" />
      <el-table-column prop="agent_name" label="坐席" />
      <el-table-column label="持续时长">
        <template #default="{ row }">{{ formatDuration(row.started_at) }}</template>
      </el-table-column>
      <el-table-column label="状态">
        <template #default="{ row }">
          <el-tag :type="getSessionStatusTag(row.status)">
            {{ getSessionStatusText(row.status) }}
          </el-tag>
        </template>
      </el-table-column>
    </el-table>
  </div>
</template>

<script setup lang="ts">
import { getMonitorSessions } from '@/api/admin'
import type { MonitorSession, MonitorStats } from '@/types'

const monitorStats = reactive<MonitorStats>({
  in_progress: 0, queued: 0, resolved_today: 0, alerts: 0,
})
const monitorSessions = ref<MonitorSession[]>([])

onMounted(async () => {
  try {
    const res = await getMonitorSessions()
    Object.assign(monitorStats, res.stats)
    monitorSessions.value = res.items
  } catch (err) {
    // Demo 兜底(与 Monitor.vue 一致)
    monitorStats.in_progress = 5
    monitorStats.queued = 0
    monitorStats.resolved_today = 42
    monitorStats.alerts = 1
    monitorSessions.value = [...]
  }
})

// 复用 formatDuration / getSessionStatusTag / getSessionStatusText
</script>

5.3 API 设计

// api/admin.ts
export async function getMonitorSessions(params?: { limit?: number }) {
  return request<MonitorSessionsData>({
    url: '/admin/monitor/sessions',
    method: 'get',
    params,
  })
}

export interface MonitorStats {
  in_progress: number
  queued: number
  resolved_today: number
  alerts: number
}

export interface MonitorSession {
  session_id: string
  employee_name: string
  agent_name: string
  status: 'pending' | 'active' | 'resolved' | 'abandoned'
  started_at: string
}

export interface MonitorSessionsData {
  stats: MonitorStats
  items: MonitorSession[]
}

6. P2-a /quick-rules-audit 顶级路由补全

6.1 修复内容(router/index.ts

{
  // 快速回复审计日志(v1.1 从快速回复规则组移至安全审计组)
  path: 'quick-rules-audit',
  name: 'QuickRulesAuditLog',
  component: () => import('@/views/quick-rules/audit.vue'),
  meta: { title: '快速回复审计日志', requiresAuth: true },
},

复用现有 views/quick-rules/audit.vuename 与嵌套 QuickRulesAudit 区分避免冲突。


7. P2-c /quick-rules/template 路由 child 补全

7.1 修复内容(router/index.ts L219-225

{
  // 快速回复模板(v5 收编:菜单已配于 menu.ts L197;复用旧 QuickReplies.vue 组件)
  path: 'quick-rules/template',
  name: 'QuickRulesTemplate',
  component: () => import('@/views/QuickReplies.vue'),
  meta: { title: '快速回复模板', requiresAuth: true },
},

复用现有 views/QuickReplies.vue(独立页面组件,用全局 quickReplyStore Pinia 单例,无父级 route props 依赖,安全作 route component)。


8. v1.2 分配模式 Tab 收编(待实施)

8.1 路由改造

- {
-   path: 'assignment-mode',
-   name: 'AssignmentMode',
-   component: () => import('@/views/AssignmentMode.vue'),
-   meta: { title: '消息分配模式', priority: 'P1' },
- },

8.2 Agents.vue 双 Tab 改造

<el-tabs v-model="activeTab" class="agents-tabs">
  <el-tab-pane label="坐席列表" name="agents-list">
    <!--  toolbar + AgentTable + 三个 dialog -->
  </el-tab-pane>
  <el-tab-pane label="分配策略" name="agents-assignment">
    <div class="page-desc">
      当前坐席 {{ agentStore.agents.length }} ...
    </div>
    <div class="mode-card" v-for="mode in modes" :key="mode.id">
      <!-- 复用 AssignmentMode.vue  mode-card -->
    </div>
  </el-tab-pane>
</el-tabs>

8.3 旧页面保留

views/AssignmentMode.vue 保留但停用(不删除),方便阶段二/三分配策略膨胀时回滚为独立页。

8.4 后端零改动

仍走 system_configs.assignment_mode + GET/PUT /api/admin/assignment-mode


9. 关键技术决策

# 决策 理由
1 单一真源 menu.config.ts 消除「Sidebar 与 router 双重维护脱节」;新增菜单仅改 1 文件
2 5 组按用户场景分组v1.1 v1.0 按优先级分组不符合实际运营场景;安全审计类功能分散
3 快速回复审计移出知识与 AI 组 审计类功能属安全合规范畴,不应混杂在业务规则配置组
4 终端安全移入安全审计组 终端安全是合规管控(联软/火绒),与底层引擎集成性质不同
5 快速回复模板作为 tab 收编 与快速回复规则同属「快速回复」业务的不同维度
6 Dashboard widget + /monitor 双视图 用户进 Dashboard 即见实时状态,无需跳转(Datadog/Grafana 同款)
7 P2 路由补全复用既有组件 audit.vue 与 QuickReplies.vue 已有完整实现,避免重复
8 v1.2 分配模式 Tab 收编可逆 旧 AssignmentMode.vue 保留文件 + 回滚步骤文档化

10. 风险与回滚

风险 概率 影响 缓解
menu.config.ts 数据错误导致菜单错位 用户困惑 双重核对(sidebar 渲染 + 路由可达)
子标签 / 角色过滤边界 普通管理员看到超管菜单 menuConfig.rolesFilter 字段校验
Tab 切换时数据二次请求 网络浪费 watch(activeTab) 懒加载或一次性加载
旧页面成僵尸代码 维护混淆 注释中写明"vX.Y 起停用,仅供回滚"
阶段二/三分配模式复杂化需拆回 需重构 已记录回滚步骤,30 分钟内可逆

回滚预案

  • 服务器 /tmp/dist-template-deploy.zip + /tmp/dist-widget.zip(最近 2 个 dist 包,保留 7 天)
  • 各版本 dist md5 在 memory 记录中可查(af00cf1c→a0c93a6c→6e8933dd→6f261495

11. 验证清单(端到端三层验证)

11.1 代码层

  • 工程师 IS_PASS: YES(单文件最小变更)
  • 主理人 Grep 核对:行号 / 函数名 / 引用一致
  • QA 工程师六维回归 PASS(路由 entry / 组件独立 / name 唯一 / menu 对齐 / 无回归 / build 成功)

11.2 Build 层

  • npm run build 成功(9.57s, 2409 modules transformed, 51 chunks
  • dist 总大小 3.32 MB / 89 文件
  • 主 chunk index-Dnr-1RNY.js (1173 KB)

11.3 部署层

  • 主机 dist md5 ↔ 服务器挂载目录 md5 一致
  • curl http://localhost/itadmin/ HTTP 200
  • 关键路由抽查:dashboard / monitor / quick-rules / quick-rules-audit / quick-rules/template / permissions-matrix 全部 200
  • nginx bind mount 源路径:/opt/wecom-it-desk/frontend-admin/dist/带 dist 后缀

11.4 浏览器层

  • 用户登录后 1-click 视觉确认(agent-browser 自动化 / 人工)
  • 关键页面:Dashboard 会话监控 widget / 5 组导航 / 快速回复模板 tab

12. 变更日志

版本 日期 变更 变更人 影响范围
v1.0 2026-07-28 12:00 初版(仅覆盖分配模式 Tab 收编) 宋献 §8
v1.1 2026-07-28 13:00 扩展为 IA 重构整体(P0+P1+P2 宋献 §1-§7, §9-§11
v1.2 2026-07-28 16:30 三件套对齐,标记「分配模式 Tab 收编」为待实施 宋献 §8 标记 🟡 待实施

13. 关联文档

  • PRD v1.2docs/01-产品文档/08-集成生态/PRD-REQ-集成-002-管理后台-v1.2.md
  • 任务说明书 v1.2docs/07-项目管理/任务说明书/任务说明书-REQ-集成-002-IA重构整体.md
  • 原型 HTMLdocs/01-产品文档/08-集成生态/原型-REQ-集成-000-管理后台-v1.0.htmlv1.1 已同步更新)
  • 整改记录docs/04-运维文档/部署运维/00-文档规范化整改记录.md(新增整改 #6 - IA 重构三件套补齐)
  • memory 日志D:\资料\03-项目开发\wecom_it_smart_desk\.workbuddy\memory\2026-07-28.mdIA 重构全过程记录)