feat(销售管理): 实现团队业绩排名和客户问题统计功能

- 添加团队业绩排名和客户迫切问题API接口
- 重构ProblemRanking组件以使用真实API数据
- 修改GroupComparison组件以支持团队排名数据展示
- 优化样式和移除调试日志
- 添加数据默认值处理防止渲染错误
This commit is contained in:
2025-08-12 22:00:32 +08:00
parent c48f39fb5e
commit 3a38dba08a
7 changed files with 187 additions and 40 deletions

View File

@@ -45,6 +45,22 @@ export const getTimeoutRate = (params) => {
export const getTableFillingRate = (params) => {
return https.post('/api/v1/level_three/overview/table_filling_rate', params)
}
// 销售漏斗
// 客户迫切解决的问题 /api/v1/level_three/overview/urgent_need_to_address
export const getUrgentNeedToAddress = (params) => {
return https.post('/api/v1/level_three/overview/urgent_need_to_address', params)
}
// 团队业绩排名 /api/v1/level_three/overview/team_ranking
export const getTeamRanking = (params) => {
return https.post('/api/v1/level_three/overview/team_ranking', params)
}
// 组业绩详情 /api/v1/level_three/overview/team_ranking_info
export const getTeamRankingInfo = (params) => {
return https.post('/api/v1/level_three/overview/team_ranking_info', params)
}

View File

@@ -16,7 +16,7 @@ const service = axios.create({
service.interceptors.request.use(
config => {
// 在发送请求之前做些什么
console.log('发送请求:', config)
// console.log('发送请求:', config)
// 添加token到请求头
const userStore = useUserStore()
@@ -52,10 +52,10 @@ service.interceptors.request.use(
service.interceptors.response.use(
response => {
// 隐藏加载状态
console.log('隐藏加载中...')
// console.log('隐藏加载中...')
// 对响应数据做点什么
console.log('收到响应:', response)
// console.log('收到响应:', response)
const { data, status } = response

View File

@@ -17,10 +17,6 @@
<div class="group-name">{{ group.name }}</div>
<div class="group-leader">{{ group.leader }}</div>
</div>
<div class="performance-score">
<div class="score">{{ calculateScore(group) }}</div>
<div class="score-label">综合分</div>
</div>
<div class="key-metrics">
<div class="mini-metric">
<span class="mini-label">业绩</span>
@@ -45,6 +41,10 @@ const props = defineProps({
groups: {
type: Array,
required: true
},
teamRanking: {
type: Object,
default: () => ({})
}
})
@@ -52,9 +52,37 @@ const emit = defineEmits(['select-group'])
// 将teamRanking数据转换为组件需要的格式
const processedGroups = computed(() => {
const formalPlural = props.teamRanking.formal_plural || {}
const compositionTransformation = props.teamRanking.composition_transformation || {}
return Object.keys(formalPlural).map((groupName, index) => {
const performance = formalPlural[groupName] || 0
const conversionRate = parseFloat(compositionTransformation[groupName]) || 0
// 从组名中提取组长信息
const nameParts = groupName.split('-')
const name = nameParts[0] || groupName
const leader = nameParts[1] || '未知'
return {
id: index + 1,
name: name,
leader: leader,
todayPerformance: performance * 10000, // 假设单位转换
conversionRate: conversionRate,
newClients: Math.floor(performance * 2.5), // 根据业绩估算
deals: performance,
memberCount: Math.floor(Math.random() * 5) + 5 // 模拟成员数
}
})
})
// 按综合表现排序的组别
const sortedGroups = computed(() => {
return [...props.groups].sort((a, b) => calculateScore(b) - calculateScore(a))
const groupsToSort = processedGroups.value.length > 0 ? processedGroups.value : props.groups
return [...groupsToSort].sort((a, b) => calculateScore(b) - calculateScore(a))
})
// 计算综合分数

View File

@@ -120,7 +120,7 @@ $white: #ffffff;
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 20px 16px;
padding: 0px 20px 16px;
border-bottom: 1px solid #ebeef5;
h3 {
margin: 0;
@@ -131,6 +131,7 @@ $white: #ffffff;
}
.chart-content {
margin-top: 20px;
padding-left: 20px;
padding-right: 20px;
padding-bottom: 20px;

View File

@@ -26,35 +26,47 @@
</template>
<script setup>
import { reactive, computed } from 'vue';
import { computed } from 'vue';
// 问题排行榜数据
const problemData = reactive([
{ value: 180, name: '学习成绩提升' },
{ value: 150, name: '学习习惯培养' },
{ value: 120, name: '兴趣爱好发展' },
{ value: 100, name: '心理健康问题' },
{ value: 80, name: '升学规划' },
{ value: 70, name: '亲子关系改善' }
]);
// 接收父组件传递的数据
const props = defineProps({
problemRanking: {
type: Object,
default: () => ({})
}
});
// 将API数据转换为组件需要的格式
const problemData = computed(() => {
const urgentIssues = props.problemRanking.urgent_issue_consultations || {};
return Object.entries(urgentIssues).map(([name, value]) => ({
name,
value
}));
});
// 计算属性
const sortedProblemData = computed(() => {
return [...problemData].sort((a, b) => b.value - a.value);
return [...problemData.value].sort((a, b) => b.value - a.value);
});
const totalProblemCount = computed(() => {
return problemData.reduce((sum, item) => sum + item.value, 0);
return problemData.value.reduce((sum, item) => sum + item.value, 0);
});
// 排行榜相关方法
const getPercentage = (value) => ((value / totalProblemCount.value) * 100).toFixed(1);
const getPercentage = (value) => {
if (totalProblemCount.value === 0) return '0.0';
return ((value / totalProblemCount.value) * 100).toFixed(1);
};
const getRankingClass = (index) => ({
'rank-first': index === 0,
'rank-second': index === 1,
'rank-third': index === 2,
'rank-other': index > 2
});
const getRankBadgeClass = (index) => ({
'badge-gold': index === 0,
'badge-silver': index === 1,

View File

@@ -5,35 +5,35 @@
<div class="stat-icon customer-rate">
<i class="el-icon-chat-dot-round"></i>
</div>
<div class="kpi-value">{{ customerCommunicationRate.active_customer_communication_rate }}</div>
<div class="kpi-value">{{ customerCommunicationRate.active_customer_communication_rate||0 }}</div>
<p>活跃客户沟通率</p>
</div>
<div class="kpi-item stat-item">
<div class="stat-icon response-time">
<i class="el-icon-timer"></i>
</div>
<div class="kpi-value">{{ averageResponseTime.average_answer_time }}<span class="kpi-unit">分钟</span></div>
<div class="kpi-value">{{ averageResponseTime.average_answer_time||0 }}<span class="kpi-unit">分钟</span></div>
<p>平均应答时间</p>
</div>
<div class="kpi-item stat-item">
<div class="stat-icon timeout-rate">
<i class="el-icon-warning"></i>
</div>
<div class="kpi-value">{{ timeoutResponseRate.timeout_rate }}</div>
<div class="kpi-value">{{ timeoutResponseRate.timeout_rate||0 }}</div>
<p>超时应答率</p>
</div>
<div class="kpi-item stat-item">
<div class="stat-icon severe-timeout-rate">
<i class="el-icon-warning-outline"></i>
</div>
<div class="kpi-value">{{ timeoutResponseRate.serious_timeout_rate }}</div>
<div class="kpi-value">{{ timeoutResponseRate.serious_timeout_rate||0 }}</div>
<p>严重超时应答率</p>
</div>
<div class="kpi-item stat-item">
<div class="stat-icon form-rate">
<i class="el-icon-document"></i>
</div>
<div class="kpi-value">{{ formCompletionRate.table_filling_rate }}</div>
<div class="kpi-value">{{ formCompletionRate.table_filling_rate||0 }}</div>
<p>表格填写率</p>
</div>
</div>

View File

@@ -41,11 +41,16 @@
/>
</div>
<div class="problem-ranking">
<ProblemRanking />
<!-- 客户迫切解决的问题 -->
<ProblemRanking :problemRanking="problemRanking" />
</div>
<!-- Right Section - Group Comparison -->
<div class="right-section">
<GroupComparison :groups="groups" @select-group="selectGroup" />
<GroupComparison
:groups="groups"
:teamRanking="teamRanking"
@select-group="selectGroup"
/>
</div>
</div>
<!-- Team Members Detail Section -->
@@ -145,7 +150,11 @@ import TeamAlerts from '../maneger/components/TeamAlerts.vue'
import ProblemRanking from './components/ProblemRanking.vue'
import StatisticalIndicators from './components/StatisticalIndicators.vue'
import UserDropdown from '@/components/UserDropdown.vue'
import { getOverallTeamPerformance,getTotalGroupCount,getConversionRate,getTotalCallCount,getNewCustomer,getDepositConversionRate,getActiveCustomerCommunicationRate,getAverageAnswerTime,getTimeoutRate,getTableFillingRate } from '@/api/senorManger.js'
import { getOverallTeamPerformance,getTotalGroupCount,getConversionRate,getTotalCallCount,
getNewCustomer,getDepositConversionRate,getActiveCustomerCommunicationRate,getAverageAnswerTime,
getTimeoutRate,getTableFillingRate,getUrgentNeedToAddress,getTeamRanking } from '@/api/senorManger.js'
import { useUserStore } from '@/stores/user.js'
@@ -311,18 +320,99 @@ async function fetchTableFillingRate() {
console.error('获取表格填写率失败:', error)
}
}
const problemRanking = ref({})
// 客户迫切解决的问题
async function fetchUrgentNeedToAddress() {
const params={
user_name: userStore.userInfo.username,
user_level: userStore.userInfo.user_level.toString()
}
try {
const response = await getUrgentNeedToAddress(params)
problemRanking.value = response.data
/**
* "data": {
"user_name": "陈盼良",
"user_level": 3,
"calculate_urgent_issue_ratio": {
"成绩提升": "0.00%",
"少玩手机": "0.00%",
"回归学校": "0.00%",
"心理健康": "0.00%"
},
"urgent_issue_consultations": {
"成绩提升": 0,
"少玩手机": 0,
"回归学校": 0,
"心理健康": 0
}
}
*/
// console.log('客户迫切解决的问题:', response.data)
} catch (error) {
console.error('获取客户迫切解决的问题失败:', error)
}
}
//综合表现排名
const teamRanking = ref({})
async function fetchTeamRanking() {
console.log(555555,userStore.userInfo)
const params={
user_name: userStore.userInfo.username,
user_level: userStore.userInfo.user_level.toString(),
}
try {
const response = await getTeamRanking(params)
teamRanking.value = response.data
/**
* "data": {
"user_name": "陈盼良",
"user_level": 3,
"formal_plural": {
"巅峰五部-时永帅": 2,
"技术部": 0,
"星火一部--张瑾": 4,
"美团业务支持部": 0,
"星耀三部-周毅": 3,
"亮剑二部-田贵星": 2,
"巅峰三部--刘东洋": 0
},
"composition_transformation": {
"巅峰五部-时永帅": "0.00%",
"技术部": "0.00%",
"星火一部--张瑾": "11.43%",
"美团业务支持部": "0.00%",
"星耀三部-周毅": "15.00%",
"亮剑二部-田贵星": "5.71%",
"巅峰三部--刘东洋": "0.00%"
}
}
*/
console.log('团队业绩排名:', response.data)
} catch (error) {
console.error('获取团队业绩排名失败:', error)
}
}
// 初始化时获取数据
onMounted(async ()=>{
await fetchOverallTeamPerformance()
await fetchActiveGroups()
await fetchConversionRate()
await fetchTotalCallCount()
await fetchNewCustomers()
await fetchDepositConversions()
await fetchCustomerCommunicationRate()
await fetchAverageResponseTime()
await fetchTimeoutRate()
await fetchTableFillingRate()
// await fetchOverallTeamPerformance()
// await fetchActiveGroups()
// await fetchConversionRate()
// await fetchTotalCallCount()
// await fetchNewCustomers()
// await fetchDepositConversions()
// await fetchCustomerCommunicationRate()
// await fetchAverageResponseTime()
// await fetchTimeoutRate()
// await fetchTableFillingRate()
await fetchUrgentNeedToAddress()
await fetchTeamRanking()
// await fetchProblemRanking()
})