feat(GoodMusic): 替换模拟API为真实语音转文本API调用
refactor(DetailedDataTable): 简化选中人员判断逻辑 feat(RankingList): 将排行榜数据获取逻辑移至组件内部 refactor(topone): 移除冗余代码并优化排行榜数据流 chore: 删除不再使用的DataTable组件
This commit is contained in:
@@ -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>
|
||||
</thead>
|
||||
<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>
|
||||
<div class="person-info">
|
||||
<div class="person-avatar">{{ (person.sale_name || person.leader_name).charAt(0) }}</div>
|
||||
@@ -202,7 +202,6 @@ const handlePersonDoubleClick = (person) => {
|
||||
default:
|
||||
targetPath = '/second-top';
|
||||
}
|
||||
|
||||
// 路由跳转
|
||||
router.push({
|
||||
path: targetPath,
|
||||
@@ -212,8 +211,6 @@ const handlePersonDoubleClick = (person) => {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 移除formatDuration函数,不再需要
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -29,10 +29,10 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, ref, defineEmits } from 'vue';
|
||||
import { defineProps, ref, defineEmits, computed, onMounted } from 'vue';
|
||||
import { getCenterPerformanceRank } from '@/api/top';
|
||||
|
||||
defineProps({
|
||||
rankingData: Array,
|
||||
const props = defineProps({
|
||||
formatNumber: Function,
|
||||
getRankClass: Function
|
||||
});
|
||||
@@ -40,10 +40,59 @@ defineProps({
|
||||
const emit = defineEmits(['periods-change']);
|
||||
|
||||
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 = () => {
|
||||
getCenterSalesRank(rankingPeriod.value);
|
||||
emit('periods-change', rankingPeriod.value);
|
||||
};
|
||||
|
||||
// 组件挂载时获取初始数据
|
||||
onMounted(() => {
|
||||
getCenterSalesRank(rankingPeriod.value);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
@@ -48,10 +48,8 @@
|
||||
<div class="dashboard-row row-3">
|
||||
<!-- 业绩排行榜 -->
|
||||
<ranking-list
|
||||
:ranking-data="formattedRankingData"
|
||||
:format-number="formatNumber"
|
||||
:get-rank-class="getRankClass"
|
||||
@periods-change="getCenterSalesRank"
|
||||
/>
|
||||
<!-- 客户类型占比 -->
|
||||
<customer-type :customer-data="customerTypeRatio" @category-change="getCustomerTypeRatio" />
|
||||
@@ -63,14 +61,13 @@
|
||||
<CampManagement />
|
||||
</div>
|
||||
<!-- 第五行 -->
|
||||
<div class="dashboard-row row-4">
|
||||
<div class="dashboard-row">
|
||||
<DetailedDataTable
|
||||
:table-data="detailData"
|
||||
:level-tree="levelTree"
|
||||
v-model:selected-person="selectedPerson"
|
||||
@filter-change="handleFilterChange"
|
||||
/>
|
||||
<DataDetailCard :selected-person="selectedPerson" />
|
||||
</div>
|
||||
<!-- 新建任务模态框 -->
|
||||
<div
|
||||
@@ -155,13 +152,12 @@ import RankingList from "./components/RankingList.vue";
|
||||
import PersonalSalesRanking from "./components/PersonalSalesRanking.vue";
|
||||
import CommunicationData from "./components/CommunicationData.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 CampManagement from "./components/CampManagement.vue";
|
||||
import DetailedDataTable from "./components/DetailedDataTable.vue";
|
||||
import DataDetailCard from "./components/DataDetailCard.vue";
|
||||
import { getOverallCompanyPerformance,getCompanyDepositConversionRate,getCompanyTotalCallCount,getCompanyNewCustomer,getCompanyConversionRate,getCompanyRealTimeProgress
|
||||
,getCompanyConversionRateVsLast,getSalesMonthlyPerformance,getCenterPerformanceRank,getCustomerTypeDistribution,getUrgentNeedToAddress,getLevelTree,getDetailedDataTable
|
||||
,getCompanyConversionRateVsLast,getSalesMonthlyPerformance,getCustomerTypeDistribution,getUrgentNeedToAddress,getLevelTree,getDetailedDataTable
|
||||
} from "@/api/top";
|
||||
const rankingPeriod = ref("month");
|
||||
const rankingData = ref([
|
||||
@@ -461,74 +457,6 @@ async function getConversionComparison(data) {
|
||||
const res = await getCompanyConversionRateVsLast(params)
|
||||
console.log(111111,res)
|
||||
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) {
|
||||
console.error("获取转化对比失败:", error);
|
||||
}
|
||||
@@ -583,37 +511,7 @@ async function getCompanySalesRank(Rank) {
|
||||
console.error("获取销售月度业绩红黑榜失败:", error);
|
||||
}
|
||||
}
|
||||
// 获取全中心业绩排行榜
|
||||
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);
|
||||
}
|
||||
}
|
||||
// 获取全中心业绩排行榜逻辑已移至 RankingList 组件
|
||||
// 客户类型占比
|
||||
const customerTypeRatio = ref({});
|
||||
async function getCustomerTypeRatio(data) {
|
||||
@@ -683,7 +581,6 @@ onMounted(async() => {
|
||||
await getTotalDeals()
|
||||
await getConversionComparison('month')
|
||||
await getCompanySalesRank('red')
|
||||
await getCenterSalesRank('periods')
|
||||
await getCustomerTypeRatio('child_education')
|
||||
await getCustomerUrgency()
|
||||
await CusotomGetLevelTree()
|
||||
|
||||
Reference in New Issue
Block a user