feat(销售管理): 实现团队业绩排名和客户问题统计功能
- 添加团队业绩排名和客户迫切问题API接口 - 重构ProblemRanking组件以使用真实API数据 - 修改GroupComparison组件以支持团队排名数据展示 - 优化样式和移除调试日志 - 添加数据默认值处理防止渲染错误
This commit is contained in:
@@ -45,6 +45,22 @@ export const getTimeoutRate = (params) => {
|
|||||||
export const getTableFillingRate = (params) => {
|
export const getTableFillingRate = (params) => {
|
||||||
return https.post('/api/v1/level_three/overview/table_filling_rate', 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const service = axios.create({
|
|||||||
service.interceptors.request.use(
|
service.interceptors.request.use(
|
||||||
config => {
|
config => {
|
||||||
// 在发送请求之前做些什么
|
// 在发送请求之前做些什么
|
||||||
console.log('发送请求:', config)
|
// console.log('发送请求:', config)
|
||||||
|
|
||||||
// 添加token到请求头
|
// 添加token到请求头
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
@@ -52,10 +52,10 @@ service.interceptors.request.use(
|
|||||||
service.interceptors.response.use(
|
service.interceptors.response.use(
|
||||||
response => {
|
response => {
|
||||||
// 隐藏加载状态
|
// 隐藏加载状态
|
||||||
console.log('隐藏加载中...')
|
// console.log('隐藏加载中...')
|
||||||
|
|
||||||
// 对响应数据做点什么
|
// 对响应数据做点什么
|
||||||
console.log('收到响应:', response)
|
// console.log('收到响应:', response)
|
||||||
|
|
||||||
const { data, status } = response
|
const { data, status } = response
|
||||||
|
|
||||||
|
|||||||
@@ -17,10 +17,6 @@
|
|||||||
<div class="group-name">{{ group.name }}</div>
|
<div class="group-name">{{ group.name }}</div>
|
||||||
<div class="group-leader">{{ group.leader }}</div>
|
<div class="group-leader">{{ group.leader }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="performance-score">
|
|
||||||
<div class="score">{{ calculateScore(group) }}</div>
|
|
||||||
<div class="score-label">综合分</div>
|
|
||||||
</div>
|
|
||||||
<div class="key-metrics">
|
<div class="key-metrics">
|
||||||
<div class="mini-metric">
|
<div class="mini-metric">
|
||||||
<span class="mini-label">业绩</span>
|
<span class="mini-label">业绩</span>
|
||||||
@@ -45,6 +41,10 @@ const props = defineProps({
|
|||||||
groups: {
|
groups: {
|
||||||
type: Array,
|
type: Array,
|
||||||
required: true
|
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(() => {
|
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))
|
||||||
})
|
})
|
||||||
|
|
||||||
// 计算综合分数
|
// 计算综合分数
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ $white: #ffffff;
|
|||||||
display: flex;
|
display: flex;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
padding: 20px 20px 16px;
|
padding: 0px 20px 16px;
|
||||||
border-bottom: 1px solid #ebeef5;
|
border-bottom: 1px solid #ebeef5;
|
||||||
h3 {
|
h3 {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
@@ -131,6 +131,7 @@ $white: #ffffff;
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chart-content {
|
.chart-content {
|
||||||
|
margin-top: 20px;
|
||||||
padding-left: 20px;
|
padding-left: 20px;
|
||||||
padding-right: 20px;
|
padding-right: 20px;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
|
|||||||
@@ -26,35 +26,47 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { reactive, computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
// 问题排行榜数据
|
// 接收父组件传递的数据
|
||||||
const problemData = reactive([
|
const props = defineProps({
|
||||||
{ value: 180, name: '学习成绩提升' },
|
problemRanking: {
|
||||||
{ value: 150, name: '学习习惯培养' },
|
type: Object,
|
||||||
{ value: 120, name: '兴趣爱好发展' },
|
default: () => ({})
|
||||||
{ value: 100, name: '心理健康问题' },
|
}
|
||||||
{ value: 80, name: '升学规划' },
|
});
|
||||||
{ value: 70, name: '亲子关系改善' }
|
|
||||||
]);
|
// 将API数据转换为组件需要的格式
|
||||||
|
const problemData = computed(() => {
|
||||||
|
const urgentIssues = props.problemRanking.urgent_issue_consultations || {};
|
||||||
|
return Object.entries(urgentIssues).map(([name, value]) => ({
|
||||||
|
name,
|
||||||
|
value
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
// 计算属性
|
// 计算属性
|
||||||
const sortedProblemData = computed(() => {
|
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(() => {
|
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) => ({
|
const getRankingClass = (index) => ({
|
||||||
'rank-first': index === 0,
|
'rank-first': index === 0,
|
||||||
'rank-second': index === 1,
|
'rank-second': index === 1,
|
||||||
'rank-third': index === 2,
|
'rank-third': index === 2,
|
||||||
'rank-other': index > 2
|
'rank-other': index > 2
|
||||||
});
|
});
|
||||||
|
|
||||||
const getRankBadgeClass = (index) => ({
|
const getRankBadgeClass = (index) => ({
|
||||||
'badge-gold': index === 0,
|
'badge-gold': index === 0,
|
||||||
'badge-silver': index === 1,
|
'badge-silver': index === 1,
|
||||||
|
|||||||
@@ -5,35 +5,35 @@
|
|||||||
<div class="stat-icon customer-rate">
|
<div class="stat-icon customer-rate">
|
||||||
<i class="el-icon-chat-dot-round"></i>
|
<i class="el-icon-chat-dot-round"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="kpi-value">{{ customerCommunicationRate.active_customer_communication_rate }}</div>
|
<div class="kpi-value">{{ customerCommunicationRate.active_customer_communication_rate||0 }}</div>
|
||||||
<p>活跃客户沟通率</p>
|
<p>活跃客户沟通率</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="kpi-item stat-item">
|
<div class="kpi-item stat-item">
|
||||||
<div class="stat-icon response-time">
|
<div class="stat-icon response-time">
|
||||||
<i class="el-icon-timer"></i>
|
<i class="el-icon-timer"></i>
|
||||||
</div>
|
</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>
|
<p>平均应答时间</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="kpi-item stat-item">
|
<div class="kpi-item stat-item">
|
||||||
<div class="stat-icon timeout-rate">
|
<div class="stat-icon timeout-rate">
|
||||||
<i class="el-icon-warning"></i>
|
<i class="el-icon-warning"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="kpi-value">{{ timeoutResponseRate.timeout_rate }}</div>
|
<div class="kpi-value">{{ timeoutResponseRate.timeout_rate||0 }}</div>
|
||||||
<p>超时应答率</p>
|
<p>超时应答率</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="kpi-item stat-item">
|
<div class="kpi-item stat-item">
|
||||||
<div class="stat-icon severe-timeout-rate">
|
<div class="stat-icon severe-timeout-rate">
|
||||||
<i class="el-icon-warning-outline"></i>
|
<i class="el-icon-warning-outline"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="kpi-value">{{ timeoutResponseRate.serious_timeout_rate }}</div>
|
<div class="kpi-value">{{ timeoutResponseRate.serious_timeout_rate||0 }}</div>
|
||||||
<p>严重超时应答率</p>
|
<p>严重超时应答率</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="kpi-item stat-item">
|
<div class="kpi-item stat-item">
|
||||||
<div class="stat-icon form-rate">
|
<div class="stat-icon form-rate">
|
||||||
<i class="el-icon-document"></i>
|
<i class="el-icon-document"></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="kpi-value">{{ formCompletionRate.table_filling_rate }}</div>
|
<div class="kpi-value">{{ formCompletionRate.table_filling_rate||0 }}</div>
|
||||||
<p>表格填写率</p>
|
<p>表格填写率</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -41,11 +41,16 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="problem-ranking">
|
<div class="problem-ranking">
|
||||||
<ProblemRanking />
|
<!-- 客户迫切解决的问题 -->
|
||||||
|
<ProblemRanking :problemRanking="problemRanking" />
|
||||||
</div>
|
</div>
|
||||||
<!-- Right Section - Group Comparison -->
|
<!-- Right Section - Group Comparison -->
|
||||||
<div class="right-section">
|
<div class="right-section">
|
||||||
<GroupComparison :groups="groups" @select-group="selectGroup" />
|
<GroupComparison
|
||||||
|
:groups="groups"
|
||||||
|
:teamRanking="teamRanking"
|
||||||
|
@select-group="selectGroup"
|
||||||
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Team Members Detail Section -->
|
<!-- Team Members Detail Section -->
|
||||||
@@ -145,7 +150,11 @@ import TeamAlerts from '../maneger/components/TeamAlerts.vue'
|
|||||||
import ProblemRanking from './components/ProblemRanking.vue'
|
import ProblemRanking from './components/ProblemRanking.vue'
|
||||||
import StatisticalIndicators from './components/StatisticalIndicators.vue'
|
import StatisticalIndicators from './components/StatisticalIndicators.vue'
|
||||||
import UserDropdown from '@/components/UserDropdown.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'
|
import { useUserStore } from '@/stores/user.js'
|
||||||
@@ -311,18 +320,99 @@ async function fetchTableFillingRate() {
|
|||||||
console.error('获取表格填写率失败:', error)
|
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 ()=>{
|
onMounted(async ()=>{
|
||||||
await fetchOverallTeamPerformance()
|
// await fetchOverallTeamPerformance()
|
||||||
await fetchActiveGroups()
|
// await fetchActiveGroups()
|
||||||
await fetchConversionRate()
|
// await fetchConversionRate()
|
||||||
await fetchTotalCallCount()
|
// await fetchTotalCallCount()
|
||||||
await fetchNewCustomers()
|
// await fetchNewCustomers()
|
||||||
await fetchDepositConversions()
|
// await fetchDepositConversions()
|
||||||
await fetchCustomerCommunicationRate()
|
// await fetchCustomerCommunicationRate()
|
||||||
await fetchAverageResponseTime()
|
// await fetchAverageResponseTime()
|
||||||
await fetchTimeoutRate()
|
// await fetchTimeoutRate()
|
||||||
await fetchTableFillingRate()
|
// await fetchTableFillingRate()
|
||||||
|
await fetchUrgentNeedToAddress()
|
||||||
|
await fetchTeamRanking()
|
||||||
|
// await fetchProblemRanking()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user