feat(销售驾驶舱): 优化销售时间线标题样式和布局调整
refactor(客户类型): 重构客户类型图表组件,增加数据校验和错误处理 fix(中心概览): 修正中心总业绩单位显示错误 style(路由导航): 添加路由导航时的面包屑和用户信息显示 chore: 移动客户类型组件至正确目录并更新引用路径
This commit is contained in:
315
my-vue-app/src/views/topOne/components/CustomerType.vue
Normal file
315
my-vue-app/src/views/topOne/components/CustomerType.vue
Normal file
@@ -0,0 +1,315 @@
|
||||
<template>
|
||||
<div class="chart-container">
|
||||
<div class="chart-header">
|
||||
<h3>客户类型占比</h3>
|
||||
<select v-model="customerTypeCategory" @change="handleCategoryChange" class="chart-select">
|
||||
<option value="profession">职业</option>
|
||||
<option value="childGrade">年级</option>
|
||||
<option value="region">地域</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="chart-content">
|
||||
<div ref="customerTypeChartRef" class="customer-type-chart"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted, onBeforeUnmount, computed, watch } from 'vue';
|
||||
import * as echarts from 'echarts';
|
||||
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
customerData: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
// 定义emit
|
||||
const emit = defineEmits(['categoryChange'])
|
||||
|
||||
const customerTypeChartRef = ref(null);
|
||||
let customerTypeChart = null;
|
||||
|
||||
const customerTypeCategory = ref('childGrade');
|
||||
|
||||
// 计算属性:将API数据转换为图表所需格式
|
||||
const chartData = computed(() => {
|
||||
if (!props.customerData || !props.customerData.customer_type_distribution) {
|
||||
return []
|
||||
}
|
||||
|
||||
let processedData = props.customerData.customer_type_distribution.map(item => ({
|
||||
name: item.category,
|
||||
value: parseFloat(item.ratio.replace('%', '')) || 0
|
||||
}))
|
||||
|
||||
// 如果是地域类型,需要按省份进行数据整理
|
||||
if (customerTypeCategory.value === 'region') {
|
||||
const provinceMap = new Map()
|
||||
|
||||
processedData.forEach(item => {
|
||||
// 提取省份名称,处理"山东省 临沂市 莒南县"这种空格分隔的格式
|
||||
let provinceName = item.name
|
||||
|
||||
// 处理空格分隔的地域数据格式(如:"山东省 临沂市 莒南县")
|
||||
if (item.name.includes(' ')) {
|
||||
const parts = item.name.split(' ')
|
||||
provinceName = parts[0] // 取第一部分作为省份
|
||||
}
|
||||
// 处理横线分隔的格式
|
||||
else if (item.name.includes('-')) {
|
||||
provinceName = item.name.split('-')[0]
|
||||
}
|
||||
// 处理包含省字的格式
|
||||
else if (item.name.includes('省') || item.name.includes('市') || item.name.includes('区')) {
|
||||
// 如果已经包含省份标识,直接使用
|
||||
if (item.name.includes('省') || item.name.includes('自治区') || item.name.endsWith('市')) {
|
||||
provinceName = item.name
|
||||
} else {
|
||||
// 根据城市名推断省份
|
||||
const cityToProvince = {
|
||||
'北京': '北京市',
|
||||
'上海': '上海市',
|
||||
'天津': '天津市',
|
||||
'重庆': '重庆市',
|
||||
'广州': '广东省',
|
||||
'深圳': '广东省',
|
||||
'珠海': '广东省',
|
||||
'佛山': '广东省',
|
||||
'东莞': '广东省',
|
||||
'中山': '广东省',
|
||||
'杭州': '浙江省',
|
||||
'宁波': '浙江省',
|
||||
'温州': '浙江省',
|
||||
'南京': '江苏省',
|
||||
'苏州': '江苏省',
|
||||
'无锡': '江苏省',
|
||||
'常州': '江苏省',
|
||||
'成都': '四川省',
|
||||
'绵阳': '四川省',
|
||||
'武汉': '湖北省',
|
||||
'宜昌': '湖北省',
|
||||
'西安': '陕西省',
|
||||
'郑州': '河南省',
|
||||
'洛阳': '河南省',
|
||||
'济南': '山东省',
|
||||
'青岛': '山东省',
|
||||
'烟台': '山东省',
|
||||
'潍坊': '山东省',
|
||||
'临沂': '山东省',
|
||||
'大连': '辽宁省',
|
||||
'沈阳': '辽宁省',
|
||||
'长春': '吉林省',
|
||||
'哈尔滨': '黑龙江省',
|
||||
'石家庄': '河北省',
|
||||
'唐山': '河北省',
|
||||
'太原': '山西省',
|
||||
'呼和浩特': '内蒙古自治区',
|
||||
'南宁': '广西壮族自治区',
|
||||
'桂林': '广西壮族自治区',
|
||||
'海口': '海南省',
|
||||
'三亚': '海南省',
|
||||
'昆明': '云南省',
|
||||
'贵阳': '贵州省',
|
||||
'拉萨': '西藏自治区',
|
||||
'兰州': '甘肃省',
|
||||
'西宁': '青海省',
|
||||
'银川': '宁夏回族自治区',
|
||||
'乌鲁木齐': '新疆维吾尔自治区',
|
||||
'合肥': '安徽省',
|
||||
'芜湖': '安徽省',
|
||||
'福州': '福建省',
|
||||
'厦门': '福建省',
|
||||
'泉州': '福建省',
|
||||
'南昌': '江西省',
|
||||
'长沙': '湖南省',
|
||||
'株洲': '湖南省',
|
||||
// 港澳台地区
|
||||
'香港': '香港特别行政区',
|
||||
'澳门': '澳门特别行政区',
|
||||
'台北': '台湾省',
|
||||
'高雄': '台湾省',
|
||||
'台中': '台湾省',
|
||||
'台南': '台湾省',
|
||||
'桃园': '台湾省',
|
||||
'新竹': '台湾省',
|
||||
'基隆': '台湾省',
|
||||
'嘉义': '台湾省',
|
||||
'台东': '台湾省',
|
||||
'花莲': '台湾省',
|
||||
'宜兰': '台湾省',
|
||||
'屏东': '台湾省',
|
||||
'彰化': '台湾省',
|
||||
'南投': '台湾省',
|
||||
'云林': '台湾省',
|
||||
'苗栗': '台湾省',
|
||||
'澎湖': '台湾省'
|
||||
}
|
||||
provinceName = cityToProvince[item.name] || item.name
|
||||
}
|
||||
}
|
||||
|
||||
// 聚合同一省份的数据
|
||||
if (provinceMap.has(provinceName)) {
|
||||
provinceMap.set(provinceName, provinceMap.get(provinceName) + item.value)
|
||||
} else {
|
||||
provinceMap.set(provinceName, item.value)
|
||||
}
|
||||
})
|
||||
|
||||
// 转换为数组格式
|
||||
processedData = Array.from(provinceMap.entries()).map(([name, value]) => ({
|
||||
name,
|
||||
value
|
||||
}))
|
||||
}
|
||||
|
||||
return processedData
|
||||
})
|
||||
|
||||
const updateChart = () => {
|
||||
if (!customerTypeChart) return;
|
||||
|
||||
// 使用真实数据
|
||||
const currentData = chartData.value
|
||||
|
||||
if (!currentData || currentData.length === 0) {
|
||||
// 如果没有数据,显示空状态
|
||||
const option = {
|
||||
title: {
|
||||
text: '暂无数据',
|
||||
left: 'center',
|
||||
top: 'middle',
|
||||
textStyle: {
|
||||
color: '#999',
|
||||
fontSize: 16
|
||||
}
|
||||
}
|
||||
}
|
||||
customerTypeChart.setOption(option, true)
|
||||
return
|
||||
}
|
||||
|
||||
const option = {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter: function(params) {
|
||||
return params.name + '<br/>' + '占比: ' + params.value + '%' + ' (' + params.percent + '%)'
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
orient: 'vertical',
|
||||
left: 'left',
|
||||
top: 'center',
|
||||
textStyle: {
|
||||
fontSize: 12
|
||||
}
|
||||
},
|
||||
series: [{
|
||||
name: '客户类型占比',
|
||||
type: 'pie',
|
||||
radius: ['40%', '70%'],
|
||||
center: ['65%', '50%'],
|
||||
avoidLabelOverlap: false,
|
||||
label: {
|
||||
show: false,
|
||||
position: 'center'
|
||||
},
|
||||
emphasis: {
|
||||
label: {
|
||||
show: true,
|
||||
fontSize: '16',
|
||||
fontWeight: 'bold'
|
||||
}
|
||||
},
|
||||
labelLine: {
|
||||
show: false
|
||||
},
|
||||
data: currentData.map((item, index) => ({
|
||||
name: item.name,
|
||||
value: item.value,
|
||||
itemStyle: {
|
||||
color: ['#5470c6', '#91cc75', '#fac858', '#ee6666', '#73c0de', '#fc8452', '#9a60b4'][index % 7]
|
||||
}
|
||||
}))
|
||||
}]
|
||||
};
|
||||
customerTypeChart.setOption(option, true);
|
||||
};
|
||||
|
||||
const initChart = () => {
|
||||
if (!customerTypeChartRef.value) return;
|
||||
customerTypeChart = echarts.init(customerTypeChartRef.value);
|
||||
updateChart();
|
||||
};
|
||||
|
||||
const resizeChart = () => customerTypeChart?.resize();
|
||||
|
||||
// 处理类别选择变化
|
||||
const handleCategoryChange = () => {
|
||||
// 映射选择值到API参数
|
||||
const distributionTypeMap = {
|
||||
'profession': 'occupation',
|
||||
'childGrade': 'child_education',
|
||||
'region': 'territory'
|
||||
}
|
||||
|
||||
// 通知父组件选择变化
|
||||
emit('categoryChange', distributionTypeMap[customerTypeCategory.value])
|
||||
}
|
||||
|
||||
// 监听数据变化
|
||||
watch(chartData, () => {
|
||||
updateChart()
|
||||
}, { deep: true })
|
||||
|
||||
onMounted(() => {
|
||||
customerTypeChart = echarts.init(customerTypeChartRef.value);
|
||||
updateChart();
|
||||
window.addEventListener('resize', resizeChart);
|
||||
// 初始化时触发一次事件,加载默认数据
|
||||
handleCategoryChange()
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
customerTypeChart?.dispose();
|
||||
window.removeEventListener('resize', resizeChart);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.chart-container {
|
||||
background: #ffffff;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
|
||||
height: 400px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.chart-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px 20px 16px;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
h3 { margin: 0; color: #303133; font-size: 18px; font-weight: 600; }
|
||||
}
|
||||
.chart-content {
|
||||
padding: 20px;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
}
|
||||
.customer-type-chart {
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
}
|
||||
.chart-select {
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid #e2e8f0;
|
||||
background-color: #f8fafc;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
||||
@@ -149,7 +149,7 @@ import SalesProgress from "./components/SalesProgress.vue";
|
||||
import TaskList from "./components/TaskList.vue";
|
||||
import FunnelChart from "./components/FunnelChart.vue";
|
||||
import CustomerProfile from "./components/CustomerProfile.vue";
|
||||
import CustomerType from "../secondTop/components/CustomerType.vue";
|
||||
import CustomerType from "./components/CustomerType.vue";
|
||||
import ProblemRanking from "../secondTop/components/ProblemRanking.vue";
|
||||
import RankingList from "./components/RankingList.vue";
|
||||
import PersonalSalesRanking from "./components/PersonalSalesRanking.vue";
|
||||
|
||||
Reference in New Issue
Block a user