Files
DJKB/my-vue-app/src/views/topOne/topone.vue
lbw_9527443 147238244e feat(topOne): 重构核心业绩指标组件并添加用户下拉菜单
- 重构KpiMetrics组件以使用API获取的真实数据
- 添加UserDropdown组件到topOne页面
- 简化http工具中的get方法
- 更新KPI卡片显示逻辑和标签文本
2025-08-15 22:14:04 +08:00

2090 lines
37 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="dashboard-container">
<!-- 页面标题 -->
<div class="dashboard-header">
<h1>管理者数据看板</h1>
<div class="header-actions">
<button class="refresh-btn" @click="refreshData">
<i class="icon-refresh"></i> 刷新数据
</button>
</div>
<!-- 头像 -->
<UserDropdown />
</div>
<!-- 第一行核心业绩指标销售实时进度下发任务 -->
<div class="dashboard-row row-1">
<!-- 核心业绩指标 -->
<kpi-metrics :kpi-data="totalDeals" :format-number="formatNumber" />
<!-- 销售实时进度 -->
<sales-progress :sales-data="salesData" />
<!-- 下发任务 -->
<task-list
:tasks="tasks"
:format-date="formatDate"
:get-task-status-text="getTaskStatusText"
@show-task-modal="showTaskModal = true"
/>
</div>
<!-- 第二行 -->
<div class="dashboard-row row-3">
<!-- 转化漏斗 -->
<funnel-chart
:funnel-data="funnelData"
:comparison-data="comparisonData"
@time-range-change="handleTimeRangeChange"
/>
<!-- 销售个人业绩排行榜 -->
<personal-sales-ranking
:ranking-data="rankingData"
:format-number="formatNumber"
:get-rank-class="getRankClass"
@period-change="handleRankingPeriodChange"
/>
<!-- 优质通话 -->
<quality-calls
:quality-calls="qualityCalls"
@play-call="playCall"
@download-call="downloadCall"
/>
</div>
<!-- 第三行 -->
<div class="dashboard-row row-3">
<!-- 业绩排行榜 -->
<ranking-list
:ranking-data="rankingData"
:format-number="formatNumber"
:get-rank-class="getRankClass"
/>
<!-- 客户类型占比 -->
<customer-type :ranking-data="customerTypeData" />
<!-- 客户迫切解决的问题排行榜 -->
<problem-ranking :ranking-data="problemRankingData" />
</div>
<!-- 第四行详细数据表格和数据详情 -->
<div class="dashboard-row">
<CampManagement />
</div>
<!-- 第五行 -->
<div class="dashboard-row row-4">
<DetailedDataTable
:table-data="tableData"
v-model:selected-person="selectedPerson"
/>
<DataDetailCard :selected-person="selectedPerson" />
</div>
<!-- 新建任务模态框 -->
<div
v-if="showTaskModal"
class="modal-overlay"
@click="showTaskModal = false"
>
<div class="modal-content" @click.stop>
<div class="modal-header">
<h3>新建任务</h3>
<button class="close-btn" @click="showTaskModal = false">×</button>
</div>
<div class="modal-body">
<div class="form-group">
<label>任务标题</label>
<input
v-model="newTask.title"
type="text"
placeholder="请输入任务标题"
/>
</div>
<div class="form-group">
<label>分配给</label>
<select v-model="newTask.assignee">
<option value="">请选择员工</option>
<option
v-for="employee in employees"
:key="employee.id"
:value="employee.name"
>
{{ employee.name }}
</option>
</select>
</div>
<div class="form-group">
<label>截止日期</label>
<input v-model="newTask.deadline" type="date" />
</div>
<div class="form-group">
<label>任务描述</label>
<textarea
v-model="newTask.description"
placeholder="请输入任务描述"
></textarea>
</div>
</div>
<div class="modal-footer">
<button class="cancel-btn" @click="showTaskModal = false">
取消
</button>
<button class="confirm-btn" @click="createTask">创建任务</button>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.dashboard-container {
height: 100vh;
overflow-y: auto;
overflow-x: hidden;
padding: 20px;
box-sizing: border-box;
background-color: #f0f2f5;
/* 触摸设备滚动优化 */
-webkit-overflow-scrolling: touch;
}
</style>
<script setup>
import { ref, reactive, computed, onMounted, nextTick } from "vue";
import UserDropdown from "@/components/UserDropdown.vue";
import KpiMetrics from "./components/KpiMetrics.vue";
import SalesProgress from "./components/SalesProgress.vue";
import TaskList from "./components/TaskList.vue";
import FunnelChart from "./components/FunnelChart.vue";
import CustomerProfile from "./components/CustomerProfile.vue";
import CustomerType from "../secondTop/components/CustomerType.vue";
import ProblemRanking from "../secondTop/components/ProblemRanking.vue";
import RankingList from "./components/RankingList.vue";
import PersonalSalesRanking from "./components/PersonalSalesRanking.vue";
import CommunicationData from "./components/CommunicationData.vue";
import QualityCalls from "./components/QualityCalls.vue";
import DataTable from "./components/DataTable.vue";
import DataDetail from "./components/DataDetail.vue";
import CampManagement from "./components/CampManagement.vue";
import DetailedDataTable from "./components/DetailedDataTable.vue";
import DataDetailCard from "./components/DataDetailCard.vue";
import { getOverallCompanyPerformance,getCompanyDepositConversionRate,getCompanyTotalCallCount,getCompanyNewCustomer,getCompanyConversionRate,getCompanyRealTimeProgress
,getCompanyConversionRateVsLast,getSalesMonthlyPerformance,getCenterPerformanceRank,getCustomerTypeDistribution,getUrgentNeedToAddress,getLevelTree,getDetailedDataTable
} from "@/api/top";
// 响应式数据
const kpiData = reactive({
salesAmount: 2850000,
salesTrend: 12.5,
dealCustomers: 156,
customerTrend: 8.3,
conversionRate: 23.8,
conversionTrend: 5.2,
});
const salesData = reactive({
successTip: "今日已成交23单超额完成目标120%",
warningTip: "有5个重要客户需要紧急跟进",
infoTip: "下午3点有重要客户会议请提前准备",
});
const communicationData = reactive({
totalDuration: 128.5,
effectiveRate: 78.3,
firstResponseTime: 45,
connectionRate: 92.1,
});
const rankingPeriod = ref("month");
const rankingData = ref([
{ id: 1, name: "张三", department: "销售一部", performance: 125000 },
{ id: 2, name: "李四", department: "销售二部", performance: 118000 },
{ id: 3, name: "王五", department: "销售一部", performance: 112000 },
{ id: 4, name: "赵六", department: "销售三部", performance: 98000 },
{ id: 5, name: "钱七", department: "销售二部", performance: 89000 },
]);
const funnelData = ref([
{ name: "线索", count: 1000, percentage: 100, color: "#4CAF50" },
{ name: "沟通", count: 650, percentage: 65, color: "#2196F3" },
{ name: "意向", count: 380, percentage: 38, color: "#FF9800" },
{ name: "预约", count: 180, percentage: 18, color: "#9C27B0" },
{ name: "成交", count: 120, percentage: 12, color: "#F44336" },
]);
// 对比数据(上期/上月数据)
const comparisonData = ref({
period: [
{ name: "线索", count: 850 },
{ name: "沟通", count: 580 },
{ name: "意向", count: 320 },
{ name: "预约", count: 150 },
{ name: "成交", count: 95 },
],
month: [
{ name: "线索", count: 920 },
{ name: "沟通", count: 610 },
{ name: "意向", count: 350 },
{ name: "预约", count: 165 },
{ name: "成交", count: 108 },
],
});
const realtimeActivities = ref([
{
id: 1,
type: "deal",
message: "张三成功签约客户李总,金额 ¥50,000",
timestamp: new Date(),
},
{
id: 2,
type: "lost",
message: "王五的客户刘总流失,原因:价格因素",
timestamp: new Date(Date.now() - 300000),
},
{
id: 3,
type: "deal",
message: "赵六成功签约客户陈总,金额 ¥80,000",
timestamp: new Date(Date.now() - 600000),
},
{
id: 4,
type: "call",
message: "李四完成与客户的重要通话时长45分钟",
timestamp: new Date(Date.now() - 900000),
},
]);
const qualityCalls = ref([
{ id: 1, callerName: "张三 - 李总", duration: 25, score: 9.2 },
{ id: 2, callerName: "王五 - 陈总", duration: 18, score: 8.8 },
{ id: 3, callerName: "赵六 - 刘总", duration: 32, score: 9.5 },
]);
const parentTypes = ref([
{ name: "关注学习成绩", percentage: 45, color: "#4CAF50" },
{ name: "重视综合素质", percentage: 30, color: "#2196F3" },
{ name: "注重兴趣培养", percentage: 25, color: "#FF9800" },
]);
const hotQuestions = ref([
{ id: 1, text: "如何提高孩子的学习成绩?", count: 156 },
{ id: 2, text: "课程费用和优惠政策?", count: 142 },
{ id: 3, text: "老师的教学经验如何?", count: 128 },
{ id: 4, text: "上课时间安排是否灵活?", count: 115 },
{ id: 5, text: "孩子不爱学习怎么办?", count: 98 },
]);
// 客户类型数据
const customerTypeData = ref([
{ name: "18-25岁", value: 120 },
{ name: "26-35岁", value: 200 },
{ name: "36-45岁", value: 150 },
{ name: "46-55岁", value: 80 },
{ name: "55岁以上", value: 50 },
]);
// 客户问题排行榜数据
const problemRankingData = ref([
{ name: "提高学习成绩", value: "65%" },
{ name: "课程费用咨询", value: "58%" },
{ name: "师资力量了解", value: "52%" },
{ name: "时间安排问题", value: "48%" },
{ name: "学习兴趣培养", value: "42%" },
{ name: "学习方法指导", value: "38%" },
{ name: "课程内容详情", value: "35%" },
]);
// 表格数据和筛选
const tableData = ref([
{
id: 1,
name: "张三",
position: "销售经理",
department: "销售一部",
dealRate: 85,
callDuration: 1200,
callCount: 45,
dealAmount: 280000,
},
{
id: 2,
name: "李四",
position: "销售专员",
department: "销售一部",
dealRate: 72,
callDuration: 980,
callCount: 38,
dealAmount: 195000,
},
{
id: 3,
name: "王五",
position: "销售经理",
department: "销售二部",
dealRate: 68,
callDuration: 1100,
callCount: 42,
dealAmount: 220000,
},
{
id: 4,
name: "赵六",
position: "销售专员",
department: "销售二部",
dealRate: 65,
callDuration: 850,
callCount: 35,
dealAmount: 165000,
},
{
id: 5,
name: "钱七",
position: "销售助理",
department: "销售三部",
dealRate: 58,
callDuration: 720,
callCount: 28,
dealAmount: 125000,
},
{
id: 6,
name: "孙八",
position: "销售经理",
department: "销售三部",
dealRate: 78,
callDuration: 1050,
callCount: 40,
dealAmount: 245000,
},
{
id: 7,
name: "周九",
position: "销售专员",
department: "销售一部",
dealRate: 62,
callDuration: 890,
callCount: 33,
dealAmount: 158000,
},
{
id: 8,
name: "吴十",
position: "销售助理",
department: "销售二部",
dealRate: 55,
callDuration: 680,
callCount: 25,
dealAmount: 112000,
},
]);
const filters = ref({
department: "",
position: "",
timeRange: "month",
dealStatus: "",
});
const sortField = ref("dealRate");
const sortOrder = ref("desc");
const selectedPerson = ref(null);
const tasks = ref([
{
id: 1,
title: "完成Q4销售目标制定",
assignee: "张三",
deadline: "2024-01-15",
status: "pending",
},
{
id: 2,
title: "客户满意度调研",
assignee: "李四",
deadline: "2024-01-20",
status: "in-progress",
},
{
id: 3,
title: "新产品培训准备",
assignee: "王五",
deadline: "2024-01-10",
status: "completed",
},
]);
const employees = ref([
{ id: 1, name: "张三" },
{ id: 2, name: "李四" },
{ id: 3, name: "王五" },
{ id: 4, name: "赵六" },
{ id: 5, name: "钱七" },
]);
const showTaskModal = ref(false);
const newTask = reactive({
title: "",
assignee: "",
deadline: "",
description: "",
});
// 计算属性
const filteredTableData = computed(() => {
let filtered = tableData.value;
// 应用筛选器
if (filters.value.department) {
filtered = filtered.filter(
(item) => item.department === filters.value.department
);
}
if (filters.value.position) {
filtered = filtered.filter(
(item) => item.position === filters.value.position
);
}
// 排序
return filtered.sort((a, b) => {
const aValue = a[sortField.value];
const bValue = b[sortField.value];
if (sortOrder.value === "desc") {
return bValue - aValue;
} else {
return aValue - bValue;
}
});
});
// 方法
const refreshData = () => {
// 刷新数据逻辑
console.log("刷新数据");
};
// 处理时间范围变化
const handleTimeRangeChange = (timeRange) => {
console.log("时间范围变化:", timeRange);
// 这里可以根据时间范围重新获取数据
// 例如fetchFunnelData(timeRange)
};
const sortBy = (field) => {
if (sortField.value === field) {
sortOrder.value = sortOrder.value === "desc" ? "asc" : "desc";
} else {
sortField.value = field;
sortOrder.value = "desc";
}
};
const getRateClass = (rate) => {
if (rate >= 80) return "high";
if (rate >= 60) return "medium";
return "low";
};
const getRateColor = (rate) => {
if (rate >= 80) return "#4CAF50";
if (rate >= 60) return "#FF9800";
return "#f44336";
};
const getRankClass = (index) => {
if (index === 0) return "gold";
if (index === 1) return "silver";
if (index === 2) return "bronze";
return "";
};
const formatNumber = (num) => {
if (num >= 10000) {
return (num / 10000).toFixed(1) + "万";
}
return num.toLocaleString();
};
const getActivityIcon = (type) => {
const icons = {
deal: "icon-check-circle",
lost: "icon-x-circle",
call: "icon-phone",
};
return icons[type] || "icon-info";
};
const formatTime = (timestamp) => {
const now = new Date();
const diff = now - timestamp;
const minutes = Math.floor(diff / 60000);
if (minutes < 1) return "刚刚";
if (minutes < 60) return `${minutes}分钟前`;
const hours = Math.floor(minutes / 60);
return `${hours}小时前`;
};
const formatDate = (dateString) => {
return new Date(dateString).toLocaleDateString("zh-CN");
};
const formatDuration = (minutes) => {
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
return hours > 0 ? `${hours}h${mins}m` : `${mins}m`;
};
const selectPerson = (person) => {
selectedPerson.value = person;
};
const getTaskStatusText = (status) => {
const statusMap = {
pending: "待处理",
"in-progress": "进行中",
completed: "已完成",
};
return statusMap[status] || status;
};
const playCall = (callId) => {
console.log("播放通话录音:", callId);
};
const downloadCall = (callId) => {
console.log("下载通话录音:", callId);
};
const createTask = () => {
if (!newTask.title || !newTask.assignee || !newTask.deadline) {
alert("请填写完整信息");
return;
}
const task = {
id: Date.now(),
title: newTask.title,
assignee: newTask.assignee,
deadline: newTask.deadline,
status: "pending",
};
tasks.value.unshift(task);
// 重置表单
Object.assign(newTask, {
title: "",
assignee: "",
deadline: "",
description: "",
});
showTaskModal.value = false;
};
// 核心数据
const totalDeals = ref({});
// 核心数据--总成交金额
async function getTotalDeals() {
try {
const res1 = await getOverallCompanyPerformance()
const res2=await getCompanyDepositConversionRate()
const res3=await getCompanyTotalCallCount()
const res4=await getCompanyNewCustomer()
const res5=await getCompanyConversionRate()
totalDeals.value={
totalDeals:res1.data,
totalAmount:res1.data,
conversionRate:res2.data,
totalCallCount:res3.data,
newCustomer:res4.data,
conversionRate:res5.data,
}
} catch (error) {
console.error("获取总成交金额失败:", error);
}
}
onMounted(async() => {
// 页面初始化逻辑
await getTotalDeals()
});
</script>
<style scoped>
.dashboard-container {
padding: 20px;
background-color: #f5f7fa;
min-height: 100vh;
max-width: 1400px;
margin: 0 auto;
}
.dashboard-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
width: 100%;
}
.dashboard-header h1 {
font-size: 28px;
font-weight: 600;
color: #1a202c;
margin: 0;
}
.header-actions {
display: flex;
gap: 12px;
}
.refresh-btn {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
background: #4299e1;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
font-size: 14px;
transition: background-color 0.2s;
}
.refresh-btn:hover {
background: #3182ce;
}
.metrics-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 20px;
margin-bottom: 24px;
}
.metric-card {
background: white;
padding: 24px;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.metric-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
}
.metric-header h3 {
font-size: 16px;
font-weight: 600;
color: #2d3748;
margin: 0;
}
.metric-period {
font-size: 12px;
color: #718096;
background: #edf2f7;
padding: 4px 8px;
border-radius: 4px;
}
.kpi-metrics {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 16px;
padding: 10px;
}
.kpi-item {
text-align: center;
padding: 12px;
background: #f8fafc;
border-radius: 8px;
}
.kpi-label {
font-size: 12px;
color: #718096;
margin-bottom: 8px;
}
.kpi-value {
font-size: 20px;
font-weight: 700;
color: #1a202c;
margin-bottom: 4px;
}
.kpi-trend {
font-size: 12px;
font-weight: 600;
padding: 2px 6px;
border-radius: 4px;
display: inline-block;
}
.kpi-trend.positive {
color: #38a169;
background: #f0fff4;
}
.kpi-trend.negative {
color: #e53e3e;
background: #fff5f5;
}
.communication-cards {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12px;
padding: 10px;
}
.comm-card {
display: flex;
align-items: center;
gap: 12px;
padding: 16px;
background: #f8fafc;
border-radius: 8px;
border: 1px solid #e2e8f0;
transition: all 0.3s ease;
}
.comm-card:hover {
background: #f1f5f9;
border-color: #cbd5e1;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.card-icon {
font-size: 24px;
width: 40px;
height: 40px;
display: flex;
align-items: center;
justify-content: center;
background: #fff;
border-radius: 50%;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.card-content {
flex: 1;
}
.card-label {
font-size: 12px;
color: #64748b;
margin-bottom: 4px;
}
.card-value {
font-size: 18px;
font-weight: 600;
color: #1e293b;
}
.sales-progress-tips {
display: flex;
flex-direction: column;
gap: 12px;
padding: 10px;
}
.tip-item {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-radius: 6px;
font-size: 14px;
}
.tip-item.success {
background: #f0fff4;
color: #38a169;
border-left: 3px solid #38a169;
}
.tip-item.warning {
background: #fffbeb;
color: #d69e2e;
border-left: 3px solid #d69e2e;
}
.tip-item.info {
background: #ebf8ff;
color: #4299e1;
border-left: 3px solid #4299e1;
}
.stat-item {
display: flex;
flex-direction: column;
gap: 4px;
}
.stat-label {
font-size: 12px;
color: #718096;
}
.stat-value {
font-size: 24px;
font-weight: 700;
color: #1a202c;
}
.stat-value.success {
color: #38a169;
}
.stat-value.danger {
color: #e53e3e;
}
.dashboard-row {
display: grid;
gap: 20px;
margin-bottom: 24px;
}
.row-1 {
grid-template-columns: 2fr 1fr 1fr;
height: 350px;
}
.row-2 {
grid-template-columns: 1fr 1fr 1fr;
height: 300px;
}
.row-3 {
grid-template-columns: 1fr 1fr 1fr;
height: 400px;
}
.row-3 .dashboard-card {
height: 400px;
overflow: hidden;
}
.row-3 .customer-profile {
height: calc(100% - 60px);
overflow-y: auto;
}
.row-4 {
grid-template-columns: 2fr 1fr;
gap: 20px;
}
.table-section,
.detail-section {
height: 600px;
overflow: hidden;
}
.data-table-container {
height: calc(100% - 60px);
overflow-y: auto;
}
.detail-content {
height: calc(100% - 60px);
overflow-y: auto;
}
.data-table tbody tr {
cursor: pointer;
transition: background-color 0.2s ease;
}
.data-table tbody tr:hover {
background-color: #f8fafc;
}
.data-table tbody tr.selected {
background-color: #e0f2fe;
border-left: 4px solid #0ea5e9;
}
.no-selection {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: #64748b;
text-align: center;
}
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.5;
}
.person-detail {
padding: 20px;
}
.detail-header {
display: flex;
align-items: center;
gap: 16px;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #e2e8f0;
}
.detail-avatar {
width: 60px;
height: 60px;
border-radius: 50%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
font-weight: 600;
}
.detail-info h4 {
margin: 0 0 4px 0;
font-size: 20px;
color: #1e293b;
}
.detail-info p {
margin: 0;
color: #64748b;
font-size: 14px;
}
.detail-placeholder {
text-align: center;
padding: 40px 20px;
color: #64748b;
}
.detail-placeholder p:first-child {
font-size: 18px;
font-weight: 600;
margin-bottom: 12px;
color: #475569;
}
.placeholder-text {
font-size: 14px;
line-height: 1.6;
opacity: 0.8;
}
.dashboard-row .dashboard-card {
/* height: 400px; */
}
.row-4 .dashboard-card {
height: auto;
min-height: 500px;
}
.dashboard-card.full-width {
width: 100%;
}
.dashboard-card {
background: white;
border-radius: 12px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
overflow: hidden;
}
.card-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px 16px;
border-bottom: 1px solid #e2e8f0;
}
.card-header h3 {
font-size: 18px;
font-weight: 600;
color: #1a202c;
margin: 0;
}
.period-select {
padding: 4px 8px;
border: 1px solid #e2e8f0;
border-radius: 4px;
font-size: 12px;
background: white;
}
.live-indicator {
color: #e53e3e;
font-size: 12px;
font-weight: 600;
}
.view-all-btn,
.add-task-btn {
padding: 6px 12px;
background: #4299e1;
color: white;
border: none;
border-radius: 4px;
font-size: 12px;
cursor: pointer;
}
.ranking-list {
padding: 0 24px 24px;
}
.ranking-item {
display: flex;
align-items: center;
gap: 16px;
padding: 12px 0;
border-bottom: 1px solid #f7fafc;
}
.ranking-item:last-child {
border-bottom: none;
}
.rank-number {
width: 32px;
height: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-weight: 700;
font-size: 14px;
background: #edf2f7;
color: #4a5568;
}
.rank-number.gold {
background: #ffd700;
color: white;
}
.rank-number.silver {
background: #c0c0c0;
color: white;
}
.rank-number.bronze {
background: #cd7f32;
color: white;
}
.employee-info {
flex: 1;
}
.employee-name {
font-weight: 600;
color: #1a202c;
margin-bottom: 2px;
}
.employee-dept {
font-size: 12px;
color: #718096;
}
.performance-value {
font-weight: 700;
color: #1a202c;
}
.funnel-chart {
padding: 24px;
}
.funnel-stage {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 12px;
}
.funnel-stage:last-child {
margin-bottom: 0;
}
.stage-bar {
height: 40px;
display: flex;
align-items: center;
justify-content: space-between;
padding: 0 16px;
border-radius: 6px;
color: white;
font-weight: 600;
min-width: 120px;
}
.stage-percentage {
font-weight: 600;
color: #4a5568;
min-width: 40px;
}
.activity-feed {
padding: 0 24px 24px;
max-height: 300px;
overflow-y: auto;
}
.activity-item {
display: flex;
gap: 12px;
padding: 12px 0;
border-bottom: 1px solid #f7fafc;
}
.activity-item:last-child {
border-bottom: none;
}
.activity-icon {
width: 32px;
height: 32px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.activity-icon.deal {
background: #f0fff4;
color: #38a169;
}
.activity-icon.lost {
background: #fff5f5;
color: #e53e3e;
}
.activity-icon.call {
background: #ebf8ff;
color: #4299e1;
}
.activity-content {
flex: 1;
}
.activity-text {
font-size: 14px;
color: #1a202c;
margin-bottom: 4px;
}
.activity-time {
font-size: 12px;
color: #718096;
}
.quality-calls {
padding: 0 24px 24px;
}
.call-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 0;
border-bottom: 1px solid #f7fafc;
}
.call-item:last-child {
border-bottom: none;
}
.caller-name {
font-weight: 600;
color: #1a202c;
margin-bottom: 4px;
}
.call-details {
display: flex;
gap: 12px;
font-size: 12px;
color: #718096;
}
.call-actions {
display: flex;
gap: 8px;
}
.play-btn,
.download-btn {
width: 32px;
height: 32px;
border: none;
border-radius: 4px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
}
.play-btn {
background: #ebf8ff;
color: #4299e1;
}
.download-btn {
background: #f7fafc;
color: #4a5568;
}
.customer-profile {
padding: 0 10px;
}
.profile-section {
margin-bottom: 24px;
}
.profile-section:last-child {
margin-bottom: 0;
}
.profile-section h4 {
font-size: 16px;
font-weight: 600;
color: #1a202c;
margin-bottom: 16px;
}
.parent-types {
display: flex;
flex-direction: column;
gap: 12px;
}
.parent-type-item {
display: flex;
flex-direction: column;
gap: 6px;
}
.type-info {
display: flex;
justify-content: space-between;
align-items: center;
}
.type-name {
font-size: 14px;
color: #1a202c;
}
.type-percentage {
font-size: 14px;
font-weight: 600;
color: #4a5568;
}
.type-bar {
height: 8px;
background: #edf2f7;
border-radius: 4px;
overflow: hidden;
}
.type-fill {
height: 100%;
transition: width 0.3s ease;
}
.hot-questions {
display: flex;
flex-direction: column;
gap: 12px;
}
.question-item {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 0;
}
.question-rank {
width: 24px;
height: 24px;
background: #4299e1;
color: white;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
font-size: 12px;
font-weight: 600;
flex-shrink: 0;
}
.question-content {
flex: 1;
}
.question-text {
font-size: 14px;
color: #1a202c;
margin-bottom: 2px;
}
.question-count {
font-size: 12px;
color: #718096;
}
/* 数据表格样式 */
.data-table-container {
padding: 24px;
}
.table-filters {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 16px;
margin-bottom: 24px;
padding: 16px;
background: #f8fafc;
border-radius: 8px;
}
.filter-group {
display: flex;
flex-direction: column;
gap: 4px;
}
.filter-group label {
font-size: 12px;
font-weight: 600;
color: #4a5568;
text-transform: uppercase;
}
.filter-group select {
padding: 8px 12px;
border: 1px solid #e2e8f0;
border-radius: 6px;
background: white;
font-size: 14px;
color: #1a202c;
cursor: pointer;
transition: border-color 0.2s;
}
.filter-group select:focus {
outline: none;
border-color: #4299e1;
box-shadow: 0 0 0 3px rgba(66, 153, 225, 0.1);
}
.data-table {
overflow-x: auto;
border-radius: 8px;
border: 1px solid #e2e8f0;
}
.data-table table {
width: 100%;
border-collapse: collapse;
background: white;
}
.data-table th {
background: #f7fafc;
padding: 12px 16px;
text-align: left;
font-weight: 600;
color: #4a5568;
border-bottom: 1px solid #e2e8f0;
font-size: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.data-table th.sortable {
cursor: pointer;
user-select: none;
position: relative;
}
.data-table th.sortable:hover {
background: #edf2f7;
}
.sort-icon {
margin-left: 4px;
opacity: 0.5;
transition: opacity 0.2s;
}
.sort-icon.active {
opacity: 1;
color: #4299e1;
}
.data-table td {
padding: 16px;
border-bottom: 1px solid #f1f5f9;
vertical-align: middle;
}
.data-table tr:hover {
background: #f8fafc;
}
.person-info {
display: flex;
align-items: center;
gap: 12px;
}
.person-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
background: #4299e1;
color: white;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 16px;
}
.person-name {
font-weight: 600;
color: #1a202c;
margin-bottom: 2px;
}
.person-position {
font-size: 12px;
color: #718096;
}
.deal-rate {
display: flex;
flex-direction: column;
gap: 4px;
min-width: 80px;
}
.rate-value {
font-weight: 600;
font-size: 14px;
}
.rate-value.high {
color: #4caf50;
}
.rate-value.medium {
color: #ff9800;
}
.rate-value.low {
color: #f44336;
}
.rate-bar {
height: 4px;
background: #edf2f7;
border-radius: 2px;
overflow: hidden;
}
.rate-fill {
height: 100%;
transition: width 0.3s ease;
}
.task-list {
padding: 0 24px 24px;
}
.task-list.compact {
max-height: 320px;
}
.task-list.compact .task-item {
padding: 12px 0;
}
.task-list.compact .task-item .task-title {
font-size: 14px;
margin-bottom: 4px;
}
.task-list.compact .task-meta {
display: flex;
flex-direction: row;
gap: 15px;
font-size: 12px;
}
.task-item {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 16px 0;
border-bottom: 1px solid #f7fafc;
}
.task-item:last-child {
border-bottom: none;
}
.task-title {
font-weight: 600;
color: #1a202c;
margin-bottom: 8px;
}
.task-meta {
display: flex;
flex-direction: row;
gap: 15px;
font-size: 12px;
color: #718096;
}
.task-status {
padding: 4px 8px;
border-radius: 4px;
font-size: 12px;
font-weight: 600;
}
.task-status.pending {
background: #fef5e7;
color: #d69e2e;
}
.task-status.in-progress {
background: #ebf8ff;
color: #4299e1;
}
.task-status.completed {
background: #f0fff4;
color: #38a169;
}
/* 模态框样式 */
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background: white;
border-radius: 12px;
width: 500px;
max-width: 90vw;
max-height: 90vh;
overflow: hidden;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 20px 24px;
border-bottom: 1px solid #e2e8f0;
}
.modal-header h3 {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #1a202c;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
color: #718096;
cursor: pointer;
padding: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
}
.modal-body {
padding: 24px;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
font-weight: 600;
color: #1a202c;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 8px 12px;
border: 1px solid #e2e8f0;
border-radius: 6px;
font-size: 14px;
box-sizing: border-box;
}
.form-group textarea {
height: 80px;
resize: vertical;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
padding: 20px 24px;
border-top: 1px solid #e2e8f0;
}
.cancel-btn {
padding: 8px 16px;
background: #f7fafc;
color: #4a5568;
border: 1px solid #e2e8f0;
border-radius: 6px;
cursor: pointer;
}
.confirm-btn {
padding: 8px 16px;
background: #4299e1;
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
}
/* 响应式设计 */
/* 大屏幕 (1400px+) */
@media (min-width: 1400px) {
.dashboard-container {
max-width: 1600px;
padding: 30px;
}
}
/* 中大屏幕 (1200px - 1399px) */
@media (max-width: 1399px) {
.dashboard-container {
max-width: 1200px;
}
}
/* 中屏幕 (992px - 1199px) */
@media (max-width: 1199px) {
.dashboard-container {
max-width: 100%;
padding: 20px 15px;
}
.row-1 {
grid-template-columns: 1fr 1fr;
height: auto;
}
.row-2 {
grid-template-columns: 1fr 1fr;
height: auto;
}
.row-3 {
grid-template-columns: 1fr 1fr;
height: auto;
}
.row-4 {
grid-template-columns: 1fr;
}
}
/* 小屏幕 (768px - 991px) */
@media (max-width: 991px) {
.dashboard-container {
padding: 15px 10px;
}
.dashboard-header h1 {
font-size: 24px;
}
.row-1,
.row-2 {
grid-template-columns: 1fr;
gap: 15px;
}
.dashboard-row {
gap: 15px;
margin-bottom: 20px;
}
.card-header {
padding: 15px 20px 12px;
}
.card-header h3 {
font-size: 16px;
}
}
/* 移动端 (576px - 767px) */
@media (max-width: 767px) {
.dashboard-container {
padding: 10px 8px;
}
.dashboard-header {
flex-direction: column;
gap: 12px;
align-items: flex-start;
margin-bottom: 20px;
}
.dashboard-header h1 {
font-size: 20px;
}
.refresh-btn {
padding: 6px 12px;
font-size: 12px;
}
.row-1,
.row-2,
.row-3,
.row-4 {
grid-template-columns: 1fr;
gap: 12px;
}
.dashboard-row {
margin-bottom: 15px;
}
.dashboard-card {
border-radius: 8px;
}
.card-header {
padding: 12px 16px 10px;
}
.card-header h3 {
font-size: 14px;
}
.metrics-row {
grid-template-columns: 1fr;
gap: 12px;
}
.kpi-metrics {
grid-template-columns: 1fr;
gap: 12px;
padding: 8px;
}
.communication-cards {
grid-template-columns: 1fr;
gap: 8px;
padding: 8px;
}
.comm-card {
padding: 12px;
}
.table-filters {
grid-template-columns: 1fr;
gap: 12px;
padding: 12px;
}
.data-table th,
.data-table td {
padding: 8px 12px;
font-size: 12px;
}
.person-info {
flex-direction: column;
align-items: flex-start;
gap: 8px;
}
.person-avatar {
width: 32px;
height: 32px;
font-size: 14px;
}
.modal-content {
width: 95vw;
margin: 10px;
}
.modal-header,
.modal-body,
.modal-footer {
padding: 16px;
}
}
/* 超小屏幕 (最大575px) */
@media (max-width: 575px) {
.dashboard-container {
padding: 8px 5px;
}
.dashboard-header h1 {
font-size: 18px;
}
.refresh-btn {
padding: 4px 8px;
font-size: 11px;
}
.dashboard-row {
gap: 8px;
margin-bottom: 12px;
}
.card-header {
padding: 10px 12px 8px;
}
.card-header h3 {
font-size: 13px;
}
.kpi-value {
font-size: 16px;
}
.kpi-label {
font-size: 10px;
}
.card-value {
font-size: 14px;
}
.card-label {
font-size: 10px;
}
.tip-item {
padding: 6px 8px;
font-size: 12px;
}
.ranking-item {
padding: 8px 0;
}
.rank-number {
width: 24px;
height: 24px;
font-size: 12px;
}
.employee-name {
font-size: 13px;
}
.employee-dept {
font-size: 10px;
}
.performance-value {
font-size: 13px;
}
.data-table th,
.data-table td {
padding: 6px 8px;
font-size: 11px;
}
.person-avatar {
width: 28px;
height: 28px;
font-size: 12px;
}
.person-name {
font-size: 13px;
}
.person-position {
font-size: 10px;
}
}
/* 图标样式 (使用字符代替实际图标) */
.icon-refresh::before {
content: "↻";
}
.icon-plus::before {
content: "+";
}
.icon-check-circle::before {
content: "✓";
}
.icon-x-circle::before {
content: "✗";
}
.icon-phone::before {
content: "☎";
}
.icon-info::before {
content: "";
}
.icon-play::before {
content: "▶";
}
.icon-download::before {
content: "↓";
}
.icon-alert-circle::before {
content: "⚠";
}
.icon-info-circle::before {
content: "";
}
/* 通用响应式优化 */
* {
box-sizing: border-box;
}
/* 防止水平滚动 */
body {
overflow-x: hidden;
}
/* 图片响应式 */
img {
max-width: 100%;
height: auto;
}
/* 表格响应式 */
.data-table {
width: 100%;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
}
/* 按钮触摸优化 */
button {
min-height: 44px;
min-width: 44px;
touch-action: manipulation;
}
/* 小屏幕下的按钮优化 */
@media (max-width: 767px) {
button {
min-height: 40px;
min-width: 40px;
}
}
/* 超小屏幕下的按钮优化 */
@media (max-width: 575px) {
button {
min-height: 36px;
min-width: 36px;
}
}
/* 文本选择优化 */
.dashboard-card {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* 可选择的文本 */
.data-table,
.person-detail,
.modal-content {
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
</style>