feat(团队管理): 实现团队成员双击跳转功能并优化异常数据处理
1. 在PerformanceRanking组件中添加双击事件跳转到销售页面 2. 重构TeamAlerts组件异常数据处理逻辑,使用后端预处理数据 3. 在seniorManager页面添加异常预警API调用和数据处理 4. 优化路由参数处理逻辑,统一使用getRequestParams方法 5. 添加面包屑导航和返回功能,提升用户体验
This commit is contained in:
@@ -62,6 +62,12 @@ export const getTeamRankingInfo = (params) => {
|
|||||||
return https.post('/api/v1/level_three/overview/team_ranking_info', params)
|
return https.post('/api/v1/level_three/overview/team_ranking_info', params)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 异常预警 /api/v1/level_three/overview/abnormal_response_rate
|
||||||
|
export const getAbnormalResponseRate = (params) => {
|
||||||
|
return https.post('/api/v1/level_three/overview/abnormal_response_rate', params)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -17,6 +17,7 @@
|
|||||||
class="table-row"
|
class="table-row"
|
||||||
:class="{ active: selectedMember && (selectedMember.user_name === member.user_name || selectedMember.id === member.id) }"
|
:class="{ active: selectedMember && (selectedMember.user_name === member.user_name || selectedMember.id === member.id) }"
|
||||||
@click="selectMember(member)"
|
@click="selectMember(member)"
|
||||||
|
@dblclick="handleDoubleClick(member)"
|
||||||
>
|
>
|
||||||
<span class="rank">{{ index + 1 }}</span>
|
<span class="rank">{{ index + 1 }}</span>
|
||||||
<span class="name">{{ member.user_name || member.name }}</span>
|
<span class="name">{{ member.user_name || member.name }}</span>
|
||||||
@@ -32,6 +33,14 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { defineProps, defineEmits, computed } from 'vue'
|
import { defineProps, defineEmits, computed } from 'vue'
|
||||||
|
import { useUserStore } from '@/stores/user'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
|
// 用户store实例
|
||||||
|
const userStore = useUserStore()
|
||||||
|
|
||||||
|
// 路由实例
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
// 定义props
|
// 定义props
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
@@ -54,12 +63,26 @@ const emit = defineEmits(['select-member'])
|
|||||||
|
|
||||||
// 计算显示的成员数据
|
// 计算显示的成员数据
|
||||||
const displayMembers = computed(() => {
|
const displayMembers = computed(() => {
|
||||||
|
let members = []
|
||||||
|
|
||||||
// 如果有groupRanking数据,优先使用
|
// 如果有groupRanking数据,优先使用
|
||||||
if (props.groupRanking && props.groupRanking.team_data && props.groupRanking.team_data.group_list) {
|
if (props.groupRanking && props.groupRanking.team_data && props.groupRanking.team_data.group_list) {
|
||||||
return props.groupRanking.team_data.group_list
|
members = props.groupRanking.team_data.group_list
|
||||||
|
} else {
|
||||||
|
// 否则使用teamMembers数据
|
||||||
|
members = props.teamMembers
|
||||||
}
|
}
|
||||||
// 否则使用teamMembers数据
|
|
||||||
return props.teamMembers
|
// 过滤掉当前登录用户本人的数据
|
||||||
|
if (userStore.userInfo && userStore.userInfo.username) {
|
||||||
|
const currentUserName = userStore.userInfo.username
|
||||||
|
return members.filter(member => {
|
||||||
|
const memberName = member.user_name || member.name
|
||||||
|
return memberName !== currentUserName
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return members
|
||||||
})
|
})
|
||||||
|
|
||||||
// 格式化金额
|
// 格式化金额
|
||||||
@@ -74,6 +97,18 @@ const formatAmount = (amount) => {
|
|||||||
const selectMember = (member) => {
|
const selectMember = (member) => {
|
||||||
emit('select-member', member)
|
emit('select-member', member)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 双击成员跳转到Sale页面
|
||||||
|
const handleDoubleClick = (member) => {
|
||||||
|
const memberName = member.user_name || member.name
|
||||||
|
router.push({
|
||||||
|
path: '/sale',
|
||||||
|
query: {
|
||||||
|
user_name: memberName,
|
||||||
|
user_level: '1'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -30,51 +30,15 @@ const props = defineProps({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
// 聚合异常数据,按人员分组
|
// 处理团队异常数据
|
||||||
const aggregatedAlerts = computed(() => {
|
const aggregatedAlerts = computed(() => {
|
||||||
const alerts = []
|
|
||||||
const data = props.abnormalData
|
const data = props.abnormalData
|
||||||
|
|
||||||
if (!data || Object.keys(data).length === 0) {
|
if (!data || !data.processedAlerts || !Array.isArray(data.processedAlerts)) {
|
||||||
return alerts
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收集所有异常人员
|
return data.processedAlerts
|
||||||
const personAbnormalities = new Map()
|
|
||||||
|
|
||||||
// 处理严重超时率异常
|
|
||||||
if (data.erious_timeout_rate_abnorma && Array.isArray(data.erious_timeout_rate_abnorma)) {
|
|
||||||
data.erious_timeout_rate_abnorma.forEach(person => {
|
|
||||||
if (!personAbnormalities.has(person)) {
|
|
||||||
personAbnormalities.set(person, [])
|
|
||||||
}
|
|
||||||
personAbnormalities.get(person).push('严重超时率异常')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 处理表格填写异常
|
|
||||||
if (data.table_filling_abnormal && Array.isArray(data.table_filling_abnormal)) {
|
|
||||||
data.table_filling_abnormal.forEach(person => {
|
|
||||||
if (!personAbnormalities.has(person)) {
|
|
||||||
personAbnormalities.set(person, [])
|
|
||||||
}
|
|
||||||
personAbnormalities.get(person).push('表格填写异常')
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// 生成聚合后的预警信息
|
|
||||||
let alertId = 1
|
|
||||||
personAbnormalities.forEach((abnormalities, person) => {
|
|
||||||
const abnormalityText = abnormalities.join('、')
|
|
||||||
alerts.push({
|
|
||||||
id: alertId++,
|
|
||||||
type: abnormalities.length > 1 ? 'danger' : 'warning',
|
|
||||||
icon: abnormalities.length > 1 ? '🔺' : '⚠',
|
|
||||||
message: `${person}存在${abnormalityText}`
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
return alerts
|
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,27 @@
|
|||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
"
|
"
|
||||||
>
|
>
|
||||||
<!-- 经理个人看板 -->
|
<!-- 动态显示:根据是否有路由参数显示不同内容 -->
|
||||||
<!-- <sale style="width: 100%;">
|
<!-- 自己访问时显示经理个人看板 -->
|
||||||
</sale> -->
|
<template v-if="!isRouteNavigation">
|
||||||
|
<sale style="width: 100%;">
|
||||||
|
</sale>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- 路由跳转时显示面包屑导航 -->
|
||||||
|
<div v-if="isRouteNavigation" class="route-header-section">
|
||||||
|
<div class="breadcrumb-container">
|
||||||
|
<div class="breadcrumb">
|
||||||
|
<span class="breadcrumb-item" @click="goBack">高级经理看板</span>
|
||||||
|
<span class="breadcrumb-separator">></span>
|
||||||
|
<span class="breadcrumb-item current">团队管理</span>
|
||||||
|
</div>
|
||||||
|
<div class="user-name">
|
||||||
|
{{ routeUserName }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 经理团队看板 -->
|
<!-- 经理团队看板 -->
|
||||||
<h1>经理团队看板</h1>
|
<h1>经理团队看板</h1>
|
||||||
<main class="dashboard-main">
|
<main class="dashboard-main">
|
||||||
@@ -50,7 +68,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted, computed } from "vue";
|
||||||
import TeamAlerts from "./components/TeamAlerts.vue";
|
import TeamAlerts from "./components/TeamAlerts.vue";
|
||||||
import TeamReport from "./components/TeamReport.vue";
|
import TeamReport from "./components/TeamReport.vue";
|
||||||
import SalesFunnel from "./components/SalesFunnel.vue";
|
import SalesFunnel from "./components/SalesFunnel.vue";
|
||||||
@@ -191,6 +209,9 @@ const teamMembers = [
|
|||||||
// 路由实例
|
// 路由实例
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
|
|
||||||
|
// 用户store实例
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
// 获取通用请求参数的函数
|
// 获取通用请求参数的函数
|
||||||
const getRequestParams = () => {
|
const getRequestParams = () => {
|
||||||
const params = {}
|
const params = {}
|
||||||
@@ -207,6 +228,22 @@ const getRequestParams = () => {
|
|||||||
|
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 判断是否为路由导航(有路由参数)
|
||||||
|
const isRouteNavigation = computed(() => {
|
||||||
|
const routeUserName = router.currentRoute.value.query.user_name || router.currentRoute.value.params.user_name
|
||||||
|
return !!routeUserName
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取路由传递的用户名
|
||||||
|
const routeUserName = computed(() => {
|
||||||
|
return router.currentRoute.value.query.user_name || router.currentRoute.value.params.user_name || ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
const goBack = () => {
|
||||||
|
router.go(-1)
|
||||||
|
}
|
||||||
// 团队战报总览
|
// 团队战报总览
|
||||||
const weekTotalData = ref({
|
const weekTotalData = ref({
|
||||||
week_total_call: {},
|
week_total_call: {},
|
||||||
@@ -398,6 +435,70 @@ onMounted(async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 路由导航顶栏样式
|
||||||
|
.route-header-section {
|
||||||
|
width: 100%;
|
||||||
|
background: rgba(255, 255, 255, 0.95);
|
||||||
|
backdrop-filter: blur(20px);
|
||||||
|
border-radius: 1rem;
|
||||||
|
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
|
||||||
|
.breadcrumb-container {
|
||||||
|
padding: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
|
rgba(102, 126, 234, 0.05),
|
||||||
|
rgba(118, 75, 162, 0.05)
|
||||||
|
);
|
||||||
|
|
||||||
|
.breadcrumb {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
.breadcrumb-item {
|
||||||
|
color: #64748b;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&:not(.current) {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: #3b82f6;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.current {
|
||||||
|
color: #1e293b;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-separator {
|
||||||
|
color: #94a3b8;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
color: #1e293b;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.dashboard-header {
|
.dashboard-header {
|
||||||
background: white;
|
background: white;
|
||||||
padding: 1.5rem 2rem;
|
padding: 1.5rem 2rem;
|
||||||
|
|||||||
@@ -5,23 +5,39 @@
|
|||||||
<!-- 顶部导航栏 -->
|
<!-- 顶部导航栏 -->
|
||||||
<!-- 数据分析区域 - 独立占据整行 -->
|
<!-- 数据分析区域 - 独立占据整行 -->
|
||||||
<section class="analytics-section-full">
|
<section class="analytics-section-full">
|
||||||
|
<!-- 动态顶栏:根据是否有路由参数显示不同内容 -->
|
||||||
<div class="section-header">
|
<div class="section-header">
|
||||||
<h1 class="app-title">销售驾驶舱</h1>
|
<!-- 路由跳转时的顶栏:面包屑 + 姓名 -->
|
||||||
<div
|
<div v-if="isRouteNavigation" class="route-header">
|
||||||
class="quick-stats"
|
<div class="breadcrumb">
|
||||||
style="display: flex; align-items: center; gap: 30px"
|
<span class="breadcrumb-item" @click="goBack">团队管理</span>
|
||||||
>
|
<span class="breadcrumb-separator">></span>
|
||||||
<div
|
<span class="breadcrumb-item current"> {{ routeUserName }}数据驾驶舱</span>
|
||||||
v-for="(stat, key) in MOCK_DATA.performance"
|
</div>
|
||||||
:key="key"
|
<div class="user-name">
|
||||||
class="quick-stat-item"
|
{{ routeUserName }}
|
||||||
>
|
|
||||||
<div class="stat-label">
|
|
||||||
{{ stat.label + ":" + stat.value.toLocaleString() }}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<UserDropdown />
|
|
||||||
|
<!-- 自己登录时的顶栏:原有样式 -->
|
||||||
|
<template v-else>
|
||||||
|
<h1 class="app-title">销售驾驶舱</h1>
|
||||||
|
<div
|
||||||
|
class="quick-stats"
|
||||||
|
style="display: flex; align-items: center; gap: 30px"
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
v-for="(stat, key) in MOCK_DATA.performance"
|
||||||
|
:key="key"
|
||||||
|
class="quick-stat-item"
|
||||||
|
>
|
||||||
|
<div class="stat-label">
|
||||||
|
{{ stat.label + ":" + stat.value.toLocaleString() }}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<UserDropdown />
|
||||||
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="section-content">
|
<div class="section-content">
|
||||||
@@ -143,6 +159,22 @@ const getRequestParams = () => {
|
|||||||
|
|
||||||
return params
|
return params
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 判断是否为路由导航(有路由参数)
|
||||||
|
const isRouteNavigation = computed(() => {
|
||||||
|
const routeUserName = router.currentRoute.value.query.user_name || router.currentRoute.value.params.user_name
|
||||||
|
return !!routeUserName
|
||||||
|
})
|
||||||
|
|
||||||
|
// 获取路由传递的用户名
|
||||||
|
const routeUserName = computed(() => {
|
||||||
|
return router.currentRoute.value.query.user_name || router.currentRoute.value.params.user_name || ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 返回上一页
|
||||||
|
const goBack = () => {
|
||||||
|
router.go(-1)
|
||||||
|
}
|
||||||
// STATE
|
// STATE
|
||||||
const selectedContactId = ref(null);
|
const selectedContactId = ref(null);
|
||||||
const contextPanelRef = ref(null);
|
const contextPanelRef = ref(null);
|
||||||
@@ -1293,6 +1325,55 @@ $primary: #3b82f6;
|
|||||||
rgba(102, 126, 234, 0.05),
|
rgba(102, 126, 234, 0.05),
|
||||||
rgba(118, 75, 162, 0.05)
|
rgba(118, 75, 162, 0.05)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// 路由导航顶栏样式
|
||||||
|
.route-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.breadcrumb {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
.breadcrumb-item {
|
||||||
|
color: $slate-600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
&:not(.current) {
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.current {
|
||||||
|
color: $slate-800;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.breadcrumb-separator {
|
||||||
|
color: $slate-400;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-name {
|
||||||
|
color: $slate-800;
|
||||||
|
font-size: 1.125rem;
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background: rgba(255, 255, 255, 0.8);
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
border: 1px solid rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// PC端保持一致布局
|
// PC端保持一致布局
|
||||||
@media (min-width: 1024px) {
|
@media (min-width: 1024px) {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
:overallTeamPerformance="overallTeamPerformance"
|
:overallTeamPerformance="overallTeamPerformance"
|
||||||
/>
|
/>
|
||||||
<div class="action-items-compact">
|
<div class="action-items-compact">
|
||||||
<TeamAlerts style="height: 300px;" />
|
<TeamAlerts style="height: 300px;" :abnormalData="teamAlerts" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<StatisticalIndicators
|
<StatisticalIndicators
|
||||||
@@ -89,6 +89,7 @@
|
|||||||
v-for="member in teamPerformanceDetail.group_details"
|
v-for="member in teamPerformanceDetail.group_details"
|
||||||
:key="member.id"
|
:key="member.id"
|
||||||
class="member-card"
|
class="member-card"
|
||||||
|
@dblclick="handleMemberDoubleClick(member)"
|
||||||
>
|
>
|
||||||
<div class="member-header">
|
<div class="member-header">
|
||||||
<div class="member-info">
|
<div class="member-info">
|
||||||
@@ -147,6 +148,7 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue'
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRouter } from 'vue-router'
|
||||||
|
|
||||||
import CenterOverview from './components/CenterOverview.vue'
|
import CenterOverview from './components/CenterOverview.vue'
|
||||||
import GroupComparison from './components/GroupComparison.vue'
|
import GroupComparison from './components/GroupComparison.vue'
|
||||||
@@ -158,7 +160,7 @@ import UserDropdown from '@/components/UserDropdown.vue'
|
|||||||
import Loading from '@/components/Loading.vue'
|
import Loading from '@/components/Loading.vue'
|
||||||
import { getOverallTeamPerformance,getTotalGroupCount,getConversionRate,getTotalCallCount,
|
import { getOverallTeamPerformance,getTotalGroupCount,getConversionRate,getTotalCallCount,
|
||||||
getNewCustomer,getDepositConversionRate,getActiveCustomerCommunicationRate,getAverageAnswerTime,
|
getNewCustomer,getDepositConversionRate,getActiveCustomerCommunicationRate,getAverageAnswerTime,
|
||||||
getTimeoutRate,getTableFillingRate,getUrgentNeedToAddress,getTeamRanking,getTeamRankingInfo } from '@/api/senorManger.js'
|
getTimeoutRate,getTableFillingRate,getUrgentNeedToAddress,getTeamRanking,getTeamRankingInfo,getAbnormalResponseRate } from '@/api/senorManger.js'
|
||||||
|
|
||||||
import { useUserStore } from '@/stores/user.js'
|
import { useUserStore } from '@/stores/user.js'
|
||||||
|
|
||||||
@@ -169,6 +171,8 @@ const severeTimeoutRate = ref(2)
|
|||||||
const formCompletionRate = ref(90)
|
const formCompletionRate = ref(90)
|
||||||
|
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
// 路由实例
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
// Loading状态
|
// Loading状态
|
||||||
const isLoading = ref(false)
|
const isLoading = ref(false)
|
||||||
@@ -184,16 +188,30 @@ const overallTeamPerformance = ref({
|
|||||||
newCustomers: {},
|
newCustomers: {},
|
||||||
depositConversions: {},
|
depositConversions: {},
|
||||||
})
|
})
|
||||||
|
// 获取通用请求参数的函数
|
||||||
|
const getRequestParams = () => {
|
||||||
|
const params = {}
|
||||||
|
// 只从路由参数获取
|
||||||
|
const routeUserLevel = router.currentRoute.value.query.user_level || router.currentRoute.value.params.user_level
|
||||||
|
const routeUserName = router.currentRoute.value.query.user_name || router.currentRoute.value.params.user_name
|
||||||
|
// 如果路由有参数,使用路由参数
|
||||||
|
if (routeUserLevel) {
|
||||||
|
params.user_level = routeUserLevel.toString()
|
||||||
|
}
|
||||||
|
if (routeUserName) {
|
||||||
|
params.user_name = routeUserName
|
||||||
|
}
|
||||||
|
|
||||||
|
return params
|
||||||
|
}
|
||||||
|
|
||||||
// 获取整体概览数据--团队总业绩
|
// 获取整体概览数据--团队总业绩
|
||||||
async function fetchOverallTeamPerformance() {
|
async function fetchOverallTeamPerformance() {
|
||||||
const params={
|
const params = getRequestParams()
|
||||||
user_name: userStore.userInfo.username,
|
const hasParams = params.user_name
|
||||||
user_level: userStore.userInfo.user_level.toString()
|
|
||||||
}
|
|
||||||
// 团队总业绩
|
// 团队总业绩
|
||||||
try {
|
try {
|
||||||
const response = await getOverallTeamPerformance(params)
|
const response = await getOverallTeamPerformance(hasParams ? params : undefined)
|
||||||
overallTeamPerformance.value.totalPerformance = response.data
|
overallTeamPerformance.value.totalPerformance = response.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取整体概览数据失败:', error)
|
console.error('获取整体概览数据失败:', error)
|
||||||
@@ -201,12 +219,10 @@ async function fetchOverallTeamPerformance() {
|
|||||||
}
|
}
|
||||||
// 获取整体概览数据--活跃组数
|
// 获取整体概览数据--活跃组数
|
||||||
async function fetchActiveGroups() {
|
async function fetchActiveGroups() {
|
||||||
const params={
|
const params = getRequestParams()
|
||||||
user_name: userStore.userInfo.username,
|
const hasParams = params.user_name
|
||||||
user_level: userStore.userInfo.user_level.toString()
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await getTotalGroupCount(params)
|
const response = await getTotalGroupCount(hasParams ? params : undefined)
|
||||||
overallTeamPerformance.value.activeGroups = response.data
|
overallTeamPerformance.value.activeGroups = response.data
|
||||||
console.log('活跃组数:', response.data)
|
console.log('活跃组数:', response.data)
|
||||||
|
|
||||||
@@ -216,12 +232,10 @@ async function fetchActiveGroups() {
|
|||||||
}
|
}
|
||||||
// 获取整体概览数据--团队转化率
|
// 获取整体概览数据--团队转化率
|
||||||
async function fetchConversionRate() {
|
async function fetchConversionRate() {
|
||||||
const params={
|
const params = getRequestParams()
|
||||||
user_name: userStore.userInfo.username,
|
const hasParams = params.user_name
|
||||||
user_level: userStore.userInfo.user_level.toString()
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await getConversionRate(params)
|
const response = await getConversionRate(hasParams ? params : undefined)
|
||||||
overallTeamPerformance.value.conversionRate = response.data
|
overallTeamPerformance.value.conversionRate = response.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取团队转化率失败:', error)
|
console.error('获取团队转化率失败:', error)
|
||||||
@@ -229,12 +243,10 @@ async function fetchConversionRate() {
|
|||||||
}
|
}
|
||||||
// 通话次数
|
// 通话次数
|
||||||
async function fetchTotalCallCount() {
|
async function fetchTotalCallCount() {
|
||||||
const params={
|
const params = getRequestParams()
|
||||||
user_name: userStore.userInfo.username,
|
const hasParams = params.user_name
|
||||||
user_level: userStore.userInfo.user_level.toString()
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await getTotalCallCount(params)
|
const response = await getTotalCallCount(hasParams ? params : undefined)
|
||||||
overallTeamPerformance.value.totalCalls = response.data
|
overallTeamPerformance.value.totalCalls = response.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取通话次数失败:', error)
|
console.error('获取通话次数失败:', error)
|
||||||
@@ -242,12 +254,10 @@ async function fetchTotalCallCount() {
|
|||||||
}
|
}
|
||||||
// 新增客户
|
// 新增客户
|
||||||
async function fetchNewCustomers() {
|
async function fetchNewCustomers() {
|
||||||
const params={
|
const params = getRequestParams()
|
||||||
user_name: userStore.userInfo.username,
|
const hasParams = params.user_name
|
||||||
user_level: userStore.userInfo.user_level.toString()
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await getNewCustomer(params)
|
const response = await getNewCustomer(hasParams ? params : undefined)
|
||||||
overallTeamPerformance.value.newCustomers = response.data
|
overallTeamPerformance.value.newCustomers = response.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取新增客户失败:', error)
|
console.error('获取新增客户失败:', error)
|
||||||
@@ -255,12 +265,10 @@ async function fetchNewCustomers() {
|
|||||||
}
|
}
|
||||||
// 定金转化
|
// 定金转化
|
||||||
async function fetchDepositConversions() {
|
async function fetchDepositConversions() {
|
||||||
const params={
|
const params = getRequestParams()
|
||||||
user_name: userStore.userInfo.username,
|
const hasParams = params.user_name
|
||||||
user_level: userStore.userInfo.user_level.toString()
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await getDepositConversionRate(params)
|
const response = await getDepositConversionRate(hasParams ? params : undefined)
|
||||||
overallTeamPerformance.value.depositConversions = response.data
|
overallTeamPerformance.value.depositConversions = response.data
|
||||||
console.log(99888999,response.data)
|
console.log(99888999,response.data)
|
||||||
|
|
||||||
@@ -276,14 +284,77 @@ const statisticalIndicators = ref({
|
|||||||
severeTimeoutRate: 0,
|
severeTimeoutRate: 0,
|
||||||
formCompletionRate: 0,
|
formCompletionRate: 0,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// 团队异常
|
||||||
|
const teamAlerts = ref({})
|
||||||
|
// 异常预警
|
||||||
|
async function fetchAbnormalResponseRate() {
|
||||||
|
const params = getRequestParams()
|
||||||
|
const hasParams = params.user_name
|
||||||
|
try {
|
||||||
|
const response = await getAbnormalResponseRate(hasParams ? params : undefined)
|
||||||
|
const rawData = response.data
|
||||||
|
|
||||||
|
// 转换数据格式,按团队分组生成预警消息
|
||||||
|
const processedAlerts = []
|
||||||
|
const teamData = new Map()
|
||||||
|
|
||||||
|
// 收集严重超时异常数据
|
||||||
|
if (rawData.team_serious_timeout_abnormal_counts_by_group) {
|
||||||
|
Object.entries(rawData.team_serious_timeout_abnormal_counts_by_group).forEach(([teamName, data]) => {
|
||||||
|
if (!teamData.has(teamName)) {
|
||||||
|
teamData.set(teamName, { timeoutCount: 0, fillingCount: 0 })
|
||||||
|
}
|
||||||
|
teamData.get(teamName).timeoutCount = data.count
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收集表格填写异常数据
|
||||||
|
if (rawData.team_table_filling_abnormal_counts_by_group) {
|
||||||
|
Object.entries(rawData.team_table_filling_abnormal_counts_by_group).forEach(([teamName, data]) => {
|
||||||
|
if (!teamData.has(teamName)) {
|
||||||
|
teamData.set(teamName, { timeoutCount: 0, fillingCount: 0 })
|
||||||
|
}
|
||||||
|
teamData.get(teamName).fillingCount = data.count
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成按团队分组的预警消息
|
||||||
|
let alertId = 1
|
||||||
|
teamData.forEach((counts, teamName) => {
|
||||||
|
const messages = []
|
||||||
|
if (counts.timeoutCount > 0) {
|
||||||
|
messages.push(`${counts.timeoutCount}人严重超时率过高`)
|
||||||
|
}
|
||||||
|
if (counts.fillingCount > 0) {
|
||||||
|
messages.push(`${counts.fillingCount}人表格填写率过低`)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messages.length > 0) {
|
||||||
|
processedAlerts.push({
|
||||||
|
id: alertId++,
|
||||||
|
type: messages.length > 1 ? 'danger' : 'warning',
|
||||||
|
icon: messages.length > 1 ? '🔺' : '⚠',
|
||||||
|
message: `${teamName}团队${messages.join(',')}`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 设置处理后的数据
|
||||||
|
teamAlerts.value = { processedAlerts }
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('获取异常预警失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// 统计指标--活跃客户沟通率
|
// 统计指标--活跃客户沟通率
|
||||||
async function fetchCustomerCommunicationRate() {
|
async function fetchCustomerCommunicationRate() {
|
||||||
const params={
|
const params = getRequestParams()
|
||||||
user_name: userStore.userInfo.username,
|
const hasParams = params.user_name
|
||||||
user_level: userStore.userInfo.user_level.toString()
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await getActiveCustomerCommunicationRate(params)
|
const response = await getActiveCustomerCommunicationRate(hasParams ? params : undefined)
|
||||||
statisticalIndicators.value.customerCommunicationRate = response.data
|
statisticalIndicators.value.customerCommunicationRate = response.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取活跃客户沟通率失败:', error)
|
console.error('获取活跃客户沟通率失败:', error)
|
||||||
@@ -291,12 +362,10 @@ async function fetchCustomerCommunicationRate() {
|
|||||||
}
|
}
|
||||||
// 统计指标--平均应答时间
|
// 统计指标--平均应答时间
|
||||||
async function fetchAverageResponseTime() {
|
async function fetchAverageResponseTime() {
|
||||||
const params={
|
const params = getRequestParams()
|
||||||
user_name: userStore.userInfo.username,
|
const hasParams = params.user_name
|
||||||
user_level: userStore.userInfo.user_level.toString()
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await getAverageAnswerTime(params)
|
const response = await getAverageAnswerTime(hasParams ? params : undefined)
|
||||||
statisticalIndicators.value.averageResponseTime = response.data
|
statisticalIndicators.value.averageResponseTime = response.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取平均应答时间失败:', error)
|
console.error('获取平均应答时间失败:', error)
|
||||||
@@ -304,12 +373,10 @@ async function fetchAverageResponseTime() {
|
|||||||
}
|
}
|
||||||
// 统计指标--超时应答率、严重超时应答率
|
// 统计指标--超时应答率、严重超时应答率
|
||||||
async function fetchTimeoutRate() {
|
async function fetchTimeoutRate() {
|
||||||
const params={
|
const params = getRequestParams()
|
||||||
user_name: userStore.userInfo.username,
|
const hasParams = params.user_name
|
||||||
user_level: userStore.userInfo.user_level.toString()
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await getTimeoutRate(params)
|
const response = await getTimeoutRate(hasParams ? params : undefined)
|
||||||
statisticalIndicators.value.timeoutResponseRate = response.data
|
statisticalIndicators.value.timeoutResponseRate = response.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取超时应答率失败:', error)
|
console.error('获取超时应答率失败:', error)
|
||||||
@@ -317,12 +384,10 @@ async function fetchTimeoutRate() {
|
|||||||
}
|
}
|
||||||
// 统计指标--表格填写率
|
// 统计指标--表格填写率
|
||||||
async function fetchTableFillingRate() {
|
async function fetchTableFillingRate() {
|
||||||
const params={
|
const params = getRequestParams()
|
||||||
user_name: userStore.userInfo.username,
|
const hasParams = params.user_name
|
||||||
user_level: userStore.userInfo.user_level.toString()
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await getTableFillingRate(params)
|
const response = await getTableFillingRate(hasParams ? params : undefined)
|
||||||
statisticalIndicators.value.formCompletionRate = response.data
|
statisticalIndicators.value.formCompletionRate = response.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取表格填写率失败:', error)
|
console.error('获取表格填写率失败:', error)
|
||||||
@@ -332,12 +397,10 @@ const problemRanking = ref({})
|
|||||||
|
|
||||||
// 客户迫切解决的问题
|
// 客户迫切解决的问题
|
||||||
async function fetchUrgentNeedToAddress() {
|
async function fetchUrgentNeedToAddress() {
|
||||||
const params={
|
const params = getRequestParams()
|
||||||
user_name: userStore.userInfo.username,
|
const hasParams = params.user_name
|
||||||
user_level: userStore.userInfo.user_level.toString()
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await getUrgentNeedToAddress(params)
|
const response = await getUrgentNeedToAddress(hasParams ? params : undefined)
|
||||||
problemRanking.value = response.data
|
problemRanking.value = response.data
|
||||||
/**
|
/**
|
||||||
* "data": {
|
* "data": {
|
||||||
@@ -398,12 +461,10 @@ async function fetchUrgentNeedToAddress() {
|
|||||||
const teamRanking = ref({})
|
const teamRanking = ref({})
|
||||||
|
|
||||||
async function fetchTeamRanking() {
|
async function fetchTeamRanking() {
|
||||||
const params={
|
const params = getRequestParams()
|
||||||
user_name: userStore.userInfo.username,
|
const hasParams = params.user_name
|
||||||
user_level: userStore.userInfo.user_level.toString(),
|
|
||||||
}
|
|
||||||
try {
|
try {
|
||||||
const response = await getTeamRanking(params)
|
const response = await getTeamRanking(hasParams ? params : undefined)
|
||||||
teamRanking.value = response.data
|
teamRanking.value = response.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取团队业绩排名失败:', error)
|
console.error('获取团队业绩排名失败:', error)
|
||||||
@@ -413,14 +474,19 @@ async function fetchTeamRanking() {
|
|||||||
// 团队业绩详情
|
// 团队业绩详情
|
||||||
const teamPerformanceDetail = ref({})
|
const teamPerformanceDetail = ref({})
|
||||||
async function fetchTeamPerformanceDetail(department) {
|
async function fetchTeamPerformanceDetail(department) {
|
||||||
const params={
|
const params = getRequestParams()
|
||||||
|
const hasParams = params.user_name
|
||||||
|
const requestParams = hasParams ? {
|
||||||
|
...params,
|
||||||
|
department: department
|
||||||
|
} : {
|
||||||
user_name: userStore.userInfo.username,
|
user_name: userStore.userInfo.username,
|
||||||
user_level: userStore.userInfo.user_level.toString(),
|
user_level: userStore.userInfo.user_level.toString(),
|
||||||
department: department
|
department: department
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
teamPerformanceDetail.value = {}
|
teamPerformanceDetail.value = {}
|
||||||
const response = await getTeamRankingInfo(params)
|
const response = await getTeamRankingInfo(requestParams)
|
||||||
teamPerformanceDetail.value = response.data
|
teamPerformanceDetail.value = response.data
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('获取团队业绩详情失败:', error)
|
console.error('获取团队业绩详情失败:', error)
|
||||||
@@ -436,6 +502,7 @@ onMounted(async ()=>{
|
|||||||
await fetchTotalCallCount()
|
await fetchTotalCallCount()
|
||||||
await fetchNewCustomers()
|
await fetchNewCustomers()
|
||||||
await fetchDepositConversions()
|
await fetchDepositConversions()
|
||||||
|
await fetchAbnormalResponseRate()
|
||||||
await fetchCustomerCommunicationRate()
|
await fetchCustomerCommunicationRate()
|
||||||
await fetchAverageResponseTime()
|
await fetchAverageResponseTime()
|
||||||
await fetchTimeoutRate()
|
await fetchTimeoutRate()
|
||||||
@@ -485,6 +552,24 @@ const selectGroup = async (group) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 处理成员双击事件
|
||||||
|
const handleMemberDoubleClick = (member) => {
|
||||||
|
console.log('双击事件触发,成员数据:', member)
|
||||||
|
|
||||||
|
// 将成员等级写死为1,所有成员都可以跳转
|
||||||
|
const memberLevel = 1
|
||||||
|
console.log('等级设置为1,准备跳转到Sale页面')
|
||||||
|
|
||||||
|
// 跳转到Sale页面,携带成员姓名和等级
|
||||||
|
router.push({
|
||||||
|
name: 'Sale',
|
||||||
|
query: {
|
||||||
|
user_name: member.name,
|
||||||
|
user_level: memberLevel
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// 格式化货币
|
// 格式化货币
|
||||||
const formatCurrency = (value) => {
|
const formatCurrency = (value) => {
|
||||||
if (value >= 10000) {
|
if (value >= 10000) {
|
||||||
@@ -749,12 +834,18 @@ const getStatusText = (status) => {
|
|||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border: 1px solid #e2e8f0;
|
border: 1px solid #e2e8f0;
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
transform: translateY(0);
|
||||||
|
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
&.status-excellent {
|
&.status-excellent {
|
||||||
border-left: 4px solid #10b981;
|
border-left: 4px solid #10b981;
|
||||||
background: linear-gradient(135deg, #ecfdf5 0%, #f0fdf4 100%);
|
background: linear-gradient(135deg, #ecfdf5 0%, #f0fdf4 100%);
|
||||||
|
|||||||
Reference in New Issue
Block a user