44e77dcb0e
**重构前**(旧编号 02-11): - docs/02-产品需求/ → 00 产品规划/PRD - docs/03-技术架构/ → 01-05 子目录散落 - docs/04-原型设计/ → 01-02 产品设计(HTML 原型) - docs/05-原型设计/ → screens/ - docs/06-测试素材/ → 02-E2E / 03-功能 / 04-版本测试 - docs/07-项目管理/ → 任务说明书/日报/计划 - docs/08-安全审计/ → 审计报告 - docs/09-堡垒运维/ → toolbox / deploy - docs/10-项目管理/ → 任务说明书(重复) - docs/11-历史归档/ → deploy-nas-archived **重构后**(新编号 00-07,语义化): - docs/00-产品开发流程与文档管理规范.md - docs/00-版本迭代总览.md - docs/01-产品文档/ (PRD/原型/认证/会话/AI 服务/坐席/集成) - docs/02-技术文档/ (技术方案/架构图/重构记录/前端改造/实现配置) - docs/03-测试文档/ (E2E/功能用例/版本报告/缺陷单) - docs/04-运维文档/ (部署运维/运维指南) - docs/05-运营文档/ (品牌推广/用户手册) - docs/06-安全审计/ (审计报告) - docs/07-项目管理/ (任务说明书/日报/计划/看板) **净收益**: - 目录编号与产品文档管理规范对齐(按文档阶段 01-07 编号) - 消除 02-产品需求 与 10-项目管理 的编号重叠 - 子目录按文档类型分组(如 01-产品文档/00-产品规划、01-产品文档/01-认证与登录) - 把运维/安全/项目管理从 0X 散落改为 04/06/07 合计 494 文件 + 78495 行 / - 14076 行
29 KiB
29 KiB
技术方案 - H5 自助诊断面板优化
REQ 编号: REQ-用户-007 版本: v1.0-Live(前端 v2.3.6 已上线,2026-07-29) 日期: 2026-07-29 作者: 宋献 (Simon) + Duckula 依赖 PRD:
docs/01-产品文档/05-用户端H5/PRD-REQ-用户-007-自助诊断面板优化-v1.0.md(11 项决策全部锁定,必须遵守) 依赖代码(v2.3.6 生产部署版):
src/frontend-h5/src/components/assistant/SelfDiagnosis.vue:内部仅 6 Tab + 内容区;通过defineExpose暴露 6 APIsrc/frontend-h5/src/components/assistant/QueueWaiting.vue:v3.0 + 新增showPosition?: booleanpropsrc/frontend-h5/src/components/assistant/RightPanel.vue:v2.3.6 —— accordion-item__header 含 actions 区 + queue-section margin-top: autosrc/frontend-h5/src/views/ChatView.vue:移动端showRightPanel = !isMobile || isMobilePanelOpen
一、现状分析
1.1 现有代码事实(v2.3.6 已核验)
| 文件 | 行号 | 现状 | v2.3.6 后状态 |
|---|---|---|---|
SelfDiagnosis.vue |
1-109 | 仅渲染 6 Tab + 内容区,无独立标题栏 | ✅ v2.3.3 移除 .self-diag__header |
SelfDiagnosis.vue |
478-512 | tabs computed 返回 { key, label, shortLabel, badge } |
✅ v2.3.2 补 shortLabel 字段 |
SelfDiagnosis.vue |
492-494 | currentTab computed 自动过滤 |
✅ 切 Tab 行为天然 |
SelfDiagnosis.vue |
524-544 | totalBadge / totalBadgeByLevel / showCelebrate |
✅ v2.3 新增全局徽章 3 computed |
SelfDiagnosis.vue |
683-690 | defineExpose 暴露 6 API |
✅ v2.3.3 暴露给 RightPanel |
SelfDiagnosis.vue |
616-628 | diagItemIcon(item) 函数 |
✅ v2.3.4 根据 label 推断 emoji |
SelfDiagnosis.vue |
631-646 | runSingleDiag(item) 统一方法 |
✅ v2.3.4 diagItem 走 runSingle,riskItem 模拟 800ms |
RightPanel.vue |
64-102 | accordion-item__header 含 🩺 + 标题 + actions 区 | ✅ v2.3.3 外层 actions 区 |
RightPanel.vue |
100 | <SelfDiagnosis ref="selfDiagRef" /> |
✅ v2.3.3 通过 ref 读取数据 |
RightPanel.vue |
128-166 | queue-header-v3 + v-else "未排队" 占位 | ✅ v2.3.6 加入未排队占位 |
RightPanel.vue |
533-539 | .right-panel__queue-section { margin-top: auto } |
✅ v2.3.6 始终置底 |
RightPanel.vue |
330-339 | .right-panel { overflow-y: auto } |
✅ v2.3.5 内容自适应 |
RightPanel.vue |
355-358 | .accordion-item--active 无 max-height |
✅ v2.3.5 删除 80% 限制 |
QueueWaiting.vue |
- | 新增 showPosition?: boolean prop |
✅ 默认 true(向后兼容) |
ChatView.vue |
- | isMobilePanelOpen ref + 顶部双按钮 |
✅ v2.3 移动端 toggle |
ChatView.vue |
- | showRightPanel = !isMobile || isMobilePanelOpen |
✅ 默认左对话 |
1.2 关键约束(v2.3.6 已锁定)
- 无后端改动:所有数据由现有 WS 推送 + polling
- 桌面端不变:≥500px 维持双栏,不会引入新开关
- Tab 切换天然:当前
currentTabcomputed 已天然过滤,无需改 SelfDiagnosis.vue 核心逻辑 - 异常计数规则:warning(W) + danger/fail(F) 计入;normal + pending(接入中) + idle(未执行) 不计入
- 标题栏位置:v2.3.3 起在 RightPanel 外层,不再保留 SelfDiagnosis 内部标题栏
- 检测按钮:v2.3.4 起每条诊断项都有独立
检测按钮;diags 走真实检测,risks 模拟 800ms - 内容高度:v2.3.5 起内容自然撑开 + 右栏整体可滚动;删除 max-height: 80%
- 排队位置:v2.3.6 起
margin-top: auto+ 未排队占位
二、目标与范围
2.1 改造目标
| 目标 | 衡量标准 |
|---|---|
| 标题栏异常徽章分级显示 | 5W · 0F 双胶囊可见,hover 提示详情 |
| 6 Tab 横向并排 | 桌面端 375px-720px 范围等分布局,激活态有蓝色下划线 |
| 移动端 toggle 切换 | 默认左对话,点击 🩺 切到右栏,桌面端不受影响 |
| queue 标题栏合并 | position/LV/分 内联展示,queue-content 仅保留答题区 |
| 异常 + 接入混合 | M1 双角标并存,红数字 + 灰斜体 |
2.2 改造范围
- 前端 3 文件改动(ChatView.vue / QueueWaiting.vue / RightPanel.vue)
- 1 文件补充(SelfDiagnosis.vue 加全局总徽章 computed)
- 无后端 API 变更
- 无数据库迁移
- 无依赖升级
三、架构设计
3.1 组件依赖图
ChatView.vue
├─ ChatPanel.vue (左对话)
└─ RightPanel.vue (右栏)
├─ SelfDiagnosis.vue (本次:新增 totalBadge)
│ ├─ 诊断 chips(执行类)
│ └─ 风险项手风琴(展示类)
├─ DynamicRecommend.vue (智能推荐)
└─ QueueWaiting.vue (本次:新增 showPosition prop)
├─ position-card (when showPosition !== false)
└─ quiz-section (always)
3.2 状态管理
| 状态 | 位置 | 说明 |
|---|---|---|
isMobile |
ChatView.vue | computed: windowWidth < 500(已有) |
isMobilePanelOpen |
ChatView.vue | 新增,ref: false,默认显示左对话 |
activeTabKey |
SelfDiagnosis.vue | ref: 'network'(已有) |
totalBadge |
SelfDiagnosis.vue | 新增,computed: 各 Tab badge 求和 |
isQueueExpanded |
RightPanel.vue | ref(已有) |
isQuizExpanded |
RightPanel.vue | ref(已有) |
四、ChatView.vue 改动(移动端 toggle)
4.1 模板改动
<template>
<div class="chat-view">
<!-- 现有左栏 -->
<div class="chat-view__left" :class="...">
<ChatPanel />
</div>
<!-- 新增:移动端 toggle 切换按钮(仅 isMobile 时显示) -->
<div v-if="isMobile" class="chat-view__mobile-toggle">
<button
:class="['mobile-toggle__btn', { 'mobile-toggle__btn--active': !isMobilePanelOpen }]"
@click="isMobilePanelOpen = false"
>
💬 对话
</button>
<button
:class="['mobile-toggle__btn', { 'mobile-toggle__btn--active': isMobilePanelOpen }]"
@click="isMobilePanelOpen = true"
>
🩺 信息
</button>
</div>
<!-- 现有拖拽手柄(仅桌面端) -->
<div v-if="showRightPanel && !isMobile" class="chat-view__resize-handle" ...>
...
</div>
<!-- 右栏显示条件改造 -->
<div v-if="showRightPanel" class="chat-view__right">
<RightPanel />
</div>
</div>
</template>
4.2 脚本改动
// 新增 state
const isMobilePanelOpen = ref(false) // 默认 false 显示左对话
// 改造 showRightPanel
const showRightPanel = computed(() => {
if (!isMobile.value) return true // 桌面端始终显示
return isMobilePanelOpen.value // 移动端由 toggle 控制
})
4.3 样式改动
.chat-view__mobile-toggle {
display: flex;
gap: 6px;
padding: 8px 12px;
background: var(--bg-secondary);
border-bottom: 1px solid var(--border-color);
}
.mobile-toggle__btn {
flex: 1;
height: 36px;
border: 1px solid var(--border-color);
border-radius: 18px;
background: var(--bg-primary);
color: var(--text-secondary);
font-size: 13px;
font-weight: 600;
cursor: pointer;
font-family: inherit;
transition: all 0.2s;
}
.mobile-toggle__btn--active {
background: var(--accent);
color: #fff;
border-color: var(--accent);
}
4.4 关注点
- 切换时不卸载左对话/右栏 DOM(用 v-show),保留滚动位置和输入文本
- 桌面端 ≥500px 时整个
.chat-view__mobile-toggle不显示(v-if="isMobile") - 移动端切到右栏时,左对话用
v-show="!isMobilePanelOpen"隐藏(不卸载)
五、SelfDiagnosis.vue 改动(v2.3.6 实际形态:仅渲染 Tab + 内容区)
5.1 v2.3.6 形态概览
v2.3.3 起 SelfDiagnosis.vue 内部不再保留 .self-diag__header 标题栏,组件仅负责:
- 6 Tab 横向并排
- 当前 Tab 的内容区(统一
.diag-item模板) - 通过
defineExpose暴露 6 API 给外层 RightPanel 读取数据/触发方法
5.2 模板(最终版)
<template>
<div class="self-diag">
<!-- REQ-用户-007 v2.3.3: 自助诊断标题栏(🩺 自助诊断 + [5W·0F] + ⚙ 一键诊断)移至外层 RightPanel.vue accordion-item__header -->
<!-- 本组件仅渲染 6 Tab + 内容区 -->
<!-- 6 Tab 横向并排标签页 -->
<div class="self-diag__tabs">
<button
v-for="tab in tabs"
:key="tab.key"
class="tab-btn"
:class="{
'tab-btn--active': activeTabKey === tab.key,
'tab-btn--has-abnormal': tab.badge > 0
}"
@click="activeTabKey = tab.key"
>
<span class="tab-btn__label">{{ tab.shortLabel || tab.label }}</span>
<span v-if="tab.badge > 0" class="tab-btn__badge">{{ tab.badge }}</span>
</button>
</div>
<!-- Tab 内容区(统一 .diag-item 模板) -->
<div class="self-diag__content">
<div class="diag-list">
<!-- 诊断条目(网络/系统 Tab) -->
<div
v-for="item in currentTab.diagItems"
:key="item.key"
class="diag-item"
:class="`diag-item--${diagStatusLevel(item.status)}`"
>
<div class="diag-item__header" @click="toggleDiag(item.key)">
<span class="diag-item__icon">{{ diagItemIcon(item) }}</span>
<span class="diag-item__name">{{ item.label }}</span>
<span class="diag-item__status" :class="`diag-item__status--${diagStatusLevel(item.status)}`">
{{ diagStatusText(item.status) }}
</span>
<button
class="diag-item__btn"
:disabled="item.status === 'running'"
@click.stop="runSingleDiag(item)"
>
{{ item.status === 'running' ? '...' : '检测' }}
</button>
<span class="diag-item__toggle">{{ diagExpanded.has(item.key) ? '▴' : '▾' }}</span>
</div>
<div v-if="diagExpanded.has(item.key) && item.detail" class="diag-item__detail">
{{ item.detail }}
</div>
</div>
<!-- 风险条目(账号/设备/安全/合规 Tab) -->
<div
v-for="item in currentTab.riskItems"
:key="item.key"
class="diag-item"
:class="[
`diag-item--${item.level}`,
{ 'diag-item--pending': item.level === 'pending' }
]"
>
<div class="diag-item__header" @click="toggleRiskItem(item.key)">
<span class="diag-item__icon">{{ item.icon }}</span>
<span class="diag-item__name">{{ item.label }}</span>
<span class="diag-item__status" :class="`diag-item__status--${item.level}`">
{{ item.statusText }}
</span>
<button
class="diag-item__btn"
:disabled="item.level === 'pending'"
@click.stop="runSingleDiag(item)"
>
检测
</button>
<span class="diag-item__toggle">{{ expandedRisks.has(item.key) ? '▴' : '▾' }}</span>
</div>
<div v-if="expandedRisks.has(item.key)" class="diag-item__detail">
<div v-for="line in item.detailLines" :key="line" class="detail-line">{{ line }}</div>
<button v-if="item.actionText" class="diag-item__action" @click.stop="handleRiskAction(item)">
{{ item.actionText }}
</button>
</div>
</div>
</div>
</div>
</div>
</template>
5.3 脚本关键改动
5.3.1 tabs computed 必须返回 shortLabel(v2.3.2 修复)
const tabs = computed(() => {
return tabsData.value.map(tab => {
const diagAbnormal = tab.diagItems.filter(
d => d.status === 'warning' || d.status === 'fail'
).length
const riskAbnormal = tab.riskItems.filter(
r => r.level === 'warning' || r.level === 'danger'
).length
return {
key: tab.key,
label: tab.label,
shortLabel: tab.shortLabel, // ← v2.3.2 关键修复:不可漏此字段
badge: diagAbnormal + riskAbnormal,
}
})
})
踩坑:v2.3 初始版 computed 仅返回 { key, label, badge },模板 fallback tab.shortLabel || tab.label 在 undefined 时静默回退到完整名(如"系统软件"),无报错。
5.3.2 全局总徽章 3 computed(v2.3 新增)
const totalBadge = computed(() => {
return tabs.value.reduce((sum, t) => sum + (t.badge || 0), 0)
})
const totalBadgeByLevel = computed(() => {
let w = 0, f = 0
tabsData.value.forEach(tab => {
w += tab.diagItems.filter(d => d.status === 'warning').length
w += tab.riskItems.filter(r => r.level === 'warning').length
f += tab.diagItems.filter(d => d.status === 'fail').length
f += tab.riskItems.filter(r => r.level === 'danger').length
})
return { w, f }
})
const showCelebrate = computed(() =>
totalBadgeByLevel.value.w === 0 && totalBadgeByLevel.value.f === 0
)
5.3.3 状态映射函数(v2.3.4)
/** DiagStatus → RiskLevel 映射(共用样式) */
function diagStatusLevel(status: DiagStatus): RiskLevel {
switch (status) {
case 'idle': return 'pending'
case 'running': return 'pending'
case 'pass': return 'normal'
case 'warning': return 'warning'
case 'fail': return 'danger'
}
}
/** 根据 DiagItem 推断 emoji 图标 */
function diagItemIcon(item: DiagItem): string {
const text = item.label
if (text.includes('网络') || text.includes('连通') || text.includes('网关')) return '🌐'
if (text.includes('DNS') || text.includes('解析')) return '📡'
if (text.includes('证书') || text.includes('SSL')) return '🔒'
// ... 8 类推断规则
return '📋'
}
5.3.4 单条检测统一方法(v2.3.4)
async function runSingleDiag(item: DiagItem | RiskItem) {
// RiskItem: 模拟 800ms 后恢复
if ('level' in item) {
const original = item.statusText
const originalLevel = item.level
item.statusText = '检测中...'
item.level = 'pending'
await new Promise(r => setTimeout(r, 800))
item.statusText = original
item.level = originalLevel
return
}
// DiagItem: 走原有 runSingle 流程
await runSingle(item.key)
}
5.3.5 defineExpose 暴露 6 API(v2.3.3 关键)
defineExpose({
totalBadge, // 全部 Tab badge 之和
totalBadgeByLevel, // { w: number, f: number }
showCelebrate, // 0 异常达标动画触发
isRunningAll: isRunningAllExport,
hasAnyDiagItems,
runAllDiag,
})
5.4 样式关键改动
- 删除
.self-diag__header整块(标题栏已迁至外层) - 保留
.self-diag__tabs(6 Tab 横向并排,高度 30px) - 新增
.diag-list/.diag-item/.diag-item__btn(统一条目模板 + 检测按钮) - 删除
.risk-list/.risk-item(已并入.diag-item)
六、QueueWaiting.vue 改动(新增 showPosition prop)
6.1 脚本改动
const props = defineProps<{
showQuiz?: boolean
showPosition?: boolean // 新增,默认 true(向后兼容)
}>()
6.2 模板改动
<template>
<div class="queue-waiting">
<!-- 改造:position-card 是否渲染由 showPosition 控制 -->
<div
v-if="props.showPosition !== false"
class="queue-waiting__position-card"
:class="positionCardClass"
>
...
</div>
<!-- 答题区始终渲染 -->
<div v-show="props.showQuiz" class="queue-waiting__quiz-section">
...
</div>
</div>
</template>
6.3 关注点
- 默认
showPosition为true(向后兼容现有调用方) RightPanel.vue传:show-position="false"即可隐藏位置卡片props.showPosition !== false是宽松判断(兼容 undefined 视为 true)
七、RightPanel.vue 改动(v2.3.6 完整形态:4 节累计 v2.3.3 + v2.3.5 + v2.3.6)
7.1 模板(最终版)
<template>
<div class="right-panel">
<!-- 手风琴区域 -->
<div class="right-panel__accordion">
<!-- ① 设备信息(默认折叠) -->
<div class="accordion-item" :class="{ 'accordion-item--active': activeAccordion === 'device' }">
<div class="accordion-item__header" @click="toggleAccordion('device')">
<span class="accordion-item__icon">💻</span>
<span class="accordion-item__title">设备信息</span>
<span class="accordion-item__toggle">{{ activeAccordion === 'device' ? '▾' : '▸' }}</span>
</div>
<div v-show="activeAccordion === 'device'" class="accordion-item__content">
<BasicInfoCard />
</div>
</div>
<!-- ② 自助诊断(默认折叠)—— v2.3.3: 标题栏含徽章+一键诊断 -->
<div class="accordion-item" :class="{ 'accordion-item--active': activeAccordion === 'diagnosis' }">
<div class="accordion-item__header" @click="toggleAccordion('diagnosis')">
<span class="accordion-item__icon">🩺</span>
<span class="accordion-item__title">自助诊断</span>
<!-- v2.3.3: 异常徽章 + 一键诊断按钮从 SelfDiagnosis 提升至本外层标题栏 -->
<div class="accordion-item__header-actions" @click.stop>
<span v-if="selfDiagRef?.showCelebrate"
class="header__total-badge header__total-badge--celebrate"
title="所有异常已处理">
<span class="badge-icon">✓</span>
今日安全
</span>
<span v-else-if="selfDiagRef"
class="header__total-badge header__total-badge--graded"
:title="`共 ${selfDiagRef.totalBadgeByLevel?.w ?? 0} 警告 ${selfDiagRef.totalBadgeByLevel?.f ?? 0} 失败`">
<span class="badge-segment badge-segment--W">{{ selfDiagRef.totalBadgeByLevel?.w ?? 0 }}W</span>
<span class="badge-sep">·</span>
<span class="badge-segment badge-segment--F">{{ selfDiagRef.totalBadgeByLevel?.f ?? 0 }}F</span>
</span>
<button v-if="selfDiagRef?.hasAnyDiagItems"
class="header__btn"
:disabled="selfDiagRef.isRunningAll || (selfDiagRef.totalBadge > 0)"
@click="selfDiagRef.runAllDiag">
{{ selfDiagRef.isRunningAll ? '诊断中...' : '一键诊断' }}
</button>
</div>
<span class="accordion-item__toggle">{{ activeAccordion === 'diagnosis' ? '▾' : '▸' }}</span>
</div>
<div v-show="activeAccordion === 'diagnosis'" class="accordion-item__content">
<SelfDiagnosis ref="selfDiagRef" />
</div>
</div>
</div>
<div class="right-panel__divider"></div>
<!-- 智能推荐区域 -->
<div class="right-panel__recommend-section" :class="{ 'accordion-item--active': isRecommendExpanded }">
<div class="accordion-item__header" @click="toggleRecommend">
<span class="accordion-item__icon">⚡</span>
<span class="accordion-item__title">智能推荐</span>
<span class="accordion-item__toggle">{{ isRecommendExpanded ? '▾' : '▸' }}</span>
<span v-if="recommendStore.unreadCount > 0" class="recommend-badge">{{ recommendStore.unreadCount }}</span>
</div>
<div v-show="isRecommendExpanded" class="recommend-content">
<DynamicRecommend />
</div>
</div>
<div class="right-panel__divider"></div>
<!-- 排队等待 —— v2.3.6: margin-top: auto + 未排队占位 -->
<div class="right-panel__queue-section">
<div class="queue-header-v3">
<div class="queue-header-v3__left" @click="toggleQueue">
<span class="queue-header-v3__icon">⏳</span>
<span class="queue-header-v3__title">排队等待</span>
<!-- v2.3: 位置/LV/分 全部合并到标题栏 -->
<!-- v2.3.6: 不在排队时显示占位 -->
<span v-if="isQueued && queuePosition > 0" class="qh-position-inline">
#<b>{{ queuePosition }}</b><small>前面{{ aheadCount }}人</small>
</span>
<span v-else class="qh-position-inline qh-position-inline--idle">
<small>未排队</small>
</span>
<span v-if="pointsInfo" class="qh-lv-inline">
LV.<b>{{ pointsInfo.level_index }}</b>
</span>
<span v-if="pointsInfo" class="qh-points-inline">
<b>{{ pointsInfo.points }}</b><i>分</i>
</span>
</div>
<div class="queue-header-v3__actions">
<button class="quiz-toggle-btn" @click="toggleQuiz">
{{ isQuizExpanded ? '收起答题' : '答题挑战' }}
</button>
</div>
</div>
<div v-show="isQueueExpanded" class="right-panel__queue-content">
<QueueWaiting
ref="queueWaitingRef"
:show-quiz="isQuizExpanded"
:show-position="false"
/>
</div>
</div>
</div>
</template>
7.2 脚本关键改动
// v2.3.3: SelfDiagnosis ref 用于读取徽章数据 + 触发一键诊断
const selfDiagRef = ref<InstanceType<typeof SelfDiagnosis> | null>(null)
// v2.3: queue-section 位置/LV/分数据
const queuePosition = computed(() => queueWaitingRef.value?.currentPosition || 0)
const aheadCount = computed(() => queueWaitingRef.value?.queueData?.queue?.ahead_count || 0)
const pointsInfo = computed(() => queueWaitingRef.value?.pointsInfo || null)
7.3 样式关键改动
/* v2.3.5: 右栏整体可滚动 */
.right-panel {
display: flex;
flex-direction: column;
height: 100%;
overflow-y: auto; /* v2.3.5 */
overflow-x: hidden;
}
/* v2.3.5: 删除 max-height 限制 */
.accordion-item--active {
/* 无 max-height: 80% */
}
/* v2.3.3: 外层标题栏 actions 区 */
.accordion-item__header-actions {
display: inline-flex;
align-items: center;
gap: 6px;
margin-left: auto;
flex-shrink: 0;
}
/* v2.3.6: queue-section 始终置底 */
.right-panel__queue-section {
flex-shrink: 0;
margin-top: auto; /* v2.3.6 关键 */
background: var(--bg-primary, #fff);
display: flex;
flex-direction: column;
}
/* v2.3.6: 未排队占位 */
.qh-position-inline--idle small {
font-style: italic;
opacity: 0.85;
}
7.4 关注点
- v2.3.3 actions 区:徽章 + 一键诊断按钮放在
.accordion-item__header-actions容器中,@click.stop阻止冒泡触发手风琴折叠 - v2.3.5 内容高度:右栏
overflow-y: auto,accordion-item 不再设max-height - v2.3.6 置底:
margin-top: auto在 flex column 容器中自动推至主轴底部,不依赖中间区块flex: 1 - v2.3.6 占位:v-else "未排队" 替换原本空荡的标题栏;
.qh-position-inline--idle斜体灰色样式 - 移动端:<500px 时整 RightPanel 默认不渲染(v-if="showRightPanel" 且
!isMobile),需点击 🩺 信息按钮才显示
八、验收标准
8.1 功能验收
v2.3 原始 7 项:
- 桌面端 ≥500px:双栏正常,自助诊断 / 智能推荐 / 排队全部正常
- 移动端 <500px:默认显示左对话;点击 🩺 切换显示右栏
- queue 标题栏:显示位置 / LV / 分 三件套
- queue-content:内不重复显示位置卡片
- 异常徽章:5W·0F 分级显示
- 异常+接入混合:显示
安全 1•2(红数字 + 灰斜体) - 0 异常:达标动画 3 秒后渐隐
- Tab 切换:只显示当前 Tab 数据,不显示其他 Tab
v2.3.3 标题栏外提(决策 ⑧):
- SelfDiagnosis 内部无独立标题栏
- 徽章 + 一键诊断按钮在 RightPanel accordion-item__header 显示
- actions 区
@click.stop不触发折叠 - defineExpose 暴露 6 API(totalBadge / totalBadgeByLevel / showCelebrate / isRunningAll / hasAnyDiagItems / runAllDiag)
v2.3.4 网络/系统 Tab 统一(决策 ⑨):
- 6 Tab 内所有条目(diagItems + riskItems)统一
.diag-item模板 - 每条诊断项有"检测"按钮;点击触发单条重检
- diagItem 检测 800ms-1.5s 后返回 Mock 结果
- riskItem 检测 800ms 模拟态后恢复
- pending 条目检测按钮禁用
- diagItemIcon 根据 label 推断 emoji(8 类规则)
v2.3.5 内容高度自适应(决策 ⑩):
- 删除 accordion-item--active max-height: 80%
- 右栏 overflow-y: auto 整体可滚动
- 自助诊断展开后内容高度由条目数自然撑开
v2.3.6 排队置底 + 未排队占位(决策 ⑪):
- queue-section margin-top: auto 始终贴底
- 排队时标题栏
#5 前面4人(蓝色背景徽章) - 未排队时标题栏
未排队(斜体灰色占位) - LV/分信息按 pointsInfo 状态显示
8.2 性能验收
- Tab 切换响应 < 100ms
- 异常徽章实时刷新 < 200ms
- 移动端 toggle 切换 < 200ms
8.3 兼容性验收
- Chrome / Edge / Safari 最新版
- iOS Safari 14+
- Android Chrome 8+
- 屏宽 320px / 375px / 720px / 1920px
九、风险与回滚
| 风险 | 等级 | 缓解措施 |
|---|---|---|
| 移动端 toggle 切换导致左对话消息丢失 | 高 | Vue v-show 切换(不卸载 DOM),保留输入栏文本和滚动位置 |
| 异常徽章实时刷新失败(WS 断连) | 中 | 保留最后一次已知状态,15s 后重连重试 |
| queue 标题栏紧凑导致小屏挤压 | 中 | flex-wrap: wrap 应对窄屏 |
| 0 异常达标动画吸引注意力过度 | 低 | 3 秒后渐隐,不阻塞操作 |
| 触达 0 异常时的统计埋点缺失 | 低 | v1.1 单独 PR,加埋点 |
十、部署方案
10.1 部署方式
jumpserver-V2 skill(统一通道),参考项目记忆 MEMORY.md。
10.2 部署顺序
- 后端无改动(skip)
- 前端 4 文件改动 → npm run build → jumpserver-V2 上传 → docker restart nginx
- 浏览器端验证(agent-browser 加载 skill)
10.3 部署前置
- AST 静态校验(虽然 .vue 不是 .py,但 prettier 校验)
- 累计 hash 变化确认(dist/assets/*.css)
10.4 v2.3.1 → v2.3.6 部署 hash 时间线
| 版本 | CSS hash | JS hash | 时间 | 关键变更 |
|---|---|---|---|---|
| v2.3 首次 | WYNGewev |
CyKwWyjb |
13:39 | 6 Tab 横向 + queue 合并 + 移动端 toggle |
| v2.3.1 (fix1) | B5iZHaWc |
DWLS0ZMb |
13:51 | 修复 Tab 网格 3×2 残留 |
| v2.3.1 (fix2) | CL_8bZ34 |
DRFXN8X- |
13:56 | 修复 Tab 标签 4 字未用简称 |
| v2.3.2 | BBycvpph |
CbImPO1p |
14:35 | tabs computed 补 shortLabel + 恢复独立标题栏 |
| v2.3.3 | CCAA9XNU |
DkUAW4Da |
15:13 | 标题栏外提到 RightPanel |
| v2.3.4 | C8kd4YJI |
DBVfD3sS |
15:38 | 统一 .diag-item + 检测按钮 |
| v2.3.5 | CNWqSvBk |
5JtvoGYc |
15:51 | 删除 max-height + overflow-y: auto |
| v2.3.6 | GZiNwzZW |
CAygTKBS |
16:56 | margin-top: auto + 未排队占位 |
10.5 关键避坑(部署阶段)
- 中英文双路径同步:本地 Windows 路径
D:\资料\03-项目开发\wecom_it_smart_desk\src\frontend-h5\≠ ASCII 路径D:\dev\wecom\src\frontend-h5\,两边必须同步修改 - vite build 用 ASCII 路径:中文路径下
pnpm install卡死;复制项目到D:\dev\wecom后正常 - build 产物路径:
src/frontend-h5/dist/ - docker restart 必须:nginx bind mount 不会自动刷新新文件
- 缓存头:nginx.conf HTML
no-cache, no-store, must-revalidate+ assetsimmutable 365d
十一、关联文档
- PRD:
docs/01-产品文档/05-用户端H5/PRD-REQ-用户-007-自助诊断面板优化-v1.0.md - 任务说明书:
docs/07-项目管理/任务说明书/任务说明书-REQ-用户-007-自助诊断面板优化.md - 测试用例:
docs/03-测试文档/03-功能测试用例/TC-REQ-用户-007-自助诊断面板优化.md - 原型图:
docs/01-产品文档/05-用户端H5/原型-REQ-用户-007-自助诊断面板优化-v1.0.html - 设计文档:
docs/02-技术文档/前端改造/设计-H5用户端实现概览-v1.0.md - 现有代码:
src/frontend-h5/src/components/assistant/SelfDiagnosis.vuesrc/frontend-h5/src/components/assistant/QueueWaiting.vuesrc/frontend-h5/src/components/assistant/RightPanel.vuesrc/frontend-h5/src/views/ChatView.vue
十二、变更日志
| 版本 | 日期 | 变更内容 | 作者 |
|---|---|---|---|
| v1.0 | 2026-07-29 12:35 | 初版:基于 PRD v1.0-Frozen 7 项决策,给出 3 文件 + 1 补充文件改动方案 + 部署方案 + 验收标准 | Duckula + 宋献 |
| v1.0-Live | 2026-07-29 17:11 | 升级:§1.1 现状表补全 v2.3.6 实际状态;§五 重写 SelfDiagnosis.vue(v2.3.3 删除 self-diag__header + defineExpose + tabs shortLabel 修复);§七 重写 RightPanel.vue(v2.3.3 外提 + v2.3.5 overflow + v2.3.6 margin-top: auto);§八 验收补 v2.3.3-v2.3.6 4 节;§十 部署方案补 hash 时间线 + 避坑;§十二 补全 v2.3.1-v2.3.6 全部 hash | Duckula |