Files
yunke/docs/superpowers/plans/2026-09-10-management-console-batch-d-employees-plan.md
T
浅念 2587e4dd08 docs(management): 批次D员工页计划——总览+详情+环比+预设补全(Task D0-D4)
评审3真问题全修:排序白名单四列(余列不排序)/服务页签图改P50P90双线
(员工级无每日率序列)/CSV扩csvExport注释行+rows适配;advisory全采:
异常卡补employee_name(一行后端)/showEmployee复用开关/formula_note归位/
伪卡片下钻衔接/compare仅显示态
2026-09-10 11:02:27 +08:00

173 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 云客经营管理后台·批次D(员工总览+员工详情)实施计划
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
**Goal:** 补上管理闭环最大断点——员工总览(41 人多维一张表)与员工详情(五维/页签/四对比/事实下钻),打通「异常→员工全景→事实」链路;附带驾驶舱 KPI 环比与时间预设补全(体验级小项)。
**Architecture:** 纯前端为主(消费批次B已就绪的 employees/detail/metrics sections/drilldown 端点)+ 两处小后端增强(dashboard kpis 加 prev 块;无新端点)。新页 `EmployeeAnalyticsView`/`EmployeeAnalyticsDetailView` + 对比抽屉;复用 C3 六组件(FilterBar/DataAsOfBadge/AnomalyList/MetricDrilldownDrawer/MetricDefsDrawer)与 useManagementFilters。依据 [v2 计划](./2026-09-08-yunke-management-console-implementation-v2.md) §6/§7/§13 + 批次B契约清单。
**Tech Stack:** 同批次C(Vue3+TS+Element Plus+useChart+vitest);后端 FastAPI 小改。
---
## 执行者必读
1. **消费的端点 as-built 形态**(写 client 前已核对 management.py:321-443):
- `GET /employees`(行结构见批次C client 的 EmployeeRow——已含 metrics/comparisons/score 摘要/anomaly_count/weight);
- `GET /employees/{id}`:`{user_id,name,is_on_job,accounts[{channel,account_id,nick_name,user_name}],metrics(22字段dict 含 user_id 键),score{total_score,grade,score_status,trial,dimensions{name,weight,score,status,metrics{code:{value,score,sample_size,sample_status,formula_note}}},metrics_detail{code:{value,prev,baseline,median,sample_size,sample_status,score,unit,name}}},anomalies[],prev(dict|null),baseline(dict|null),scope,data_as_of,metric_version,config_source}`;
- `GET /employees/{id}/metrics?section=`:`{section,metrics(该域字段dict),trend[{day,cols...}](无数据日计数0/分位None),comparisons(源指标名键),scope,data_as_of,metric_version}`;
- drilldown/metric-definitions 已有 client。
2. **前端惯例**同批次C必读(seq 守卫/useChart/URL query/csvExport.ts 全量翻页+中文 BOM/命名路由跳转)。
3. **不做**:辅导/快照(E);员工对比不新开 API(勾选 2-5 行客户端拼数据);「指标解释抽屉」= 复用 MetricDefsDrawer(点击列头 ? 图标打开)——不做每指标单独解释面板(v1 §6.5 的完整版二期)。
4. 仓库并行会话照旧;中文 conventional 提交;master 直提。
## 文件地图
| 文件 | 动作 |
|---|---|
| `yunkes/src/api/management.ts` | 补 getEmployeeDetail/getEmployeeMetrics + types(D0) |
| `yunkes/src/composables/useManagementFilters.ts` | 预设补 今日/昨日/本周/上周(D0) |
| `yunkes/src/views/management/EmployeeAnalyticsView.vue` | 新建(D1) |
| `yunkes/src/components/management/EmployeeCompareDrawer.vue` | 新建(D1,对比抽屉) |
| `yunkes/src/views/management/EmployeeAnalyticsDetailView.vue` | 新建(D2) |
| `yunkes/src/router/index.ts` + `MainLayout.vue` | 两路由+两菜单(D1/D2 各自) |
| `yunke-server/app/api/routers/management.py` + `api_assembly.py` | dashboard kpis 加 prev 块(D3) |
| `yunkes/src/components/management/ManagementKpiCard.vue` + `AnomalyList.vue` + 驾驶舱页 | delta 显示 + 员工跳详情(D3) |
---
### Task D0: client 补全 + 预设扩展
- [ ] `api/management.ts` 追加:
```typescript
export interface EmployeeAccount { channel: string; account_id: string; nick_name: string | null; user_name: string | null }
export interface ScoreMetricDetail { value: number | null; prev: number | null; baseline: number | null; median: number | null; sample_size: number; sample_status: string; score: number | null; unit: string; name: string }
// formula_note 在 dimensions[dim].metrics[code] 上(scoring.py 维度级明细),不在 metrics_detail
export interface EmployeeDetailResp {
user_id: string; name: string | null; is_on_job: boolean | null
accounts: EmployeeAccount[]
metrics: Record<string, number | string | null>
score: {
total_score: number | null; grade: string | null; score_status: string; trial: boolean
dimensions: Record<string, { name: string; weight: number; score: number | null; status: string; metrics?: Record<string, { value: number | null; score: number | null; sample_size: number; sample_status: string; formula_note?: string }> }>
metrics_detail: Record<string, ScoreMetricDetail>
}
anomalies: AnomalyCard[]
prev: Record<string, number | string | null> | null
baseline: Record<string, number | string | null> | null
scope: MgmtScope; data_as_of: number | null; metric_version: string; config_source: 'db' | 'default'
}
export interface SectionResp {
section: string; metrics: Record<string, number | null>
trend: Record<string, number | null>[]
comparisons: Record<string, { current: number | null; prev: number | null; baseline: number | null; median: number | null }>
scope: MgmtScope; data_as_of: number | null; metric_version: string
}
export type MetricsSection = 'overview' | 'service' | 'customer' | 'workload' | 'calls'
export const getEmployeeDetail = (userId: string, channel: MgmtChannel, start: number, end: number) =>
getJson<EmployeeDetailResp>(`/management/employees/${userId}`, { channel, start, end })
export const getEmployeeMetrics = (userId: string, section: MetricsSection, channel: MgmtChannel, start: number, end: number) =>
getJson<SectionResp>(`/management/employees/${userId}/metrics`, { section, channel, start, end })
```
(写入前 curl 真机抽查 detail 响应键核对——同批次C C1 惯例,以真实响应为准修。)
- [ ] `useManagementFilters.ts` 预设补:`Preset` 加 `'today' | 'yesterday' | 'thisWeek' | 'lastWeek'`;`presetRange` 四分支(today=当天单日窗;yesterday=昨日单日;thisWeek=本周一..今天;lastWeek=上周一..周日,**周一为一周起点**);vitest 补 5 例(today/yesterday/thisWeek 周中+周一/lastWeek 跨月)。FilterBar 的预设选项组同步加(el-select options),分组显示:常用(今日/昨日/近7/近30)/日历(本周/上周/本月/上月)/自定义。
- [ ] vue-tsc + `npm run test`(23+ 过)+ commit `feat(management): client补员工详情/分段接口+时间预设补今日昨日本周上周`
### Task D1: 员工总览页
**Files:** Create `EmployeeAnalyticsView.vue` + `EmployeeCompareDrawer.vue`;改 router(`/management/employees`)+ MainLayout(管理驾驶舱下加「员工分析」菜单项)。
**页面结构**:
```text
ManagementFilterBar(复用;compare 选择器在此页透出——el-select 对比方式:上期/个人基线/团队中位数→apply({compare}))
工具行:keyword el-input(placeholder 姓名/ID搜索) | status el-select(全部/在职/离线) | 视图 el-radio-group×4
(management=概览/workload=负载/service=服务/customer=客户经营——切换列组)| 指标口径按钮 | 导出 CSV | DataAsOfBadge
主表 el-table(服务端排序 sort-change→query;行选择框多选 2-5 人→「对比」按钮开抽屉):
固定列:姓名(链接进详情)+ID 小字、在职 tag
management 视图列:综合分(total_score+grade tag+trial*)、异常(anomaly_count 红数字+weight)、活跃客户、消息量、串/被回、维度三列(customer/service/workload score 各 0-100 或「--」)
workload 视图列:活跃客户、活跃群(日均)、消息收/发、通话量/秒、串
service 视图列:首响P50/P90(formatMsDuration)、2h回复率、24h回复率(formatRatio)、被回/串
customer 视图列:新客/降温/沉睡/回流(期末+期内注)、活跃占比
列头 ? 图标→打开 MetricDefsDrawer(复用)
空数据/样本不足单元格显示「--」灰字(sample_status=insufficient 时 total 亦然)
分页 el-pagination(服务端 page/page_size 20;total)
```
**行为**:query 真源(view/keyword/status/sort_by/sort_order/page 全进 URL);seq 守卫;**服务端排序仅四列**(后端 sort_by 白名单:anomaly_severity[默认]/total_score/active_friends/cust_threads——其余列头不显示排序箭头,tooltip「该列不支持服务端排序」);`sort_by=total_score` 时 null 排最后由后端保证;compare 参数仅客户端显示态(employees 端点无此参数,行恒带全部四对比值);**CSV**:扩展 `utils/csvExport.ts` 支持可选前置注释行(`exportListToCsv(..., preamble?: string[])`——含口径说明如「1:1口径·窗口 20260810-20260908·试算」)+ fetchPage 适配器(/employees 返回 `{rows,total}`,适配为 csvExport 期望的 `{items,total}`),导出当前视图列全量翻页。
**EmployeeCompareDrawer**:props `{ rows: EmployeeRow[] }`(勾选行已有数据,不另发请求);内容:五维分对比条(3 可用维度每人一行,横向 bar 0-100)+ 关键指标表(活跃客户/P90/2h率/异常数 每人一列)+ 顶部说明「同口径同时窗对比,不产生优劣结论」;>5 人禁开。
**路由/菜单**:
```typescript
{ path: '/management/employees', name: 'management-employees',
component: () => import('@/views/management/EmployeeAnalyticsView.vue'),
meta: { title: '员工分析' } },
```
- [ ] vue-tsc/test/build + 真机初查(表格渲染 41 人/切视图列变/排序翻页/keyword/CSV 打开内容对)+ commit `feat(management): 员工总览页——四视图/服务端排序分页/对比抽屉/CSV导出`
### Task D2: 员工详情页
**Files:** Create `EmployeeAnalyticsDetailView.vue`;router 加 `/management/employees/:userId`(props:true;**activeMenu 需高亮父菜单「员工分析」**——MainLayout 的 el-menu :default-active 绑定的是路径,嵌套路由时用 route.meta 或 watch 设 index——看 MainLayout 现有 active 处理方式,最简方案:菜单 index 用路径匹配 + 详情页 route path 前缀匹配高亮,若现结构不支持则 meta.activeMenu='/management/employees' 并在 el-menu :default-active 计算)。
**页面结构**:
```text
头部卡:姓名(+在职 tag)+ID | 账号清单(渠道 tag+account_id+昵称,点击跳 staff-chat/通讯录按渠道)
| 本期试算分大数字+grade tag+score_status(insufficient 时灰注「样本不足·不参与考核」)+trial*
| DataAsOfBadge | 返回列表链接
五维条(v1 §7.2 概览的核心):横向五条 bar——3 可用维度实分数值,action/risk 灰「待数据」占位
(ScoreBreakdown 简版:直接在页内画,不新建组件,el-progress/自定义 div)
el-tabs 五页签(懒加载:激活才调 getEmployeeMetrics):
概览: 六数卡取自**detail.metrics**(msg/活跃/串/通话——section 端点字段有限,期值以 detail 为准)
+ 四对比小表(comparisons 直渲染,源指标名行)
服务质量: 数卡 P50/P90 + 2h/24h回复率(**客户端从 metrics.reply_2h_count/cust_threads 派生,
与 derive_metrics 同式**——section 趋势无每日率序列) + 趋势图=**首响 P50/P90 双线(ms 轴)
±串数(次轴可选)**——不做率右轴(员工级无每日率数据,率的团队级序列在驾驶舱)
客户经营: 期末四态数卡(取自 detail.metrics) + 降温/沉睡下钻按钮(→DrilldownDrawer lifecycle 分支)
工作负载: 数卡取 detail.metrics(收发/活跃客户/群/通话量) + 趋势图用 section=workload 的
active_friends/active_rooms 双线(trend 列为日值 active_rooms)
通话: 通话四数(detail.metrics) + call_followup 下钻按钮
**页签下钻按钮与 MetricDrilldownDrawer 的衔接**:抽屉是 card 驱动——按钮合成最小伪卡片
`{title:'首响P90明细', drilldown:{type:'metric',query:{metric_code:'first_reply_p90',user_id}}}` 传入
异常区:AnomalyList(复用,@drill→MetricDrilldownDrawer 复用)
评分明细折叠面板(el-collapse「评分怎么算出来的」): metrics_detail 表(code/名称/当前/上期/基线/中位/样本/得分)
+ prev/baseline 原始 PeriodMetrics 两个 JSON 折叠(调试用,灰字注明)
```
**行为**:query 真源(channel/start/end/compare/section 进 URL,页签切换 replace);seq 守卫每页签独立(切页签取消旧渲染);404 员工→el-result 错误页+返回链接;「创建辅导记录」按钮**占位禁用**(tooltip「批次E上线」)。
- [ ] vue-tsc/test/build + 真机初查(王云青详情:五维条/五页签懒加载/异常/评分明细展开/账号跳聊天)+ commit `feat(management): 员工详情页——五维条/五页签懒加载/四对比/评分明细/事实下钻复用`
### Task D3: 驾驶舱 KPI 环比 + 异常跳员工
**后端**(`management.py` dashboard + `api_assembly.py`):①dashboard 端点 kpis 加 `prev_kpis` 块(active_customers/active_employees/msg_total/cust_threads——dashboard 走 assemble_employees(with_prev=False),需加一次 prev 窗 period_metrics 全员调用,B5 实测三窗合计 ~0.3s,加一窗 +0.1s 量级)+ `msg_delta`((cur-prev)/prev,prev=0→null)。lifecycle/通话不加环比(期末/全渠道口径环比意义弱,注明)。②**异常卡补 `employee_name`**(assemble_employees 手里已有全员 name——组装 anomalies 时带上,一行);同步 AnomalyCard type。测试补:dashboard 响应含 prev_kpis 且数字=prev 窗聚合 + anomalies[0].employee_name 非空。
**前端**:KpiCard 加 `delta?: number | null` prop(↑绿↓红,方向语义由调用方定——涨=好传正绿;不传不显示);驾驶舱三卡接 delta(活跃客户/活跃员工/消息量);AnomalyList 的 title 下加员工行(`employee_name` 链接→`/management/employees/{user_id}`)+ 新增 `showEmployee?: boolean` prop(默认 true;D2 员工详情页复用时传 false 免自链接)。
- [ ] 后端 pytest(test_management_api.py 补 prev_kpis 断言)+ 前端走查三卡箭头/异常跳员工 + commit `feat(management): 驾驶舱KPI环比(prev_kpis+delta箭头)+异常卡跳员工详情`
### Task D4: 真机走查 + 验收
- [ ] 全链路走查(Playwright):驾驶舱→异常卡→**员工详情**(新链路!)→页签→下钻→聊天;员工总览→对比抽屉→CSV;1366px;刷新保持;
- [ ] 验收对照(v1 §19.1 员工子集):能查看所有员工多维状态 ✓/任一分数展开为指标公式样本事实 ✓(评分明细面板)/四对比 ✓/样本不足不评分显示 ✓/异常两次点击进事实 ✓/默认排序非总分榜 ✓;
- [ ] 四门禁(vue-tsc/vitest/build/后端 pytest);
- [ ] commit 收尾零散 + 走查记录追加核实文档「批次D走查」小节。
---
## 批次D验收清单
- [ ] 员工总览:41 人四视图/服务端排序分页/keyword/对比 2-5 人/CSV 含口径注释;
- [ ] 员工详情:五维条 3+2 占位/五页签懒加载/四对比/评分明细可展开/三处下钻按钮进抽屉/账号跳聊天;
- [ ] 驾驶舱:三 KPI 有环比箭头/异常卡可跳员工详情(闭环打通);
- [ ] 时间预设全套(今日/昨日/近7/近30/本周/上周/本月/上月/自定义);
- [ ] URL 全参数保持;四门禁绿;后端仅 dashboard 一处小改且测试补齐。
## 批次E-F
E:followups 辅导闭环(写端点+schemas_management+详情页按钮激活)+ 考核快照(生成/冻结/列表/导出)。F:调度接入(analytics_tick 第三步+full_sync 13/14 步+scheduler 镜像重建还 9d3cc8e 旧账)+ dashboard 性能修复(unreplied 索引三选一)+ 服务器部署+回测试运行。