Compare commits
2 Commits
f0ad14e025
...
182130ba6a
| Author | SHA1 | Date | |
|---|---|---|---|
| 182130ba6a | |||
| d182673552 |
@@ -177,6 +177,7 @@
|
|||||||
<script>
|
<script>
|
||||||
import { SimpleChatService } from '@/utils/ChatService.js'
|
import { SimpleChatService } from '@/utils/ChatService.js'
|
||||||
import MarkdownIt from 'markdown-it'
|
import MarkdownIt from 'markdown-it'
|
||||||
|
import axios from 'axios'
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'GroupRank',
|
name: 'GroupRank',
|
||||||
@@ -314,7 +315,10 @@ export default {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// 模拟转换过程
|
// 模拟转换过程
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000))
|
await axios.post('http://192.168.3.104:8000/api/asr/sync?priority=10', {
|
||||||
|
url: recording.url
|
||||||
|
})
|
||||||
|
|
||||||
|
|
||||||
// 这里应该调用实际的语音转文本API
|
// 这里应该调用实际的语音转文本API
|
||||||
// 目前使用模拟数据
|
// 目前使用模拟数据
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="dashboard-card">
|
<div class="dashboard-card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3>沟通总数据</h3>
|
<h3>沟通总数据</h3>
|
||||||
<span class="metric-period">本周</span>
|
<span class="metric-periods">本周</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="communication-cards">
|
<div class="communication-cards">
|
||||||
<div class="comm-card">
|
<div class="comm-card">
|
||||||
@@ -68,7 +68,7 @@ defineProps({
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric-period {
|
.metric-periods {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,233 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div class="dashboard-card table-section">
|
|
||||||
<div class="card-header">
|
|
||||||
<h3>详细数据表格</h3>
|
|
||||||
</div>
|
|
||||||
<div class="data-table-container">
|
|
||||||
<!-- 筛选器 -->
|
|
||||||
<div class="table-filters">
|
|
||||||
<div class="filter-group">
|
|
||||||
<label>部门:</label>
|
|
||||||
<select v-model="filters.department">
|
|
||||||
<option value="">全部部门</option>
|
|
||||||
<option value="销售一部">销售一部</option>
|
|
||||||
<option value="销售二部">销售二部</option>
|
|
||||||
<option value="销售三部">销售三部</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="filter-group">
|
|
||||||
<label>职位:</label>
|
|
||||||
<select v-model="filters.position">
|
|
||||||
<option value="">全部职位</option>
|
|
||||||
<option value="销售经理">销售经理</option>
|
|
||||||
<option value="销售专员">销售专员</option>
|
|
||||||
<option value="销售助理">销售助理</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="filter-group">
|
|
||||||
<label>时间范围:</label>
|
|
||||||
<select v-model="filters.timeRange">
|
|
||||||
<option value="today">今日</option>
|
|
||||||
<option value="week">本周</option>
|
|
||||||
<option value="month">本月</option>
|
|
||||||
<option value="quarter">本季度</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="filter-group">
|
|
||||||
<label>成交状态:</label>
|
|
||||||
<select v-model="filters.dealStatus">
|
|
||||||
<option value="">全部状态</option>
|
|
||||||
<option value="已成交">已成交</option>
|
|
||||||
<option value="跟进中">跟进中</option>
|
|
||||||
<option value="已失效">已失效</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 数据表格 -->
|
|
||||||
<div class="data-table">
|
|
||||||
<table>
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>人员</th>
|
|
||||||
<th @click="$emit('sort-by', 'dealRate')" class="sortable">
|
|
||||||
成交率
|
|
||||||
<span class="sort-icon" :class="{ active: sortField === 'dealRate' }">
|
|
||||||
{{ sortOrder === 'desc' ? '↓' : '↑' }}
|
|
||||||
</span>
|
|
||||||
</th>
|
|
||||||
<th @click="$emit('sort-by', 'callDuration')" class="sortable">
|
|
||||||
通话时长
|
|
||||||
<span class="sort-icon" :class="{ active: sortField === 'callDuration' }">
|
|
||||||
{{ sortOrder === 'desc' ? '↓' : '↑' }}
|
|
||||||
</span>
|
|
||||||
</th>
|
|
||||||
<th>通话次数</th>
|
|
||||||
<th>成交金额</th>
|
|
||||||
<th>部门</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody>
|
|
||||||
<tr v-for="person in filteredTableData" :key="person.id" @click="$emit('select-person', person)" :class="{ selected: selectedPerson && selectedPerson.id === person.id }">
|
|
||||||
<td>
|
|
||||||
<div class="person-info">
|
|
||||||
<div class="person-avatar">{{ person.name.charAt(0) }}</div>
|
|
||||||
<div>
|
|
||||||
<div class="person-name">{{ person.name }}</div>
|
|
||||||
<div class="person-position">{{ person.position }}</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
<div class="deal-rate">
|
|
||||||
<span class="rate-value" :class="getRateClass(person.dealRate)">{{ person.dealRate }}%</span>
|
|
||||||
<div class="rate-bar">
|
|
||||||
<div class="rate-fill" :style="{ width: person.dealRate + '%', backgroundColor: getRateColor(person.dealRate) }"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
<td>{{ formatDuration(person.callDuration) }}</td>
|
|
||||||
<td>{{ person.callCount }}次</td>
|
|
||||||
<td>¥{{ person.dealAmount.toLocaleString() }}</td>
|
|
||||||
<td>{{ person.department }}</td>
|
|
||||||
</tr>
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { defineProps, defineEmits, reactive } from 'vue';
|
|
||||||
|
|
||||||
defineProps({
|
|
||||||
filteredTableData: Array,
|
|
||||||
selectedPerson: Object,
|
|
||||||
sortField: String,
|
|
||||||
sortOrder: String,
|
|
||||||
getRateClass: Function,
|
|
||||||
getRateColor: Function,
|
|
||||||
formatDuration: Function
|
|
||||||
});
|
|
||||||
|
|
||||||
defineEmits(['sort-by', 'select-person']);
|
|
||||||
|
|
||||||
const filters = reactive({
|
|
||||||
department: '',
|
|
||||||
position: '',
|
|
||||||
timeRange: 'month',
|
|
||||||
dealStatus: ''
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.dashboard-card.table-section {
|
|
||||||
grid-column: 1 / 3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-table-container {
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.table-filters {
|
|
||||||
display: flex;
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-group label {
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-group select {
|
|
||||||
padding: 6px 10px;
|
|
||||||
border: 1px solid #ccc;
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-table table {
|
|
||||||
width: 100%;
|
|
||||||
border-collapse: collapse;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-table th, .data-table td {
|
|
||||||
padding: 12px 15px;
|
|
||||||
text-align: left;
|
|
||||||
border-bottom: 1px solid #eee;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-table th.sortable {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sort-icon {
|
|
||||||
opacity: 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sort-icon.active {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.person-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.person-avatar {
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background-color: #4CAF50;
|
|
||||||
color: white;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-right: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.person-name {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.person-position {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deal-rate .rate-value {
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deal-rate .rate-bar {
|
|
||||||
height: 6px;
|
|
||||||
background-color: #f0f0f0;
|
|
||||||
border-radius: 3px;
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deal-rate .rate-fill {
|
|
||||||
height: 100%;
|
|
||||||
border-radius: 3px;
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr {
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr:hover {
|
|
||||||
background-color: #f5f5f5;
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr.selected {
|
|
||||||
background-color: #e8f5e9;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -50,7 +50,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(person, index) in displayTableData" :key="index" @click="$emit('update:selectedPerson', person)" @dblclick="handlePersonDoubleClick(person)" :class="{ selected: selectedPerson && (selectedPerson.sale_name === person.sale_name || selectedPerson.leader_name === person.leader_name) }">
|
<tr v-for="(person, index) in displayTableData" :key="index" @click="$emit('update:selectedPerson', person)" @dblclick="handlePersonDoubleClick(person)" :class="{ selected: selectedPerson === person }">
|
||||||
<td>
|
<td>
|
||||||
<div class="person-info">
|
<div class="person-info">
|
||||||
<div class="person-avatar">{{ (person.sale_name || person.leader_name).charAt(0) }}</div>
|
<div class="person-avatar">{{ (person.sale_name || person.leader_name).charAt(0) }}</div>
|
||||||
@@ -202,7 +202,6 @@ const handlePersonDoubleClick = (person) => {
|
|||||||
default:
|
default:
|
||||||
targetPath = '/second-top';
|
targetPath = '/second-top';
|
||||||
}
|
}
|
||||||
|
|
||||||
// 路由跳转
|
// 路由跳转
|
||||||
router.push({
|
router.push({
|
||||||
path: targetPath,
|
path: targetPath,
|
||||||
@@ -212,8 +211,6 @@ const handlePersonDoubleClick = (person) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 移除formatDuration函数,不再需要
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
<h3>转化对比图</h3>
|
<h3>转化对比图</h3>
|
||||||
<div class="time-selector">
|
<div class="time-selector">
|
||||||
<select v-model="selectedTimeRange" @change="handleTimeRangeChange" class="time-select">
|
<select v-model="selectedTimeRange" @change="handleTimeRangeChange" class="time-select">
|
||||||
<option value="period">本期 vs 上期</option>
|
<option value="periods">本期 vs 上期</option>
|
||||||
<option value="month">本月 vs 上月</option>
|
<option value="month">本月 vs 上月</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
@@ -67,15 +67,15 @@ const props = defineProps({
|
|||||||
|
|
||||||
const emit = defineEmits(['time-range-change']);
|
const emit = defineEmits(['time-range-change']);
|
||||||
|
|
||||||
const selectedTimeRange = ref('period');
|
const selectedTimeRange = ref('periods');
|
||||||
|
|
||||||
// 计算属性:当前和上一期的标签
|
// 计算属性:当前和上一期的标签
|
||||||
const currentPeriodLabel = computed(() => {
|
const currentPeriodLabel = computed(() => {
|
||||||
return selectedTimeRange.value === 'period' ? '本期' : '本月';
|
return selectedTimeRange.value === 'periods' ? '本期' : '本月';
|
||||||
});
|
});
|
||||||
|
|
||||||
const previousPeriodLabel = computed(() => {
|
const previousPeriodLabel = computed(() => {
|
||||||
return selectedTimeRange.value === 'period' ? '上期' : '上月';
|
return selectedTimeRange.value === 'periods' ? '上期' : '上月';
|
||||||
});
|
});
|
||||||
|
|
||||||
// 计算属性:图表数据
|
// 计算属性:图表数据
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['period-change', 'ranking-type-change']);
|
const emit = defineEmits(['periods-change', 'ranking-type-change']);
|
||||||
|
|
||||||
const rankingPeriod = ref('month');
|
const rankingPeriod = ref('month');
|
||||||
const rankingType = ref('red'); // 'red' 为红榜,'black' 为黑榜
|
const rankingType = ref('red'); // 'red' 为红榜,'black' 为黑榜
|
||||||
@@ -94,7 +94,7 @@ const displayData = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const handlePeriodChange = () => {
|
const handlePeriodChange = () => {
|
||||||
emit('period-change', rankingPeriod.value);
|
emit('periods-change', rankingPeriod.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRankingTypeChange = (type) => {
|
const handleRankingTypeChange = (type) => {
|
||||||
@@ -168,7 +168,7 @@ const handleRankingTypeChange = (type) => {
|
|||||||
color: #2c3e50;
|
color: #2c3e50;
|
||||||
}
|
}
|
||||||
|
|
||||||
.period-select {
|
.periods-select {
|
||||||
border: 1px solid #ddd;
|
border: 1px solid #ddd;
|
||||||
border-radius: 6px;
|
border-radius: 6px;
|
||||||
padding: 8px 12px;
|
padding: 8px 12px;
|
||||||
@@ -178,11 +178,11 @@ const handleRankingTypeChange = (type) => {
|
|||||||
transition: border-color 0.3s ease;
|
transition: border-color 0.3s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
.period-select:hover {
|
.periods-select:hover {
|
||||||
border-color: #3498db;
|
border-color: #3498db;
|
||||||
}
|
}
|
||||||
|
|
||||||
.period-select:focus {
|
.periods-select:focus {
|
||||||
outline: none;
|
outline: none;
|
||||||
border-color: #3498db;
|
border-color: #3498db;
|
||||||
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
|
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.2);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="dashboard-card">
|
<div class="dashboard-card">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
<h3>团队业绩排行榜</h3>
|
<h3>团队业绩排行榜</h3>
|
||||||
<select v-model="rankingPeriod" class="period-select" @change="onPeriodChange">
|
<select v-model="rankingPeriod" class="periods-select" @change="onPeriodChange">
|
||||||
<option value="periods">本期</option>
|
<option value="periods">本期</option>
|
||||||
<option value="month">月度</option>
|
<option value="month">月度</option>
|
||||||
<option value="year">年度</option>
|
<option value="year">年度</option>
|
||||||
@@ -17,9 +17,9 @@
|
|||||||
<div class="employee-info">
|
<div class="employee-info">
|
||||||
<div class="employee-name">{{ item.name }}</div>
|
<div class="employee-name">{{ item.name }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="employee-dept">转化率:{{ item.average_deals_per_member }}</div>
|
<!-- <div class="employee-dept">转化率:{{ item.average_deals_per_member }}</div> -->
|
||||||
<div class="performance-value">
|
<div class="performance-value">
|
||||||
成交单数:{{ formatNumber(item.performance) }}
|
成单数:{{ formatNumber(item.performance) }}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -29,21 +29,70 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, ref, defineEmits } from 'vue';
|
import { defineProps, ref, defineEmits, computed, onMounted } from 'vue';
|
||||||
|
import { getCenterPerformanceRank } from '@/api/top';
|
||||||
|
|
||||||
defineProps({
|
const props = defineProps({
|
||||||
rankingData: Array,
|
|
||||||
formatNumber: Function,
|
formatNumber: Function,
|
||||||
getRankClass: Function
|
getRankClass: Function
|
||||||
});
|
});
|
||||||
|
|
||||||
const emit = defineEmits(['period-change']);
|
const emit = defineEmits(['periods-change']);
|
||||||
|
|
||||||
const rankingPeriod = ref('periods');
|
const rankingPeriod = ref('periods');
|
||||||
|
const centerSalesRank = ref({});
|
||||||
|
|
||||||
|
// 计算属性:转换 centerSalesRank 数据格式
|
||||||
|
const rankingData = computed(() => {
|
||||||
|
if (!centerSalesRank.value) {
|
||||||
|
return []; // 返回空数组
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据不同的时间段获取对应的数据字段
|
||||||
|
let rankList = null;
|
||||||
|
if (rankingPeriod.value === 'periods' && centerSalesRank.value.center_performance_periods_rank) {
|
||||||
|
rankList = centerSalesRank.value.center_performance_periods_rank;
|
||||||
|
} else if (rankingPeriod.value === 'month' && centerSalesRank.value.center_performance_month_rank) {
|
||||||
|
rankList = centerSalesRank.value.center_performance_month_rank;
|
||||||
|
} else if (rankingPeriod.value === 'year' && centerSalesRank.value.center_performance_year_rank) {
|
||||||
|
rankList = centerSalesRank.value.center_performance_year_rank;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!rankList) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return rankList.map((item, index) => ({
|
||||||
|
id: index + 1,
|
||||||
|
name: item.center_leader,
|
||||||
|
performance: item.total_deals,
|
||||||
|
average_deals_per_member: item.average_deals_per_member
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
// 获取全中心业绩排行榜数据
|
||||||
|
async function getCenterSalesRank(data) {
|
||||||
|
const params = {
|
||||||
|
check_type: data // periods、month、year
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const res = await getCenterPerformanceRank(params);
|
||||||
|
console.log('获取中心业绩排行榜:', res);
|
||||||
|
centerSalesRank.value = res.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取全中心业绩排行榜失败:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onPeriodChange = () => {
|
const onPeriodChange = () => {
|
||||||
emit('period-change', rankingPeriod.value);
|
getCenterSalesRank(rankingPeriod.value);
|
||||||
|
emit('periods-change', rankingPeriod.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 组件挂载时获取初始数据
|
||||||
|
onMounted(() => {
|
||||||
|
getCenterSalesRank(rankingPeriod.value);
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -70,7 +119,7 @@ const onPeriodChange = () => {
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.period-select {
|
.periods-select {
|
||||||
border: 1px solid #ccc;
|
border: 1px solid #ccc;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
padding: 6px 10px;
|
padding: 6px 10px;
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ defineProps({
|
|||||||
font-size: 18px;
|
font-size: 18px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric-period {
|
.metric-periods {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: #666;
|
color: #666;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@
|
|||||||
:ranking-data="formattedSalesRankingData"
|
:ranking-data="formattedSalesRankingData"
|
||||||
:format-number="formatNumber"
|
:format-number="formatNumber"
|
||||||
:get-rank-class="getRankClass"
|
:get-rank-class="getRankClass"
|
||||||
@period-change="handleRankingPeriodChange"
|
@periods-change="handleRankingPeriodChange"
|
||||||
@ranking-type-change="getCompanySalesRank"
|
@ranking-type-change="getCompanySalesRank"
|
||||||
/>
|
/>
|
||||||
<!-- 优质通话 -->
|
<!-- 优质通话 -->
|
||||||
@@ -48,10 +48,8 @@
|
|||||||
<div class="dashboard-row row-3">
|
<div class="dashboard-row row-3">
|
||||||
<!-- 业绩排行榜 -->
|
<!-- 业绩排行榜 -->
|
||||||
<ranking-list
|
<ranking-list
|
||||||
:ranking-data="formattedRankingData"
|
|
||||||
:format-number="formatNumber"
|
:format-number="formatNumber"
|
||||||
:get-rank-class="getRankClass"
|
:get-rank-class="getRankClass"
|
||||||
@period-change="getCenterSalesRank"
|
|
||||||
/>
|
/>
|
||||||
<!-- 客户类型占比 -->
|
<!-- 客户类型占比 -->
|
||||||
<customer-type :customer-data="customerTypeRatio" @category-change="getCustomerTypeRatio" />
|
<customer-type :customer-data="customerTypeRatio" @category-change="getCustomerTypeRatio" />
|
||||||
@@ -63,14 +61,13 @@
|
|||||||
<CampManagement />
|
<CampManagement />
|
||||||
</div>
|
</div>
|
||||||
<!-- 第五行 -->
|
<!-- 第五行 -->
|
||||||
<div class="dashboard-row row-4">
|
<div class="dashboard-row">
|
||||||
<DetailedDataTable
|
<DetailedDataTable
|
||||||
:table-data="detailData"
|
:table-data="detailData"
|
||||||
:level-tree="levelTree"
|
:level-tree="levelTree"
|
||||||
v-model:selected-person="selectedPerson"
|
v-model:selected-person="selectedPerson"
|
||||||
@filter-change="handleFilterChange"
|
@filter-change="handleFilterChange"
|
||||||
/>
|
/>
|
||||||
<DataDetailCard :selected-person="selectedPerson" />
|
|
||||||
</div>
|
</div>
|
||||||
<!-- 新建任务模态框 -->
|
<!-- 新建任务模态框 -->
|
||||||
<div
|
<div
|
||||||
@@ -155,13 +152,12 @@ import RankingList from "./components/RankingList.vue";
|
|||||||
import PersonalSalesRanking from "./components/PersonalSalesRanking.vue";
|
import PersonalSalesRanking from "./components/PersonalSalesRanking.vue";
|
||||||
import CommunicationData from "./components/CommunicationData.vue";
|
import CommunicationData from "./components/CommunicationData.vue";
|
||||||
import QualityCalls from "./components/QualityCalls.vue";
|
import QualityCalls from "./components/QualityCalls.vue";
|
||||||
import DataTable from "./components/DataTable.vue";
|
// import DataTable from "./components/DataTable.vue";
|
||||||
import DataDetail from "./components/DataDetail.vue";
|
import DataDetail from "./components/DataDetail.vue";
|
||||||
import CampManagement from "./components/CampManagement.vue";
|
import CampManagement from "./components/CampManagement.vue";
|
||||||
import DetailedDataTable from "./components/DetailedDataTable.vue";
|
import DetailedDataTable from "./components/DetailedDataTable.vue";
|
||||||
import DataDetailCard from "./components/DataDetailCard.vue";
|
|
||||||
import { getOverallCompanyPerformance,getCompanyDepositConversionRate,getCompanyTotalCallCount,getCompanyNewCustomer,getCompanyConversionRate,getCompanyRealTimeProgress
|
import { getOverallCompanyPerformance,getCompanyDepositConversionRate,getCompanyTotalCallCount,getCompanyNewCustomer,getCompanyConversionRate,getCompanyRealTimeProgress
|
||||||
,getCompanyConversionRateVsLast,getSalesMonthlyPerformance,getCenterPerformanceRank,getCustomerTypeDistribution,getUrgentNeedToAddress,getLevelTree,getDetailedDataTable
|
,getCompanyConversionRateVsLast,getSalesMonthlyPerformance,getCustomerTypeDistribution,getUrgentNeedToAddress,getLevelTree,getDetailedDataTable
|
||||||
} from "@/api/top";
|
} from "@/api/top";
|
||||||
const rankingPeriod = ref("month");
|
const rankingPeriod = ref("month");
|
||||||
const rankingData = ref([
|
const rankingData = ref([
|
||||||
@@ -433,7 +429,7 @@ const formattedComparisonData = computed(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 根据 check_type 确定时间范围键
|
// 根据 check_type 确定时间范围键
|
||||||
const timeRangeKey = checkType === 'month' ? 'month' : 'period';
|
const timeRangeKey = checkType === 'month' ? 'month' : 'periods';
|
||||||
const stageOrder = ['线索总数', '加微', '到课', '付定金', '成交'];
|
const stageOrder = ['线索总数', '加微', '到课', '付定金', '成交'];
|
||||||
|
|
||||||
const comparisonArray = stageOrder.map(stageName => ({
|
const comparisonArray = stageOrder.map(stageName => ({
|
||||||
@@ -445,7 +441,7 @@ const formattedComparisonData = computed(() => {
|
|||||||
|
|
||||||
// 同时返回period和month两个键,确保组件能找到对应数据
|
// 同时返回period和month两个键,确保组件能找到对应数据
|
||||||
const result = {
|
const result = {
|
||||||
period: comparisonArray,
|
periods: comparisonArray,
|
||||||
month: comparisonArray
|
month: comparisonArray
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -455,80 +451,12 @@ const formattedComparisonData = computed(() => {
|
|||||||
|
|
||||||
async function getConversionComparison(data) {
|
async function getConversionComparison(data) {
|
||||||
const params={
|
const params={
|
||||||
check_type:data
|
check_type:data //month periods
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const res = await getCompanyConversionRateVsLast(params)
|
const res = await getCompanyConversionRateVsLast(params)
|
||||||
console.log(111111,res)
|
console.log(111111,res)
|
||||||
conversionComparison.value = res.data
|
conversionComparison.value = res.data
|
||||||
/**
|
|
||||||
* data
|
|
||||||
:
|
|
||||||
{user_name: "赵世敬", user_level: 5, check_type: "month",…}
|
|
||||||
check_type
|
|
||||||
:
|
|
||||||
"month"
|
|
||||||
company_current_rate
|
|
||||||
:
|
|
||||||
{线索总数: 14051, 加微: 3238, 到课: 7614, 付定金: 168, 成交: 136}
|
|
||||||
付定金
|
|
||||||
:
|
|
||||||
168
|
|
||||||
到课
|
|
||||||
:
|
|
||||||
7614
|
|
||||||
加微
|
|
||||||
:
|
|
||||||
3238
|
|
||||||
成交
|
|
||||||
:
|
|
||||||
136
|
|
||||||
线索总数
|
|
||||||
:
|
|
||||||
14051
|
|
||||||
company_current_vs_last_rate
|
|
||||||
:
|
|
||||||
{线索总数: "-20.16%", 加微: "-2.70%", 到课: "+114.90%", 付定金: "+∞%", 成交: "+∞%"}
|
|
||||||
付定金
|
|
||||||
:
|
|
||||||
"+∞%"
|
|
||||||
到课
|
|
||||||
:
|
|
||||||
"+114.90%"
|
|
||||||
加微
|
|
||||||
:
|
|
||||||
"-2.70%"
|
|
||||||
成交
|
|
||||||
:
|
|
||||||
"+∞%"
|
|
||||||
线索总数
|
|
||||||
:
|
|
||||||
"-20.16%"
|
|
||||||
company_last_rate
|
|
||||||
:
|
|
||||||
{线索总数: 17598, 加微: 3328, 到课: 3543, 付定金: 0, 成交: 0}
|
|
||||||
付定金
|
|
||||||
:
|
|
||||||
0
|
|
||||||
到课
|
|
||||||
:
|
|
||||||
3543
|
|
||||||
加微
|
|
||||||
:
|
|
||||||
3328
|
|
||||||
成交
|
|
||||||
:
|
|
||||||
0
|
|
||||||
线索总数
|
|
||||||
:
|
|
||||||
17598
|
|
||||||
user_level
|
|
||||||
:
|
|
||||||
5
|
|
||||||
user_name
|
|
||||||
:
|
|
||||||
"赵世敬"
|
|
||||||
*/
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取转化对比失败:", error);
|
console.error("获取转化对比失败:", error);
|
||||||
}
|
}
|
||||||
@@ -567,7 +495,7 @@ const formattedSalesRankingData = computed(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 处理销售排行榜期间变化
|
// 处理销售排行榜期间变化
|
||||||
const handleRankingPeriodChange = (period) => {
|
const handleRankingPeriodChange = (periods) => {
|
||||||
// 根据期间参数调用相应的函数,这里默认调用红榜数据
|
// 根据期间参数调用相应的函数,这里默认调用红榜数据
|
||||||
getCompanySalesRank('red');
|
getCompanySalesRank('red');
|
||||||
};
|
};
|
||||||
@@ -579,200 +507,11 @@ async function getCompanySalesRank(Rank) {
|
|||||||
try {
|
try {
|
||||||
const res = await getSalesMonthlyPerformance(params)
|
const res = await getSalesMonthlyPerformance(params)
|
||||||
companySalesRank.value = res.data
|
companySalesRank.value = res.data
|
||||||
/**
|
|
||||||
* "data": {
|
|
||||||
"user_name": "赵世敬",
|
|
||||||
"user_level": 5,
|
|
||||||
"rank_type": "red",
|
|
||||||
"sales_monthly_performance_red": [
|
|
||||||
{
|
|
||||||
"name": "贾星草",
|
|
||||||
"department": "洋葱管理层",
|
|
||||||
"deal_count": 2,
|
|
||||||
"conversion_rate": "2.78%",
|
|
||||||
"rank": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "常志洁",
|
|
||||||
"department": "星火二部--王志恒",
|
|
||||||
"deal_count": 2,
|
|
||||||
"conversion_rate": "4.17%",
|
|
||||||
"rank": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "李俊",
|
|
||||||
"department": "星火二部--王志恒",
|
|
||||||
"deal_count": 2,
|
|
||||||
"conversion_rate": "3.77%",
|
|
||||||
"rank": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "高有桔",
|
|
||||||
"department": "勇士一部-张茂华",
|
|
||||||
"deal_count": 2,
|
|
||||||
"conversion_rate": "3.12%",
|
|
||||||
"rank": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "马肖剑",
|
|
||||||
"department": "星耀三部-周毅",
|
|
||||||
"deal_count": 1,
|
|
||||||
"conversion_rate": "1.05%",
|
|
||||||
"rank": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "刘思雨",
|
|
||||||
"department": "星耀三部-周毅",
|
|
||||||
"deal_count": 1,
|
|
||||||
"conversion_rate": "2.27%",
|
|
||||||
"rank": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "王慧",
|
|
||||||
"department": "聚星三部--张卓",
|
|
||||||
"deal_count": 1,
|
|
||||||
"conversion_rate": "2.00%",
|
|
||||||
"rank": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "寇帅杰",
|
|
||||||
"department": "巅峰一部-贾星草",
|
|
||||||
"deal_count": 1,
|
|
||||||
"conversion_rate": "2.13%",
|
|
||||||
"rank": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "王奥博",
|
|
||||||
"department": "巅峰二部-纪洋洋",
|
|
||||||
"deal_count": 1,
|
|
||||||
"conversion_rate": "1.14%",
|
|
||||||
"rank": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "董富忠",
|
|
||||||
"department": "巅峰三部--刘东洋",
|
|
||||||
"deal_count": 1,
|
|
||||||
"conversion_rate": "0.95%",
|
|
||||||
"rank": 10
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
// 黑榜数据
|
|
||||||
/**
|
|
||||||
* "data": {
|
|
||||||
"user_name": "赵世敬",
|
|
||||||
"user_level": 5,
|
|
||||||
"rank_type": "black",
|
|
||||||
"sales_monthly_performance_black": [
|
|
||||||
{
|
|
||||||
"name": "马然",
|
|
||||||
"department": "美团业务支持部",
|
|
||||||
"deal_count": 0,
|
|
||||||
"conversion_rate": "0.00%",
|
|
||||||
"rank": 1
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "郭可英",
|
|
||||||
"department": "技术部",
|
|
||||||
"deal_count": 0,
|
|
||||||
"conversion_rate": "0.00%",
|
|
||||||
"rank": 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "杨启晨",
|
|
||||||
"department": "星火一部--张瑾",
|
|
||||||
"deal_count": 0,
|
|
||||||
"conversion_rate": "0.00%",
|
|
||||||
"rank": 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "程慧仟",
|
|
||||||
"department": "星耀三部-周毅",
|
|
||||||
"deal_count": 0,
|
|
||||||
"conversion_rate": "0.00%",
|
|
||||||
"rank": 4
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "常琳",
|
|
||||||
"department": "亮剑二部-田贵星",
|
|
||||||
"deal_count": 0,
|
|
||||||
"conversion_rate": "0.00%",
|
|
||||||
"rank": 5
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "李晓雪",
|
|
||||||
"department": "星火一部--张瑾",
|
|
||||||
"deal_count": 0,
|
|
||||||
"conversion_rate": "0.00%",
|
|
||||||
"rank": 6
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "杨朵朵",
|
|
||||||
"department": "星耀三部-周毅",
|
|
||||||
"deal_count": 0,
|
|
||||||
"conversion_rate": "0.00%",
|
|
||||||
"rank": 7
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "张明起",
|
|
||||||
"department": "星耀一部-吕明月",
|
|
||||||
"deal_count": 0,
|
|
||||||
"conversion_rate": "0.00%",
|
|
||||||
"rank": 8
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "刘英杰",
|
|
||||||
"department": "星耀一部-吕明月",
|
|
||||||
"deal_count": 0,
|
|
||||||
"conversion_rate": "0.00%",
|
|
||||||
"rank": 9
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "孟凡玉",
|
|
||||||
"department": "星耀一部-吕明月",
|
|
||||||
"deal_count": 0,
|
|
||||||
"conversion_rate": "0.00%",
|
|
||||||
"rank": 10
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取销售月度业绩红黑榜失败:", error);
|
console.error("获取销售月度业绩红黑榜失败:", error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// 获取全中心业绩排行榜
|
// 获取全中心业绩排行榜逻辑已移至 RankingList 组件
|
||||||
const centerSalesRank = ref({});
|
|
||||||
|
|
||||||
// 计算属性:转换 centerSalesRank 数据格式以适配 ranking-list 组件
|
|
||||||
const formattedRankingData = computed(() => {
|
|
||||||
if (!centerSalesRank.value || !centerSalesRank.value.center_performance_periods_rank) {
|
|
||||||
return rankingData.value; // 返回默认数据
|
|
||||||
}
|
|
||||||
|
|
||||||
const rankList = centerSalesRank.value.center_performance_periods_rank;
|
|
||||||
|
|
||||||
return rankList.map((item, index) => ({
|
|
||||||
id: index + 1,
|
|
||||||
name: item.center_leader,
|
|
||||||
performance: item.total_deals,
|
|
||||||
average_deals_per_member: item.average_deals_per_member
|
|
||||||
}));
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getCenterSalesRank(data) {
|
|
||||||
const params={
|
|
||||||
check_type:data //periods、month、year
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
const res = await getCenterPerformanceRank(params)
|
|
||||||
console.log(1222222,res)
|
|
||||||
centerSalesRank.value = res.data
|
|
||||||
} catch (error) {
|
|
||||||
console.error("获取全中心业绩排行榜失败:", error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 客户类型占比
|
// 客户类型占比
|
||||||
const customerTypeRatio = ref({});
|
const customerTypeRatio = ref({});
|
||||||
async function getCustomerTypeRatio(data) {
|
async function getCustomerTypeRatio(data) {
|
||||||
@@ -805,55 +544,6 @@ async function CusotomGetLevelTree() {
|
|||||||
const res = await getLevelTree()
|
const res = await getLevelTree()
|
||||||
console.log(1222222,res)
|
console.log(1222222,res)
|
||||||
levelTree.value = res.data
|
levelTree.value = res.data
|
||||||
/**
|
|
||||||
* "data": {
|
|
||||||
"user_name": "赵世敬",
|
|
||||||
"user_level": 5,
|
|
||||||
"level_tree": {
|
|
||||||
"center_leaders": [
|
|
||||||
{
|
|
||||||
"name": "郭可英",
|
|
||||||
"advanced_managers": [
|
|
||||||
{
|
|
||||||
"name": "李小燕",
|
|
||||||
"managers": []
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "郭子奇",
|
|
||||||
"managers": [
|
|
||||||
{
|
|
||||||
"name": "杨朵朵"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "张明起"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "刘瑞",
|
|
||||||
"advanced_managers": [
|
|
||||||
{
|
|
||||||
"name": "陈盼良",
|
|
||||||
"managers": [
|
|
||||||
{
|
|
||||||
"name": "马然"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "杨启晨"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "韦少杰"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取级别树失败:", error);
|
console.error("获取级别树失败:", error);
|
||||||
}
|
}
|
||||||
@@ -862,19 +552,15 @@ async function CusotomGetLevelTree() {
|
|||||||
const detailData = ref({});
|
const detailData = ref({});
|
||||||
async function getDetailData(params) {
|
async function getDetailData(params) {
|
||||||
if(params?.center_leader){
|
if(params?.center_leader){
|
||||||
// alert(11111)
|
|
||||||
try {
|
try {
|
||||||
const res = await getDetailedDataTable(params)
|
const res = await getDetailedDataTable(params)
|
||||||
console.log('详细数据表格:', res)
|
|
||||||
detailData.value = res.data
|
detailData.value = res.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取详细数据表格失败:", error);
|
console.error("获取详细数据表格失败:", error);
|
||||||
}
|
}
|
||||||
}else{
|
}else{
|
||||||
// alert(22222)
|
|
||||||
try {
|
try {
|
||||||
const res = await getDetailedDataTable()
|
const res = await getDetailedDataTable()
|
||||||
console.log('详细数据表格:', res)
|
|
||||||
detailData.value = res.data
|
detailData.value = res.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("获取详细数据表格失败:", error);
|
console.error("获取详细数据表格失败:", error);
|
||||||
@@ -895,7 +581,6 @@ onMounted(async() => {
|
|||||||
await getTotalDeals()
|
await getTotalDeals()
|
||||||
await getConversionComparison('month')
|
await getConversionComparison('month')
|
||||||
await getCompanySalesRank('red')
|
await getCompanySalesRank('red')
|
||||||
await getCenterSalesRank('periods')
|
|
||||||
await getCustomerTypeRatio('child_education')
|
await getCustomerTypeRatio('child_education')
|
||||||
await getCustomerUrgency()
|
await getCustomerUrgency()
|
||||||
await CusotomGetLevelTree()
|
await CusotomGetLevelTree()
|
||||||
@@ -978,7 +663,7 @@ onMounted(async() => {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.metric-period {
|
.metric-periods {
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: #718096;
|
color: #718096;
|
||||||
background: #edf2f7;
|
background: #edf2f7;
|
||||||
@@ -1317,7 +1002,7 @@ onMounted(async() => {
|
|||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.period-select {
|
.periods-select {
|
||||||
padding: 4px 8px;
|
padding: 4px 8px;
|
||||||
border: 1px solid #e2e8f0;
|
border: 1px solid #e2e8f0;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|||||||
Reference in New Issue
Block a user