1499 lines
49 KiB
Vue
1499 lines
49 KiB
Vue
<template>
|
||
<div class="senior-manager-dashboard">
|
||
<!-- Header -->
|
||
<header class="dashboard-header">
|
||
<div class="header-content">
|
||
<div class="logo-section">
|
||
<!-- 动态顶栏:根据是否有路由参数显示不同内容 -->
|
||
<!-- 路由跳转时的顶栏:面包屑 + 姓名 -->
|
||
<div v-if="isRouteNavigation" class="route-header">
|
||
<div class="breadcrumb">
|
||
<span class="breadcrumb-item" @click="goBack">团队管理</span>
|
||
<span class="breadcrumb-separator">></span>
|
||
<span class="breadcrumb-item current"> {{ routeUserName }}中心组长指挥台</span>
|
||
</div>
|
||
<div class="user-name">
|
||
{{ routeUserName }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 自己登录时的顶栏:原有样式 -->
|
||
<template v-else>
|
||
<div class="header-text">
|
||
<h1>中心组长指挥台</h1>
|
||
<p>统筹多组运营,优化资源配置,驱动业绩增长,实现团队协同发展。</p>
|
||
</div>
|
||
|
||
</template>
|
||
|
||
<div v-if="!isRouteNavigation">
|
||
<!-- 用户下拉菜单 -->
|
||
<UserDropdown />
|
||
</div>
|
||
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<!-- Main Content -->
|
||
<main class="dashboard-main">
|
||
<!-- Top Section - Center Overview and Action Items -->
|
||
<div class="top-section">
|
||
<!-- Center Performance Overview -->
|
||
<CenterOverview :overall-data="overallCenterPerformance" @update-check-type="updateCheckType" />
|
||
|
||
<!-- Action Items (Compact) -->
|
||
<div class="action-items-compact">
|
||
<ActionItems :selected-group="selectedGroup" />
|
||
</div>
|
||
</div>
|
||
<div class="BB-section">
|
||
<!--客户类型占比-->
|
||
<CustomerType :customer-data="customerTypeDistribution" @category-change="handleCustomerTypeChange" />
|
||
<!-- 优秀录音 -->
|
||
<GoodMusic :record-data="goodRecord" />
|
||
<!-- 客户问题排行 -->
|
||
<ProblemRanking :ranking-data="formattedUrgentNeedData" />
|
||
</div>
|
||
<!-- Bottom Section -->
|
||
<div class="bottom-section">
|
||
<!-- Left Section - Group Performance Ranking -->
|
||
<div class="left-section">
|
||
<GroupRanking :groups="groups" :selected-group="selectedGroup" :conversion-data="conversionRateVsAverage" @select-group="selectGroup" />
|
||
</div>
|
||
|
||
<!-- Right Section - Group Comparison -->
|
||
<div class="right-section">
|
||
<GroupComparison :groups="groups" :senior-manager-data="seniorManagerList" :group-list="groupList" @select-group="selectGroup" @manager-change="handleManagerChange" />
|
||
</div>
|
||
</div>
|
||
|
||
|
||
<!-- Team Members Detail Section -->
|
||
<div class="team-detail-section" v-if="selectedGroup">
|
||
<div class="team-detail-header">
|
||
<h2>{{ selectedGroup.name }} - 团队成员详情</h2>
|
||
<div class="team-summary">
|
||
<div class="summary-item">
|
||
<span class="label">组长:</span>
|
||
<span class="value">{{ selectedGroup.leader }}</span>
|
||
</div>
|
||
<div class="summary-item">
|
||
<span class="label">成员数:</span>
|
||
<span class="value">{{ (groupPerformance && groupPerformance.group_details) ? groupPerformance.group_details.length : 0 }}人</span>
|
||
</div>
|
||
<div class="summary-item">
|
||
<span class="label">转化率:</span>
|
||
<span class="value">{{ selectedGroup.conversionRate }}%</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="members-grid">
|
||
<div v-for="member in selectedGroup.members" :key="member.id" class="member-card"
|
||
:class="getStatusClass(member.status)" @dblclick="navigateToSale(member.name)">
|
||
<div class="member-header">
|
||
<div class="member-info">
|
||
<h3 class="member-name">{{ member.name }}</h3>
|
||
<p class="member-position">{{ member.position }}</p>
|
||
</div>
|
||
<!-- 本人排名 -->
|
||
<div class="member-ranking">
|
||
<span class="ranking-label">排名:</span>
|
||
<span class="ranking-value">{{ member.rank }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="member-metrics">
|
||
<div class="metric-row">
|
||
<div class="metric-item">
|
||
<span class="metric-label">今日业绩</span>
|
||
<span class="metric-value">{{ member.todayPerformance}}</span>
|
||
</div>
|
||
<div class="metric-item">
|
||
<span class="metric-label">月度业绩</span>
|
||
<span class="metric-value">{{ member.monthlyPerformance }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="metric-row">
|
||
<div class="metric-item">
|
||
<span class="metric-label">转化率<i class="info-icon" @mouseenter="showTooltip($event, 'teamPerformance')" @mouseleave="hideTooltip">ⓘ</i></span>
|
||
<span class="metric-value">{{ member.conversionRate }}%</span>
|
||
</div>
|
||
<div class="metric-item">
|
||
<span class="metric-label">通话次数</span>
|
||
<span class="metric-value">{{ member.callCount }}</span>
|
||
</div>
|
||
<!-- Tooltip 组件 -->
|
||
<Tooltip
|
||
:visible="tooltip.visible"
|
||
:x="tooltip.x"
|
||
:y="tooltip.y"
|
||
:title="tooltip.title"
|
||
:description="tooltip.description"
|
||
/>
|
||
</div>
|
||
|
||
<div class="metric-row">
|
||
<div class="metric-item">
|
||
<span class="metric-label">新增客户</span>
|
||
<span class="metric-value">{{ member.newClients }}</span>
|
||
</div>
|
||
<div class="metric-item">
|
||
<span class="metric-label">成交订单</span>
|
||
<span class="metric-value">{{ member.deals }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
</main>
|
||
|
||
<!-- Loading 组件 -->
|
||
<Loading :visible="isLoading" text="数据加载中..." />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted, computed,reactive } from 'vue'
|
||
|
||
import CenterOverview from './components/CenterOverview.vue'
|
||
import GroupComparison from './components/GroupComparison.vue'
|
||
import GroupRanking from './components/GroupRanking.vue'
|
||
import ActionItems from './components/ActionItems.vue'
|
||
import CustomerDetail from './components/CustomerDetail.vue'
|
||
import CustomerType from './components/CustomerType.vue'
|
||
import GoodMusic from './components/GoodMusic.vue'
|
||
import ProblemRanking from './components/ProblemRanking.vue'
|
||
import seniorManager from './components/seniorManager.vue'
|
||
import UserDropdown from '@/components/UserDropdown.vue'
|
||
import Loading from '@/components/Loading.vue'
|
||
import Tooltip from '@/components/Tooltip.vue'
|
||
import {
|
||
getOverallCenterPerformance, getTotalGroupCount, getCenterConversionRate, getTotalCallCount, getNewCustomer
|
||
, getDepositConversionRate, getCustomerTypeDistribution, getUrgentNeedToAddress, getCenterAdvancedManagerList, getTeamRanking,
|
||
getTeamRankingInfo, getConversionRateVsAverage,getCampPeriodAdmin ,getExcellentRecordFile } from '@/api/secondTop.js'
|
||
import { useRouter } from 'vue-router'
|
||
import { useUserStore } from '@/stores/user.js'
|
||
// 路由实例
|
||
const router = useRouter();
|
||
// 用户store实例
|
||
const userStore = useUserStore();
|
||
const CheckType = ref('month')
|
||
// 营期调控逻辑
|
||
// This would ideally come from a prop or API call based on the logged-in user
|
||
const centerData = ref({
|
||
id: 1,
|
||
name: '一中心',
|
||
startDate: '2025-08-18',
|
||
stages: [
|
||
{ name: '接数据', days: 3, color: '#ffc107', startDate: '', endDate: '' },
|
||
{ name: '课一', days: 1, color: '#0dcaf0', startDate: '', endDate: '' },
|
||
{ name: '课二', days: 1, color: '#0d6efd', startDate: '', endDate: '' },
|
||
{ name: '课三', days: 1, color: '#6f42c1', startDate: '', endDate: '' },
|
||
{ name: '课四', days: 1, color: '#d63384', startDate: '', endDate: '' }
|
||
]
|
||
});
|
||
const getCurrentStage = (center) => {
|
||
const today = new Date();
|
||
today.setHours(0, 0, 0, 0);
|
||
const startDate = new Date(center.startDate);
|
||
startDate.setHours(0, 0, 0, 0);
|
||
|
||
if (today < startDate) {
|
||
return '未开始';
|
||
}
|
||
|
||
const diffTime = Math.abs(today - startDate);
|
||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) + 1; // 当天算第一天
|
||
|
||
let cumulativeDays = 0;
|
||
for (let i = 0; i < center.stages.length; i++) {
|
||
const stage = center.stages[i];
|
||
cumulativeDays += stage.days;
|
||
if (diffDays <= cumulativeDays) {
|
||
return stage.name;
|
||
}
|
||
}
|
||
|
||
return '已结束';
|
||
};
|
||
|
||
const currentStage = computed(() => getCurrentStage(centerData.value));
|
||
|
||
const isDataReceivingStage = computed(() => currentStage.value === '接数据');
|
||
|
||
// The '接数据' stage object
|
||
const dataReceivingStage = computed(() => centerData.value.stages.find(s => s.name === '接数据'));
|
||
|
||
const recalculateStageDates = () => {
|
||
const startDate = new Date(centerData.value.startDate);
|
||
let cumulativeDays = 0;
|
||
centerData.value.stages.forEach(stage => {
|
||
const stageStartDate = new Date(startDate);
|
||
stageStartDate.setDate(startDate.getDate() + cumulativeDays);
|
||
stage.startDate = stageStartDate.toISOString().split('T')[0];
|
||
|
||
cumulativeDays += stage.days;
|
||
|
||
const stageEndDate = new Date(startDate);
|
||
stageEndDate.setDate(startDate.getDate() + cumulativeDays - 1);
|
||
stage.endDate = stageEndDate.toISOString().split('T')[0];
|
||
});
|
||
};
|
||
// 保存营期
|
||
const saveCampSettings = async () => {
|
||
recalculateStageDates();
|
||
// 准备API请求参数
|
||
const params = {
|
||
...getRequestParams(),
|
||
receipt_data_time: dataReceivingStage.value.days.toString()
|
||
};
|
||
|
||
try {
|
||
const res = await getCampPeriodAdmin(params);
|
||
if (res.code === 200) {
|
||
console.log('营期设置保存成功:', res.data);
|
||
alert('营期设置已保存!');
|
||
} else {
|
||
alert('保存失败,请重试');
|
||
}
|
||
} catch (error) {
|
||
console.error('保存营期设置失败:', error);
|
||
alert('保存失败,请重试');
|
||
}
|
||
};
|
||
const padding111 = ref('11')
|
||
// 结束营期
|
||
const finishCamp = async () => {
|
||
try {
|
||
const params = {
|
||
...getRequestParams(),
|
||
is_camp_finish: padding111.value.toString()
|
||
};
|
||
|
||
const res = await getCampPeriodAdmin(params);
|
||
if (res.code === 200) {
|
||
console.log('营期结束成功:', res.data);
|
||
alert('营期已成功结束!');
|
||
// 可以在这里添加页面跳转或其他后续操作
|
||
} else {
|
||
alert('结束营期失败,请重试');
|
||
}
|
||
} catch (error) {
|
||
console.error('结束营期失败:', error);
|
||
alert('结束营期失败,请重试!');
|
||
}
|
||
};
|
||
|
||
// console.log('currentStage', userStore.userInfo)
|
||
|
||
// 获取,修改当前营期
|
||
const campPeriodAdmin = ref({})
|
||
async function CenterCampPeriodAdmin() {
|
||
const params = {
|
||
user_name: userStore.userInfo.username
|
||
,user_level: userStore.userInfo.user_level.toString()
|
||
}
|
||
|
||
const res = await getCampPeriodAdmin(params)
|
||
if (res.code === 200) {
|
||
campPeriodAdmin.value = res.data
|
||
|
||
// 根据API返回的数据更新centerData
|
||
if (res.data.camp_period) {
|
||
const campPeriod = res.data.camp_period
|
||
|
||
// 解析接数据阶段的开始日期作为营期开始日期
|
||
if (campPeriod.receipt_data_time) {
|
||
const startDate = campPeriod.receipt_data_time.split(' to ')[0].split(' ')[0]
|
||
centerData.value.startDate = startDate
|
||
}
|
||
|
||
// 计算各阶段天数
|
||
const calculateDays = (timeRange) => {
|
||
if (!timeRange) return 1
|
||
const [start, end] = timeRange.split(' to ')
|
||
const startDate = new Date(start.split(' ')[0])
|
||
const endDate = new Date(end.split(' ')[0])
|
||
return Math.ceil((endDate - startDate) / (1000 * 60 * 60 * 24)) + 1
|
||
}
|
||
|
||
// 更新各阶段天数
|
||
centerData.value.stages.forEach(stage => {
|
||
switch(stage.name) {
|
||
case '接数据':
|
||
stage.days = calculateDays(campPeriod.receipt_data_time)
|
||
break
|
||
case '课一':
|
||
stage.days = calculateDays(campPeriod.class_one)
|
||
break
|
||
case '课二':
|
||
stage.days = calculateDays(campPeriod.class_two)
|
||
break
|
||
case '课三':
|
||
stage.days = calculateDays(campPeriod.class_three)
|
||
break
|
||
case '课四':
|
||
stage.days = calculateDays(campPeriod.class_four)
|
||
break
|
||
}
|
||
})
|
||
|
||
// 重新计算阶段日期
|
||
recalculateStageDates()
|
||
}
|
||
}
|
||
}
|
||
|
||
// 组别数据
|
||
const groups = ref([])
|
||
// loading 状态
|
||
const isLoading = ref(false)
|
||
|
||
// 获取通用请求参数的函数
|
||
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
|
||
}
|
||
|
||
// 判断是否为路由导航(有路由参数)
|
||
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 overallCenterPerformance = ref({
|
||
CenterPerformance: {},
|
||
TotalGroupCount: {},
|
||
CenterConversionRate: {},
|
||
TotalCallCount: {},
|
||
NewCustomer: {},
|
||
DepositConversionRate: {}
|
||
})
|
||
// 客户类型
|
||
const customerTypeDistribution = ref({})
|
||
// 迫切解决的问题
|
||
const urgentNeedToAddress = ref({})
|
||
|
||
// 格式化迫切解决问题数据为组件所需格式
|
||
const formattedUrgentNeedData = computed(() => {
|
||
if (urgentNeedToAddress.value && urgentNeedToAddress.value.center_urgent_issue_ratio) {
|
||
return Object.entries(urgentNeedToAddress.value.center_urgent_issue_ratio).map(([name, value]) => ({
|
||
name,
|
||
value
|
||
}))
|
||
}
|
||
return []
|
||
})
|
||
|
||
// 中心总业绩
|
||
async function CenterOverallCenterPerformance() {
|
||
const params = getRequestParams()
|
||
const hasParams = params.user_name
|
||
try {
|
||
const res = await getOverallCenterPerformance(hasParams ? {
|
||
...params,
|
||
check_type: CheckType.value
|
||
} : {check_type: CheckType.value})
|
||
if (res.code === 200) {
|
||
overallCenterPerformance.value.CenterPerformance = res.data
|
||
}
|
||
} catch (error) {
|
||
console.error('获取中心整体概览失败:', error)
|
||
}
|
||
}
|
||
// 活跃组数
|
||
async function CenterTotalGroupCount() {
|
||
const params = getRequestParams()
|
||
const hasParams = params.user_name
|
||
try {
|
||
const res = await getTotalGroupCount(hasParams ? {
|
||
...params,
|
||
check_type: CheckType.value
|
||
} : {check_type: CheckType.value})
|
||
if (res.code === 200) {
|
||
overallCenterPerformance.value.TotalGroupCount = res.data
|
||
}
|
||
} catch (error) {
|
||
console.error('获取中心整体概览失败:', error)
|
||
}
|
||
}
|
||
// 中心转化率
|
||
async function CenterConversionRate() {
|
||
const params = getRequestParams()
|
||
const hasParams = params.user_name
|
||
try {
|
||
const res = await getCenterConversionRate(hasParams ? {
|
||
...params,
|
||
check_type: CheckType.value
|
||
} : {check_type: CheckType.value})
|
||
if (res.code === 200) {
|
||
overallCenterPerformance.value.CenterConversionRate = res.data
|
||
}
|
||
} catch (error) {
|
||
console.error('获取中心整体概览失败:', error)
|
||
}
|
||
}
|
||
// 中心通话次数
|
||
async function CenterTotalCallCount() {
|
||
const params = getRequestParams()
|
||
const hasParams = params.user_name
|
||
try {
|
||
const res = await getTotalCallCount(hasParams ? {
|
||
...params,
|
||
check_type: CheckType.value
|
||
} : {check_type: CheckType.value})
|
||
if (res.code === 200) {
|
||
overallCenterPerformance.value.TotalCallCount = res.data
|
||
}
|
||
} catch (error) {
|
||
console.error('获取中心整体概览失败:', error)
|
||
}
|
||
}
|
||
// 新增客户
|
||
async function CenterNewCustomer() {
|
||
const params = getRequestParams()
|
||
const hasParams = params.user_name
|
||
try {
|
||
const res = await getNewCustomer(hasParams ? {
|
||
...params,
|
||
check_type: CheckType.value
|
||
} : {check_type: CheckType.value})
|
||
if (res.code === 200) {
|
||
overallCenterPerformance.value.NewCustomer = res.data
|
||
}
|
||
} catch (error) {
|
||
console.error('获取中心整体概览失败:', error)
|
||
}
|
||
}
|
||
// 定金转化率
|
||
async function CenterDepositConversionRate() {
|
||
const params = getRequestParams()
|
||
const hasParams = params.user_name
|
||
try {
|
||
const res = await getDepositConversionRate(hasParams ? {
|
||
...params,
|
||
check_type: CheckType.value
|
||
} : {check_type: CheckType.value})
|
||
if (res.code === 200) {
|
||
overallCenterPerformance.value.DepositConversionRate = res.data
|
||
}
|
||
} catch (error) {
|
||
console.error('获取中心整体概览失败:', error)
|
||
}
|
||
}
|
||
// 客户类型
|
||
async function CenterCustomerType(distributionType = 'child_education') {
|
||
const params = getRequestParams()
|
||
const hasParams = params.user_name
|
||
// 添加distribution_type参数
|
||
const requestParams = hasParams ? { ...params, distribution_type: distributionType } : { distribution_type: distributionType }
|
||
try {
|
||
const res = await getCustomerTypeDistribution(requestParams)
|
||
if (res.code === 200) {
|
||
customerTypeDistribution.value = res.data
|
||
}
|
||
} catch (error) {
|
||
console.error('获取客户类型分布失败:', error)
|
||
}
|
||
}
|
||
|
||
// 处理客户类型选择变化
|
||
const handleCustomerTypeChange = (distributionType) => {
|
||
CenterCustomerType(distributionType)
|
||
}
|
||
// 客户迫切解决的问题
|
||
async function CenterUrgentNeedToAddress() {
|
||
const params = getRequestParams()
|
||
const hasParams = params.user_name
|
||
try {
|
||
const res = await getUrgentNeedToAddress(hasParams ? params : undefined)
|
||
if (res.code === 200) {
|
||
urgentNeedToAddress.value = res.data
|
||
}
|
||
} catch (error) {
|
||
console.error('获取中心整体概览失败:', error)
|
||
}
|
||
}
|
||
// 各阶段转化率
|
||
const conversionRateVsAverage = ref({})
|
||
async function CenterConversionRateVsAverage() {
|
||
const params = getRequestParams()
|
||
const hasParams = params.user_name
|
||
try {
|
||
const res = await getConversionRateVsAverage(hasParams ? params : undefined)
|
||
if (res.code === 200) {
|
||
conversionRateVsAverage.value = res.data
|
||
}
|
||
} catch (error) {
|
||
console.error('获取中心整体概览失败:', error)
|
||
}
|
||
}
|
||
|
||
// 综合排名---高级经理列表
|
||
const seniorManagerList = ref([])
|
||
async function CenterSeniorManagerList() {
|
||
const params = getRequestParams()
|
||
const hasParams = params.user_name
|
||
try {
|
||
const res = await getCenterAdvancedManagerList(hasParams ? params : undefined)
|
||
if (res.code === 200) {
|
||
seniorManagerList.value = res.data
|
||
}
|
||
} catch (error) {
|
||
console.error('获取中心整体概览失败:', error)
|
||
}
|
||
}
|
||
// 综合排名---组别列表
|
||
const groupList = ref([])
|
||
async function CenterGroupList(selectedManagerId = 'all') {
|
||
const params = getRequestParams()
|
||
const hasParams = params.user_name
|
||
|
||
// 根据选择的经理构建请求参数
|
||
let requestParams = hasParams ? { ...params } : {}
|
||
|
||
if (selectedManagerId === 'all') {
|
||
requestParams.get_all = "true"
|
||
} else {
|
||
// 根据manager id找到对应的名字
|
||
const selectedManager = seniorManagerList.value?.center_advanced_managers?.find((name, index) => `manager_${index}` === selectedManagerId)
|
||
console.log('Found Manager:', selectedManager)
|
||
if (selectedManager) {
|
||
requestParams.team_leader_name = selectedManager
|
||
}
|
||
}
|
||
try {
|
||
const res = await getTeamRanking(requestParams)
|
||
console.log('API Response:', res)
|
||
if (res.code === 200) {
|
||
groupList.value = res.data
|
||
console.log('Updated groupList:', groupList.value)
|
||
}
|
||
} catch (error) {
|
||
console.error('获取团队排名失败:', error)
|
||
}
|
||
}
|
||
|
||
// 团队业绩详情数据
|
||
const groupPerformance = ref({})
|
||
|
||
// 根据传来的组名字来获取组业绩详情
|
||
async function CenterGroupPerformance(groupName) {
|
||
const params = getRequestParams()
|
||
const hasParams = params.user_name
|
||
const requestParams = hasParams ? {
|
||
...params,
|
||
department: groupName
|
||
} : {
|
||
department: groupName
|
||
}
|
||
|
||
try {
|
||
const res = await getTeamRankingInfo(requestParams)
|
||
|
||
if (res.code === 200) {
|
||
groupPerformance.value = res.data
|
||
}
|
||
} catch (error) {
|
||
console.error('获取团队排名失败:', error)
|
||
}
|
||
}
|
||
|
||
|
||
// 当前选中的组别,默认为第一个
|
||
const selectedGroup = ref(groups[0])
|
||
|
||
// 选择组别函数
|
||
const selectGroup = async (group) => {
|
||
selectedGroup.value = group
|
||
console.log('选中的组别111:', group)
|
||
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_deals,
|
||
monthlyPerformance: member.monthly_deals,
|
||
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 = {
|
||
...selectedGroup.value,
|
||
members: formattedMembers
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('获取团队详情失败:', error)
|
||
}
|
||
}
|
||
|
||
// 处理高级经理选择变化
|
||
const handleManagerChange = (selectedManagerId) => {
|
||
CenterGroupList(selectedManagerId)
|
||
}
|
||
|
||
// 格式化货币
|
||
const formatCurrency = (value) => {
|
||
if (value >= 10000) {
|
||
return (value / 10000).toFixed(1) + '万'
|
||
}
|
||
return value.toLocaleString()
|
||
}
|
||
|
||
// 获取状态样式类
|
||
const getStatusClass = (status) => {
|
||
return `status-${status}`
|
||
}
|
||
|
||
// 获取状态文本
|
||
const getStatusText = (status) => {
|
||
const statusMap = {
|
||
excellent: '优秀',
|
||
good: '良好',
|
||
average: '一般',
|
||
attention: '需关注',
|
||
poor: '待提升'
|
||
}
|
||
return statusMap[status] || '未知'
|
||
}
|
||
|
||
// 双击成员卡片跳转到销售页面
|
||
const navigateToSale = (userName) => {
|
||
router.push({
|
||
path: '/sale',
|
||
query: {
|
||
user_name: userName,
|
||
user_level: '1'
|
||
}
|
||
})
|
||
}
|
||
// 获取优秀录音
|
||
const goodRecord = ref([])
|
||
// 获取优秀录音文件
|
||
async function getGoodRecord() {
|
||
const params = getRequestParams()
|
||
const hasParams = params.user_name
|
||
const requestParams = hasParams ? {
|
||
...params,
|
||
} : {
|
||
}
|
||
try {
|
||
const res = await getExcellentRecordFile(requestParams)
|
||
if (res.code === 200) {
|
||
goodRecord.value = res.data
|
||
/**
|
||
* "data": {
|
||
"user_name": "张三丰",
|
||
"user_level": 4,
|
||
"excellent_record_list": {
|
||
"马然": [
|
||
{
|
||
"sale_name": "马然",
|
||
"sop": null,
|
||
"context": "[4.61s - 324.55s] Speaker 1: 哎,星宇吗?能看到常老师吧。嗯嗯,好,老师也把摄像头翻转过来,带您看一下咱们平台。嗯,你看能看到吧啊,可以看到嗯,咱们规模够大吧啊,我们是全国也是全互联网规模最大的解决青少年心理问题的平台。所以为什么一开始就跟您说孩子问题不用担心,不用害怕。对于您来说,现在不知道该怎么去跟孩子沟通了,我们也能够感受到孩子也很痛苦,他也想改变,他也想让自己变优秀,但是到底怎么引导着孩子,让他成为一个优秀的自己呢?我们每天去处理的都是这样的事情。因为我刚刚跟您说的很多的各种阶段的、初中的、高中的,甚至说进入大学的孩子,也是本来也是一一个很优秀的孩子,真的就是面对这些问题,面对这些困难,他不知道怎么去处理我们家长到底如何给予孩子帮助?你看这个这是央视的品牌,中国,咱们是重点推荐品牌,这是央视的力量栏目组。咱们是受邀专访企业,这是咱们赵院长去年去参加腾讯的育儿盛典,给他们颁发的综合实力。家庭教育品牌这边呢是咱们前台暖洋葱,家庭教育能看到吧啊,看到了嗯,你看咱们的规模都是非常大的。我们的愿景就是让咱们孩子开心快乐的去主动学习自己,能够把精力放在学习上,我们也不用再去天天担心孩子了,孩子也能够有个更好的发展。咱们的使命就是改善我们的家庭教育,影响幸福代人孩只是孩子一一个成绩,有一良好的发展,更多多也孩孩子一个良好的品良良好心心态,够够自己己人生生展展,是咱们这几天上课的,也是说赵世杰、赵院长还记得吧?中科院的高级心理咨询师,也是我们暖阳村教研中心的主任课就在他那里买的。对对,然后最擅长的就是解决提升孩子这些学习力内驱力,让他主动学习,提升成绩,解决这些各种原因,厌学休学,不想上学,不想学习依赖手机沉迷游戏的问题。可以说,赵院孩子问题,赵院长眼中中等程度算不上,非常好解决,又不是说真的去割动脉了。那孩子也有这样一个行为,但是划破皮是不是?但是我们也需要重视视,毕竟现在是发的就是这个对,也是有这样一个感觉自己心理上的痛苦,当心理上的痛苦,他无法承受的时候,他就只能通过伤害自身身体来缓解内心的这种压力了。这边呢是咱们一个发展历程。你看我们是零九年的时候建立的,当时在北京、北京十七个家里面,九位老师都是如何高无一例外,孩子学习能力是有的,包括甚至小学的时候都非常听话,也非常开朗。但是你会发现,一旦进入初中,一旦进入高中,各种各样的情况,各种各样的问题就开始出现了。比如说跟咱们孩子一样,学校里面老师批评了孩子自尊心受到打击了,都甚至同学矛盾、矛盾,甚至到校园霸凌的程度。好,有的也是爸爸妈妈长期在家里面吵架,甚至说长期的对于孩子缺乏陪伴,孩子没有安全感,是不是那孩子的这个性格方面就肯定爸爸爸。嗯嗯,这一点咱们对于孩子做的还是很好的,但孩子面对的问题是各种各样的,从来没从来没挑战。嗯,有一个幸福的家庭,那这样已经也是超过大部分的来来解决的这些家长了。那关键就是孩子在面对其他问题的时候,我们到底能够给予到孩子一个什么样的帮助。由于孩子他没有一个解决的问题,他孩子就是孩子他不知道该怎么办,他的想法都是非常幼稚的,非常单纯的。我们家长到底如何引导着孩子给到他力量温暖和引导,让他去解决这些问题,对不对?你们这一开始咱们就是洋葱互助会的形式成立的。零九年到一三年,咱们互助会就累计帮助了数万人了。到一五年,我们正式成立了暖洋葱家庭教育。一七年两年的时间,咱们迅速发展,当时已经是非常大的规模了,能看到吧?很多人,你看你看现在国家也开始重视青少年的心理健康了,要求学校里面搭建什么校园心理工作室?我们想让孩子长大之后成为一个优秀的人。首先,他得能够心理健康的长大吧,对不对?那他们需要找到专业的平台呀,对,那就找到咱们咱们去帮助全国多所学校搭建校园经理工作室。二二年,全国多家媒体登门进行独家的专访。二三年抖音平台家庭教育板块给咱们颁发新锐领学官的证书,什么意思呢?孩子妈妈家庭教育这个板块,我们要说自己是第二的话,那就没人有资格说自己是第一了。领学官领学官领了在家学习的人才叫领学官。这个是二四年央视的两个栏目,对我们进行独家专访的原因。我们也成功的帮助,数万组家庭走向幸福。孩子们现在对我们洋葱有一个大概的了解了吧?啊嗯所以说专业的事情交给专业的人。咱们这些问题呢,我会反馈咱们赵院长让他去进行针对性的讲课。我稍后也整理一下咱们机构的资料,包括跟您孩子类似的他们的一些成长改变发给咱们,咱们可以了解一下课程。开始之前我也会提醒咱们有问题了,随时联系我,好吧,可以可以可以。那那老师今天就咱们沟通到这里,他的妈妈再见。好的好的好的嗯,\n[324.71s - 325.87s] Speaker 0: 好的,好,谢谢啊。\n",
|
||
"obj_file_name": "http://192.168.3.112:5000/api/record/download/马然-20分钟通话-25-08-20_20-17-36-347230-987.mp3",
|
||
"score": 25.0
|
||
},
|
||
{
|
||
"sale_name": "马然",
|
||
"sop": null,
|
||
"context": "[8.79s - 22.35s] Speaker 0: 我刚在给你打电话接话啊,我可以给初二给人打电话,给喂我的妈妈刚刚玩一下就行。现是进入直播间学习上班哦,\n[27.25s - 34.91s] Speaker 0: 但是这个哎新宇妈妈一个月开始十分钟吗?\n[34.91s - 36.13s] Speaker 1: 啊,能听到吗?\n[36.13s - 37.81s] Speaker 0: 稍等一下哈哦哦,\n[38.15s - 197.56s] Speaker 1: 等一下他在忙是吧,不能不能听得到。你说嗯信号怎么好哦,行行行,是咱们第三节直播已经开始十多分钟了,是来提醒了们上课的,看到没有?我刚才没听没有没有嗯嗯,老师今天晚上都忙,没时间吗?这几天赶货比较忙哦,赶货干啊,刚吃饭厂里上班的哦哦哦,厂里上班咱们就是刚休息是吗?一会儿是还要去吗?对,刚吃饭回来。对,哎呦,虽然我上课这两天上课都是边上班边听的。嗯嗯嗯,对,咱们可以戴个耳机边上班边听。嗯啊对对对,孩子这两天咱们跟孩子沟通情况怎么样啊,孩子没回来了,还在学校。嗯,嗯,哦咱们这两天也没给他发消息是吧?呃,手机没说,反正嗯手机没有嗯对手机收了。嗯,那郑院长昨天也提到了咱们的一对一指导,帮助孩子去解决这样一个问题。不管是心理方面了,还是对于学习这些的,咱们是呃怎么考虑的呢?我刚今天又听了我今天的那个课听差不多了。嗯,嗯,那个有点贵呀。嗯,好的,妈妈两个月的一对一指导啊。咱们如果说了解的话,平时都是一万以上的,也只有咱们这一天赵院长给我们的一个优惠名额是六千八。咱们也而且还是你抢到这个名额优惠名额有三百抵三千。今天晚上价值五千九百八的证件训练,也是截止赠送了三个六千八。说实话,孩孩子这样一个情况,我们这种情况,我们这种家庭嗯嗯非常啊对我非常理解咱们这样一个心情,对不对?但是如果说你孩子这样一个问题,你想两个月的老师这样一堆指导,你要是我我给你找一个一千块的让你报名,你想的是啥样的,老师给他们指导啊,对不对?哎,对于孩子来说,家家庭太困难了,我们哎,太难了,反正先写在外面的事。没事没事,咱们先嗯今天晚上依然该听课听课。好吧,有很多家长都是刚刚还没进去,没没去,刚刚吃饭有点忙。嗯,行行,刚刚回来,我就是稍后把链接发给咱们。然后晚上的时候,我也我我嗯整理跟咱们家庭情况比较相似的,都可以发给咱们,咱们可以看一看了解一下,好吧,我都还没没来得及看。你昨天给我发了好多,我都没没仔细看。哎呦啊,那都是时间太忙,工作太忙。那行,咱们先进直播间签到打卡吧,行吧,进直播间签到打卡,结果回放。嗯,好谢行吧,再见。好好好好好再见嗯,拜拜。\n",
|
||
"obj_file_name": "http://192.168.3.112:5000/api/record/download/马然-20分钟通话-25-08-20_20-24-43-653520-759.mp3",
|
||
"score": 55.0
|
||
}
|
||
]
|
||
}
|
||
}
|
||
*/
|
||
}
|
||
} catch (error) {
|
||
console.error('获取优秀录音失败:', error)
|
||
}
|
||
}
|
||
|
||
|
||
onMounted(async () => {
|
||
try {
|
||
isLoading.value = true
|
||
|
||
// 判断页面进入方式:如果是通过路由跳转进入(URL中有user_name和user_level参数),则不发送CenterCampPeriodAdmin请求
|
||
// 如果是直接登录进入页面,则发送请求
|
||
const currentQuery = router.currentRoute.value.query
|
||
const isFromRoute = currentQuery.fromRoute ||
|
||
sessionStorage.getItem('fromRoute') ||
|
||
(currentQuery.user_name && currentQuery.user_level)
|
||
|
||
if (!isFromRoute) {
|
||
// 直接登录进入页面时才调用CenterCampPeriodAdmin
|
||
await CenterCampPeriodAdmin()
|
||
}
|
||
// CenterCampPeriodAdmin中已经调用了recalculateStageDates,这里不需要重复调用
|
||
await CenterOverallCenterPerformance()
|
||
await CenterTotalGroupCount()
|
||
await CenterConversionRate()
|
||
await CenterTotalCallCount()
|
||
await CenterNewCustomer()
|
||
await CenterDepositConversionRate()
|
||
await CenterCustomerType()
|
||
await CenterUrgentNeedToAddress()
|
||
await CenterConversionRateVsAverage()
|
||
await CenterSeniorManagerList()
|
||
// 获取优秀录音
|
||
await getGoodRecord()
|
||
await CenterGroupList('all') // 初始化加载全部高级经理数据
|
||
} catch (error) {
|
||
console.error('数据加载失败:', error)
|
||
} finally {
|
||
isLoading.value = false
|
||
}
|
||
})
|
||
|
||
// 更新CheckType并重新获取数据
|
||
const updateCheckType = async (newValue) => {
|
||
CheckType.value = newValue
|
||
console.log('CheckType已更新为:', newValue)
|
||
|
||
// 重新调用相关API函数
|
||
isLoading.value = true
|
||
try {
|
||
await CenterOverallCenterPerformance()
|
||
await CenterTotalGroupCount()
|
||
await CenterConversionRate()
|
||
await CenterTotalCallCount()
|
||
await CenterNewCustomer()
|
||
await CenterDepositConversionRate()
|
||
} catch (error) {
|
||
console.error('重新获取数据失败:', error)
|
||
} finally {
|
||
isLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 工具提示状态
|
||
const tooltip = reactive({
|
||
visible: false,
|
||
x: 0,
|
||
y: 0,
|
||
title: '',
|
||
description: ''
|
||
})
|
||
|
||
// 指标描述
|
||
const metricDescriptions = {
|
||
teamPerformance: {
|
||
title: '转化率',
|
||
description: '本期最终成交/本期客户总数'
|
||
}
|
||
}
|
||
|
||
// 显示工具提示
|
||
const showTooltip = (event, metricType) => {
|
||
const metric = metricDescriptions[metricType]
|
||
if (metric) {
|
||
tooltip.title = metric.title
|
||
tooltip.description = metric.description
|
||
tooltip.x = event.clientX
|
||
tooltip.y = event.clientY
|
||
tooltip.visible = true
|
||
}
|
||
}
|
||
|
||
// 隐藏工具提示
|
||
const hideTooltip = () => {
|
||
tooltip.visible = false
|
||
}
|
||
</script>
|
||
|
||
<style lang="scss" scoped>
|
||
@import '@/assets/styles/main.scss';
|
||
|
||
.senior-manager-dashboard {
|
||
min-height: 100vh;
|
||
background-color: #f8fafc;
|
||
}
|
||
|
||
.dashboard-header {
|
||
background: white;
|
||
padding: 1.5rem 2rem;
|
||
border-bottom: 1px solid #e2e8f0;
|
||
|
||
.header-content {
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
}
|
||
|
||
.logo-section {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.header-text {
|
||
h1 {
|
||
font-size: 1.6rem;
|
||
font-weight: bold;
|
||
color: #1e293b;
|
||
margin: 0 0 0.25rem 0;
|
||
}
|
||
|
||
p {
|
||
color: #64748b;
|
||
margin: 0;
|
||
font-size: 0.95rem;
|
||
}
|
||
}
|
||
|
||
.stage-info {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
|
||
.stage-label {
|
||
color: #64748b;
|
||
font-size: 0.9rem;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.stage-value {
|
||
color: #3b82f6;
|
||
font-size: 0.9rem;
|
||
font-weight: 600;
|
||
background: #eff6ff;
|
||
padding: 0.25rem 0.75rem;
|
||
border-radius: 1rem;
|
||
margin-left: 0.5rem;
|
||
}
|
||
}
|
||
}
|
||
|
||
.dashboard-main {
|
||
max-width: 1400px;
|
||
margin: 0 auto;
|
||
padding: 1rem;
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.top-section {
|
||
display: grid;
|
||
grid-template-columns: 2fr 1fr;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.bottom-section {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 1rem;
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.left-section,
|
||
.right-section {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.right-section {
|
||
max-height: 600px;
|
||
overflow: auto;
|
||
}
|
||
|
||
.action-items-compact {
|
||
height: 380px;
|
||
overflow: hidden;
|
||
|
||
:deep(.action-items) {
|
||
height: 100%;
|
||
padding: 1rem;
|
||
|
||
.actions-header {
|
||
margin-bottom: 1rem;
|
||
|
||
h2 {
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.header-controls {
|
||
gap: 0.5rem;
|
||
|
||
.priority-filter {
|
||
padding: 0.4rem;
|
||
font-size: 0.8rem;
|
||
}
|
||
|
||
.add-btn {
|
||
padding: 0.4rem 0.8rem;
|
||
font-size: 0.8rem;
|
||
}
|
||
}
|
||
}
|
||
|
||
.actions-summary {
|
||
grid-template-columns: repeat(4, 1fr);
|
||
gap: 0.5rem;
|
||
margin-bottom: 1rem;
|
||
|
||
.summary-item {
|
||
padding: 0.5rem;
|
||
|
||
.summary-count {
|
||
font-size: 1.2rem;
|
||
}
|
||
|
||
.summary-label {
|
||
font-size: 0.7rem;
|
||
}
|
||
}
|
||
}
|
||
|
||
.actions-list {
|
||
max-height: 230px;
|
||
overflow-y: auto;
|
||
|
||
.action-item {
|
||
padding: 0.75rem;
|
||
margin-bottom: 0.5rem;
|
||
|
||
.action-content {
|
||
.action-header {
|
||
margin-bottom: 0.25rem;
|
||
|
||
.action-title {
|
||
font-size: 0.9rem;
|
||
}
|
||
|
||
.action-meta {
|
||
gap: 0.25rem;
|
||
|
||
.priority-badge {
|
||
padding: 0.2rem 0.4rem;
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.due-date {
|
||
font-size: 0.7rem;
|
||
}
|
||
}
|
||
}
|
||
|
||
.action-description {
|
||
font-size: 0.8rem;
|
||
margin-bottom: 0.5rem;
|
||
}
|
||
|
||
.action-details {
|
||
margin-bottom: 0.5rem;
|
||
|
||
.detail-item {
|
||
font-size: 0.75rem;
|
||
}
|
||
}
|
||
|
||
.action-footer {
|
||
.action-buttons {
|
||
|
||
.btn-edit,
|
||
.btn-delete {
|
||
padding: 0.25rem 0.5rem;
|
||
font-size: 0.7rem;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.customer-detail-section {
|
||
padding: 1rem;
|
||
margin-top: 0.75rem;
|
||
}
|
||
}
|
||
|
||
// BB-section 布局
|
||
.BB-section {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr 1fr;
|
||
gap: 1rem;
|
||
margin-top: 1rem;
|
||
align-items: stretch;
|
||
|
||
>* {
|
||
height: 100%;
|
||
min-height: 400px;
|
||
}
|
||
}
|
||
|
||
// 团队成员详情区域
|
||
.team-detail-section {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 1.5rem;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
margin-top: 1rem;
|
||
|
||
.team-detail-header {
|
||
margin-bottom: 2rem;
|
||
|
||
h2 {
|
||
font-size: 1.4rem;
|
||
font-weight: 600;
|
||
color: #1e293b;
|
||
margin: 0 0 1rem 0;
|
||
}
|
||
|
||
.team-summary {
|
||
display: flex;
|
||
gap: 2rem;
|
||
flex-wrap: wrap;
|
||
|
||
.summary-item {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.5rem;
|
||
|
||
.label {
|
||
font-size: 0.9rem;
|
||
color: #64748b;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.value {
|
||
font-size: 0.9rem;
|
||
color: #1e293b;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.members-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(320px, 1fr));
|
||
gap: 1rem;
|
||
|
||
.member-card {
|
||
background: #f8fafc;
|
||
border-radius: 10px;
|
||
padding: 1rem;
|
||
border: 1px solid #e2e8f0;
|
||
transition: all 0.2s ease;
|
||
|
||
&:hover {
|
||
transform: translateY(-2px);
|
||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||
}
|
||
|
||
&.status-excellent {
|
||
border-left: 4px solid #10b981;
|
||
background: linear-gradient(135deg, #ecfdf5 0%, #f0fdf4 100%);
|
||
}
|
||
|
||
&.status-good {
|
||
border-left: 4px solid #3b82f6;
|
||
background: linear-gradient(135deg, #eff6ff 0%, #f0f9ff 100%);
|
||
}
|
||
|
||
&.status-average {
|
||
border-left: 4px solid #f59e0b;
|
||
background: linear-gradient(135deg, #fffbeb 0%, #fefce8 100%);
|
||
}
|
||
|
||
&.status-attention {
|
||
border-left: 4px solid #f97316;
|
||
background: linear-gradient(135deg, #fff7ed 0%, #ffedd5 100%);
|
||
}
|
||
|
||
&.status-poor {
|
||
border-left: 4px solid #ef4444;
|
||
background: linear-gradient(135deg, #fef2f2 0%, #fee2e2 100%);
|
||
}
|
||
|
||
.member-header {
|
||
display: flex;
|
||
justify-content: space-between;
|
||
align-items: flex-start;
|
||
margin-bottom: 1rem;
|
||
|
||
.member-info {
|
||
flex: 1;
|
||
|
||
.member-name {
|
||
font-size: 1.1rem;
|
||
font-weight: 600;
|
||
color: #1e293b;
|
||
margin: 0 0 0.25rem 0;
|
||
}
|
||
|
||
.member-position {
|
||
font-size: 0.85rem;
|
||
color: #64748b;
|
||
margin: 0 0 0.25rem 0;
|
||
}
|
||
|
||
.member-phone {
|
||
font-size: 0.8rem;
|
||
color: #94a3b8;
|
||
margin: 0;
|
||
}
|
||
}
|
||
|
||
.member-status {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
gap: 0.25rem;
|
||
|
||
.status-badge {
|
||
padding: 0.25rem 0.5rem;
|
||
border-radius: 4px;
|
||
font-size: 0.75rem;
|
||
font-weight: 500;
|
||
|
||
&.excellent {
|
||
background: #10b981;
|
||
color: white;
|
||
}
|
||
|
||
&.good {
|
||
background: #3b82f6;
|
||
color: white;
|
||
}
|
||
|
||
&.average {
|
||
background: #f59e0b;
|
||
color: white;
|
||
}
|
||
|
||
&.attention {
|
||
background: #f97316;
|
||
color: white;
|
||
}
|
||
|
||
&.poor {
|
||
background: #ef4444;
|
||
color: white;
|
||
}
|
||
}
|
||
|
||
.join-date {
|
||
font-size: 0.75rem;
|
||
color: #94a3b8;
|
||
}
|
||
}
|
||
}
|
||
|
||
.member-metrics {
|
||
.metric-row {
|
||
display: grid;
|
||
grid-template-columns: 1fr 1fr;
|
||
gap: 1rem;
|
||
margin-bottom: 0.75rem;
|
||
|
||
&:last-child {
|
||
margin-bottom: 0;
|
||
}
|
||
|
||
.metric-item {
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 0.25rem;
|
||
|
||
.metric-label {
|
||
font-size: 0.75rem;
|
||
color: #64748b;
|
||
font-weight: 500;
|
||
}
|
||
|
||
.metric-value {
|
||
font-size: 0.9rem;
|
||
color: #1e293b;
|
||
font-weight: 600;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
.info-icon {
|
||
color: #94a3b8;
|
||
font-size: 0.75rem;
|
||
cursor: pointer;
|
||
opacity: 0.7;
|
||
transition: all 0.2s ease;
|
||
margin-left: 0.25rem;
|
||
|
||
&:hover {
|
||
opacity: 1;
|
||
color: #3b82f6;
|
||
transform: scale(1.1);
|
||
}
|
||
}
|
||
// 客户详情区域
|
||
.customer-detail-section {
|
||
background: white;
|
||
border-radius: 12px;
|
||
padding: 1.5rem;
|
||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
// 移动端适配
|
||
@media (max-width: 768px) {
|
||
.dashboard-header {
|
||
padding: 1rem;
|
||
|
||
.header-text {
|
||
h1 {
|
||
font-size: 1.3rem;
|
||
}
|
||
|
||
p {
|
||
font-size: 0.85rem;
|
||
}
|
||
}
|
||
}
|
||
|
||
.dashboard-main {
|
||
padding: 0.5rem;
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.top-section {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.bottom-section {
|
||
grid-template-columns: 1fr;
|
||
}
|
||
|
||
.BB-section {
|
||
grid-template-columns: 1fr;
|
||
gap: 0.75rem;
|
||
}
|
||
|
||
.right-section {
|
||
height: auto;
|
||
}
|
||
|
||
.action-items-compact {
|
||
height: 300px;
|
||
|
||
:deep(.action-items) {
|
||
.actions-summary {
|
||
grid-template-columns: repeat(4, 1fr);
|
||
|
||
.summary-item {
|
||
padding: 0.4rem;
|
||
|
||
.summary-count {
|
||
font-size: 1rem;
|
||
}
|
||
|
||
.summary-label {
|
||
font-size: 0.6rem;
|
||
}
|
||
}
|
||
}
|
||
|
||
.actions-list {
|
||
max-height: 150px;
|
||
}
|
||
}
|
||
}
|
||
|
||
.team-detail-section {
|
||
padding: 1rem;
|
||
margin-top: 0.75rem;
|
||
|
||
.team-detail-header {
|
||
margin-bottom: 1.5rem;
|
||
|
||
h2 {
|
||
font-size: 1.2rem;
|
||
}
|
||
|
||
.team-summary {
|
||
gap: 1rem;
|
||
|
||
.summary-item {
|
||
|
||
.label,
|
||
.value {
|
||
font-size: 0.8rem;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
.members-grid {
|
||
grid-template-columns: 1fr;
|
||
gap: 0.75rem;
|
||
|
||
.member-card {
|
||
padding: 0.75rem;
|
||
|
||
.member-header {
|
||
flex-direction: column;
|
||
gap: 0.75rem;
|
||
|
||
.member-status {
|
||
align-items: flex-start;
|
||
flex-direction: row;
|
||
gap: 0.5rem;
|
||
}
|
||
}
|
||
|
||
.member-metrics {
|
||
.metric-row {
|
||
grid-template-columns: 1fr;
|
||
gap: 0.5rem;
|
||
margin-bottom: 0.5rem;
|
||
|
||
.metric-item {
|
||
.metric-label {
|
||
font-size: 0.7rem;
|
||
}
|
||
|
||
.metric-value {
|
||
font-size: 0.8rem;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 路由导航顶栏样式
|
||
.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: #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);
|
||
}
|
||
}
|
||
.stage-control {
|
||
margin-left: 20px;
|
||
display: flex;
|
||
align-items: center;
|
||
}
|
||
|
||
.control-label {
|
||
margin-right: 10px;
|
||
font-size: 14px;
|
||
color: #606266;
|
||
}
|
||
|
||
.days-input {
|
||
width: 60px;
|
||
text-align: center;
|
||
margin-right: 10px;
|
||
}
|
||
|
||
.save-button {
|
||
padding: 5px 10px;
|
||
font-size: 12px;
|
||
}
|
||
|
||
.finish-camp-button {
|
||
padding: 8px 16px;
|
||
font-size: 14px;
|
||
background-color: #dc3545;
|
||
color: white;
|
||
border: none;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
transition: background-color 0.2s;
|
||
|
||
&:hover {
|
||
background-color: #c82333;
|
||
}
|
||
}
|
||
</style> |