Files
DJKB/my-vue-app/src/views/senorManger/components/GroupComparison.vue
lbw_9527443 d6a4ff3843 feat(团队管理): 实现团队业绩详情功能并优化组名解析
重构团队管理页面,使用API获取真实业绩数据替换模拟数据
添加团队业绩详情接口调用逻辑
优化组名解析逻辑以支持单横线和双横线格式
移除冗余的模拟数据代码
2025-08-12 22:58:35 +08:00

387 lines
9.0 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="group-comparison">
<!-- 综合排名 -->
<div class="ranking-section">
<h3>综合表现排名</h3>
<div class="ranking-grid compact">
<div
v-for="(group, index) in sortedGroups"
:key="group.id"
class="ranking-card"
:class="getRankingClass(index)"
@click="$emit('select-group', group)"
>
<div class="rank-badge">{{ index + 1 }}</div>
<div class="group-info">
<div class="group-name">{{ group.name }}</div>
<div class="group-leader">{{ group.leader }}</div>
</div>
<div class="key-metrics">
<div class="mini-metric">
<span class="mini-label">业绩</span>
<span class="mini-value">{{ formatCurrency(group.todayPerformance) }}</span>
</div>
<div class="mini-metric">
<span class="mini-label">转化</span>
<span class="mini-value">{{ group.conversionRate }}%</span>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, computed } from 'vue'
const props = defineProps({
groups: {
type: Array,
required: true
},
teamRanking: {
type: Object,
default: () => ({})
}
})
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
// 从组名中提取组长信息,处理单个'-'和双个'--'两种格式
let name, leader
if (groupName.includes('--')) {
// 处理双横线格式:"星火一部--张瑾"
const parts = groupName.split('--')
name = parts[0] || groupName
leader = parts[1] || '未知'
} else if (groupName.includes('-')) {
// 处理单横线格式:"巅峰五部-时永帅"
const parts = groupName.split('-')
name = parts[0] || groupName
leader = parts[1] || '未知'
} else {
// 没有分隔符的情况:"技术部"
name = groupName
leader = '未知'
}
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 groupsToSort = processedGroups.value.length > 0 ? processedGroups.value : props.groups
return [...groupsToSort].sort((a, b) => calculateScore(b) - calculateScore(a))
})
// 计算综合分数
const calculateScore = (group) => {
const performanceScore = (group.todayPerformance / 200000) * 30
const conversionScore = (group.conversionRate / 10) * 25
const clientScore = (group.newClients / 50) * 25
const dealScore = (group.deals / 20) * 20
return Math.round(performanceScore + conversionScore + clientScore + dealScore)
}
// 格式化指标值
const formatMetricValue = (value, unit) => {
switch (unit) {
case 'currency':
return formatCurrency(value)
case 'percent':
return value + '%'
case 'number':
return value.toString()
default:
return value
}
}
// 格式化货币
const formatCurrency = (value) => {
if (value >= 10000) {
return (value / 10000).toFixed(1) + '万'
}
return value.toLocaleString()
}
// 获取进度条宽度
const getProgressWidth = (value, metricKey) => {
const maxValues = {
todayPerformance: 200000,
conversionRate: 10,
newClients: 50,
deals: 20
}
return Math.min((value / maxValues[metricKey]) * 100, 100)
}
// 获取表现等级样式
const getPerformanceClass = (value, metricKey) => {
const thresholds = {
todayPerformance: { excellent: 120000, good: 80000 },
conversionRate: { excellent: 6, good: 4 },
newClients: { excellent: 25, good: 15 },
deals: { excellent: 8, good: 5 }
}
const threshold = thresholds[metricKey]
if (value >= threshold.excellent) return 'excellent'
if (value >= threshold.good) return 'good'
return 'poor'
}
// 获取排名样式
const getRankingClass = (index) => {
if (index === 0) return 'rank-1'
if (index === 1) return 'rank-2'
if (index === 2) return 'rank-3'
return 'rank-other'
}
// 获取趋势图标
const getTrendIcon = (trend) => {
const icons = {
up: '↗',
down: '↘',
stable: '→'
}
return icons[trend] || '→'
}
// 获取预警图标
const getAlertIcon = (level) => {
const icons = {
warning: '⚠️',
info: '',
urgent: '🚨'
}
return icons[level] || ''
}
</script>
<style lang="scss" scoped>
.group-comparison {
background: white;
border-radius: 12px;
padding: 1rem;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
height: 24rem;
overflow: auto;
// 综合排名
.ranking-section {
// margin-bottom: 2rem;
h3 {
font-size: 1.1rem;
font-weight: 600;
color: #374151;
margin: 0 0 1rem 0;
}
.ranking-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1rem;
&.compact {
grid-template-columns: repeat(1, 1fr);
gap: 0.75rem;
.ranking-card {
padding: 0.75rem;
gap: 0.5rem;
.rank-badge {
width: 24px;
height: 24px;
font-size: 0.8rem;
}
.group-info {
.group-name {
font-size: 0.9rem;
}
.group-leader {
font-size: 0.75rem;
}
}
.performance-score {
.score {
font-size: 1.1rem;
}
.score-label {
font-size: 0.7rem;
}
}
.key-metrics {
.mini-metric {
.mini-label {
font-size: 0.7rem;
}
.mini-value {
font-size: 0.8rem;
}
}
}
}
}
}
.ranking-card {
display: flex;
align-items: center;
padding: 1rem;
border-radius: 10px;
cursor: pointer;
transition: all 0.2s ease;
&:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
&.rank-1 {
background: linear-gradient(135deg, #ffd700 0%, #ffed4e 100%);
border: 2px solid #f59e0b;
}
&.rank-2 {
background: linear-gradient(135deg, #c0c0c0 0%, #e5e7eb 100%);
border: 2px solid #9ca3af;
}
&.rank-3 {
background: linear-gradient(135deg, #cd7f32 0%, #d97706 100%);
border: 2px solid #b45309;
}
&.rank-other {
background: white;
border: 1px solid #e5e7eb;
}
.rank-badge {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 1.2rem;
margin-right: 1rem;
background: rgba(255, 255, 255, 0.9);
color: #1f2937;
}
.group-info {
flex: 1;
.group-name {
font-weight: 600;
color: #1f2937;
margin-bottom: 0.25rem;
}
.group-leader {
font-size: 0.85rem;
color: #6b7280;
}
}
.performance-score {
text-align: center;
margin: 0 1rem;
.score {
font-size: 1.5rem;
font-weight: bold;
color: #1f2937;
}
.score-label {
font-size: 0.75rem;
color: #6b7280;
}
}
.key-metrics {
display: flex;
flex-direction: column;
gap: 0.25rem;
.mini-metric {
display: flex;
justify-content: space-between;
gap: 0.5rem;
.mini-label {
font-size: 0.75rem;
color: #6b7280;
}
.mini-value {
font-size: 0.75rem;
font-weight: 600;
color: #1f2937;
}
}
}
}
}
}
// 移动端适配
@media (max-width: 768px) {
.group-comparison {
padding: 0.75rem;
.ranking-grid {
grid-template-columns: 1fr;
&.compact {
grid-template-columns: 1fr;
}
}
.ranking-card {
.key-metrics {
display: none;
}
}
}
}
</style>