ManageScreen · Technical Deep Dive
新屏入口逻辑
深度解析
从路由守卫 → layout初始化 → 头部交互 → 自选弹窗 → 联调参考
每一步为什么这么做,每一行代码的设计意图
01 — ARCHITECTURE
全局架构总览
用户从URL进入,到页面渲染完成,经历了几个关键节点
layout.vue 是整个新屏的"中枢"——它负责初始化用户信息、决定路由方向、派发全局事件。所有子页面通过 Pinia Store 获取状态,不直接与 layout 通信。这种"中枢 + 订阅"模式让子页面完全解耦,可独立开发和联调。
02 — ROUTE GUARD
路由守卫 permission.js
用户请求的第一道关卡 — 决定是否放行
用 some + startsWith 匹配路径前缀,而非精确匹配。这样 /manageScreen/branchCompany 也会被放行。
如果 pageRuleType 为空(首次进入、刷新),强制跳转 /screen。因为新屏需要从 layout.vue 的 getInfo 获取 ruleType 后才能路由到正确子页面。
本项目嵌入在联通内部系统 iframe 中,已有外层统一登录。路由守卫只做「路径合法性」校验,不做 token/session 检查。
新屏的子路由(branchCompany / grid / campService)是动态的,且可能后续新增。用前缀匹配避免每新增一个子路由就要同步更新白名单。
03 — BOOTSTRAP
main.js 应用引导
App实例的创建与插件注册顺序
import './permission' 是在模块顶层执行的,它注册了 router.beforeEach。这意味着在 Vue app 挂载前,路由守卫就已就绪。当
app.mount('#app') 触发首次路由解析时,守卫已经在工作了。
createWebHistory('/wghtml/asiahtml/budgetv/')部署在联通内部目录下,这个 base path 让 Vue Router 在解析
/manageScreen 时实际对应 /wghtml/asiahtml/budgetv/manageScreen。所有
router.push 路径都不需要带这个前缀。
04 — LAYOUT.VUE
layout.vue 核心中枢
新屏最重要的文件 — 模板仅 9 行,逻辑承载整个初始化链路
头部组件通过 4 个事件向 layout 报告用户操作。layout 不渲染任何业务UI,只做"事件转发 → Store更新"。
branchCompany / grid / campService 渲染在这里。子页面通过 watch(useUserStore().userInfo) 响应 Store 变化,不直接与 layout 通信。
「单一职责」:layout 专注于全局状态初始化和事件中继。业务展示完全委托给子路由。这样切换 ruleType(分公司→网格→营服)时,只需切换子路由,layout 无需改动。
05 — URL PARAMS
getUrlParams() URL参数解析
为什么不能直接用 window.location.search?
createWebHistory(history 模式),但外部系统(联通内部平台)跳转过来的 URL 可能包含 hash 符号 #。new URL() 会把 # 后面的内容当作 fragment,导致 searchParams 解析失败。先将
# 编码为 %23,让 URL 构造器正确解析 query 参数。
如果外部系统传参格式变了(比如换成 JSON 嵌在某个参数里),只需改这一个函数。当前主要用于 ruleType 参数覆盖。
06 — INITIALIZATION
getInfo() 核心初始化链
页面加载时自动执行,是新屏一切逻辑的起点
先 toPageRuter(路由跳转)再 $patch(写 Store)。如果反过来,子页面的 watch(userInfo) 会在路由切换前触发,导致旧子页面收到新数据,产生闪烁或错误请求。
07 — HTTP UTILITY
spost 请求机制 plugins/spost.js
自研的响应式 HTTP 工具,基于 PromiseState 模式
reactive 对象,包含:p — pending(请求中)o — ok(成功)e — error(失败)c — code(响应码)m — message(响应消息)d — data(响应数据)s — sequence(请求序号)模板中可直接绑定
store.p 显示 loading 状态。
formData.append('JsonParam', JSON.stringify(params))后端接口统一从
JsonParam 字段解析参数。这是联通内部框架的约定。联调时,所有接口的参数都要放在
JsonParam 里,不是 JSON body。
接口返回格式:{ code, message, data }。spost 自动将 data 赋值给 store.d,失败时弹 ElMessage.error。
08 — ROUTING DECISION
toPageRuter() 路由决策引擎
根据 ruleType 将用户导向正确的子页面
直接赋值 pageRuleType,而不是通过 $patch userInfo.ruleType。因为子页面 watch(userInfo) 是 deep 的——如果先改 userInfo.ruleType,watch 立即触发,子页面在路由切换之前就开始请求接口,产生竞态。
01 02 → 分公司
13 135 136 → 营服中心
其他 → 网格
确保子页面组件已挂载完毕后,再执行 $patch 写 Store。子页面的 watch 监听器是在组件 setup 中创建的,如果不 await,可能 Store 更新时组件还未挂载,watch 不会触发。
09 — STATE MANAGEMENT
Store 写入策略
userInfo 的每个字段何时写入、为什么这样写
$patch 只触发一次响应式更新(批量)。如果分别写 state.userInfo.ruleType = ...、state.userInfo.sellArea = ...,会触发多次 watch,子页面可能连续发出多个接口请求。$patch 保证子页面只收到一次完整的数据更新。
LJ = 累计(cumulative),DY = 当月(current month)。初始化时固定为累计维度。用户可通过 Header 的维度切换按钮改为当月。子页面请求接口时会携带这个维度参数,影响查询的时间范围。
10 — ORG CHANGE
handleOrgChange 组织切换的精密流程
4个事件处理器中最复杂的一个 — 可能触发路由跳转
用户在组织树选择时可能跨层级:从「分公司」跳到「营服」。这需要切换子路由(branchCompany → campService)。如果先 patch,旧页面会用新数据请求接口(参数不匹配),导致接口报错。
$patch monthId。月份切换不涉及路由跳转,子页面 watch 到 monthId 变化后重新请求接口。$patch tabType。切换核算方式(如"全口径/收入"),不涉及路由。子页面根据 tabType 切换展示的指标集。$patch dimension。LJ(累计)/DY(当月) 切换。子页面根据维度决定查询时间范围和展示格式。11 — HEADER COMPONENT
header.vue 4大控件区详解
维度 · 核算方式 · 组织树 · 日期选择器 — 每个控件的内部机制
LJ(累计) / DY(当月)点击时
emit('dimensionChange', val)。layout 将值写入 Store,子页面据此决定查询的时间维度。初始值:来自 Store 的
userInfo.dimension,首次为 'LJ'。
'INDEX_VALUE_JYZRZT_SJKJHJ'。通过
ruleArr = ['01','02','13','136','135','137'] 判断是否显示额外 tooltip 提示。作用:子页面根据 tabType 决定展示哪组指标。
el-tree-select 实现,懒加载:· 根节点:
/tree/sellAreaNewJfysJs· 下钻:
/tree/sellAreaDrillNewJfysJs选中节点时 emit
orgChange({ruleType, id, text, zqgzType})。根级节点额外携带 zqgzType。
el-date-picker,type 为 month。初始值从
getBaseInfo 接口返回的 monthId。切换月份后 emit
dateChange(month),layout 更新 Store,所有子页面重新查询该月数据。
12 — LAZY TREE
组织树懒加载机制
两级接口 + el-tree-select lazy 模式的协作
/tree/sellAreaNewJfysJs时机: header 组件 mounted 时
返回: 顶级组织列表(分公司/营服/网格)
/tree/sellAreaDrillNewJfysJs时机: 用户展开节点时触发
参数: 父节点的 sellArea + ruleType
根级节点额外返回 zqgzType
→ layout.handleOrgChange → toPageRuter → $patch
· 首屏接口耗时过长
· 前端渲染卡顿
· 大部分节点用户永远不会点开
懒加载 = 用户展开时才请求子节点,首屏只加载十几个根节点。
下钻接口需要在 JsonParam 中传递 sellArea(父节点编码)和 ruleType(组织类型)。如果某节点是叶子节点,后端返回空数组,前端自动标记为 isLeaf: true。
仅根级节点返回此字段,表示"增长工作类型"。子页面(如分公司底部模块)可能需要这个参数来筛选特定指标。Header 在根级选中时会把 zqgzType 写入 Store。
13 — DATA SUBSCRIPTION
子页面数据订阅模式
branchCompany/index.vue 如何响应 layout 的状态变化
deep: true 确保 userInfo 内任何字段变化都会触发回调。因为 layout 使用 $patch,一次 patch 只触发一次 watch。
子页面用 form 作为本地副本,而不是直接传递 Store 到子组件。这样子组件的 props 是简单对象,避免了子组件直接依赖 Store。
维度值通过 v-model:srType 传给 topContent,再传给 income 组件。income 组件点击收入类型按钮时可以修改 srType。
首次 getInfo 接口不返回 tabType,Store 中初始也没有。|| 'INDEX_VALUE_JYZRZT_SJKJHJ' 确保子页面始终有一个有效的 tab,不会因为 undefined 导致组件渲染异常。
14 — INCOME DIALOG
自选弹窗设计 (收入)
yourSelfDialog.vue — 用户选择自定义展示哪些指标
-
联网收入 (fixed, disabled)
- 移宽固 → 移动 / 宽带 / 固话
- BPO
- 专线联网 → 子项
- 物联网连接
-
算网收入 (fixed, disabled)
- 项目 → 5个子项
- 标品 → 4个子项
- 数据中心
- 移宽规模稳盘工程
- 百万智家组网工程
- 云智产品日新工程
getUserZxZbData返回用户当前已选的指标列表,含固定项和动态项。
保存选择:
updateUserZbInfo将用户勾选的指标 ID 列表提交后端。后端记住用户偏好,下次打开恢复选中状态。
0101010000、0101020000(收入)QSJ、RHZK(业务)这些固定项始终展示,用户只能选择/取消动态指标。checkbox 的
disabled 属性阻止交互。
接口返回的数据结构需要包含 id、label、checked(是否已选)、disabled(是否固定)、children(子节点)字段。前端据此渲染树形 checkbox。
15 — COST DIALOG
成本自选弹窗 costDialog.vue
与收入弹窗结构类似,但全部为固定展示项
-
联网通信成本
- 移宽固
- 物联网连接
-
算网数智成本
- 项目
- 标品
- 数据中心
- 第三方设备
- 移宽固
- 国际费用租赁
- 本地
成本弹窗:目前全部为固定项,没有动态可选部分。底部预留了 3 个占位成本项,等待后端接口就绪后扩展。
设计上保持了与收入弹窗一致的 UI 结构(树形 checkbox + 确认/取消按钮),便于后续统一扩展为可自选模式。
产品第一期只要求收入指标可自选,成本指标暂时固定展示。但弹窗的 UI 骨架已就位,后端接口就绪后只需:1) 接入 API 获取动态成本项,2) 移除 disabled 属性,3) 添加保存逻辑。改动量极小。
16 — INDICATOR CARD
IndicatorCard 表格式指标卡
新屏核心展示组件 — 替代旧屏的 incomeCard/businessCard
| Prop | 类型 | 说明 |
|---|---|---|
title | String | 卡片标题(如"基准指标") |
titleColor | String | 标题主题色 |
data | Array | 指标数据列表 |
columns | Array | 列配置 {label, width, key} |
titleColumns | Array | 表头列配置 |
isClick | Boolean | 行是否可点击 |
数据超过 3 条时自动显示"展开"按钮。收起时只显示前 3 条。避免指标过多时页面过长。
isClick=true 时,点击行高亮并显示渐变边框。选中行的数据通过 emit 传给父组件,用于展开趋势图弹窗。
根据 title 关键字(移动/宽带/固话/项目...)自动匹配对应图标。无需外部传入图标路径。
17 — UTILITY COMPONENTS
CompareValue & DrawerDialog
两个高复用辅助组件的设计细节
正值 → #e50017 红色(上升)
负值 → #22ac86 绿色(下降)
零值 → #8e9ca9 灰色(持平)
特殊文本处理:
·
"去年无发生" — 去年无数据,无法计算同比·
"无预算目标" — 无预算基准,无法计算完成率这两种情况直接显示文本,不走颜色逻辑。
·
Teleport(to="body") — 渲染到 body 下,避免被父级 overflow 裁切· CSS
transition — 右滑进出动画· 遮罩层点击关闭
· 宽度: 35% 视口
为什么用 Teleport:
抽屉需要覆盖整个视口右侧。如果不 Teleport,它会被父级
.container 的 overflow: auto 裁切。Teleport 让它脱离组件树渲染层级,直接挂在 body 上。内容:通过 slot 插入,复用于趋势图、渠道筛选等场景。
18 — API REFERENCE
联调接口参考表
新屏涉及的所有接口 — 参数格式均为 FormData + JsonParam
| 接口路径 | 触发时机 | JsonParam 参数 | 返回关键字段 |
|---|---|---|---|
/ManagementChart/getBaseInfo |
layout.vue 初始化 | {} 空对象 |
ruleType, sellArea, sellAreaDesc, dayId, monthId |
/tree/sellAreaNewJfysJs |
header mounted(根节点) | {} 空对象 |
组织树根节点列表 |
/tree/sellAreaDrillNewJfysJs |
展开组织树节点 | {sellArea, ruleType} |
子节点列表 (id, label, isLeaf) |
getUserZxZbData |
打开收入自选弹窗 | 用户标识相关 | 已选指标列表 (id, label, checked, disabled) |
updateUserZbInfo |
收入自选弹窗保存 | 勾选的指标 ID 数组 | 保存结果 |
所有接口统一使用 spost 发送。请求体为 FormData,参数放在 JsonParam 字段中(JSON 字符串)。响应格式:{code, message, data}。
当前子页面(income/cost/profit)内的数据展示部分仍使用 mock 数据。联调时需替换为真实接口调用,参数从 form 和 srType 中获取。
19 — SEQUENCE DIAGRAM
完整生命周期时序
从用户打开URL到页面完全渲染的每一步
/wghtml/asiahtml/budgetv/manageScreen → Vue Router 匹配 /manageScreen 路由
白名单匹配 /manageScreen → next() 放行
组件 setup 执行 → 显示全屏 Loading → 调用 getInfo()
请求后端获取用户基础信息 → 解析 URL 参数 → 覆盖 ruleType(如有)
设置 pageRuleType → 根据 ruleType 路由到 branchCompany / campService / grid → await 确保子页面挂载
批量更新 userInfo → 子页面 watch 触发 → form 同步 → 子组件开始渲染/请求业务接口
Header mounted → 请求组织树根节点 → 渲染日期选择器 → 同步 Store 中的维度/tab
finally { loadingInstance.close() } — 无论成功失败都关闭 Loading,用户看到完整页面
20 — INTEGRATION CHECKLIST
联调 Checklist
接口对接时的核心注意事项
FormData,参数放在 JsonParam 字段中。不要用
application/json Content-Type。使用
spost(store, url, params) 即可自动处理。
mockData 需替换为真实接口。参数从
props.form 获取:dateTime、sellArea、ruleType。维度从
props.srType 获取。
watch(form, handler, {deep: true}) 会在 Store 更新时触发。确保接口请求在 watch 回调中发起,且 form 的每个字段都有值时才请求(防空参)。
const store = newStore() 创建响应式 store。store.p 判断加载中,store.d 获取数据。模板中
v-loading="store.p" 即可自动管理 loading 状态。
联调新接口只需 3 步:1) const xxxStore = newStore() 创建 store,2) 在 watch 中调用 spost(xxxStore, '/接口路径', {参数}),3) 模板绑定 xxxStore.d 渲染数据。
ManageScreen · Technical Reference
联调参考手册
路由守卫 → 中枢初始化 → 头部控件 → 自选弹窗 → 子页面订阅
每一步都有明确的"为什么"
键盘 ← → 翻页 · 底部按钮导航 · 右侧圆点快速跳转 · 内容超出可滚动查看