feat(management): client补员工详情/分段接口+时间预设补今日昨日本周上周

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
浅念
2026-09-10 11:07:25 +08:00
parent 2587e4dd08
commit ddeda3fa22
4 changed files with 76 additions and 5 deletions
+30
View File
@@ -70,3 +70,33 @@ export const getEmployees = (params: Record<string, unknown>) =>
export const getEmployeeDrilldown = (userId: string, metricCode: DrilldownCode, start: number, end: number, channel: MgmtChannel, page = 1, pageSize = 20) =>
getJson<DrilldownResp>(`/management/employees/${userId}/drilldown`, { metric_code: metricCode, start, end, channel, page, page_size: pageSize })
export const getMetricDefs = () => getJson<MetricDefsResp>('/management/metric-definitions')
// ---- 批次D:员工详情 + 分段指标(契约已真机 8000 抽查核对,见上) ----
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 维度级明细),不在 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 })
@@ -10,10 +10,18 @@
placeholder="近30天"
@change="onPresetChange"
>
<el-option label="近7天" value="last7" />
<el-option label="近30天" value="last30" />
<el-option label="本月" value="thisMonth" />
<el-option label="上月" value="lastMonth" />
<el-option-group label="常用">
<el-option label="今日" value="today" />
<el-option label="昨日" value="yesterday" />
<el-option label="近7天" value="last7" />
<el-option label="近30天" value="last30" />
</el-option-group>
<el-option-group label="日历">
<el-option label="本周" value="thisWeek" />
<el-option label="上周" value="lastWeek" />
<el-option label="本月" value="thisMonth" />
<el-option label="上月" value="lastMonth" />
</el-option-group>
<el-option label="自定义" value="custom" />
</el-select>
@@ -50,6 +50,26 @@ describe('presetRange(注入 today,确定性日期)', () => {
expect(presetRange('lastMonth', D(20260115))).toEqual({ start: 20251201, end: 20251231 })
})
it('today → 今天单日(2026-09-10 → 20260910..20260910)', () => {
expect(presetRange('today', D(20260910))).toEqual({ start: 20260910, end: 20260910 })
})
it('yesterday → 昨日单日(2026-09-10 → 20260909..20260909)', () => {
expect(presetRange('yesterday', D(20260910))).toEqual({ start: 20260909, end: 20260909 })
})
it('thisWeek 周中(2026-07-02 周四 → 周一到今天 20260629..20260702)', () => {
expect(presetRange('thisWeek', D(20260702))).toEqual({ start: 20260629, end: 20260702 })
})
it('thisWeek 周一当天 → 单日(2026-06-29 周一 → 20260629..20260629)', () => {
expect(presetRange('thisWeek', D(20260629))).toEqual({ start: 20260629, end: 20260629 })
})
it('lastWeek 跨月边界(2026-07-02 周四 → 上周一..上周日 20260622..20260628)', () => {
expect(presetRange('lastWeek', D(20260702))).toEqual({ start: 20260622, end: 20260628 })
})
it('custom → 今天单日兜底(2026-09-10 → 20260910..20260910)', () => {
expect(presetRange('custom', D(20260910))).toEqual({ start: 20260910, end: 20260910 })
})
+14 -1
View File
@@ -3,7 +3,7 @@
import { computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
export type Preset = 'last7' | 'last30' | 'thisMonth' | 'lastMonth' | 'custom'
export type Preset = 'today' | 'yesterday' | 'last7' | 'last30' | 'thisWeek' | 'lastWeek' | 'thisMonth' | 'lastMonth' | 'custom'
export const CHANNELS = [
{ value: 'all', label: '全部渠道' },
{ value: 'wechat', label: '微信' },
@@ -22,7 +22,20 @@ function addDays(d: Date, n: number): Date {
/** 预设 → [start,end] yyyymmdd。 */
export function presetRange(p: Preset, today = new Date()): { start: number; end: number } {
const t0 = new Date(today.getFullYear(), today.getMonth(), today.getDate())
// 周一为一周起点(getDay 周日=0 → 偏移成周一=0..周日=6)
const dow = (t0.getDay() + 6) % 7
switch (p) {
case 'today':
return { start: ymd(t0), end: ymd(t0) }
case 'yesterday':
return { start: ymd(addDays(t0, -1)), end: ymd(addDays(t0, -1)) }
case 'thisWeek':
// 周一..今天;周一当天即单日
return { start: ymd(addDays(t0, -dow)), end: ymd(t0) }
case 'lastWeek': {
const monday = addDays(t0, -dow - 7)
return { start: ymd(monday), end: ymd(addDays(monday, 6)) }
}
case 'last7':
return { start: ymd(addDays(t0, -6)), end: ymd(t0) }
case 'last30':