feat(api): 新增销售漏斗和黄金联络时段API接口

feat(views): 添加销售漏斗和黄金联络时段数据展示功能
refactor(views): 优化客户详情组件的数据处理逻辑
fix(views): 修复业绩数据显示字段不一致问题
style(views): 调整路由导航顶栏样式
This commit is contained in:
2025-08-19 21:45:15 +08:00
parent 182130ba6a
commit 2b75f1b568
8 changed files with 326 additions and 138 deletions

View File

@@ -66,4 +66,14 @@ export const getCustomerCallInfo = (params) => {
return https.post('/api/v1/sales_timeline/get_customer_call_info', params) return https.post('/api/v1/sales_timeline/get_customer_call_info', params)
} }
// 销售漏斗 /api/v1/sales/sales_funnel
export const getSalesFunnel = (params) => {
return https.post('/api/v1/sales/sales_funnel', params)
}
// 黄金联络 /api/v1/sales/get_gold_contact_time
export const getGoldContactTime = (params) => {
return https.post('/api/v1/sales/get_gold_contact_time', params)
}

View File

@@ -143,67 +143,7 @@ const teamMembers = [
newClients: 1, newClients: 1,
deals: 0, deals: 0,
avgDealValue: 0, avgDealValue: 0,
}, }
{
id: 6,
name: "陈雨",
rank: 6,
performance: 45000,
conversion: 3.2,
calls: 98,
callTime: 4.1,
newClients: 4,
deals: 1,
avgDealValue: 45000,
},
{
id: 7,
name: "周杰",
rank: 7,
performance: 38000,
conversion: 2.8,
calls: 115,
callTime: 5.3,
newClients: 5,
deals: 1,
avgDealValue: 38000,
},
{
id: 8,
name: "吴梅",
rank: 8,
performance: 22000,
conversion: 1.9,
calls: 87,
callTime: 3.7,
newClients: 3,
deals: 1,
avgDealValue: 22000,
},
{
id: 9,
name: "孙涛",
rank: 9,
performance: 15000,
conversion: 1.2,
calls: 92,
callTime: 4.0,
newClients: 2,
deals: 1,
avgDealValue: 15000,
},
{
id: 10,
name: "马丽",
rank: 10,
performance: 8000,
conversion: 0.8,
calls: 68,
callTime: 2.5,
newClients: 1,
deals: 1,
avgDealValue: 8000,
},
]; ];
// 路由实例 // 路由实例

View File

@@ -15,17 +15,17 @@
<button <button
@click="startSopAnalysis" @click="startSopAnalysis"
class="analysis-button sop-button" class="analysis-button sop-button"
:disabled="isSopAnalysisLoading" :disabled="true"
> >
{{ isSopAnalysisLoading ? 'SOP分析中...' : 'SOP通话分析' }} {{ isSopAnalysisLoading ? 'SOP分析中...' : 'SOP通话分析' }}
</button> </button>
<button <!-- <button
@click="startDemandAnalysis" @click="startDemandAnalysis"
class="analysis-button demand-button" class="analysis-button demand-button"
:disabled="isDemandAnalysisLoading" :disabled="isDemandAnalysisLoading"
> >
{{ isDemandAnalysisLoading ? '诉求分析中...' : '客户诉求分析' }} {{ isDemandAnalysisLoading ? '诉求分析中...' : '客户诉求分析' }}
</button> </button> -->
</div> </div>
</div> </div>
@@ -65,8 +65,7 @@
</div> </div>
<!-- 下方整行区域 --> <!-- 下方整行区域 -->
<div class="bottom-row"> <!-- <div class="bottom-row">
<!-- 客户诉求分析 -->
<div class="analysis-section demand-analysis"> <div class="analysis-section demand-analysis">
<div class="section-header"> <div class="section-header">
<h4>客户诉求分析</h4> <h4>客户诉求分析</h4>
@@ -80,7 +79,7 @@
</div> </div>
</div> </div>
</div> </div>
</div> </div> -->
</div> </div>
</div> </div>
@@ -101,6 +100,18 @@ const props = defineProps({
selectedContact: { selectedContact: {
type: Object, type: Object,
default: null default: null
},
formInfo: {
type: Object,
default: () => ({})
},
chatRecords: {
type: Array,
default: () => []
},
callRecords: {
type: Array,
default: () => []
} }
}); });
@@ -115,8 +126,8 @@ const isSopAnalysisLoading = ref(false); // SOP分析加载状态
const isDemandAnalysisLoading = ref(false); // 诉求分析加载状态 const isDemandAnalysisLoading = ref(false); // 诉求分析加载状态
// Dify API配置 // Dify API配置
const DIFY_API_KEY_01 = 'app-wbR1P1j6kvdBK8Q1qXzdswzP'; const DIFY_API_KEY_01 = 'app-h4uBo5kOGoiYhjuBF1AHZi8b'; //基础信息分析
const DIFY_API_KEY = 'app-37VXHRieOnq17BSury9ONavG'; const DIFY_API_KEY = 'app-ZIJSFWbcdZLufkwCp9RrvpUR';
// 初始化ChatService // 初始化ChatService
const chatService_01 = new SimpleChatService(DIFY_API_KEY_01); const chatService_01 = new SimpleChatService(DIFY_API_KEY_01);
const chatService = new SimpleChatService(DIFY_API_KEY); const chatService = new SimpleChatService(DIFY_API_KEY);
@@ -174,17 +185,82 @@ const startBasicAnalysis = async () => {
isBasicAnalysisLoading.value = true; isBasicAnalysisLoading.value = true;
basicAnalysisResult.value = ''; basicAnalysisResult.value = '';
// 构建表单信息
const formData = props.formInfo || {};
let formInfoText = '暂无表单信息';
if (Object.keys(formData).length > 0) {
const basicInfo = [];
const additionalInfo = [];
// 处理基础信息字段
const basicFields = {
name: '姓名',
mobile: '手机号',
occupation: '职业',
territory: '地区',
child_name: '孩子姓名',
child_gender: '孩子性别',
child_education: '孩子教育阶段',
child_relation: '与孩子关系'
};
Object.entries(basicFields).forEach(([key, label]) => {
if (formData[key] && formData[key] !== '暂无' && formData[key] !== '') {
basicInfo.push(`${label}: ${formData[key]}`);
}
});
// 处理 additional_info 数组
if (formData.additional_info && Array.isArray(formData.additional_info)) {
formData.additional_info.forEach(item => {
if (item.topic && item.answer) {
additionalInfo.push(`${item.topic}\n答案: ${item.answer}`);
}
});
}
// 组合所有信息
const allInfo = [];
if (basicInfo.length > 0) {
allInfo.push('=== 基础信息 ===');
allInfo.push(...basicInfo);
}
if (additionalInfo.length > 0) {
allInfo.push('\n=== 问卷信息 ===');
allInfo.push(...additionalInfo);
}
formInfoText = allInfo.length > 0 ? allInfo.join('\n') : '暂无表单信息';
}
// 构建聊天记录信息
const chatData = props.chatRecords || [];
const chatInfoText = chatData.messages.length > 0 ?
`聊天记录数量: ${chatData.messages.length}\n最近聊天内容: ${JSON.stringify(chatData.messages.slice(-3), null, 2)}` :
'暂无聊天记录';
// 构建通话记录信息
const callData = props.callRecords || [];
const callInfoText = callData.length > 0 ?
`通话记录数量: ${callData.length}\n通话记录详情: ${JSON.stringify(callData, null, 2)}` :
'暂无通话记录';
const query = `请对客户进行基础信息分析: const query = `请对客户进行基础信息分析:
客户姓名:${props.selectedContact.name} 客户姓名:${props.selectedContact.name}
联系电话:${props.selectedContact.phone || '未提供'} 联系电话:${props.selectedContact.phone || '未提供'}
邮箱:${props.selectedContact.email || '未提供'}
公司:${props.selectedContact.company || '未提供'}
职位:${props.selectedContact.position || '未提供'}
销售阶段:${props.selectedContact.salesStage || '未知'} 销售阶段:${props.selectedContact.salesStage || '未知'}
健康度:${props.selectedContact.health || '未知'}%
请分析客户的基本情况、背景信息和初步画像。`; === 表单信息 ===
${formInfoText}
=== 聊天记录 ===
${chatInfoText}
=== 通话记录 ===
${callInfoText}
请基于以上客户的表单信息、聊天记录和通话记录,分析客户的基本情况、背景信息和初步画像。`;
try { try {
await chatService_01.sendMessage( await chatService_01.sendMessage(
query, query,

View File

@@ -99,7 +99,7 @@
</template> </template>
<script setup> <script setup>
import { ref, reactive, onMounted, onBeforeUnmount, computed } from 'vue'; import { ref, reactive, onMounted, onBeforeUnmount, computed, watch } from 'vue';
import StatisticData from './StatisticData.vue'; import StatisticData from './StatisticData.vue';
import * as echarts from 'echarts'; import * as echarts from 'echarts';
import Chart from 'chart.js/auto'; import Chart from 'chart.js/auto';
@@ -130,10 +130,7 @@ const props = defineProps({
}, },
contactTimeData: { contactTimeData: {
type: Object, type: Object,
default: () => ({ default: () => ({})
labels: ['9-10点', '10-11点', '11-12点', '14-15点', '15-16点', '16-17点'],
data: [65, 85, 80, 92, 75, 60]
})
}, },
statisticsData: { statisticsData: {
type: Object, type: Object,
@@ -176,12 +173,6 @@ const totalProblemCount = computed(() => {
return props.urgentProblemData.reduce((sum, item) => sum + item.value, 0); return props.urgentProblemData.reduce((sum, item) => sum + item.value, 0);
}); });
// --- 方法 ---
// Chart.js: 创建或更新图表 // Chart.js: 创建或更新图表
const createOrUpdateChart = (chartId, canvasRef, config) => { const createOrUpdateChart = (chartId, canvasRef, config) => {
if (chartInstances[chartId]) { if (chartInstances[chartId]) {
@@ -198,10 +189,10 @@ const renderPersonalFunnelChart = () => {
const config = { const config = {
type: 'bar', type: 'bar',
data: { data: {
labels: funnelData.labels, labels: funnelData.value.labels,
datasets: [{ datasets: [{
label: '数量', data: funnelData.data, label: '数量', data: funnelData.value.data,
backgroundColor: ['rgba(59, 130, 246, 0.8)', 'rgba(16, 185, 129, 0.8)', 'rgba(245, 158, 11, 0.8)', 'rgba(239, 68, 68, 0.8)'], backgroundColor: ['rgba(59, 130, 246, 0.8)', 'rgba(16, 185, 129, 0.8)', 'rgba(245, 158, 11, 0.8)', 'rgba(239, 68, 68, 0.8)', 'rgba(168, 85, 247, 0.8)'],
borderWidth: 1 borderWidth: 1
}] }]
}, },
@@ -219,12 +210,19 @@ const renderPersonalFunnelChart = () => {
// Chart.js: 渲染黄金联络时段图 // Chart.js: 渲染黄金联络时段图
const renderContactTimeChart = () => { const renderContactTimeChart = () => {
if (!props.contactTimeData || !props.contactTimeData.gold_contact_success_rate) {
return;
}
const labels = Object.keys(props.contactTimeData.gold_contact_success_rate);
const data = Object.values(props.contactTimeData.gold_contact_success_rate).map(rate => parseFloat(rate));
const config = { const config = {
type: 'line', type: 'line',
data: { data: {
labels: contactTimeData.labels, labels: labels,
datasets: [{ datasets: [{
label: '成功率', data: contactTimeData.data, label: '成功率', data: data,
borderColor: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.1)', borderColor: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.1)',
borderWidth: 3, tension: 0.4, fill: true, pointRadius: 4, borderWidth: 3, tension: 0.4, fill: true, pointRadius: 4,
pointBackgroundColor: '#10b981', pointBorderColor: '#ffffff', pointBorderWidth: 2 pointBackgroundColor: '#10b981', pointBorderColor: '#ffffff', pointBorderWidth: 2
@@ -254,6 +252,10 @@ const getRankBadgeClass = (index) => ({ 'badge-gold': index === 0, 'badge-silver
watch(() => props.contactTimeData, () => {
renderContactTimeChart();
}, { deep: true });
// --- 生命周期钩子 --- // --- 生命周期钩子 ---
onMounted(() => { onMounted(() => {

View File

@@ -93,7 +93,16 @@
<span class="call-duration">{{ call.duration }}</span> <span class="call-duration">{{ call.duration }}</span>
<span class="call-time">{{ call.time }}</span> <span class="call-time">{{ call.time }}</span>
</div> </div>
<div class="call-summary">{{ call.summary }}</div> <div class="call-actions">
<button class="action-btn download-btn" @click="downloadRecording(call)">
<i class="icon-download"></i>
录音下载
</button>
<button class="action-btn view-btn" @click="viewTranscript(call)">
<i class="icon-view"></i>
查看原文
</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -230,6 +239,43 @@ const callRecords = computed(() => {
} }
] ]
}) })
// 录音下载方法
const downloadRecording = (call) => {
console.log('下载录音:', call)
// 检查是否有录音文件地址
if (call.record_file_addr_list && call.record_file_addr_list.length > 0) {
const recordingUrl = call.record_file_addr_list[0]
// 从URL中提取文件名
const urlParts = recordingUrl.split('/')
const fileName = urlParts[urlParts.length - 1]
// 创建下载链接
const link = document.createElement('a')
link.href = recordingUrl
link.download = fileName
link.target = '_blank'
// 触发下载
document.body.appendChild(link)
link.click()
document.body.removeChild(link)
console.log(`正在下载录音文件: ${fileName}`)
} else {
alert('该通话记录暂无录音文件')
}
}
// 查看原文方法
const viewTranscript = (call) => {
// 这里可以根据实际需求实现查看原文逻辑
console.log('查看原文:', call)
// 示例:打开模态框显示通话原文
alert(`查看 ${call.time} 的通话原文:\n\n${call.summary}`)
}
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@@ -481,10 +527,56 @@ const callRecords = computed(() => {
} }
} }
.call-summary { .call-actions {
font-size: 14px; display: flex;
color: #374151; gap: 12px;
line-height: 1.5; margin-top: 12px;
.action-btn {
display: flex;
align-items: center;
gap: 6px;
padding: 8px 12px;
border: none;
border-radius: 6px;
font-size: 12px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
&.download-btn {
background: #dbeafe;
color: #3b82f6;
&:hover {
background: #bfdbfe;
transform: translateY(-1px);
}
}
&.view-btn {
background: #d1fae5;
color: #059669;
&:hover {
background: #a7f3d0;
transform: translateY(-1px);
}
}
i {
width: 14px;
height: 14px;
&.icon-download::before {
content: '⬇';
}
&.icon-view::before {
content: '👁';
}
}
}
} }
} }
} }

View File

@@ -1,18 +1,17 @@
<template> <template>
<div class="sales-dashboard"> <div class="sales-dashboard">
<!-- 页面加载状态 --> <!-- 页面加载状态 -->
<!-- <Loading :visible="isPageLoading" text="正在加载数据..." /> --> <Loading :visible="isPageLoading" text="正在加载数据..." />
<!-- 顶部导航栏 --> <!-- 顶部导航栏 -->
<!-- 销售时间线区域 --> <!-- 销售时间线区域 -->
<section class="timeline-section"> <section class="timeline-section">
<div class="section-header"> <div class="section-header">
<!-- 动态顶栏根据是否有路由参数显示不同内容 --> <!-- 动态顶栏根据是否有路由参数显示不同内容 -->
<!-- 路由跳转时的顶栏面包屑 + 姓名 --> <!-- 路由跳转时的顶栏面包屑 + 姓名 -->
<div v-if="isRouteNavigation" class="route-header"> <div v-if="isRouteNavigation" class="route-header" style="display: flex; justify-content: space-between; align-items: center;">
<div class="breadcrumb"> <div class="breadcrumb" style="display: flex; flex-direction: column;">
<span class="breadcrumb-item" @click="goBack">团队管理</span> <span class="breadcrumb-item" @click="goBack">团队管理 >{{ routeUserName }}</span>
<span class="breadcrumb-separator">></span> <span class="breadcrumb-item current"> 数据驾驶舱</span>
<span class="breadcrumb-item current"> {{ routeUserName }}数据驾驶舱</span>
</div> </div>
<div class="user-name"> <div class="user-name">
{{ routeUserName }} {{ routeUserName }}
@@ -67,6 +66,7 @@
:selected-contact="selectedContact" :selected-contact="selectedContact"
:form-info="formInfo" :form-info="formInfo"
:chat-info="chatRecords" :chat-info="chatRecords"
:call-info="callRecords"
@view-form-data="handleViewFormData" @view-form-data="handleViewFormData"
@view-chat-data="handleViewChatData" @view-chat-data="handleViewChatData"
@view-call-data="handleViewCallData" /> @view-call-data="handleViewCallData" />
@@ -83,7 +83,11 @@
<h2>客户详情</h2> <h2>客户详情</h2>
</div> </div>
<div class="section-content"> <div class="section-content">
<CustomerDetail :selected-contact="selectedContact" /> <CustomerDetail
:selected-contact="selectedContact"
:form-info="formInfo"
:chat-records="chatRecords"
:call-records="callRecords" />
</div> </div>
</section> </section>
</main> </main>
@@ -101,13 +105,12 @@
v-else v-else
:kpi-data="kpiData" :kpi-data="kpiData"
:funnel-data="funnelData" :funnel-data="funnelData"
:contact-time-data="contactTimeData" :contact-time-data="goldContactTime"
:statistics-data="statisticsData" :statistics-data="statisticsData"
:urgent-problem-data="urgentProblemData" :urgent-problem-data="urgentProblemData"
/> />
</div> </div>
</section> </section>
</div> </div>
</template> </template>
@@ -123,7 +126,7 @@ import UserDropdown from "@/components/UserDropdown.vue";
import Loading from "@/components/Loading.vue"; import Loading from "@/components/Loading.vue";
import {getCustomerAttendance,getTodayCall,getProblemDistribution,getTableFillingRate,getAverageResponseTime, import {getCustomerAttendance,getTodayCall,getProblemDistribution,getTableFillingRate,getAverageResponseTime,
getWeeklyActiveCommunicationRate,getTimeoutResponseRate,getCustomerCallInfo,getCustomerChatInfo,getCustomerFormInfo, getWeeklyActiveCommunicationRate,getTimeoutResponseRate,getCustomerCallInfo,getCustomerChatInfo,getCustomerFormInfo,
getConversionRateAndAllocatedData,getCustomerAttendanceAfterClass4,getPayMoneyCustomers} from "@/api/api.js" getConversionRateAndAllocatedData,getCustomerAttendanceAfterClass4,getPayMoneyCustomers,getSalesFunnel,getGoldContactTime} from "@/api/api.js"
// 路由实例 // 路由实例
const router = useRouter(); const router = useRouter();
@@ -429,10 +432,7 @@ async function getTimeline() {
weChat_avatar: customer.weChat_avatar, weChat_avatar: customer.weChat_avatar,
pay_status: customer.pay_status pay_status: customer.pay_status
}) })
// 后三个阶段的客户数据已存储在courseCustomers['课1-4']中不需要合并到customersList // 后三个阶段的客户数据已存储在courseCustomers['课1-4']中不需要合并到customersList
} }
} }
// 成交阶段 // 成交阶段
@@ -513,7 +513,15 @@ async function getCustomerCall() {
const res = await getCustomerCallInfo(params) const res = await getCustomerCallInfo(params)
if(res.code === 200) { if(res.code === 200) {
callRecords.value = res.data callRecords.value = res.data
/**
* "data": {
"user_name": "常琳",
"customer_name": "191桐桐爸爸高一男",
"record_file_addr_list": [
"http://192.168.3.112:5000/api/record/download/杨振彦-20分钟通话-25-08-19_07-23-37-744009-835.mp3"
]
}
*/
} }
} catch (error) { } catch (error) {
// 静默处理错误 // 静默处理错误
@@ -531,10 +539,26 @@ const selectedContact = computed(() => {
return MOCK_DATA.contacts.find((c) => c.id === selectedContactId.value) || null; return MOCK_DATA.contacts.find((c) => c.id === selectedContactId.value) || null;
}); });
const funnelData = computed(() => ({ const funnelData = computed(() => {
labels: ["线索", "沟通", "意向", "预约", "成交"], if (!SalesFunnel.value || !SalesFunnel.value.sale_funnel) {
data: MOCK_DATA.personalFunnel, return {
})); labels: ["线索总数", "有效沟通", "到课数据", "预付定金", "成功签单"],
data: [0, 0, 0, 0, 0]
};
}
const funnel = SalesFunnel.value.sale_funnel;
return {
labels: ["线索总数", "有效沟通", "到课数据", "预付定金", "成功签单"],
data: [
funnel.线索总数 || 0,
funnel.有效沟通 || 0,
funnel.到课数据 || 0,
funnel.预付定金 || 0,
funnel.成功签单 || 0
]
};
});
const contactTimeData = computed(() => ({ const contactTimeData = computed(() => ({
labels: MOCK_DATA.contactTimeAnalysis.labels, labels: MOCK_DATA.contactTimeAnalysis.labels,
@@ -682,7 +706,6 @@ const handleStageSelect = (stage, extraData = null) => {
currentFilteredCustomers.value = []; currentFilteredCustomers.value = [];
} }
}; };
const handleViewFormData = async (contact) => { const handleViewFormData = async (contact) => {
// 获取客户表单数据 // 获取客户表单数据
await getCustomerForm(); await getCustomerForm();
@@ -700,18 +723,54 @@ const handleViewChatData = async (contact) => {
const handleViewCallData = (contact) => { const handleViewCallData = (contact) => {
// TODO: 实现通话录音查看逻辑 // TODO: 实现通话录音查看逻辑
}; };
// 销售漏斗
const SalesFunnel = ref([])
async function CenterGetSalesFunnel() {
const params = getRequestParams()
const hasParams = params.user_name
const res = await getSalesFunnel(hasParams?params:undefined)
if(res.code === 200){
SalesFunnel.value = res.data
/**
* "data": {
"user_name": "常琳",
"user_level": 1,
"sale_funnel": {
"线索总数": 11,
"有效沟通": 9,
"到课数据": 8,
"预付定金": 0,
"成功签单": 0
}
}
*/
}
}
// 黄金联络时间段
const goldContactTime = ref([])
async function CenterGetGoldContactTime() {
const params = getRequestParams()
const hasParams = params.user_name
const res = await getGoldContactTime(hasParams?params:undefined)
if(res.code === 200){
goldContactTime.value = res.data
}
}
// LIFECYCLE HOOKS // LIFECYCLE HOOKS
onMounted(async () => { onMounted(async () => {
try { try {
isPageLoading.value = true isPageLoading.value = true
await getCoreKpi() await getCoreKpi()
await CenterGetGoldContactTime()
await CenterGetSalesFunnel()
await getCustomerForm() await getCustomerForm()
await getCustomerChat() await getCustomerChat()
await getUrgentProblem() await getUrgentProblem()
await getCustomerCall() await getCustomerCall()
await getTimeline() await getTimeline()
await getCustomerPayMoney() await getCustomerPayMoney()
// 等待数据加载完成后选择默认客户 // 等待数据加载完成后选择默认客户
await nextTick(); await nextTick();
@@ -1323,10 +1382,8 @@ $primary: #3b82f6;
// 路由导航顶栏样式 // 路由导航顶栏样式
.route-header { .route-header {
display: flex;
justify-content: space-between;
align-items: center;
width: 100%; width: 100%;
padding: 0 2rem;
.breadcrumb { .breadcrumb {
display: flex; display: flex;

View File

@@ -112,11 +112,11 @@
<div class="metric-row"> <div class="metric-row">
<div class="metric-item"> <div class="metric-item">
<span class="metric-label">今日业绩</span> <span class="metric-label">今日业绩</span>
<span class="metric-value">{{ formatCurrency(member.todayPerformance) }}</span> <span class="metric-value">{{ member.todayPerformance}}</span>
</div> </div>
<div class="metric-item"> <div class="metric-item">
<span class="metric-label">月度业绩</span> <span class="metric-label">月度业绩</span>
<span class="metric-value">{{ formatCurrency(member.monthlyPerformance) }}</span> <span class="metric-value">{{ member.monthlyPerformance }}</span>
</div> </div>
</div> </div>
@@ -147,6 +147,9 @@
</div> </div>
</main> </main>
<!-- Loading 组件 -->
<Loading :visible="isLoading" text="数据加载中..." />
</div> </div>
</template> </template>
@@ -163,16 +166,18 @@
import ProblemRanking from './components/ProblemRanking.vue' import ProblemRanking from './components/ProblemRanking.vue'
import seniorManager from './components/seniorManager.vue' import seniorManager from './components/seniorManager.vue'
import UserDropdown from '@/components/UserDropdown.vue' import UserDropdown from '@/components/UserDropdown.vue'
import Loading from '@/components/Loading.vue'
import { import {
getOverallCenterPerformance, getTotalGroupCount, getCenterConversionRate, getTotalCallCount, getNewCustomer getOverallCenterPerformance, getTotalGroupCount, getCenterConversionRate, getTotalCallCount, getNewCustomer
, getDepositConversionRate, getCustomerTypeDistribution, getUrgentNeedToAddress, getCenterAdvancedManagerList, getTeamRanking, , getDepositConversionRate, getCustomerTypeDistribution, getUrgentNeedToAddress, getCenterAdvancedManagerList, getTeamRanking,
getTeamRankingInfo, getConversionRateVsAverage getTeamRankingInfo, getConversionRateVsAverage
} from '@/api/secondTop.js' } from '@/api/secondTop.js'
import { useRouter } from 'vue-router' import { useRouter } from 'vue-router'
import { useUserStore } from '@/stores/user.js' import { useUserStore } from '@/stores/user.js'
// 组别数据 // 组别数据
const groups = ref([]) const groups = ref([])
// loading 状态
const isLoading = ref(false)
// 路由实例 // 路由实例
const router = useRouter(); const router = useRouter();
@@ -455,8 +460,8 @@ const conversionRateVsAverage = ref({})
phone: '***-****-****', // 隐藏手机号 phone: '***-****-****', // 隐藏手机号
status: member.rank === 1 ? 'excellent' : member.rank === 2 ? 'good' : 'average', status: member.rank === 1 ? 'excellent' : member.rank === 2 ? 'good' : 'average',
joinDate: '2023-01-01', // 默认入职日期 joinDate: '2023-01-01', // 默认入职日期
todayPerformance: member.today_performance || 0, todayPerformance: member.today_deals,
monthlyPerformance: member.monthly_performance || 0, monthlyPerformance: member.monthly_deals,
conversionRate: parseFloat(member.conversion_rate_this_period) || 0, conversionRate: parseFloat(member.conversion_rate_this_period) || 0,
callCount: member.call_count_this_period || 0, callCount: member.call_count_this_period || 0,
newClients: member.new_customers_this_period || 0, newClients: member.new_customers_this_period || 0,
@@ -506,17 +511,24 @@ const conversionRateVsAverage = ref({})
return statusMap[status] || '未知' return statusMap[status] || '未知'
} }
onMounted(async () => { onMounted(async () => {
await CenterOverallCenterPerformance() try {
await CenterTotalGroupCount() isLoading.value = true
await CenterConversionRate() await CenterOverallCenterPerformance()
await CenterTotalCallCount() await CenterTotalGroupCount()
await CenterNewCustomer() await CenterConversionRate()
await CenterDepositConversionRate() await CenterTotalCallCount()
await CenterCustomerType() await CenterNewCustomer()
await CenterUrgentNeedToAddress() await CenterDepositConversionRate()
await CenterConversionRateVsAverage() await CenterCustomerType()
await CenterSeniorManagerList() await CenterUrgentNeedToAddress()
await CenterGroupList('all') // 初始化加载全部高级经理数据 await CenterConversionRateVsAverage()
await CenterSeniorManagerList()
await CenterGroupList('all') // 初始化加载全部高级经理数据
} catch (error) {
console.error('数据加载失败:', error)
} finally {
isLoading.value = false
}
}) })
</script> </script>

View File

@@ -75,7 +75,7 @@
</div> </div>
<div class="summary-item"> <div class="summary-item">
<span class="label">今日业绩:</span> <span class="label">今日业绩:</span>
<span class="value">{{ formatCurrency(selectedGroup.todayPerformance) }}</span> <span class="value">{{ selectedGroup.todayPerformance }}</span>
</div> </div>
<div class="summary-item"> <div class="summary-item">
<span class="label">转化率:</span> <span class="label">转化率:</span>
@@ -100,12 +100,12 @@
<div class="metric-row"> <div class="metric-row">
<div class="metric-item"> <div class="metric-item">
<span class="metric-label">今日业绩</span> <span class="metric-label">今日业绩</span>
<span class="metric-value">{{ member.today_performance }}</span> <span class="metric-value">{{ member.today_deals }}</span>
</div> </div>
<div class="metric-item"> <div class="metric-item">
<span class="metric-label">月度业绩</span> <span class="metric-label">月度业绩</span>
<span class="metric-value">{{ member.monthly_performance }}</span> <span class="metric-value">{{ member.monthly_deals}}</span>
</div> </div>
</div> </div>
@@ -201,7 +201,6 @@ const getRequestParams = () => {
if (routeUserName) { if (routeUserName) {
params.user_name = routeUserName params.user_name = routeUserName
} }
return params return params
} }