diff --git a/yunkes/src/api/management.ts b/yunkes/src/api/management.ts index 3a0656a..d9b8d5a 100644 --- a/yunkes/src/api/management.ts +++ b/yunkes/src/api/management.ts @@ -70,3 +70,33 @@ export const getEmployees = (params: Record) => export const getEmployeeDrilldown = (userId: string, metricCode: DrilldownCode, start: number, end: number, channel: MgmtChannel, page = 1, pageSize = 20) => getJson(`/management/employees/${userId}/drilldown`, { metric_code: metricCode, start, end, channel, page, page_size: pageSize }) export const getMetricDefs = () => getJson('/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 + score: { + total_score: number | null; grade: string | null; score_status: string; trial: boolean + dimensions: Record }> + metrics_detail: Record + } + anomalies: AnomalyCard[] + prev: Record | null + baseline: Record | null + scope: MgmtScope; data_as_of: number | null; metric_version: string; config_source: 'db' | 'default' +} +export interface SectionResp { + section: string; metrics: Record + trend: Record[] + comparisons: Record + 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(`/management/employees/${userId}`, { channel, start, end }) +export const getEmployeeMetrics = (userId: string, section: MetricsSection, channel: MgmtChannel, start: number, end: number) => + getJson(`/management/employees/${userId}/metrics`, { section, channel, start, end }) diff --git a/yunkes/src/components/management/ManagementFilterBar.vue b/yunkes/src/components/management/ManagementFilterBar.vue index 3bc0daf..d7d4b71 100644 --- a/yunkes/src/components/management/ManagementFilterBar.vue +++ b/yunkes/src/components/management/ManagementFilterBar.vue @@ -10,10 +10,18 @@ placeholder="近30天" @change="onPresetChange" > - - - - + + + + + + + + + + + + diff --git a/yunkes/src/composables/__tests__/useManagementFilters.spec.ts b/yunkes/src/composables/__tests__/useManagementFilters.spec.ts index 6984091..dff4afd 100644 --- a/yunkes/src/composables/__tests__/useManagementFilters.spec.ts +++ b/yunkes/src/composables/__tests__/useManagementFilters.spec.ts @@ -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 }) }) diff --git a/yunkes/src/composables/useManagementFilters.ts b/yunkes/src/composables/useManagementFilters.ts index 5df4828..98761e6 100644 --- a/yunkes/src/composables/useManagementFilters.ts +++ b/yunkes/src/composables/useManagementFilters.ts @@ -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':