feat(客户数据): 添加一键导出功能并集成xlsx库
- 在ProblemRanking组件中添加导出按钮和功能 - 新增导出API接口并修改axios基础URL - 添加xlsx依赖用于Excel文件生成 - 实现客户数据展平处理和Excel导出逻辑
This commit is contained in:
@@ -85,6 +85,10 @@ export const cancelSwitchHistoryCampPeriod = (params) => {
|
||||
return https.post('/api/v1/level_four/overview/cancel_switch_history_camp_period', params)
|
||||
}
|
||||
|
||||
// 一键导出 api/v1/level_four/overview/export_customers
|
||||
export const exportCustomers = (params) => {
|
||||
return https.post('http://192.168.15.56:8890/api/v1/level_four/overview/export_customers', params)
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -5,8 +5,8 @@ import { useUserStore } from '@/stores/user'
|
||||
|
||||
// 创建axios实例
|
||||
const service = axios.create({
|
||||
baseURL: 'https://mldash.nycjy.cn/' || '', // API基础路径,支持完整URL
|
||||
// baseURL: 'http://192.168.15.121:8890' || '', // API基础路径,支持完整URL
|
||||
// baseURL: 'https://mldash.nycjy.cn/' || '', // API基础路径,支持完整URL
|
||||
baseURL: 'http://192.168.15.121:8890' || '', // API基础路径,支持完整URL
|
||||
timeout: 100000, // 请求超时时间
|
||||
headers: {
|
||||
'Content-Type': 'application/json;charset=UTF-8'
|
||||
|
||||
@@ -19,9 +19,6 @@
|
||||
历史
|
||||
</button>
|
||||
<button v-if="isViewingHistory" @click="returnToCurrentPeriod" class="current-btn">
|
||||
<svg viewBox="0 0 24 24" width="16" height="16">
|
||||
<path fill="currentColor" d="M12,2A10,10 0 0,1 22,12A10,10 0 0,1 12,22A10,10 0 0,1 2,12A10,10 0 0,1 12,2M12,4A8,8 0 0,0 4,12A8,8 0 0,0 12,20A8,8 0 0,0 20,12A8,8 0 0,0 12,4M12,6A6,6 0 0,1 18,12A6,6 0 0,1 12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6M12,8A4,4 0 0,0 8,12A4,4 0 0,0 12,16A4,4 0 0,0 16,12A4,4 0 0,0 12,8Z"/>
|
||||
</svg>
|
||||
返回当前
|
||||
</button>
|
||||
<button @click="nextMonth" class="nav-btn">
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
<div class="chart-container">
|
||||
<div class="chart-header">
|
||||
<h3>客户迫切解决的问题排行榜</h3>
|
||||
<button @click="exportData">一键导出</button>
|
||||
</div>
|
||||
<div class="chart-content">
|
||||
<div v-if="sortedData.length > 0" class="problem-ranking">
|
||||
@@ -33,7 +34,13 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed } from 'vue';
|
||||
import { computed,onMounted } from 'vue';
|
||||
import { exportCustomers } from '@/api/secondTop';
|
||||
import { useUserStore } from "@/stores/user";
|
||||
import { ElMessage } from 'element-plus';
|
||||
import * as XLSX from 'xlsx';
|
||||
// 用户store实例
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 定义Props,接收一个包含 { name: string, value: string | number } 的数组
|
||||
const props = defineProps({
|
||||
@@ -73,6 +80,111 @@ const getRankingClass = (index) => {
|
||||
const getRankBadgeClass = (index) => {
|
||||
return ['badge-gold', 'badge-silver', 'badge-bronze'][index] || 'badge-default';
|
||||
};
|
||||
|
||||
async function exportData() {
|
||||
const params = {
|
||||
user_name: userStore.userInfo.username,
|
||||
user_level: userStore.userInfo.user_level.toString(),
|
||||
}
|
||||
|
||||
try {
|
||||
ElMessage.info('正在导出数据,请稍候...')
|
||||
const res = await exportCustomers(params)
|
||||
|
||||
if (res.code === 200 && res.data && res.data.length > 0) {
|
||||
// 处理数据,将复杂的嵌套对象展平
|
||||
const exportData = res.data.map(customer => {
|
||||
const flatData = {
|
||||
'昵称': customer.nickname || '',
|
||||
'性别': customer.gender || '',
|
||||
'跟进人': customer.follow_up_name || '',
|
||||
'手机号': customer.phone || '',
|
||||
'是否入群': customer.is_in_group || '',
|
||||
'用户ID': customer.mantis_user_id || '',
|
||||
}
|
||||
|
||||
// 处理微信表单信息
|
||||
if (customer.wechat_form) {
|
||||
flatData['家长姓名'] = customer.wechat_form.name || ''
|
||||
flatData['孩子姓名'] = customer.wechat_form.child_name || ''
|
||||
flatData['孩子性别'] = customer.wechat_form.child_gender || ''
|
||||
flatData['职业'] = customer.wechat_form.occupation || ''
|
||||
flatData['孩子教育阶段'] = customer.wechat_form.child_education || ''
|
||||
flatData['与孩子关系'] = customer.wechat_form.child_relation || ''
|
||||
flatData['联系电话'] = customer.wechat_form.mobile || ''
|
||||
flatData['地区'] = customer.wechat_form.territory || ''
|
||||
flatData['创建时间'] = customer.wechat_form.created_at ? new Date(customer.wechat_form.created_at).toLocaleString() : ''
|
||||
flatData['更新时间'] = customer.wechat_form.updated_at ? new Date(customer.wechat_form.updated_at).toLocaleString() : ''
|
||||
}
|
||||
|
||||
// 处理到课情况
|
||||
if (customer.live) {
|
||||
flatData['课一到课情况'] = customer.live['课一'] || ''
|
||||
flatData['课二到课情况'] = customer.live['课二'] || ''
|
||||
flatData['课三到课情况'] = customer.live['课三'] || ''
|
||||
flatData['课四到课情况'] = customer.live['课四'] || ''
|
||||
}
|
||||
|
||||
// 处理问卷调查信息
|
||||
if (customer.wechat_form && customer.wechat_form.additional_info) {
|
||||
customer.wechat_form.additional_info.forEach((item, index) => {
|
||||
flatData[`问题${index + 1}`] = item.topic || ''
|
||||
flatData[`答案${index + 1}`] = item.answer || ''
|
||||
})
|
||||
}
|
||||
|
||||
return flatData
|
||||
})
|
||||
|
||||
// 创建工作簿
|
||||
const wb = XLSX.utils.book_new()
|
||||
const ws = XLSX.utils.json_to_sheet(exportData)
|
||||
|
||||
// 设置列宽
|
||||
const colWidths = [
|
||||
{ wch: 10 }, // 昵称
|
||||
{ wch: 6 }, // 性别
|
||||
{ wch: 12 }, // 跟进人
|
||||
{ wch: 15 }, // 手机号
|
||||
{ wch: 10 }, // 是否入群
|
||||
{ wch: 20 }, // 用户ID
|
||||
{ wch: 12 }, // 家长姓名
|
||||
{ wch: 12 }, // 孩子姓名
|
||||
{ wch: 8 }, // 孩子性别
|
||||
{ wch: 12 }, // 职业
|
||||
{ wch: 12 }, // 孩子教育阶段
|
||||
{ wch: 15 }, // 与孩子关系
|
||||
{ wch: 15 }, // 联系电话
|
||||
{ wch: 20 }, // 地区
|
||||
{ wch: 20 }, // 创建时间
|
||||
{ wch: 20 }, // 更新时间
|
||||
]
|
||||
ws['!cols'] = colWidths
|
||||
|
||||
// 添加工作表到工作簿
|
||||
XLSX.utils.book_append_sheet(wb, ws, '客户数据')
|
||||
|
||||
// 生成文件名(包含当前时间)
|
||||
const now = new Date()
|
||||
const fileName = `客户数据导出_${now.getFullYear()}${(now.getMonth() + 1).toString().padStart(2, '0')}${now.getDate().toString().padStart(2, '0')}_${now.getHours().toString().padStart(2, '0')}${now.getMinutes().toString().padStart(2, '0')}.xlsx`
|
||||
|
||||
// 导出文件
|
||||
XLSX.writeFile(wb, fileName)
|
||||
|
||||
ElMessage.success(`导出成功!共导出 ${exportData.length} 条数据`)
|
||||
} else {
|
||||
ElMessage.warning('暂无数据可导出')
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('导出失败:', error)
|
||||
ElMessage.error('导出失败,请稍后重试')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onMounted(async ()=>{
|
||||
await exportData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@@ -89,12 +201,40 @@ const getRankBadgeClass = (index) => {
|
||||
.chart-header {
|
||||
padding: 20px 20px 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
h3 {
|
||||
margin: 0;
|
||||
color: #303133;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 8px 16px;
|
||||
background: linear-gradient(135deg, #409eff, #3a8ee6);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
|
||||
|
||||
&:hover {
|
||||
background: linear-gradient(135deg, #3a8ee6, #337ecc);
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 8px rgba(64, 158, 255, 0.4);
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 4px rgba(64, 158, 255, 0.3);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chart-content {
|
||||
|
||||
Reference in New Issue
Block a user