支持客户、对话记录与统计报告 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
+23 -4
View File
@@ -7,7 +7,7 @@ import {
} from '@ant-design/icons'
import dayjs, { type Dayjs } from 'dayjs'
import {
archiveSession, batchArchiveSessions, getAvailableAgents, getChannels, getSession, getSessions,
archiveSession, batchArchiveSessions, exportSessionsCSV, getAvailableAgents, getChannels, getSession, getSessions,
type AvailableAgent, type Channel, type Message, type Session, type SessionEvent,
} from '@/services/api'
import { ChatImage } from '@/components/common/ImagePreview'
@@ -162,6 +162,7 @@ const ChatHistory = () => {
const [detailLoading, setDetailLoading] = useState(false)
const [checkedIds, setCheckedIds] = useState<number[]>([])
const [archiving, setArchiving] = useState(false)
const [exporting, setExporting] = useState(false)
const loadSessions = useCallback(async () => {
setLoading(true)
@@ -335,11 +336,29 @@ const ChatHistory = () => {
<div className="flex items-center gap-2">
<button
type="button"
className="flex items-center gap-1.5 h-8 px-3 rounded-lg text-sm text-neutral-600 border border-neutral-200 bg-white hover:bg-neutral-50 cursor-pointer"
onClick={() => message.info('导出功能后续版本提供')}
disabled={exporting}
className="flex items-center gap-1.5 h-8 px-3 rounded-lg text-sm text-neutral-600 border border-neutral-200 bg-white hover:bg-neutral-50 cursor-pointer disabled:opacity-50"
onClick={async () => {
setExporting(true)
try {
await exportSessionsCSV({
status: statusFilter,
agent_id: agentFilter,
channel_id: channelFilter,
search: search || undefined,
from: dateRange?.[0] ? dateRange[0].format('YYYY-MM-DD') : undefined,
to: dateRange?.[1] ? dateRange[1].format('YYYY-MM-DD') : undefined,
})
message.success('对话记录已导出')
} catch (e) {
message.error(e instanceof Error ? e.message : '导出失败')
} finally {
setExporting(false)
}
}}
>
<DownloadOutlined />
导出
{exporting ? '导出中…' : '导出'}
</button>
<button
type="button"
+14 -2
View File
@@ -8,7 +8,7 @@ import {
DeleteOutlined, ExportOutlined, CloseOutlined, GlobalOutlined, MessageOutlined,
} from '@ant-design/icons'
import {
createCustomer, deleteCustomer, getCustomer, getCustomers, updateCustomer,
createCustomer, deleteCustomer, exportCustomersCSV, getCustomer, getCustomers, updateCustomer,
type Customer, type Session,
} from '@/services/api'
import { useAuth } from '@/stores/auth'
@@ -140,6 +140,7 @@ const Customers = () => {
const [page, setPage] = useState(1)
const [pageSize, setPageSize] = useState(10)
const [search, setSearch] = useState('')
const [exporting, setExporting] = useState(false)
const [tagFilter, setTagFilter] = useState('all')
const [selectedCustomer, setSelectedCustomer] = useState<Customer | null>(null)
const [editingId, setEditingId] = useState<number | null>(null)
@@ -321,8 +322,19 @@ const Customers = () => {
<div className="flex items-center gap-2 ml-auto">
<Button
icon={<ExportOutlined />}
loading={exporting}
className="!text-[13px] !h-8 !px-3 !font-medium !text-neutral-600 !border-neutral-200"
onClick={() => message.info('导出功能后续版本提供')}
onClick={async () => {
setExporting(true)
try {
await exportCustomersCSV({ search: search || undefined })
message.success('客户列表已导出')
} catch (e) {
message.error(e instanceof Error ? e.message : '导出失败')
} finally {
setExporting(false)
}
}}
>
导出
</Button>
+16 -4
View File
@@ -6,7 +6,7 @@ import {
} from '@ant-design/icons'
import { Column, Line, Pie } from '@ant-design/charts'
import {
getAgentPerformance, getChannelDistribution, getKPIs, getResponseDistribution, getSessionTrend,
exportStatisticsCSV, getAgentPerformance, getChannelDistribution, getKPIs, getResponseDistribution, getSessionTrend,
type StatisticsKpis,
} from '@/services/api'
@@ -34,6 +34,7 @@ const Statistics = () => {
name: string; conversations: number; avgResponse: number; satisfaction: number
}[]>([])
const [loading, setLoading] = useState(true)
const [exporting, setExporting] = useState(false)
useEffect(() => {
const load = async () => {
@@ -208,11 +209,22 @@ const Statistics = () => {
</div>
<button
type="button"
className="h-8 px-3 rounded-lg text-sm text-neutral-600 border border-neutral-200 bg-white hover:bg-neutral-50 flex items-center gap-1.5 cursor-pointer"
onClick={() => message.info('导出报告功能后续版本提供')}
disabled={exporting}
className="h-8 px-3 rounded-lg text-sm text-neutral-600 border border-neutral-200 bg-white hover:bg-neutral-50 flex items-center gap-1.5 cursor-pointer disabled:opacity-50"
onClick={async () => {
setExporting(true)
try {
await exportStatisticsCSV()
message.success('统计报告已导出')
} catch (e) {
message.error(e instanceof Error ? e.message : '导出失败')
} finally {
setExporting(false)
}
}}
>
<DownloadOutlined />
导出报告
{exporting ? '导出中…' : '导出报告'}
</button>
</div>
</header>