feat(secondTop): 优化团队成员展示和排序逻辑

refactor(sale): 调整数据分析区域布局和样式
fix(router): 移除路由元信息注释
style(StatisticData): 调整统计卡片网格布局
feat(api): 新增顶级管理接口文件
This commit is contained in:
2025-08-15 21:36:15 +08:00
parent a96a25355f
commit 74aa6c3235
5 changed files with 157 additions and 136 deletions

68
my-vue-app/src/api/top.js Normal file
View File

@@ -0,0 +1,68 @@
import https from '../utils/https'
// 获取全公司当月单数、当月完成度及其与上月对比情况 /api/v1/level_five/overview/overall_company_performance
export const getOverallCompanyPerformance = () => {
return https.get('/api/v1/level_five/overview/overall_company_performance')
}
// 获取全公司当月定金转化率、上月定金转化率以及对比情况 /api/v1/level_five/overview/company_deposit_conversion_rate
export const getCompanyDepositConversionRate = () => {
return https.get('/api/v1/level_five/overview/company_deposit_conversion_rate')
}
// 获取全公司当月总通话次数、当月有效通话次数以及对比情况 /api/v1/level_five/overview/company_total_call_count
export const getCompanyTotalCallCount = () => {
return https.get('/api/v1/level_five/overview/company_total_call_count')
}
// 获取全公司当月新增客户、当月意向客户以及新增客户的对比情况 /api/v1/level_five/overview/company_new_customer
export const getCompanyNewCustomer = () => {
return https.get('/api/v1/level_five/overview/company_new_customer')
}
// 获取全公司当月转化率以及与上月的对比情况 /api/v1/level_five/overview/company_conversion_rate
export const getCompanyConversionRate = () => {
return https.get('/api/v1/level_five/overview/company_conversion_rate')
}
// 获取各中心实时进度 /api/v1/level_five/overview/company_real_time_progress
export const getCompanyRealTimeProgress = () => {
return https.get('/api/v1/level_five/overview/company_real_time_progress')
}
// 获取全公司转化对比 /api/v1/level_five/overview/company_conversion_rate_vs_last
export const getCompanyConversionRateVsLast = () => {
return https.post('/api/v1/level_five/overview/company_conversion_rate_vs_last')
}
// 获取全公司销售月度业绩红黑榜 /api/v1/level_five/overview/sales_monthly_performance
export const getSalesMonthlyPerformance = (params) => {
return https.post('/api/v1/level_five/overview/sales_monthly_performance', params)
}
// 获取全中心业绩排行榜 /api/v1/level_five/overview/center_performance_rank
export const getCenterPerformanceRank = (params) => {
return https.post('/api/v1/level_five/overview/center_performance_rank', params)
}
// 获取全公司客户类型分布 /api/v1/level_five/overview/customer_type_distribution
export const getCustomerTypeDistribution = (params) => {
return https.post('/api/v1/level_five/overview/customer_type_distribution', params)
}
// 获取全公司的客户迫切解决的问题 /api/v1/level_five/overview/urgent_need_to_address
export const getUrgentNeedToAddress = () => {
return https.get('/api/v1/level_five/overview/urgent_need_to_address')
}
// 获取级别树 /api/v1/level_five/overview/level_tree
export const getLevelTree = () => {
return https.get('/api/v1/level_five/overview/level_tree')
}
// 获取详细数据表格 /api/v1/level_five/overview/detailed_data_table
export const getDetailedDataTable = (params) => {
return https.post('/api/v1/level_five/overview/detailed_data_table', params)
}

View File

@@ -17,37 +17,37 @@ const routes = [
path: '/login',
name: 'Login',
component: Login,
meta: { requiresAuth: false }
// meta: { requiresAuth: false }
},
{
path: '/sale',
name: 'Sale',
component: Sale,
meta: { requiresAuth: true, minLevel: 1 }
// meta: { requiresAuth: true, minLevel: 1 }
},
{
path: '/manager',
name: 'Manager',
component: Manager,
meta: { requiresAuth: true, minLevel: 2 }
// meta: { requiresAuth: true, minLevel: 2 }
},
{
path: '/senior-manager',
name: 'SeniorManager',
component: SeniorManager,
meta: { requiresAuth: true, minLevel: 3 }
// meta: { requiresAuth: true, minLevel: 3 }
},
{
path: '/second-top',
name: 'SecondTop',
component: SecondTop,
meta: { requiresAuth: true, minLevel: 4 }
// meta: { requiresAuth: true, minLevel: 4 }
},
{
path: '/top',
name: 'Top',
component: TopOne,
meta: { requiresAuth: true, minLevel: 5 }
// meta: { requiresAuth: true, minLevel: 5 }
}
]

View File

@@ -102,7 +102,7 @@ $white: #ffffff;
// 统计指标卡片特定样式
.stats-grid-inner {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
gap: 0.75rem;
}

View File

@@ -40,22 +40,6 @@
</template>
</div>
<div class="section-content">
<!-- 数据分析区域加载状态 -->
<div v-if="isKpiLoading || isStatisticsLoading || isUrgentProblemLoading" class="section-loading">
<div class="loading-spinner"></div>
<div class="loading-text">正在加载数据分析...</div>
</div>
<!-- 数据分析内容 -->
<PersonalDashboard
v-else
:kpi-data="kpiData"
:funnel-data="funnelData"
:contact-time-data="contactTimeData"
:statistics-data="statisticsData"
:urgent-problem-data="urgentProblemData"
/>
</div>
</section>
<!-- 销售时间线区域 -->
<section class="timeline-section">
@@ -119,7 +103,25 @@
</section>
</main>
</div>
<section class="analytics-section-full" style="width: 100%;">
<div class="section-content">
<!-- 数据分析区域加载状态 -->
<div v-if="isKpiLoading || isStatisticsLoading || isUrgentProblemLoading" class="section-loading">
<div class="loading-spinner"></div>
<div class="loading-text">正在加载数据分析...</div>
</div>
<!-- 数据分析内容 -->
<PersonalDashboard
v-else
:kpi-data="kpiData"
:funnel-data="funnelData"
:contact-time-data="contactTimeData"
:statistics-data="statisticsData"
:urgent-problem-data="urgentProblemData"
/>
</div>
</section>
</div>
</template>
@@ -132,7 +134,6 @@ import CustomerDetail from "./components/CustomerDetail.vue";
import PersonalDashboard from "./components/PersonalDashboard.vue";
import SalesTimelineWithTaskList from "./components/SalesTimelineWithTaskList.vue";
import RawDataCards from "./components/RawDataCards.vue";
// import FloatingTodo from "./components/FloatingTodo.vue";
import UserDropdown from "@/components/UserDropdown.vue";
import Loading from "@/components/Loading.vue";
import {getCustomerAttendance,getTodayCall,getProblemDistribution,getTableFillingRate,getAverageResponseTime,
@@ -501,50 +502,6 @@ async function getCustomerChat() {
}
try {
const res = await getCustomerChatInfo(params)
/**
* message:[
* 0:{content
:
"您好孩子妈妈,我是负责咱们本次课程的王慧老师[玫瑰]"
format_add_time
:
"2天前"
format_direction
:
"我"
type
:
"文本"},
1:{
content
:
"嗯,王老师好"
format_add_time
:
"2天前"
format_direction
:
"客户"
type
:
"文本"
},
3:{
content
:
"孩子妈妈,您好,这个是咱们的青少年成长评估表。您这个抽一分钟的时间填写一下孩子的基本信息。填写完了之后,您跟老师说一声。"
format_add_time
:
"2天前"
format_direction
:
"我"
type
:
"语音"
}
* ]
*/
if(res.code === 200) {
chatRecords.value = res.data
console.log('聊天数据获取成功:', res.data)
@@ -854,9 +811,6 @@ $primary: #3b82f6;
box-sizing: border-box;
}
}
// 主要布局
.main-layout {
width: 100vw;
@@ -1348,7 +1302,7 @@ $primary: #3b82f6;
min-height: 400px;
@include desktop {
min-height: 450px;
min-height: 150px;
}
@include tablet {

View File

@@ -67,11 +67,7 @@
</div>
<div class="summary-item">
<span class="label">成员数:</span>
<span class="value">{{ selectedGroup.memberCount }}</span>
</div>
<div class="summary-item">
<span class="label">今日业绩:</span>
<span class="value">{{ formatCurrency(selectedGroup.todayPerformance) }}</span>
<span class="value">{{ (groupPerformance && groupPerformance.group_details) ? groupPerformance.group_details.length : 0 }}</span>
</div>
<div class="summary-item">
<span class="label">转化率:</span>
@@ -87,11 +83,11 @@
<div class="member-info">
<h3 class="member-name">{{ member.name }}</h3>
<p class="member-position">{{ member.position }}</p>
<p class="member-phone">{{ member.phone }}</p>
</div>
<div class="member-status">
<span class="status-badge" :class="member.status">{{ getStatusText(member.status) }}</span>
<span class="join-date">入职: {{ member.joinDate }}</span>
<!-- 本人排名 -->
<div class="member-ranking">
<span class="ranking-label">排名:</span>
<span class="ranking-value">{{ member.rank }}</span>
</div>
</div>
@@ -207,7 +203,6 @@
return []
})
// 中心总业绩
async function CenterOverallCenterPerformance() {
const params = getRequestParams()
@@ -484,55 +479,60 @@ const conversionRateVsAverage = ref({})
// 选择组别函数
const selectGroup = async (group) => {
selectedGroup.value = group
console.log('选中的组别111:', group)
// 获取组名并调用API获取团队成员详情
let departmentName = group.name
// // 获取组名并调用API获取团队成员详情
// let departmentName = group.name
// 如果有groupList数据尝试找到完整的部门名称
if (groupList.value && groupList.value.formal_plural) {
const departmentKeys = Object.keys(groupList.value.formal_plural)
console.log('所有部门名称:', departmentKeys)
console.log('当前选中组别名称:', group.name)
// // 如果有groupList数据尝试找到完整的部门名称
// if (groupList.value && groupList.value.formal_plural) {
// const departmentKeys = Object.keys(groupList.value.formal_plural)
// console.log('所有部门名称:', departmentKeys)
// console.log('当前选中组别名称:', group.name)
// 优先进行精确匹配
let matchedDepartment = departmentKeys.find(key => key === group.name)
// // 优先进行精确匹配
// let matchedDepartment = departmentKeys.find(key => key === group.name)
// 如果精确匹配失败,尝试模糊匹配
if (!matchedDepartment) {
matchedDepartment = departmentKeys.find(key => {
// 提取部门主要名称进行匹配(去掉经理名字部分)
const mainName = key.split('-')[0] || key
const groupMainName = group.name.split('-')[0] || group.name
return mainName.includes(groupMainName) || groupMainName.includes(mainName)
})
}
// // 如果精确匹配失败,尝试模糊匹配
// if (!matchedDepartment) {
// matchedDepartment = departmentKeys.find(key => {
// // 提取部门主要名称进行匹配(去掉经理名字部分)
// const mainName = key.split('-')[0] || key
// const groupMainName = group.name.split('-')[0] || group.name
// return mainName.includes(groupMainName) || groupMainName.includes(mainName)
// })
// }
if (matchedDepartment) {
departmentName = matchedDepartment
}
}
// if (matchedDepartment) {
// departmentName = matchedDepartment
// }
// }
console.log('选中的组别:', group.name, '-> 发送的部门名称:', departmentName)
// console.log('选中的组别:', group.name, '-> 发送的部门名称:', departmentName)
const departmentName = group.name+'-'+group.leader
try {
await CenterGroupPerformance(departmentName)
// 将API返回的数据整理后更新到selectedGroup的members字段
if (groupPerformance.value && groupPerformance.value.group_details) {
const formattedMembers = groupPerformance.value.group_details.map((member, index) => ({
id: index + 1,
name: member.name,
position: '销售顾问', // 默认职位
phone: '***-****-****', // 隐藏手机号
status: member.rank === 1 ? 'excellent' : member.rank === 2 ? 'good' : 'average',
joinDate: '2023-01-01', // 默认入职日期
todayPerformance: member.today_performance || 0,
monthlyPerformance: member.monthly_performance || 0,
conversionRate: parseFloat(member.conversion_rate_this_period) || 0,
callCount: member.call_count_this_period || 0,
newClients: member.new_customers_this_period || 0,
deals: member.deals_this_period || 0
}))
const formattedMembers = groupPerformance.value.group_details
.map((member, index) => ({
id: index + 1,
name: member.name,
position: '销售顾问', // 默认职位
phone: '***-****-****', // 隐藏手机号
status: member.rank === 1 ? 'excellent' : member.rank === 2 ? 'good' : 'average',
joinDate: '2023-01-01', // 默认入职日期
todayPerformance: member.today_performance || 0,
monthlyPerformance: member.monthly_performance || 0,
conversionRate: parseFloat(member.conversion_rate_this_period) || 0,
callCount: member.call_count_this_period || 0,
newClients: member.new_customers_this_period || 0,
deals: member.deals_this_period || 0,
rank: member.rank || 0
}))
.sort((a, b) => b.deals - a.deals) // 根据成交单数从高到低排序
// 更新selectedGroup的members数据
selectedGroup.value = {
@@ -575,16 +575,15 @@ const conversionRateVsAverage = ref({})
return statusMap[status] || '未知'
}
onMounted(async () => {
await CenterOverallCenterPerformance()
await CenterTotalGroupCount()
await CenterConversionRate()
await CenterTotalCallCount()
await CenterNewCustomer()
await CenterDepositConversionRate()
await CenterCustomerType()
await CenterUrgentNeedToAddress()
await CenterConversionRateVsAverage()
// await CenterOverallCenterPerformance()
// await CenterTotalGroupCount()
// await CenterConversionRate()
// await CenterTotalCallCount()
// await CenterNewCustomer()
// await CenterDepositConversionRate()
// await CenterCustomerType()
// await CenterUrgentNeedToAddress()
// await CenterConversionRateVsAverage()
await CenterSeniorManagerList()
await CenterGroupList('all') // 初始化加载全部高级经理数据
})