feat(销售驾驶舱): 优化销售时间线标题样式和布局调整

refactor(客户类型): 重构客户类型图表组件,增加数据校验和错误处理

fix(中心概览): 修正中心总业绩单位显示错误

style(路由导航): 添加路由导航时的面包屑和用户信息显示

chore: 移动客户类型组件至正确目录并更新引用路径
This commit is contained in:
2025-08-18 20:44:29 +08:00
parent 8eff57cf05
commit f0ad14e025
7 changed files with 458 additions and 154 deletions

View File

@@ -7,7 +7,7 @@
<span class="card-title">中心总业绩</span>
<span class="card-trend positive">{{ props.overallData.CenterPerformance?.center_monthly_vs_previous_deals }} vs 上期</span>
</div>
<div class="card-value">{{ props.overallData.CenterPerformance.center_monthly_deal_count || '552,000' }} </div>
<div class="card-value">{{ props.overallData.CenterPerformance.center_monthly_deal_count || '552,000' }} </div>
<div class="card-subtitle">月目标完成率: {{ props.overallData.CenterPerformance?.center_monthly_target_completion_rate || '56%' }}</div>
</div>

View File

@@ -15,7 +15,7 @@
</template>
<script setup>
import { ref, reactive, onMounted, onBeforeUnmount, computed, watch } from 'vue';
import { ref, reactive, onMounted, onBeforeUnmount, computed, watch, nextTick } from 'vue';
import * as echarts from 'echarts';
// 定义props
@@ -40,10 +40,12 @@ const chartData = computed(() => {
return []
}
let processedData = props.customerData.customer_type_distribution.map(item => ({
name: item.category,
value: parseFloat(item.ratio.replace('%', '')) || 0
}))
let processedData = props.customerData.customer_type_distribution
.filter(item => item && item.category) // 过滤掉无效数据
.map(item => ({
name: item.category,
value: parseFloat((item.ratio || '0').replace('%', '')) || 0
}))
// 如果是地域类型,需要按省份进行数据整理
if (customerTypeCategory.value === 'region') {
@@ -53,6 +55,11 @@ const chartData = computed(() => {
// 提取省份名称,处理"山东省 临沂市 莒南县"这种空格分隔的格式
let provinceName = item.name
// 检查item.name是否存在避免null引用错误
if (!item.name || typeof item.name !== 'string') {
return // 跳过无效数据
}
// 处理空格分隔的地域数据格式(如:"山东省 临沂市 莒南县"
if (item.name.includes(' ')) {
const parts = item.name.split(' ')
@@ -170,7 +177,7 @@ const chartData = computed(() => {
})
const updateChart = () => {
if (!customerTypeChart) return;
if (!customerTypeChart || !customerTypeChartRef.value) return;
// 使用真实数据
const currentData = chartData.value
@@ -265,9 +272,21 @@ watch(chartData, () => {
updateChart()
}, { deep: true })
// 监听类别变化,确保切换时立即更新图表
watch(customerTypeCategory, () => {
nextTick(() => {
updateChart()
})
})
onMounted(() => {
customerTypeChart = echarts.init(customerTypeChartRef.value);
updateChart();
// 使用nextTick确保DOM完全渲染后再初始化图表
nextTick(() => {
if (customerTypeChartRef.value) {
customerTypeChart = echarts.init(customerTypeChartRef.value);
updateChart();
}
});
window.addEventListener('resize', resizeChart);
// 初始化时触发一次事件,加载默认数据
handleCategoryChange()

View File

@@ -4,16 +4,33 @@
<header class="dashboard-header">
<div class="header-content">
<div class="logo-section">
<div class="header-text">
<h1>中心组长指挥台</h1>
<p>统筹多组运营优化资源配置驱动业绩增长实现团队协同发展</p>
<!-- 动态顶栏根据是否有路由参数显示不同内容 -->
<!-- 路由跳转时的顶栏面包屑 + 姓名 -->
<div v-if="isRouteNavigation" class="route-header">
<div class="breadcrumb">
<span class="breadcrumb-item" @click="goBack">团队管理</span>
<span class="breadcrumb-separator">></span>
<span class="breadcrumb-item current"> {{ routeUserName }}中心组长指挥台</span>
</div>
<div class="user-name">
{{ routeUserName }}
</div>
</div>
<!-- 营期阶段信息 -->
<div class="stage-info" style="margin-left: 100px;">
<span class="stage-label">营期所属阶段</span>
<span class="stage-value">接数据</span>
</div>
<!-- 自己登录时的顶栏原有样式 -->
<template v-else>
<div class="header-text">
<h1>中心组长指挥台</h1>
<p>统筹多组运营优化资源配置驱动业绩增长实现团队协同发展</p>
</div>
<!-- 营期阶段信息 -->
<div class="stage-info" style="margin-left: 100px;">
<span class="stage-label">营期所属阶段</span>
<span class="stage-value">接数据</span>
</div>
</template>
<div>
<!-- 用户下拉菜单 -->
<UserDropdown />
@@ -178,6 +195,22 @@
return params
}
// 判断是否为路由导航(有路由参数)
const isRouteNavigation = computed(() => {
const routeUserName = router.currentRoute.value.query.user_name || router.currentRoute.value.params.user_name
return !!routeUserName
})
// 获取路由传递的用户名
const routeUserName = computed(() => {
return router.currentRoute.value.query.user_name || router.currentRoute.value.params.user_name || ''
})
// 返回上一页
const goBack = () => {
router.go(-1)
}
// 中心整体概览
const overallCenterPerformance = ref({
CenterPerformance: {},
@@ -282,7 +315,7 @@
}
}
// 客户类型
async function CenterCustomerType(distributionType = 'occupation') {
async function CenterCustomerType(distributionType = 'child_education') {
const params = getRequestParams()
const hasParams = params.user_name
// 添加distribution_type参数
@@ -393,79 +426,6 @@ const conversionRateVsAverage = ref({})
if (res.code === 200) {
groupPerformance.value = res.data
/**
* "data": {
"department": "巅峰三部-刘东洋",
"group_details": [
{
"name": "徐小玉",
"today_performance": 0.0,
"monthly_performance": 0.0,
"conversion_rate_this_period": "0.00%",
"new_customers_this_period": 41,
"deals_this_period": 0,
"deals_this_month": 0,
"call_count_this_period": 0,
"rank": 2
},
{
"name": "唐梦",
"today_performance": 0.0,
"monthly_performance": 0.0,
"conversion_rate_this_period": "0.00%",
"new_customers_this_period": 39,
"deals_this_period": 0,
"deals_this_month": 0,
"call_count_this_period": 0,
"rank": 2
},
{
"name": "董富忠",
"today_performance": 0,
"monthly_performance": 6500.0,
"conversion_rate_this_period": "1.72%",
"new_customers_this_period": 58,
"deals_this_period": 1,
"deals_this_month": 1,
"call_count_this_period": 0,
"rank": 1
},
{
"name": "王娟娟",
"today_performance": 0.0,
"monthly_performance": 0.0,
"conversion_rate_this_period": "0.00%",
"new_customers_this_period": 51,
"deals_this_period": 0,
"deals_this_month": 0,
"call_count_this_period": 0,
"rank": 2
},
{
"name": "罗荣海",
"today_performance": 0.0,
"monthly_performance": 0.0,
"conversion_rate_this_period": "0.00%",
"new_customers_this_period": 0,
"deals_this_period": 0,
"deals_this_month": 0,
"call_count_this_period": 0,
"rank": 2
},
{
"name": "代秀珍",
"today_performance": 0.0,
"monthly_performance": 0.0,
"conversion_rate_this_period": "0.00%",
"new_customers_this_period": 39,
"deals_this_period": 0,
"deals_this_month": 0,
"call_count_this_period": 0,
"rank": 2
}
]
}
*/
}
} catch (error) {
console.error('获取团队排名失败:', error)
@@ -480,35 +440,6 @@ const conversionRateVsAverage = ref({})
const selectGroup = async (group) => {
selectedGroup.value = group
console.log('选中的组别111:', group)
// // 获取组名并调用API获取团队成员详情
// let departmentName = group.name
// // 如果有groupList数据尝试找到完整的部门名称
// if (groupList.value && groupList.value.formal_plural) {
// const departmentKeys = Object.keys(groupList.value.formal_plural)
// console.log('所有部门名称:', departmentKeys)
// console.log('当前选中组别名称:', group.name)
// // 优先进行精确匹配
// let matchedDepartment = departmentKeys.find(key => key === group.name)
// // 如果精确匹配失败,尝试模糊匹配
// if (!matchedDepartment) {
// matchedDepartment = departmentKeys.find(key => {
// // 提取部门主要名称进行匹配(去掉经理名字部分)
// const mainName = key.split('-')[0] || key
// const groupMainName = group.name.split('-')[0] || group.name
// return mainName.includes(groupMainName) || groupMainName.includes(mainName)
// })
// }
// if (matchedDepartment) {
// departmentName = matchedDepartment
// }
// }
// console.log('选中的组别:', group.name, '-> 发送的部门名称:', departmentName)
const departmentName = group.name+'-'+group.leader
try {
@@ -575,15 +506,15 @@ const conversionRateVsAverage = ref({})
return statusMap[status] || '未知'
}
onMounted(async () => {
// await CenterOverallCenterPerformance()
// await CenterTotalGroupCount()
// await CenterConversionRate()
// await CenterTotalCallCount()
// await CenterNewCustomer()
// await CenterDepositConversionRate()
// await CenterCustomerType()
// await CenterUrgentNeedToAddress()
// await CenterConversionRateVsAverage()
await CenterOverallCenterPerformance()
await CenterTotalGroupCount()
await CenterConversionRate()
await CenterTotalCallCount()
await CenterNewCustomer()
await CenterDepositConversionRate()
await CenterCustomerType()
await CenterUrgentNeedToAddress()
await CenterConversionRateVsAverage()
await CenterSeniorManagerList()
await CenterGroupList('all') // 初始化加载全部高级经理数据
})
@@ -1142,4 +1073,53 @@ const conversionRateVsAverage = ref({})
}
}
}
// 路由导航顶栏样式
.route-header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%;
.breadcrumb {
display: flex;
align-items: center;
gap: 0.5rem;
.breadcrumb-item {
color: #64748b;
font-size: 0.875rem;
font-weight: 500;
&:not(.current) {
cursor: pointer;
transition: color 0.2s;
&:hover {
color: #3b82f6;
}
}
&.current {
color: #1e293b;
font-weight: 600;
}
}
.breadcrumb-separator {
color: #94a3b8;
font-size: 0.875rem;
}
}
.user-name {
color: #1e293b;
font-size: 1.125rem;
font-weight: 600;
padding: 0.5rem 1rem;
background: rgba(255, 255, 255, 0.8);
border-radius: 0.5rem;
border: 1px solid rgba(0, 0, 0, 0.1);
}
}
</style>