feat(统计模式): 添加按月/按期统计切换功能并优化客户阶段显示

- 在CenterOverview组件中添加统计模式切换按钮
- 实现统计模式切换逻辑并触发数据重新加载
- 优化SalesTimelineWithTaskList的客户阶段显示和计算逻辑
- 更新API调用参数以支持不同统计模式
- 调整样式和布局以适应新功能
This commit is contained in:
2025-08-26 11:34:50 +08:00
parent 87cc0e4976
commit 58c6ec1c53
7 changed files with 394 additions and 36 deletions

View File

@@ -25,9 +25,12 @@
<h3 class="stage-title">{{ stage.displayName || stage.name }}</h3>
<div class="stage-stats">
<span class="stage-count">{{ stage.name === '全部' ? customersCount : stage.count }}</span>
<span class="stage-label">客户</span>
<span class="stage-label"></span>
</div>
<div v-if="stage.name !== '全部'" class="stage-percentage">{{ getPercentage(stage.count) }}%</div>
<div class="stage-unName">
<span class="stage-unName" v-if="stage.unName">{{ stage.unName }}</span>
</div>
<div v-if="stage.name !== '全部'" class="stage-percentage">{{ getPercentage(stage.count, stage.id, stage.name) }}%</div>
</div>
</div>
</div>
@@ -217,6 +220,50 @@ const getStageCount = (stageType) => {
return props.payMoneyCustomersCount || (props.payMoneyCustomersList?.length || 0);
}
// 待填表单阶段统计customer_occupation或customer_child_education字段为'未知'的客户数量
if (stageType === '待填表单') {
if (props.customersList?.length) {
return props.customersList.filter(customer => {
return (customer.customer_occupation === '未知' || !customer.customer_occupation) ||
(customer.customer_child_education === '未知' || !customer.customer_child_education);
}).length;
}
return props.data[stageType] || 0;
}
// 待到课阶段:统计没有到课数据的客户数量
if (stageType === '待到课') {
if (props.customersList?.length) {
return props.customersList.filter(customer => {
// 根据getAttendedLessons函数的逻辑判断是否为'未到课'
const classNum = customer.class_num;
const classSituation = customer.class_situation;
// 优先检查class_num字段
if (classNum && Array.isArray(classNum) && classNum.length > 0) {
return false; // 有class_num数据不是待到课
}
// 检查class_situation字段
if (!classSituation) return true; // 没有class_situation是待到课
if (Array.isArray(classSituation)) {
return classSituation.length === 0; // 空数组,是待到课
}
if (typeof classSituation === 'object') {
const lessonNumbers = Object.keys(classSituation)
.map(key => parseInt(key))
.filter(num => !isNaN(num));
return lessonNumbers.length === 0; // 没有有效的课程数据,是待到课
}
return true; // 其他情况默认为待到课
}).length;
}
return props.data[stageType] || 0;
}
// 课1-4阶段从courseCustomers获取数量
if (stageType === '课1-4' && props.courseCustomers?.['课1-4']) {
return props.courseCustomers['课1-4'].length;
@@ -258,11 +305,11 @@ const stages = computed(() => {
const stageList = [
{ id: 0, name: '全部', displayName: '全部', count: totalCount, color: '#f3f4f6' },
{ id: 1, name: '待加微', displayName: '待加微', count: getStageCount('待加微'), color: '#e3f2fd' },
{ id: 2, name: '待填表单', displayName: '待填表单', count: getStageCount('待填表单'), color: '#90caf9' },
{ id: 3, name: '待入群', displayName: '待入群', count: getStageCount('待入群'), color: '#bbdefb' },
{ id: 4, name: '待联系', displayName: '待联系', count: getStageCount('待联系'), color: '#bbdefb' },
{ id: 5, name: '待到课', displayName: '待到课', count: getStageCount('待到课'), color: '#bbdefb' },
{ id: 1, name: '待加微', displayName: '待加微', count: getStageCount('待加微'), color: '#e3f2fd' ,unName:'已加微'},
{ id: 2, name: '待填表单', displayName: '待填表单', count: getStageCount('待填表单'), color: '#90caf9' ,unName:'已填表单'},
{ id: 3, name: '待入群', displayName: '待入群', count: getStageCount('待入群'), color: '#bbdefb' ,unName:'已入群'},
{ id: 4, name: '待联系', displayName: '待联系', count: getStageCount('待联系'), color: '#bbdefb' ,unName:'已联系'},
{ id: 5, name: '待到课', displayName: '待到课', count: getStageCount('待到课'), color: '#bbdefb' ,unName:'已到课'},
{ id: 6, name: '课1', displayName: '课1', count: getStageCount('课1'), color: '#81c784' },
{ id: 7, name: '课2', displayName: '课2', count: getStageCount('课2'), color: '#64b5f6' },
{ id: 8, name: '课3', displayName: '课3', count: getStageCount('课3'), color: '#ffb74d' },
@@ -277,9 +324,31 @@ const stages = computed(() => {
});
const getPercentage = (count) => {
const getPercentage = (count, stageId, stageName) => {
if (totalCustomers.value === 0) return 0;
return Math.round((count / totalCustomers.value) * 100);
// 待填表单阶段特殊计算:(总数-有数据人数)/总数
if (stageName === '待填表单') {
return Math.round((totalCustomers.value - count) / totalCustomers.value * 100);
}
// 待到课阶段特殊计算:(总数-有到课数据人数)/总数
if (stageName === '待到课') {
return Math.round((totalCustomers.value - count) / totalCustomers.value * 100);
}
// 阶段1-5使用现在的算法转化率
if (stageId >= 1 && stageId <= 5) {
return Math.round((totalCustomers.value - count) / totalCustomers.value * 100);
}
// 阶段5-13使用直接数量除以总数
if (stageId >5 && stageId <= 13) {
return Math.round((count / totalCustomers.value) * 100);
}
// 其他阶段保持原有逻辑
return Math.round((totalCustomers.value - count) / totalCustomers.value * 100);
};
@@ -328,6 +397,40 @@ const selectStage = (stageName) => {
if (props.courseCustomers?.['课1-4']) {
filteredCustomers = props.courseCustomers['课1-4'].filter(customer => customer.type === stageName);
}
} else if (stageName === '待填表单') {
// 待填表单阶段筛选customer_occupation或customer_child_education字段为'未知'的客户
filteredCustomers = props.customersList.filter(customer => {
return (customer.customer_occupation === '未知' || !customer.customer_occupation) ||
(customer.customer_child_education === '未知' || !customer.customer_child_education);
});
} else if (stageName === '待到课') {
// 待到课阶段:筛选没有到课数据的客户
filteredCustomers = props.customersList.filter(customer => {
// 根据getAttendedLessons函数的逻辑判断是否为'未到课'
const classNum = customer.class_num;
const classSituation = customer.class_situation;
// 优先检查class_num字段
if (classNum && Array.isArray(classNum) && classNum.length > 0) {
return false; // 有class_num数据不是待到课
}
// 检查class_situation字段
if (!classSituation) return true; // 没有class_situation是待到课
if (Array.isArray(classSituation)) {
return classSituation.length === 0; // 空数组,是待到课
}
if (typeof classSituation === 'object') {
const lessonNumbers = Object.keys(classSituation)
.map(key => parseInt(key))
.filter(num => !isNaN(num));
return lessonNumbers.length === 0; // 没有有效的课程数据,是待到课
}
return true; // 其他情况默认为待到课
});
} else {
// 前6个阶段从customersList中筛选
filteredCustomers = props.customersList.filter(customer => customer.type === stageName);
@@ -650,8 +753,9 @@ $indigo: #4f46e5;
.stage-content {
.stage-title {
font-size: 1rem;
color: $slate-800;
font-weight: 600;
font-weight: 500;
}
.stage-count {