1aadc80d6b
Co-Authored-By: Claude Code <noreply@anthropic.com>
87 lines
4.5 KiB
TypeScript
87 lines
4.5 KiB
TypeScript
// 数据分析板块 API(2026-09-04)。client.ts 的 http 已带 Bearer + baseURL=/api,
|
|
// getJson/requestJson 负责解包与泛型。Page/Channel 沿用 types.ts 现成定义。
|
|
import { getJson, requestJson } from './client'
|
|
import type { Channel, Page } from './types'
|
|
|
|
export type { Channel }
|
|
export type Scope = 'today' | 'week' | 'month'
|
|
// 看板 v2(2026-09-07):overview 渠道参数支持 'all'=全平台(后端译成
|
|
// channel=None);列表页(churn/unreplied)仍是单渠道,沿用 types.ts 的 Channel
|
|
export type DashboardChannel = 'all' | Channel
|
|
|
|
export interface OverviewCards {
|
|
msg_total: number
|
|
active_customers: number
|
|
active_staff: number
|
|
unmapped_accounts: number
|
|
call_cnt: number
|
|
first_contact: number
|
|
prev_msg_total?: number
|
|
msg_total_delta?: number | null
|
|
}
|
|
export interface TrendPoint { day: number; msg: number; active_customers: number; call_cnt: number }
|
|
export interface AnomalyStaff { user_id: string; yesterday: number; prev7_mean: number; new?: boolean }
|
|
export interface Anomaly { total_delta_pct: number; yesterday_total: number; prev7_mean: number; staff: AnomalyStaff[] }
|
|
// 员工活跃排行(2026-09-07 管理者反馈补强):active_*=窗内 distinct peer,
|
|
// msg_delta 仅周/月且当前/前窗均非 0 才有值
|
|
export interface StaffRankingRow {
|
|
user_id: string; name: string; unmapped: boolean
|
|
msg_total: number; active_friends: number; active_rooms: number
|
|
call_cnt: number; msg_delta: number | null
|
|
}
|
|
export interface ActionCounts { unreplied_2h: number; churn_real_30d: number; sensitive_pending: number }
|
|
// by_channel/trend_by_channel 仅 channel='all' 时后端才返回(可选字段);
|
|
// 渠道趋势序列 call_cnt 恒 0——通话量无渠道语义不拆,总量走 trend
|
|
export interface PerChannelCards { msg_total: number; active_customers: number; first_contact: number }
|
|
export interface OverviewResp {
|
|
scope: Scope
|
|
channel: Channel | 'all'
|
|
days: number[]
|
|
cards: OverviewCards
|
|
trend: TrendPoint[]
|
|
anomaly: Anomaly | null
|
|
staff_ranking: StaffRankingRow[]
|
|
actions: ActionCounts
|
|
by_channel?: { wechat: PerChannelCards; whatsapp: PerChannelCards }
|
|
trend_by_channel?: { wechat: TrendPoint[]; whatsapp: TrendPoint[] }
|
|
}
|
|
export interface HealthResp { data_as_of: number | null; entities: { table: string; last_seen: string | null }[] }
|
|
export interface ChurnRow {
|
|
friend_id: string; channel: string; nickname: string; owner_account_id: string | null
|
|
user_id: string | null; last_chat_time: number | null; hist_msgs: number; active_days: number
|
|
}
|
|
export interface UnrepliedRow {
|
|
peer_id: string; channel: string; owner_account_id: string; last_ts: number
|
|
wait_ms: number; preview: string; msg_id: string
|
|
}
|
|
export interface SensitiveWordRow {
|
|
id: number; word: string; category: string | null; enabled: boolean
|
|
min_len: number; exclude_words: string[]
|
|
}
|
|
export interface SensitiveHitRow {
|
|
id: number; word_id: number; word: string; category: string | null; msg_id: string
|
|
channel: string; owner_account_id: string | null; talker_id: string | null
|
|
room_id: string | null; ts: number | null; snippet: string | null; disposition: string
|
|
}
|
|
|
|
export const getOverview = (channel: DashboardChannel, scope: Scope) =>
|
|
getJson<OverviewResp>('/analytics/overview', { channel, scope })
|
|
export const getAnalyticsHealth = () => getJson<HealthResp>('/analytics/health')
|
|
export const getChurn = (params: Record<string, unknown>) =>
|
|
getJson<Page<ChurnRow>>('/analytics/churn', params)
|
|
export const getUnreplied = (params: Record<string, unknown>) =>
|
|
getJson<Page<UnrepliedRow>>('/analytics/unreplied', params)
|
|
export const getWords = () => getJson<SensitiveWordRow[]>('/analytics/sensitive/words')
|
|
export const previewWord = (data: { word: string; exclude_words?: string[] }) =>
|
|
requestJson<{ count: number; warn: boolean }>('post', '/analytics/sensitive/words/preview', data)
|
|
export const createWord = (data: { word: string; category?: string; exclude_words?: string[] }) =>
|
|
requestJson<{ id: number; word: string; enabled: boolean }>('post', '/analytics/sensitive/words', data)
|
|
export const patchWord = (id: number, data: Record<string, unknown>) =>
|
|
requestJson<Record<string, unknown>>('patch', `/analytics/sensitive/words/${id}`, data)
|
|
export const deleteWord = (id: number) =>
|
|
requestJson<{ ok: boolean }>('delete', `/analytics/sensitive/words/${id}`)
|
|
export const getHits = (params: Record<string, unknown>) =>
|
|
getJson<Page<SensitiveHitRow>>('/analytics/sensitive/hits', params)
|
|
export const disposeHits = (ids: number[], disposition: string) =>
|
|
requestJson<{ updated: number }>('patch', '/analytics/sensitive/hits', { ids, disposition })
|