支持客户、对话记录与统计报告 CSV 导出

- 新增 /customers/export、/sessions/export、/statistics/export
- 沿用列表筛选与角色可见范围,UTF-8 BOM 便于 Excel 打开
- 前端三处导出按钮接入真实下载
This commit is contained in:
yml2213
2026-07-15 15:10:46 +08:00
parent 216ab2c85a
commit c43f420875
8 changed files with 571 additions and 11 deletions
+35 -1
View File
@@ -1,4 +1,4 @@
import { get, post, put, del, getList, postForm } from './request'
import { get, post, put, del, getList, postForm, downloadFile } from './request'
export interface LoginParams { username: string; password: string }
export interface LoginResult { token: string; user_id: number; tenant_id: number; nickname: string; role: string }
@@ -253,6 +253,40 @@ export const getCustomers = (params?: { search?: string; status?: string; source
if (params?.pageSize) search.set('pageSize', String(params.pageSize))
return getList<Customer>(`/customers?${search}`)
}
export const exportCustomersCSV = (params?: { search?: string; status?: string; source?: string }) => {
const search = new URLSearchParams()
if (params?.search) search.set('search', params.search)
if (params?.status) search.set('status', params.status)
if (params?.source) search.set('source', params.source)
const qs = search.toString()
return downloadFile(`/customers/export${qs ? `?${qs}` : ''}`, `customers_${Date.now()}.csv`)
}
export const exportSessionsCSV = (params?: {
status?: string
priority?: string
agent_id?: number | string
channel_id?: number | string
search?: string
from?: string
to?: string
}) => {
const search = new URLSearchParams()
if (params?.status) search.set('status', params.status)
if (params?.priority) search.set('priority', params.priority)
if (params?.agent_id != null && params.agent_id !== '') search.set('agent_id', String(params.agent_id))
if (params?.channel_id != null && params.channel_id !== '') search.set('channel_id', String(params.channel_id))
if (params?.search) search.set('search', params.search)
if (params?.from) search.set('from', params.from)
if (params?.to) search.set('to', params.to)
const qs = search.toString()
return downloadFile(`/sessions/export${qs ? `?${qs}` : ''}`, `sessions_${Date.now()}.csv`)
}
export const exportStatisticsCSV = () =>
downloadFile('/statistics/export', `statistics_${Date.now()}.csv`)
export const getCustomer = (id: number) => get<{ customer: Customer; sessions: Session[] }>(`/customers/${id}`)
export const createCustomer = (data: Partial<Customer>) => post<Customer>('/customers', data)
export const updateCustomer = (id: number, data: Partial<Customer>) => put<Customer>(`/customers/${id}`, data)
+35
View File
@@ -53,3 +53,38 @@ export const getList = <T>(url: string) => request<ListResponse<T>>(url)
export const postForm = <T>(url: string, form: FormData) =>
request<Response<T>>(url, { method: 'POST', body: form })
/** 下载 CSV 等二进制响应(带鉴权) */
export async function downloadFile(path: string, fallbackName: string) {
const headers: Record<string, string> = {}
if (token) headers.Authorization = `Bearer ${token}`
const res = await fetch(`${BASE}${path}`, { headers })
const ct = res.headers.get('Content-Type') || ''
if (!res.ok || ct.includes('application/json')) {
let msg = '导出失败'
try {
const json = await res.json()
if (json?.message) msg = json.message
} catch { /* ignore */ }
throw new Error(msg)
}
const blob = await res.blob()
let filename = fallbackName
const cd = res.headers.get('Content-Disposition') || ''
const m = /filename\*=UTF-8''([^;]+)|filename="?([^";]+)"?/i.exec(cd)
if (m) {
try {
filename = decodeURIComponent(m[1] || m[2])
} catch {
filename = m[1] || m[2] || fallbackName
}
}
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = filename
document.body.appendChild(a)
a.click()
a.remove()
URL.revokeObjectURL(url)
}