|
|
@ -92,6 +92,7 @@ |
|
|
|
<el-button @click="get7Days()">近7天</el-button> |
|
|
|
</el-form-item> |
|
|
|
<el-form-item> |
|
|
|
<el-button type="success" @click="exportExcel">导出Excel</el-button> |
|
|
|
<el-button type="primary" @click="search">查询</el-button> |
|
|
|
<el-button type="success" @click="reset(ruleFormRef)">重置</el-button> |
|
|
|
</el-form-item> |
|
|
@ -164,13 +165,70 @@ |
|
|
|
</el-pagination> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 导出信息确认对话框 --> |
|
|
|
<el-dialog |
|
|
|
v-model="showExportInfoPanel" |
|
|
|
title="导出信息确认" |
|
|
|
width="400px" |
|
|
|
:close-on-click-modal="false" |
|
|
|
> |
|
|
|
<div class="info-panel-header">导出信息</div> |
|
|
|
<div v-if="!detailY.jwcode && !detailY.ipAddress && !detailY.sourceType && !detailY.sourceName && (!detailY.startTime || !detailY.endTime)"> |
|
|
|
你正在导出所有数据 |
|
|
|
</div> |
|
|
|
<div v-else> |
|
|
|
你正在导出以下数据 |
|
|
|
</div> |
|
|
|
<div v-if="detailY.jwcode">精网号:{{ detailY.jwcode }}</div> |
|
|
|
<div v-if="detailY.ipAddress">地区:{{ detailY.ipAddress }}</div> |
|
|
|
<div v-if="detailY.sourceType"> |
|
|
|
消费类型:{{ consumList.value.find(item => item.value === detailY.sourceType)?.text }} |
|
|
|
</div> |
|
|
|
<div v-if="detailY.sourceName">直播间:{{ detailY.sourceName }}</div> |
|
|
|
<div v-if="detailY.startTime && detailY.endTime"> |
|
|
|
时间范围:{{ detailY.startTime }} 至 {{ detailY.endTime }} |
|
|
|
</div> |
|
|
|
<template #footer> |
|
|
|
<span class="dialog-footer"> |
|
|
|
<el-button @click="showExportInfoPanel = false">取消</el-button> |
|
|
|
<el-button type="primary" @click="doExportExcel">导出</el-button> |
|
|
|
</span> |
|
|
|
</template> |
|
|
|
</el-dialog> |
|
|
|
|
|
|
|
<!-- 导出进度对话框 --> |
|
|
|
<el-dialog |
|
|
|
v-model="isExporting" |
|
|
|
title="正在导出" |
|
|
|
width="400px" |
|
|
|
:close-on-click-modal="false" |
|
|
|
:show-close="false" |
|
|
|
> |
|
|
|
<el-progress |
|
|
|
:percentage="exportProgress" |
|
|
|
:stroke-width="15" |
|
|
|
striped |
|
|
|
animated |
|
|
|
/> |
|
|
|
<div class="export-status"> |
|
|
|
已导出 {{ Math.round((exportProgress / 100) * total) }} 条 / 共 {{ total }} 条 |
|
|
|
</div> |
|
|
|
<template #footer> |
|
|
|
<el-button type="danger" @click="cancelExport">取消导出</el-button> |
|
|
|
</template> |
|
|
|
</el-dialog> |
|
|
|
</template> |
|
|
|
<script setup lang="ts"> |
|
|
|
import { ref } from 'vue' |
|
|
|
import type { FormInstance } from 'element-plus' |
|
|
|
// import { ElMessage } from 'element-plus' |
|
|
|
import { ElMessage } from 'element-plus' |
|
|
|
import moment from 'moment' |
|
|
|
import API from '@/util/http' |
|
|
|
import axios from 'axios' |
|
|
|
// 导入 xlsx 库的相关方法 |
|
|
|
import * as XLSX from 'xlsx' |
|
|
|
const { utils, writeFile } = XLSX |
|
|
|
|
|
|
|
// 充值明细表格 |
|
|
|
const tableData = ref([]) |
|
|
@ -332,6 +390,158 @@ const getArea = async () => { |
|
|
|
console.log('请求失败', error) |
|
|
|
} |
|
|
|
} |
|
|
|
// 新增导出相关响应式变量 |
|
|
|
const showExportInfoPanel = ref(false) |
|
|
|
const exportProgress = ref(0) |
|
|
|
const isExporting = ref(false) |
|
|
|
const exportCancelToken = ref<any>(null) |
|
|
|
|
|
|
|
// 导出 Excel 表头 |
|
|
|
const headers = [ |
|
|
|
'序号', |
|
|
|
'姓名', |
|
|
|
'精网号', |
|
|
|
'地区', |
|
|
|
'消费类型', |
|
|
|
'金豆价格', |
|
|
|
'直播间', |
|
|
|
'消费时间' |
|
|
|
] |
|
|
|
|
|
|
|
// 点击导出按钮显示信息面板 |
|
|
|
const exportExcel = () => { |
|
|
|
showExportInfoPanel.value = true |
|
|
|
} |
|
|
|
|
|
|
|
// 执行导出操作 |
|
|
|
const doExportExcel = async () => { |
|
|
|
try { |
|
|
|
isExporting.value = true |
|
|
|
exportProgress.value = 0 |
|
|
|
showExportInfoPanel.value = false |
|
|
|
|
|
|
|
// 初始化 Excel |
|
|
|
const wb = utils.book_new() |
|
|
|
const ws = utils.aoa_to_sheet([headers]) |
|
|
|
utils.book_append_sheet(wb, ws, 'Sheet1') |
|
|
|
|
|
|
|
// 流式写入配置 |
|
|
|
const writer = { |
|
|
|
write: (d: any[][], o: any) => { |
|
|
|
if (!d) return |
|
|
|
utils.sheet_add_aoa(ws, d, { origin: -1 }) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
let page = 1 |
|
|
|
let totalExported = 0 |
|
|
|
const pageSize = 5000 // 每次请求 5000 条 |
|
|
|
let totalRecords = 0 |
|
|
|
|
|
|
|
// 首次请求获取总记录数 |
|
|
|
const firstResult = await API({ |
|
|
|
url: '/dou/getSpend', |
|
|
|
method: 'post', |
|
|
|
data: { |
|
|
|
pageNum: 1, |
|
|
|
pageSize, |
|
|
|
spend: { |
|
|
|
...detailY.value |
|
|
|
} |
|
|
|
} |
|
|
|
}) |
|
|
|
totalRecords = firstResult.data.total |
|
|
|
|
|
|
|
// 创建取消令牌 |
|
|
|
const CancelToken = axios.CancelToken |
|
|
|
exportCancelToken.value = CancelToken.source() |
|
|
|
|
|
|
|
// 处理首次请求的数据 |
|
|
|
const firstData = firstResult.data.list |
|
|
|
if (firstData.length) { |
|
|
|
const rows = firstData.map((row: any, index: number) => { |
|
|
|
return [ |
|
|
|
totalExported + index + 1, |
|
|
|
row.nickname || '', |
|
|
|
row.jwcode || '', |
|
|
|
row.ipAddress || '', |
|
|
|
consumList.value.find(item => item.value === row.sourceType)?.text || '', |
|
|
|
row.jinbiCostTotal || 0, |
|
|
|
row.room || '', |
|
|
|
moment(row.createTime).format('YYYY-MM-DD HH:mm:ss') || '' |
|
|
|
] |
|
|
|
}) |
|
|
|
writer.write(rows,{}) |
|
|
|
totalExported += firstData.length |
|
|
|
exportProgress.value = Math.round((totalExported / totalRecords) * 100) |
|
|
|
page++ |
|
|
|
} |
|
|
|
|
|
|
|
while (totalExported < totalRecords) { |
|
|
|
const result = await API({ |
|
|
|
url: '/dou/getSpend', |
|
|
|
method: 'post', |
|
|
|
data: { |
|
|
|
pageNum: page, |
|
|
|
pageSize, |
|
|
|
spend: { |
|
|
|
...detailY.value |
|
|
|
} |
|
|
|
}, |
|
|
|
cancelToken: exportCancelToken.value.token |
|
|
|
}) |
|
|
|
|
|
|
|
const data = result.data.list |
|
|
|
if (!data.length) break |
|
|
|
|
|
|
|
// 转换数据 |
|
|
|
const rows = data.map((row: any, index: number) => [ |
|
|
|
totalExported + index + 1, |
|
|
|
row.nickname || '', |
|
|
|
row.jwcode || '', |
|
|
|
row.ipAddress || '', |
|
|
|
consumList.value.find(item => item.value === row.sourceType)?.text || '', |
|
|
|
row.jinbiCostTotal || 0, |
|
|
|
row.room || '', |
|
|
|
moment(row.createTime).format('YYYY-MM-DD HH:mm:ss') || '' |
|
|
|
]) |
|
|
|
|
|
|
|
// 流式写入 |
|
|
|
writer.write(rows,{}) |
|
|
|
totalExported += data.length |
|
|
|
exportProgress.value = Math.round((totalExported / totalRecords) * 100) |
|
|
|
|
|
|
|
// 内存控制:每 500 页释放内存 |
|
|
|
if (page % 500 === 0) { |
|
|
|
await new Promise(resolve => setTimeout(resolve, 0)) |
|
|
|
} |
|
|
|
|
|
|
|
page++ |
|
|
|
} |
|
|
|
|
|
|
|
// 生成最终文件 |
|
|
|
writeFile(wb, '金豆消费明细.xlsx') |
|
|
|
exportProgress.value = 100 |
|
|
|
isExporting.value = false |
|
|
|
exportCancelToken.value = null |
|
|
|
ElMessage.success(`导出成功,共${totalExported}条数据`) |
|
|
|
} catch (error) { |
|
|
|
if (!axios.isCancel(error)) { |
|
|
|
ElMessage.error(`导出失败: ${error.message}`) |
|
|
|
} |
|
|
|
isExporting.value = false |
|
|
|
exportCancelToken.value = null |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 取消导出操作 |
|
|
|
const cancelExport = () => { |
|
|
|
if (exportCancelToken.value) { |
|
|
|
exportCancelToken.value.cancel('用户取消导出') |
|
|
|
ElMessage.warning('导出已取消') |
|
|
|
isExporting.value = false |
|
|
|
} |
|
|
|
} |
|
|
|
// 挂载 |
|
|
|
getInit() |
|
|
|
getCount() |
|
|
|