feat(团队管理): 实现团队成员双击跳转功能并优化异常数据处理
1. 在PerformanceRanking组件中添加双击事件跳转到销售页面 2. 重构TeamAlerts组件异常数据处理逻辑,使用后端预处理数据 3. 在seniorManager页面添加异常预警API调用和数据处理 4. 优化路由参数处理逻辑,统一使用getRequestParams方法 5. 添加面包屑导航和返回功能,提升用户体验
This commit is contained in:
@@ -17,6 +17,7 @@
|
||||
class="table-row"
|
||||
:class="{ active: selectedMember && (selectedMember.user_name === member.user_name || selectedMember.id === member.id) }"
|
||||
@click="selectMember(member)"
|
||||
@dblclick="handleDoubleClick(member)"
|
||||
>
|
||||
<span class="rank">{{ index + 1 }}</span>
|
||||
<span class="name">{{ member.user_name || member.name }}</span>
|
||||
@@ -32,6 +33,14 @@
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, computed } from 'vue'
|
||||
import { useUserStore } from '@/stores/user'
|
||||
import { useRouter } from 'vue-router'
|
||||
|
||||
// 用户store实例
|
||||
const userStore = useUserStore()
|
||||
|
||||
// 路由实例
|
||||
const router = useRouter()
|
||||
|
||||
// 定义props
|
||||
const props = defineProps({
|
||||
@@ -54,12 +63,26 @@ const emit = defineEmits(['select-member'])
|
||||
|
||||
// 计算显示的成员数据
|
||||
const displayMembers = computed(() => {
|
||||
let members = []
|
||||
|
||||
// 如果有groupRanking数据,优先使用
|
||||
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) => {
|
||||
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>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
|
||||
@@ -30,51 +30,15 @@ const props = defineProps({
|
||||
}
|
||||
})
|
||||
|
||||
// 聚合异常数据,按人员分组
|
||||
// 处理团队异常数据
|
||||
const aggregatedAlerts = computed(() => {
|
||||
const alerts = []
|
||||
const data = props.abnormalData
|
||||
|
||||
if (!data || Object.keys(data).length === 0) {
|
||||
return alerts
|
||||
if (!data || !data.processedAlerts || !Array.isArray(data.processedAlerts)) {
|
||||
return []
|
||||
}
|
||||
|
||||
// 收集所有异常人员
|
||||
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
|
||||
return data.processedAlerts
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -10,9 +10,27 @@
|
||||
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>
|
||||
<main class="dashboard-main">
|
||||
@@ -50,7 +68,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, onMounted } from "vue";
|
||||
import { ref, onMounted, computed } from "vue";
|
||||
import TeamAlerts from "./components/TeamAlerts.vue";
|
||||
import TeamReport from "./components/TeamReport.vue";
|
||||
import SalesFunnel from "./components/SalesFunnel.vue";
|
||||
@@ -191,6 +209,9 @@ const teamMembers = [
|
||||
// 路由实例
|
||||
const router = useRouter();
|
||||
|
||||
// 用户store实例
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 获取通用请求参数的函数
|
||||
const getRequestParams = () => {
|
||||
const params = {}
|
||||
@@ -207,6 +228,22 @@ const getRequestParams = () => {
|
||||
|
||||
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({
|
||||
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 {
|
||||
background: white;
|
||||
padding: 1.5rem 2rem;
|
||||
|
||||
Reference in New Issue
Block a user