重构销售时间线组件,增加对客户阶段数据的动态计算和展示 优化API端点配置,新增课1-4后阶段和成交客户的数据接口 调整样式和布局,增加客户类型标签显示 延长HTTP请求超时时间至100秒
1050 lines
26 KiB
Vue
1050 lines
26 KiB
Vue
<template>
|
||
<div class="senior-manager-dashboard">
|
||
<!-- Header -->
|
||
<header class="dashboard-header">
|
||
<div class="header-content">
|
||
<div class="logo-section">
|
||
<div class="header-text">
|
||
<h1>高级经理指挥台</h1>
|
||
<p>统筹多组运营,优化资源配置,驱动业绩增长,实现团队协同发展。</p>
|
||
</div>
|
||
<UserDropdown class="header-ringht" style="margin-left: auto;" />
|
||
</div>
|
||
</div>
|
||
</header>
|
||
<!-- Main Content -->
|
||
<main class="dashboard-main">
|
||
<div class="top-section">
|
||
<CenterOverview
|
||
style="height: 330px;"
|
||
:overallTeamPerformance="overallTeamPerformance"
|
||
/>
|
||
<div class="action-items-compact">
|
||
<TeamAlerts style="height: 300px;" />
|
||
</div>
|
||
</div>
|
||
<StatisticalIndicators
|
||
:customerCommunicationRate="statisticalIndicators.customerCommunicationRate"
|
||
:averageResponseTime="statisticalIndicators.averageResponseTime"
|
||
:timeoutResponseRate="statisticalIndicators.timeoutResponseRate"
|
||
:severeTimeoutRate="statisticalIndicators.severeTimeoutRate"
|
||
:formCompletionRate="statisticalIndicators.formCompletionRate"
|
||
/>
|
||
<!-- Bottom Section -->
|
||
<div class="bottom-section">
|
||
<!-- Left Section - Group Performance Ranking -->
|
||
<div class="left-section">
|
||
<GroupRanking
|
||
:groups="groups"
|
||
:selected-group="selectedGroup"
|
||
@select-group="selectGroup"
|
||
/>
|
||
</div>
|
||
<div class="problem-ranking">
|
||
<!-- 客户迫切解决的问题 -->
|
||
<ProblemRanking :problemRanking="problemRanking" />
|
||
</div>
|
||
<!-- Right Section - Group Comparison -->
|
||
<div class="right-section">
|
||
<GroupComparison
|
||
:groups="groups"
|
||
:teamRanking="teamRanking"
|
||
@select-group="selectGroup"
|
||
/>
|
||
</div>
|
||
</div>
|
||
<!-- Team Members Detail Section -->
|
||
<div class="team-detail-section" v-if="selectedGroup">
|
||
<!-- 团队详情加载状态 -->
|
||
<div v-if="isTeamDetailLoading" class="team-loading">
|
||
<div class="loading-spinner"></div>
|
||
<div class="loading-text">正在加载团队详情...</div>
|
||
</div>
|
||
<!-- 团队详情内容 -->
|
||
<div v-else>
|
||
<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">{{ teamPerformanceDetail.group_details?.length || 0 }}人</span>
|
||
</div>
|
||
<div class="summary-item">
|
||
<span class="label">今日业绩:</span>
|
||
<span class="value">{{ formatCurrency(selectedGroup.todayPerformance) }}</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 teamPerformanceDetail.group_details"
|
||
:key="member.id"
|
||
class="member-card"
|
||
>
|
||
<div class="member-header">
|
||
<div class="member-info">
|
||
<h3 class="member-name">{{ member.name }}</h3>
|
||
</div>
|
||
</div>
|
||
<div class="member-metrics">
|
||
<div class="metric-row">
|
||
<div class="metric-item">
|
||
<span class="metric-label">今日业绩</span>
|
||
<span class="metric-value">{{ member.today_performance }}</span>
|
||
|
||
</div>
|
||
<div class="metric-item">
|
||
<span class="metric-label">月度业绩</span>
|
||
<span class="metric-value">{{ member.monthly_performance }}</span>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div class="metric-row">
|
||
<div class="metric-item">
|
||
<span class="metric-label">转化率</span>
|
||
<span class="metric-value">{{ member.conversion_rate_this_period }}</span>
|
||
</div>
|
||
<div class="metric-item">
|
||
<span class="metric-label">通话次数</span>
|
||
<span class="metric-value">{{ member.call_count_this_period }}</span>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="metric-row">
|
||
<div class="metric-item">
|
||
<span class="metric-label">新增客户</span>
|
||
<span class="metric-value">{{ member.new_customers_this_period }}</span>
|
||
</div>
|
||
<div class="metric-item">
|
||
<span class="metric-label">成交订单</span>
|
||
<span class="metric-value">{{ member.deals_this_period }}</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
|
||
</div>
|
||
</main>
|
||
|
||
<!-- Loading组件 -->
|
||
<Loading :visible="isLoading" text="正在加载数据..." />
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted } from 'vue'
|
||
|
||
import CenterOverview from './components/CenterOverview.vue'
|
||
import GroupComparison from './components/GroupComparison.vue'
|
||
import GroupRanking from './components/GroupRanking.vue'
|
||
import TeamAlerts from '../maneger/components/TeamAlerts.vue'
|
||
import ProblemRanking from './components/ProblemRanking.vue'
|
||
import StatisticalIndicators from './components/StatisticalIndicators.vue'
|
||
import UserDropdown from '@/components/UserDropdown.vue'
|
||
import Loading from '@/components/Loading.vue'
|
||
import { getOverallTeamPerformance,getTotalGroupCount,getConversionRate,getTotalCallCount,
|
||
getNewCustomer,getDepositConversionRate,getActiveCustomerCommunicationRate,getAverageAnswerTime,
|
||
getTimeoutRate,getTableFillingRate,getUrgentNeedToAddress,getTeamRanking,getTeamRankingInfo } from '@/api/senorManger.js'
|
||
|
||
import { useUserStore } from '@/stores/user.js'
|
||
|
||
const customerCommunicationRate = ref(85)
|
||
const averageResponseTime = ref(15)
|
||
const timeoutResponseRate = ref(5)
|
||
const severeTimeoutRate = ref(2)
|
||
const formCompletionRate = ref(90)
|
||
|
||
const userStore = useUserStore()
|
||
|
||
// Loading状态
|
||
const isLoading = ref(false)
|
||
// 团队详情加载状态
|
||
const isTeamDetailLoading = ref(false)
|
||
|
||
// 整体概览
|
||
const overallTeamPerformance = ref({
|
||
totalPerformance: {},
|
||
activeGroups: {},
|
||
conversionRate: {},
|
||
totalCalls: {},
|
||
newCustomers: {},
|
||
depositConversions: {},
|
||
})
|
||
|
||
// 获取整体概览数据--团队总业绩
|
||
async function fetchOverallTeamPerformance() {
|
||
const params={
|
||
user_name: userStore.userInfo.username,
|
||
user_level: userStore.userInfo.user_level.toString()
|
||
}
|
||
// 团队总业绩
|
||
try {
|
||
const response = await getOverallTeamPerformance(params)
|
||
overallTeamPerformance.value.totalPerformance = response.data
|
||
} catch (error) {
|
||
console.error('获取整体概览数据失败:', error)
|
||
}
|
||
}
|
||
// 获取整体概览数据--活跃组数
|
||
async function fetchActiveGroups() {
|
||
const params={
|
||
user_name: userStore.userInfo.username,
|
||
user_level: userStore.userInfo.user_level.toString()
|
||
}
|
||
try {
|
||
const response = await getTotalGroupCount(params)
|
||
overallTeamPerformance.value.activeGroups = response.data
|
||
console.log('活跃组数:', response.data)
|
||
|
||
} catch (error) {
|
||
console.error('获取活跃组数失败:', error)
|
||
}
|
||
}
|
||
// 获取整体概览数据--团队转化率
|
||
async function fetchConversionRate() {
|
||
const params={
|
||
user_name: userStore.userInfo.username,
|
||
user_level: userStore.userInfo.user_level.toString()
|
||
}
|
||
try {
|
||
const response = await getConversionRate(params)
|
||
overallTeamPerformance.value.conversionRate = response.data
|
||
} catch (error) {
|
||
console.error('获取团队转化率失败:', error)
|
||
}
|
||
}
|
||
// 通话次数
|
||
async function fetchTotalCallCount() {
|
||
const params={
|
||
user_name: userStore.userInfo.username,
|
||
user_level: userStore.userInfo.user_level.toString()
|
||
}
|
||
try {
|
||
const response = await getTotalCallCount(params)
|
||
overallTeamPerformance.value.totalCalls = response.data
|
||
} catch (error) {
|
||
console.error('获取通话次数失败:', error)
|
||
}
|
||
}
|
||
// 新增客户
|
||
async function fetchNewCustomers() {
|
||
const params={
|
||
user_name: userStore.userInfo.username,
|
||
user_level: userStore.userInfo.user_level.toString()
|
||
}
|
||
try {
|
||
const response = await getNewCustomer(params)
|
||
overallTeamPerformance.value.newCustomers = response.data
|
||
} catch (error) {
|
||
console.error('获取新增客户失败:', error)
|
||
}
|
||
}
|
||
// 定金转化
|
||
async function fetchDepositConversions() {
|
||
const params={
|
||
user_name: userStore.userInfo.username,
|
||
user_level: userStore.userInfo.user_level.toString()
|
||
}
|
||
try {
|
||
const response = await getDepositConversionRate(params)
|
||
overallTeamPerformance.value.depositConversions = response.data
|
||
console.log(99888999,response.data)
|
||
|
||
} catch (error) {
|
||
console.error('获取定金转化失败:', error)
|
||
}
|
||
}
|
||
|
||
const statisticalIndicators = ref({
|
||
customerCommunicationRate: 0,
|
||
averageResponseTime: 0,
|
||
timeoutResponseRate: 0,
|
||
severeTimeoutRate: 0,
|
||
formCompletionRate: 0,
|
||
})
|
||
// 统计指标--活跃客户沟通率
|
||
async function fetchCustomerCommunicationRate() {
|
||
const params={
|
||
user_name: userStore.userInfo.username,
|
||
user_level: userStore.userInfo.user_level.toString()
|
||
}
|
||
try {
|
||
const response = await getActiveCustomerCommunicationRate(params)
|
||
statisticalIndicators.value.customerCommunicationRate = response.data
|
||
} catch (error) {
|
||
console.error('获取活跃客户沟通率失败:', error)
|
||
}
|
||
}
|
||
// 统计指标--平均应答时间
|
||
async function fetchAverageResponseTime() {
|
||
const params={
|
||
user_name: userStore.userInfo.username,
|
||
user_level: userStore.userInfo.user_level.toString()
|
||
}
|
||
try {
|
||
const response = await getAverageAnswerTime(params)
|
||
statisticalIndicators.value.averageResponseTime = response.data
|
||
} catch (error) {
|
||
console.error('获取平均应答时间失败:', error)
|
||
}
|
||
}
|
||
// 统计指标--超时应答率、严重超时应答率
|
||
async function fetchTimeoutRate() {
|
||
const params={
|
||
user_name: userStore.userInfo.username,
|
||
user_level: userStore.userInfo.user_level.toString()
|
||
}
|
||
try {
|
||
const response = await getTimeoutRate(params)
|
||
statisticalIndicators.value.timeoutResponseRate = response.data
|
||
} catch (error) {
|
||
console.error('获取超时应答率失败:', error)
|
||
}
|
||
}
|
||
// 统计指标--表格填写率
|
||
async function fetchTableFillingRate() {
|
||
const params={
|
||
user_name: userStore.userInfo.username,
|
||
user_level: userStore.userInfo.user_level.toString()
|
||
}
|
||
try {
|
||
const response = await getTableFillingRate(params)
|
||
statisticalIndicators.value.formCompletionRate = response.data
|
||
} catch (error) {
|
||
console.error('获取表格填写率失败:', error)
|
||
}
|
||
}
|
||
const problemRanking = ref({})
|
||
|
||
// 客户迫切解决的问题
|
||
async function fetchUrgentNeedToAddress() {
|
||
const params={
|
||
user_name: userStore.userInfo.username,
|
||
user_level: userStore.userInfo.user_level.toString()
|
||
}
|
||
try {
|
||
const response = await getUrgentNeedToAddress(params)
|
||
problemRanking.value = response.data
|
||
/**
|
||
* "data": {
|
||
"user_name": "陈盼良",
|
||
"user_level": 3,
|
||
"calculate_urgent_issue_ratio": {
|
||
"成绩提升": "0.00%",
|
||
"少玩手机": "0.00%",
|
||
"回归学校": "0.00%",
|
||
"心理健康": "0.00%"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 团队详情加载样式
|
||
.team-loading {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 3rem;
|
||
|
||
.loading-spinner {
|
||
width: 40px;
|
||
height: 40px;
|
||
border: 4px solid #f3f3f3;
|
||
border-top: 4px solid #3498db;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.loading-text {
|
||
color: #666;
|
||
font-size: 0.9rem;
|
||
}
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); },
|
||
"urgent_issue_consultations": {
|
||
"成绩提升": 0,
|
||
"少玩手机": 0,
|
||
"回归学校": 0,
|
||
"心理健康": 0
|
||
}
|
||
}
|
||
*/
|
||
// console.log('客户迫切解决的问题:', response.data)
|
||
} catch (error) {
|
||
console.error('获取客户迫切解决的问题失败:', error)
|
||
}
|
||
}
|
||
//综合表现排名
|
||
const teamRanking = ref({})
|
||
|
||
async function fetchTeamRanking() {
|
||
const params={
|
||
user_name: userStore.userInfo.username,
|
||
user_level: userStore.userInfo.user_level.toString(),
|
||
}
|
||
try {
|
||
const response = await getTeamRanking(params)
|
||
teamRanking.value = response.data
|
||
} catch (error) {
|
||
console.error('获取团队业绩排名失败:', error)
|
||
}
|
||
}
|
||
|
||
// 团队业绩详情
|
||
const teamPerformanceDetail = ref({})
|
||
async function fetchTeamPerformanceDetail(department) {
|
||
const params={
|
||
user_name: userStore.userInfo.username,
|
||
user_level: userStore.userInfo.user_level.toString(),
|
||
department: department
|
||
}
|
||
try {
|
||
teamPerformanceDetail.value = {}
|
||
const response = await getTeamRankingInfo(params)
|
||
teamPerformanceDetail.value = response.data
|
||
} catch (error) {
|
||
console.error('获取团队业绩详情失败:', error)
|
||
}
|
||
}
|
||
// 初始化时获取数据
|
||
onMounted(async ()=>{
|
||
try {
|
||
isLoading.value = true
|
||
await fetchOverallTeamPerformance()
|
||
await fetchActiveGroups()
|
||
await fetchConversionRate()
|
||
await fetchTotalCallCount()
|
||
await fetchNewCustomers()
|
||
await fetchDepositConversions()
|
||
await fetchCustomerCommunicationRate()
|
||
await fetchAverageResponseTime()
|
||
await fetchTimeoutRate()
|
||
await fetchTableFillingRate()
|
||
await fetchUrgentNeedToAddress()
|
||
await fetchTeamRanking()
|
||
} catch (error) {
|
||
console.error('数据加载失败:', error)
|
||
} finally {
|
||
isLoading.value = false
|
||
}
|
||
})
|
||
|
||
// 组别数据
|
||
const groups=[]
|
||
// 当前选中的组别,默认为第一个
|
||
const selectedGroup = ref(groups[0])
|
||
|
||
// 选择组别函数
|
||
const selectGroup = async (group) => {
|
||
selectedGroup.value = group
|
||
// 获取部门名称并调用团队业绩详情接口
|
||
// 从teamRanking数据中查找对应的原始部门名称
|
||
let department = group.name
|
||
if (teamRanking.value && teamRanking.value.formal_plural) {
|
||
// 在formal_plural中查找匹配的部门名称
|
||
const departmentKeys = Object.keys(teamRanking.value.formal_plural)
|
||
const matchedDepartment = departmentKeys.find(key => {
|
||
// 提取部门名称的主要部分进行匹配
|
||
const mainName = key.split('-')[0] || key
|
||
return group.name.includes(mainName) || mainName.includes(group.name)
|
||
})
|
||
if (matchedDepartment) {
|
||
department = matchedDepartment
|
||
}
|
||
}
|
||
console.log('选中的部门:', group.name, '-> 发送的部门名称:', department)
|
||
|
||
// 设置团队详情加载状态
|
||
isTeamDetailLoading.value = true
|
||
try {
|
||
await fetchTeamPerformanceDetail(department)
|
||
} catch (error) {
|
||
console.error('获取团队详情失败:', error)
|
||
} finally {
|
||
isTeamDetailLoading.value = false
|
||
}
|
||
}
|
||
|
||
// 格式化货币
|
||
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] || '未知'
|
||
}
|
||
</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;
|
||
}
|
||
}
|
||
}
|
||
|
||
.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 1fr;
|
||
gap: 1rem;
|
||
margin-top: 1rem;
|
||
}
|
||
|
||
.left-section, .right-section{
|
||
display: flex;
|
||
flex-direction: column;
|
||
gap: 1rem;
|
||
}
|
||
|
||
.right-section {
|
||
overflow: auto;
|
||
}
|
||
|
||
|
||
|
||
.action-items-compact {
|
||
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: 200px;
|
||
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;
|
||
}
|
||
}
|
||
|
||
// 团队成员详情区域
|
||
.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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 客户详情区域
|
||
.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;
|
||
}
|
||
|
||
// 团队详情加载状态
|
||
.team-loading {
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
padding: 3rem;
|
||
|
||
.loading-spinner {
|
||
width: 40px;
|
||
height: 40px;
|
||
border: 3px solid #e2e8f0;
|
||
border-top: 3px solid #3b82f6;
|
||
border-radius: 50%;
|
||
animation: spin 1s linear infinite;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
.loading-text {
|
||
color: #64748b;
|
||
font-size: 0.9rem;
|
||
}
|
||
}
|
||
|
||
@keyframes spin {
|
||
0% { transform: rotate(0deg); }
|
||
100% { transform: rotate(360deg); }
|
||
}
|
||
|
||
// 移动端适配
|
||
@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;
|
||
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;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</style> |