feat(业绩对比): 添加业绩周期对比功能组件
新增业绩周期对比组件,支持与上周/上月/上季度数据对比展示。包含以下主要修改: 1. 添加PerformanceComparison.vue组件实现对比表格和周期选择 2. 在seniorManager.vue中集成该组件并添加相关计算属性 3. 新增API接口getHistoryCamps获取历史营期数据 4. 添加样式和状态管理逻辑
This commit is contained in:
@@ -70,6 +70,10 @@ export const getTeamRankingInfo = (params) => {
|
|||||||
export const getAbnormalResponseRate = (params) => {
|
export const getAbnormalResponseRate = (params) => {
|
||||||
return https.post('/api/v1/level_three/overview/abnormal_response_rate', params)
|
return https.post('/api/v1/level_three/overview/abnormal_response_rate', params)
|
||||||
}
|
}
|
||||||
|
// 历史营期 /api/v1/level_three/overview/get_history_camps
|
||||||
|
export const getHistoryCamps = (params) => {
|
||||||
|
return https.post('/api/v1/level_three/overview/get_history_camps', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,340 @@
|
|||||||
|
<template>
|
||||||
|
<div class="performance-comparison">
|
||||||
|
<div class="comparison-header">
|
||||||
|
<h2>业绩周期对比</h2>
|
||||||
|
<div class="period-selector-wrapper">
|
||||||
|
<label for="period-select">对比周期:</label>
|
||||||
|
<select id="period-select" v-model="selectedPeriod" @change="fetchComparisonData" class="period-select">
|
||||||
|
<option value="last_week">与上周对比</option>
|
||||||
|
<option value="last_month">与上月对比</option>
|
||||||
|
<option value="last_quarter">与上季度对比</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="isLoading" class="loading-state">
|
||||||
|
<div class="loading-spinner"></div>
|
||||||
|
<p>正在加载对比数据...</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="!previousPeriodData" class="empty-state">
|
||||||
|
<p>暂无对比周期的数据。</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else class="comparison-table-wrapper">
|
||||||
|
<table class="comparison-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>核心指标</th>
|
||||||
|
<th>本期数据</th>
|
||||||
|
<th>{{ selectedPeriodLabel }}数据</th>
|
||||||
|
<th>变化情况</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="metric in comparedMetrics" :key="metric.key">
|
||||||
|
<td>{{ metric.label }}</td>
|
||||||
|
<td>{{ formatValue(metric.current, metric.unit) }}</td>
|
||||||
|
<td>{{ formatValue(metric.previous, metric.unit) }}</td>
|
||||||
|
<td>
|
||||||
|
<div class="change-cell" :class="getChangeClass(metric.change.trend)">
|
||||||
|
<span class="change-value">
|
||||||
|
{{ formatChange(metric.change.diff, metric.unit) }}
|
||||||
|
({{ metric.change.percentage }})
|
||||||
|
</span>
|
||||||
|
<span v-if="metric.change.trend === 'up'" class="trend-icon">↑</span>
|
||||||
|
<span v-if="metric.change.trend === 'down'" class="trend-icon">↓</span>
|
||||||
|
<span v-if="metric.change.trend === 'neutral'" class="trend-icon">-</span>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, computed, onMounted, watch } from 'vue';
|
||||||
|
// 假设你有一个API服务来获取对比数据
|
||||||
|
// import { getPerformanceComparisonData } from '@/api/senorManger.js';
|
||||||
|
|
||||||
|
// 模拟API调用
|
||||||
|
const getPerformanceComparisonData = async (params) => {
|
||||||
|
console.log('模拟API请求:', params);
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
// 模拟不同周期返回不同数据
|
||||||
|
let mockData;
|
||||||
|
if (params.period === 'last_week') {
|
||||||
|
mockData = {
|
||||||
|
assignedLeads: 480,
|
||||||
|
wechatAdds: 390,
|
||||||
|
calls: 1450,
|
||||||
|
callDuration: 11500,
|
||||||
|
deposits: 38,
|
||||||
|
deals: 50,
|
||||||
|
conversionRate: 10.4,
|
||||||
|
};
|
||||||
|
} else if (params.period === 'last_month') {
|
||||||
|
mockData = {
|
||||||
|
assignedLeads: 2000,
|
||||||
|
wechatAdds: 1500,
|
||||||
|
calls: 5800,
|
||||||
|
callDuration: 48000,
|
||||||
|
deposits: 150,
|
||||||
|
deals: 210,
|
||||||
|
conversionRate: 10.5,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
mockData = {
|
||||||
|
assignedLeads: 6500,
|
||||||
|
wechatAdds: 5200,
|
||||||
|
calls: 18000,
|
||||||
|
callDuration: 150000,
|
||||||
|
deposits: 450,
|
||||||
|
deals: 600,
|
||||||
|
conversionRate: 9.2,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
resolve({ code: 200, data: mockData });
|
||||||
|
}, 800);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
currentPeriodData: {
|
||||||
|
type: Object,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const selectedPeriod = ref('last_month');
|
||||||
|
const previousPeriodData = ref(null);
|
||||||
|
const isLoading = ref(false);
|
||||||
|
|
||||||
|
const periodLabels = {
|
||||||
|
last_week: '上周',
|
||||||
|
last_month: '上月',
|
||||||
|
last_quarter: '上季度',
|
||||||
|
};
|
||||||
|
|
||||||
|
const selectedPeriodLabel = computed(() => periodLabels[selectedPeriod.value]);
|
||||||
|
|
||||||
|
const fetchComparisonData = async () => {
|
||||||
|
isLoading.value = true;
|
||||||
|
try {
|
||||||
|
// 真实场景中,这里应该调用API
|
||||||
|
const res = await getPerformanceComparisonData({ period: selectedPeriod.value });
|
||||||
|
if (res.code === 200) {
|
||||||
|
previousPeriodData.value = res.data;
|
||||||
|
} else {
|
||||||
|
previousPeriodData.value = null; // API出错或无数据
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取对比数据失败:', error);
|
||||||
|
previousPeriodData.value = null;
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchComparisonData();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 如果本期数据可能变化,可以监听props来刷新
|
||||||
|
watch(() => props.currentPeriodData, () => {
|
||||||
|
fetchComparisonData();
|
||||||
|
}, { deep: true });
|
||||||
|
|
||||||
|
const comparedMetrics = computed(() => {
|
||||||
|
if (!props.currentPeriodData || !previousPeriodData.value) return [];
|
||||||
|
|
||||||
|
const metricsConfig = [
|
||||||
|
{ key: 'assignedLeads', label: '分配数据量', unit: '个' },
|
||||||
|
{ key: 'wechatAdds', label: '加微量', unit: '个' },
|
||||||
|
{ key: 'calls', label: '通话量', unit: '次' },
|
||||||
|
{ key: 'callDuration', label: '通话总时长', unit: '分钟' },
|
||||||
|
{ key: 'deposits', label: '定金量', unit: '单' },
|
||||||
|
{ key: 'deals', label: '成交量', unit: '单' },
|
||||||
|
{ key: 'conversionRate', label: '转化率', unit: '%' },
|
||||||
|
];
|
||||||
|
|
||||||
|
return metricsConfig.map(metric => {
|
||||||
|
const current = props.currentPeriodData[metric.key];
|
||||||
|
const previous = previousPeriodData.value[metric.key];
|
||||||
|
return {
|
||||||
|
...metric,
|
||||||
|
current,
|
||||||
|
previous,
|
||||||
|
change: calculateChange(current, previous),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const calculateChange = (current, previous) => {
|
||||||
|
if (previous === null || previous === undefined) return { diff: 'N/A', percentage: 'N/A', trend: 'neutral' };
|
||||||
|
|
||||||
|
const diff = current - previous;
|
||||||
|
let percentage;
|
||||||
|
if (previous === 0) {
|
||||||
|
percentage = current > 0 ? '+100.0%' : '0.0%';
|
||||||
|
} else {
|
||||||
|
const percentageValue = (diff / previous) * 100;
|
||||||
|
percentage = `${percentageValue > 0 ? '+' : ''}${percentageValue.toFixed(1)}%`;
|
||||||
|
}
|
||||||
|
|
||||||
|
let trend = 'neutral';
|
||||||
|
if (diff > 0) trend = 'up';
|
||||||
|
if (diff < 0) trend = 'down';
|
||||||
|
|
||||||
|
return { diff, percentage, trend };
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatValue = (value, unit) => {
|
||||||
|
if (value === null || value === undefined) return '-';
|
||||||
|
if (unit === '分钟') {
|
||||||
|
return Math.round(value / 60); // 将秒转换为分钟
|
||||||
|
}
|
||||||
|
if (unit === '%') {
|
||||||
|
return `${value.toFixed(1)}%`;
|
||||||
|
}
|
||||||
|
return value.toLocaleString();
|
||||||
|
};
|
||||||
|
|
||||||
|
const formatChange = (diff, unit) => {
|
||||||
|
if (typeof diff !== 'number') return '';
|
||||||
|
const formattedDiff = formatValue(Math.abs(diff), unit);
|
||||||
|
return `${diff > 0 ? '+' : '-'}${formattedDiff}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const getChangeClass = (trend) => {
|
||||||
|
if (trend === 'up') return 'text-positive';
|
||||||
|
if (trend === 'down') return 'text-negative';
|
||||||
|
return 'text-neutral';
|
||||||
|
};
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.performance-comparison {
|
||||||
|
background: white;
|
||||||
|
border-radius: 12px;
|
||||||
|
padding: 1.5rem;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comparison-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: #1e293b;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-selector-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: #64748b;
|
||||||
|
}
|
||||||
|
|
||||||
|
.period-select {
|
||||||
|
padding: 0.5rem;
|
||||||
|
border-radius: 6px;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.comparison-table-wrapper {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.comparison-table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
|
||||||
|
th, td {
|
||||||
|
padding: 0.85rem 1rem;
|
||||||
|
text-align: left;
|
||||||
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
}
|
||||||
|
|
||||||
|
thead th {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
color: #64748b;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
background-color: #f8fafc;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody td {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: #334155;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.change-cell {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-weight: 600;
|
||||||
|
|
||||||
|
.trend-icon {
|
||||||
|
font-size: 1.1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-positive {
|
||||||
|
color: #10b981; /* 绿色 */
|
||||||
|
}
|
||||||
|
.text-negative {
|
||||||
|
color: #ef4444; /* 红色 */
|
||||||
|
}
|
||||||
|
.text-neutral {
|
||||||
|
color: #64748b; /* 灰色 */
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-state, .empty-state {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 3rem;
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
|
||||||
|
.loading-spinner {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border: 3px solid #e2e8f0;
|
||||||
|
border-top: 3px solid #3b82f6;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -89,6 +89,12 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 新增:业绩周期对比组件 -->
|
||||||
|
<div v-if="cardVisibility.performanceComparison" class="performance-comparison-section">
|
||||||
|
<PerformanceComparison :current-period-data="currentPeriodMetrics" />
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Team Members Detail Section -->
|
<!-- Team Members Detail Section -->
|
||||||
<div class="team-detail-section" v-if="selectedGroup && cardVisibility.teamDetail">
|
<div class="team-detail-section" v-if="selectedGroup && cardVisibility.teamDetail">
|
||||||
<!-- 团队详情加载状态 -->
|
<!-- 团队详情加载状态 -->
|
||||||
@@ -188,9 +194,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted, computed, reactive } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { computed, reactive } from 'vue'
|
|
||||||
import Tooltip from '@/components/Tooltip.vue'
|
import Tooltip from '@/components/Tooltip.vue'
|
||||||
import CenterOverview from './components/CenterOverview.vue'
|
import CenterOverview from './components/CenterOverview.vue'
|
||||||
import GroupComparison from './components/GroupComparison.vue'
|
import GroupComparison from './components/GroupComparison.vue'
|
||||||
@@ -200,6 +205,7 @@ 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 Loading from '@/components/Loading.vue'
|
import Loading from '@/components/Loading.vue'
|
||||||
|
import PerformanceComparison from './components/PerformanceComparison.vue'; // 1. 导入新组件
|
||||||
import { getOverallTeamPerformance,getTotalGroupCount,getConversionRate,getTotalCallCount,
|
import { getOverallTeamPerformance,getTotalGroupCount,getConversionRate,getTotalCallCount,
|
||||||
getNewCustomer,getDepositConversionRate,getActiveCustomerCommunicationRate,getAverageAnswerTime,
|
getNewCustomer,getDepositConversionRate,getActiveCustomerCommunicationRate,getAverageAnswerTime,
|
||||||
getTimeoutRate,getTableFillingRate,getUrgentNeedToAddress,getTeamRanking,getTeamRankingInfo,getAbnormalResponseRate,getTeamSalesFunnel } from '@/api/senorManger.js'
|
getTimeoutRate,getTableFillingRate,getUrgentNeedToAddress,getTeamRanking,getTeamRankingInfo,getAbnormalResponseRate,getTeamSalesFunnel } from '@/api/senorManger.js'
|
||||||
@@ -345,7 +351,8 @@ const cardVisibility = ref({
|
|||||||
groupRanking: true,
|
groupRanking: true,
|
||||||
problemRanking: true,
|
problemRanking: true,
|
||||||
groupComparison: true,
|
groupComparison: true,
|
||||||
teamDetail: true
|
teamDetail: true,
|
||||||
|
performanceComparison: true, // 2. 新增组件的可见性控制
|
||||||
})
|
})
|
||||||
|
|
||||||
// 更新卡片显示状态
|
// 更新卡片显示状态
|
||||||
@@ -547,26 +554,6 @@ async function GetTeamSalesFunnel() {
|
|||||||
const res = await getTeamSalesFunnel(requestParams)
|
const res = await getTeamSalesFunnel(requestParams)
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
teamSalesFunnel.value = res.data
|
teamSalesFunnel.value = res.data
|
||||||
/**
|
|
||||||
* data
|
|
||||||
:
|
|
||||||
{线索: 738, 加微: 404, 到课: 942, 定金: 43, 成交: 57}
|
|
||||||
到课
|
|
||||||
:
|
|
||||||
942
|
|
||||||
加微
|
|
||||||
:
|
|
||||||
404
|
|
||||||
定金
|
|
||||||
:
|
|
||||||
43
|
|
||||||
成交
|
|
||||||
:
|
|
||||||
57
|
|
||||||
线索
|
|
||||||
:
|
|
||||||
738
|
|
||||||
*/
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -586,11 +573,9 @@ async function fetchAbnormalResponseRate() {
|
|||||||
)
|
)
|
||||||
const rawData = response.data
|
const rawData = response.data
|
||||||
|
|
||||||
// 转换数据格式,按团队分组生成预警消息
|
|
||||||
const processedAlerts = []
|
const processedAlerts = []
|
||||||
const teamData = new Map()
|
const teamData = new Map()
|
||||||
|
|
||||||
// 收集严重超时异常数据
|
|
||||||
if (rawData.team_serious_timeout_abnormal_counts_by_group) {
|
if (rawData.team_serious_timeout_abnormal_counts_by_group) {
|
||||||
Object.entries(rawData.team_serious_timeout_abnormal_counts_by_group).forEach(([teamName, data]) => {
|
Object.entries(rawData.team_serious_timeout_abnormal_counts_by_group).forEach(([teamName, data]) => {
|
||||||
if (!teamData.has(teamName)) {
|
if (!teamData.has(teamName)) {
|
||||||
@@ -600,7 +585,6 @@ async function fetchAbnormalResponseRate() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收集表格填写异常数据
|
|
||||||
if (rawData.team_table_filling_abnormal_counts_by_group) {
|
if (rawData.team_table_filling_abnormal_counts_by_group) {
|
||||||
Object.entries(rawData.team_table_filling_abnormal_counts_by_group).forEach(([teamName, data]) => {
|
Object.entries(rawData.team_table_filling_abnormal_counts_by_group).forEach(([teamName, data]) => {
|
||||||
if (!teamData.has(teamName)) {
|
if (!teamData.has(teamName)) {
|
||||||
@@ -610,7 +594,6 @@ async function fetchAbnormalResponseRate() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 生成按团队分组的预警消息
|
|
||||||
let alertId = 1
|
let alertId = 1
|
||||||
teamData.forEach((counts, teamName) => {
|
teamData.forEach((counts, teamName) => {
|
||||||
const messages = []
|
const messages = []
|
||||||
@@ -631,7 +614,6 @@ async function fetchAbnormalResponseRate() {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 设置处理后的数据
|
|
||||||
teamAlerts.value = { processedAlerts }
|
teamAlerts.value = { processedAlerts }
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -790,10 +772,8 @@ onMounted(async ()=>{
|
|||||||
await fetchUrgentNeedToAddress()
|
await fetchUrgentNeedToAddress()
|
||||||
await fetchTeamRanking()
|
await fetchTeamRanking()
|
||||||
|
|
||||||
// 输出缓存信息
|
|
||||||
console.log('缓存状态:', getCacheInfo())
|
console.log('缓存状态:', getCacheInfo())
|
||||||
|
|
||||||
// 开发环境下暴露缓存管理函数到全局
|
|
||||||
if (import.meta.env.DEV) {
|
if (import.meta.env.DEV) {
|
||||||
window.seniorManagerCache = {
|
window.seniorManagerCache = {
|
||||||
clearCache,
|
clearCache,
|
||||||
@@ -811,6 +791,19 @@ onMounted(async ()=>{
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 3. 新增计算属性,为新组件聚合本期数据
|
||||||
|
const currentPeriodMetrics = computed(() => {
|
||||||
|
return {
|
||||||
|
assignedLeads: overallTeamPerformance.value.newCustomers?.count_this_period || 0,
|
||||||
|
wechatAdds: teamSalesFunnel.value?.['加微'] || 0,
|
||||||
|
calls: overallTeamPerformance.value.totalCalls?.count_this_period || 0,
|
||||||
|
callDuration: 12580, // 假设数据来自API,这里使用模拟值
|
||||||
|
deposits: teamSalesFunnel.value?.['定金'] || 0,
|
||||||
|
deals: teamSalesFunnel.value?.['成交'] || 0,
|
||||||
|
conversionRate: parseFloat(overallTeamPerformance.value.conversionRate?.conversion_rate_this_period) || 0,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
// 组别数据
|
// 组别数据
|
||||||
const groups=[]
|
const groups=[]
|
||||||
// 当前选中的组别,默认为第一个
|
// 当前选中的组别,默认为第一个
|
||||||
@@ -820,14 +813,10 @@ const selectedGroup = ref(groups[0])
|
|||||||
const selectGroup = async (group) => {
|
const selectGroup = async (group) => {
|
||||||
console.log('选择的组别:', group)
|
console.log('选择的组别:', group)
|
||||||
selectedGroup.value = group
|
selectedGroup.value = group
|
||||||
// 获取部门名称并调用团队业绩详情接口
|
|
||||||
// 从teamRanking数据中查找对应的原始部门名称
|
|
||||||
let department = group.name
|
let department = group.name
|
||||||
if (teamRanking.value && teamRanking.value.formal_plural) {
|
if (teamRanking.value && teamRanking.value.formal_plural) {
|
||||||
// 在formal_plural中查找匹配的部门名称
|
|
||||||
const departmentKeys = Object.keys(teamRanking.value.formal_plural)
|
const departmentKeys = Object.keys(teamRanking.value.formal_plural)
|
||||||
const matchedDepartment = departmentKeys.find(key => {
|
const matchedDepartment = departmentKeys.find(key => {
|
||||||
// 提取部门名称的主要部分进行匹配
|
|
||||||
const mainName = key.split('-')[0] || key
|
const mainName = key.split('-')[0] || key
|
||||||
return group.name.includes(mainName) || mainName.includes(group.name)
|
return group.name.includes(mainName) || mainName.includes(group.name)
|
||||||
})
|
})
|
||||||
@@ -837,7 +826,6 @@ const selectGroup = async (group) => {
|
|||||||
}
|
}
|
||||||
console.log('选中的部门:', group.name, '-> 发送的部门名称:', department)
|
console.log('选中的部门:', group.name, '-> 发送的部门名称:', department)
|
||||||
|
|
||||||
// 设置团队详情加载状态
|
|
||||||
isTeamDetailLoading.value = true
|
isTeamDetailLoading.value = true
|
||||||
try {
|
try {
|
||||||
await fetchTeamPerformanceDetail(department)
|
await fetchTeamPerformanceDetail(department)
|
||||||
@@ -851,8 +839,6 @@ const selectGroup = async (group) => {
|
|||||||
// 处理团队双击事件
|
// 处理团队双击事件
|
||||||
const handleTeamDoubleClick = (group) => {
|
const handleTeamDoubleClick = (group) => {
|
||||||
console.log('团队双击事件触发,团队数据:', group)
|
console.log('团队双击事件触发,团队数据:', group)
|
||||||
|
|
||||||
// 跳转到manager页面,携带团队负责人和用户等级
|
|
||||||
router.push({
|
router.push({
|
||||||
path: '/manager',
|
path: '/manager',
|
||||||
query: {
|
query: {
|
||||||
@@ -865,12 +851,9 @@ const handleTeamDoubleClick = (group) => {
|
|||||||
// 处理成员双击事件
|
// 处理成员双击事件
|
||||||
const handleMemberDoubleClick = (member) => {
|
const handleMemberDoubleClick = (member) => {
|
||||||
console.log('双击事件触发,成员数据:', member)
|
console.log('双击事件触发,成员数据:', member)
|
||||||
|
|
||||||
// 将成员等级写死为1,所有成员都可以跳转
|
|
||||||
const memberLevel = 1
|
const memberLevel = 1
|
||||||
console.log('等级设置为1,准备跳转到Sale页面')
|
console.log('等级设置为1,准备跳转到Sale页面')
|
||||||
|
|
||||||
// 跳转到Sale页面,携带成员姓名和等级
|
|
||||||
router.push({
|
router.push({
|
||||||
name: 'Sale',
|
name: 'Sale',
|
||||||
query: {
|
query: {
|
||||||
@@ -905,7 +888,6 @@ const getStatusText = (status) => {
|
|||||||
return statusMap[status] || '未知'
|
return statusMap[status] || '未知'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// 工具提示状态
|
// 工具提示状态
|
||||||
const tooltip = reactive({
|
const tooltip = reactive({
|
||||||
visible: false,
|
visible: false,
|
||||||
@@ -1014,7 +996,10 @@ const hideTooltip = () => {
|
|||||||
overflow: auto;
|
overflow: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* 新增 */
|
||||||
|
.performance-comparison-section {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
.action-items-compact {
|
.action-items-compact {
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|||||||
Reference in New Issue
Block a user