feat(ui): 所有列表页加CSV导出按钮(全量拉取+中文BOM兼容Excel)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
浅念
2026-08-15 10:31:21 +08:00
parent 9bec4dd7c8
commit edd6a27371
6 changed files with 327 additions and 11 deletions
+46
View File
@@ -2,6 +2,7 @@
import { computed, onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage, type TabPaneName } from 'element-plus'
import { Download } from '@element-plus/icons-vue'
import { getCalls } from '@/api'
import type { CallChannel, PhoneCall } from '@/api/types'
import {
@@ -12,6 +13,7 @@ import {
formatTime,
} from '@/utils/format'
import { buildCallSections, readField } from '@/utils/callFields'
import { exportListToCsv } from '@/utils/csvExport'
import VoiceClipModal from '@/components/VoiceClipModal.vue'
// Calls aren't tied to a messaging channel — `channel` on a call is the call
@@ -91,6 +93,38 @@ function onTabChange(_name: TabPaneName): void {
load()
}
// ---- header 导出:当前 tab 通话类型全量记录 CSV ----
const exporting = ref(false)
async function exportCalls(): Promise<void> {
if (total.value === 0) {
ElMessage.warning('当前列表为空,无数据可导出')
return
}
exporting.value = true
try {
const channel = callType.value
const result = await exportListToCsv({
fetchPage: (page, pageSize) => getCalls({ channel, page, page_size: pageSize }),
columns: [
{ key: 'user_id_name', label: '员工', format: (r) => r.user_id_name ?? r.user_id ?? '' },
{ key: 'call_number', label: '客户', format: (r) => r.call_number ?? '' },
{ key: 'call_status', label: '状态', format: (r) => callStatusLabel(r.call_status, r.channel) },
{ key: 'incoming_call', label: '方向', format: (r) => callDirectionLabel(r.incoming_call) },
{ key: 'call_duration', label: '通话时长', format: (r) => formatCallDuration(r.call_duration) },
{ key: 'start_call_time', label: '通话时间', format: (r) => (r.start_call_time ? formatTime(r.start_call_time) : '') },
{ key: 'record', label: '有无录音', format: (r) => (r.record_media_hash || r.record_file ? '有' : '无') },
],
filename: `${tabs.find((t) => t.value === callType.value)?.label ?? '通话'}记录`,
})
ElMessage.success(`已导出 ${result.rows} 条`)
} catch (e) {
ElMessage.error(`导出失败:${(e as Error).message}`)
} finally {
exporting.value = false
}
}
/**
* 当前表格要渲染的行。非 voice_call tab 原样返回;voice_call tab 按 voiceScope
* 依 isRoom(0=好友, 1=群)做页内过滤。云客 isRoom 可能是 number/string/boolean,
@@ -217,6 +251,11 @@ onMounted(load)
共 {{ total }} 条 · 类型:{{ tabs.find(t => t.value === callType)?.label }}
</span>
</div>
<div class="yk-calls__actions">
<el-button size="small" :loading="exporting" @click="exportCalls">
<el-icon><Download /></el-icon> 导出
</el-button>
</div>
</div>
<el-tabs v-model="callType" class="yk-calls__tabs" @tab-change="onTabChange">
@@ -490,6 +529,13 @@ onMounted(load)
font-size: 13px;
}
.yk-calls__actions {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
}
.yk-cell__sub {
color: #909399;
font-size: 12px;