- 添加任务列表获取和状态更新API调用 - 修改任务列表组件显示格式和状态标签 - 优化日期格式化处理逻辑 - 调整任务列表样式和交互效果 - 注释掉部分不需要的API调用
2204 lines
41 KiB
Vue
2204 lines
41 KiB
Vue
<template>
|
||
<div class="dashboard-container">
|
||
<!-- 页面标题 -->
|
||
<div class="dashboard-header">
|
||
<h1>管理者数据看板</h1>
|
||
<!-- 头像 -->
|
||
<UserDropdown />
|
||
</div>
|
||
|
||
<!-- 第一行:核心业绩指标、销售实时进度、下发任务 -->
|
||
<div class="dashboard-row row-1">
|
||
<!-- 核心业绩指标 -->
|
||
<kpi-metrics :kpi-data="totalDeals" :format-number="formatNumber" />
|
||
<!-- 销售实时进度 -->
|
||
<sales-progress :sales-data="realTimeProgress" />
|
||
<!-- 下发任务 -->
|
||
<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="formattedFunnelData"
|
||
:comparison-data="formattedComparisonData"
|
||
@time-range-change="handleTimeRangeChange"
|
||
/>
|
||
<!-- 销售个人业绩排行榜 -->
|
||
<personal-sales-ranking
|
||
:ranking-data="formattedSalesRankingData"
|
||
:format-number="formatNumber"
|
||
:get-rank-class="getRankClass"
|
||
@periods-change="handleRankingPeriodChange"
|
||
@ranking-type-change="getCompanySalesRank"
|
||
/>
|
||
<!-- 优质通话 -->
|
||
<quality-calls
|
||
:quality-calls="qualityCalls"
|
||
@play-call="playCall"
|
||
@download-call="downloadCall"
|
||
/>
|
||
</div>
|
||
<!-- 第三行 -->
|
||
<div class="dashboard-row row-3">
|
||
<!-- 业绩排行榜 -->
|
||
<ranking-list
|
||
:format-number="formatNumber"
|
||
:get-rank-class="getRankClass"
|
||
/>
|
||
<!-- 客户类型占比 -->
|
||
<customer-type :customer-data="customerTypeRatio" @category-change="getCustomerTypeRatio" />
|
||
<!-- 客户迫切解决的问题排行榜 -->
|
||
<problem-ranking :ranking-data="problemRankingData" />
|
||
</div>
|
||
<!-- 第四行:详细数据表格和数据详情 -->
|
||
<div class="dashboard-row" v-show="false">
|
||
<CampManagement />
|
||
</div>
|
||
<!-- 第五行 -->
|
||
<div class="dashboard-row" >
|
||
<DetailedDataTable
|
||
:table-data="detailData"
|
||
:level-tree="levelTree"
|
||
v-model:selected-person="selectedPerson"
|
||
@filter-change="handleFilterChange"
|
||
/>
|
||
</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 assigneeOptions"
|
||
:key="employee.wechat_id"
|
||
:value="employee.wechat_id"
|
||
>
|
||
{{ 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 axios from "axios";
|
||
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 "./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 { getOverallCompanyPerformance,getCompanyDepositConversionRate,getCompanyTotalCallCount,getCompanyNewCustomer,getCompanyConversionRate,getCompanyRealTimeProgress
|
||
,getCompanyConversionRateVsLast,getSalesMonthlyPerformance,getCustomerTypeDistribution,getUrgentNeedToAddress,getLevelTree,getDetailedDataTable,assignTasks } from "@/api/top";
|
||
|
||
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 sortField = ref("dealRate");
|
||
const sortOrder = ref("desc");
|
||
const selectedPerson = ref(null);
|
||
|
||
const tasks = ref([]);
|
||
|
||
const employees = ref([
|
||
{ id: 1, name: "张三" }
|
||
]);
|
||
|
||
const showTaskModal = ref(false);
|
||
const newTask = reactive({
|
||
title: "",
|
||
assignee: "",
|
||
deadline: "",
|
||
description: "",
|
||
});
|
||
// 获取任务列表
|
||
const getTaskList = async () => {
|
||
try {
|
||
const res = await axios.post('http://192.168.15.56:8890/api/v1/level_five/overview/view_tasks', {}, {
|
||
headers: {
|
||
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||
}
|
||
})
|
||
console.log(888888,res)
|
||
if (res.data.code === 200) {
|
||
const apiTasks = res.data.data.tasks || res.data.data
|
||
// 将API数据格式转换为TaskList组件期望的格式
|
||
tasks.value = apiTasks.map(task => ({
|
||
id: task.task_id,
|
||
title: task.task_title,
|
||
assignee: task.assignee || '未分配',
|
||
deadline: task.expiration_date,
|
||
status: task.state === '待处理' ? 'pending' : task.state === '正在处理' ? 'in-progress' : 'completed',
|
||
description: task.task_content,
|
||
created_at: task.created_at
|
||
}))
|
||
console.log(777777,tasks.value)
|
||
/**
|
||
* tasks
|
||
:
|
||
[,…]
|
||
0
|
||
:
|
||
{task_id: "1755748690560728_22d55cc618784537973481228a15956a", task_title: "55", task_content: "222",…}
|
||
created_at
|
||
:
|
||
"2025-08-21 11:58:10"
|
||
expiration_date
|
||
:
|
||
"20250808"
|
||
state
|
||
:
|
||
"待处理"
|
||
task_content
|
||
:
|
||
"222"
|
||
task_id
|
||
:
|
||
"1755748690560728_22d55cc618784537973481228a15956a"
|
||
task_title
|
||
:
|
||
"55"
|
||
1
|
||
:
|
||
{task_id: "1755745331126891_650206e5b6d345699de3e3e406a2600e", task_title: "测试任务",…}
|
||
created_at
|
||
:
|
||
"2025-08-21 11:02:11"
|
||
expiration_date
|
||
:
|
||
"121221"
|
||
state
|
||
:
|
||
"待处理"
|
||
task_content
|
||
:
|
||
"测试任务"
|
||
task_id
|
||
:
|
||
"1755745331126891_650206e5b6d345699de3e3e406a2600e"
|
||
task_title
|
||
:
|
||
"测试任务"
|
||
2
|
||
:
|
||
{task_id: "1755745330094989_528dd87dc13a4a5bb33c9c272fb1a482", task_title: "测试任务",…}
|
||
created_at
|
||
:
|
||
"2025-08-21 11:02:10"
|
||
expiration_date
|
||
:
|
||
"121221"
|
||
state
|
||
:
|
||
"已完成"
|
||
task_content
|
||
:
|
||
"测试任务"
|
||
task_id
|
||
:
|
||
"1755745330094989_528dd87dc13a4a5bb33c9c272fb1a482"
|
||
task_title
|
||
:
|
||
"测试任务"
|
||
*/
|
||
}
|
||
} catch (error) {
|
||
console.error('获取任务列表失败:', error)
|
||
}
|
||
}
|
||
// 下拉框人员
|
||
const assigneeOptions = ref([]);
|
||
async function name() {
|
||
try {
|
||
console.log('开始获取下属人员列表...');
|
||
const res = await axios.get('http://192.168.15.56:8890/api/v1/level_five/overview/get_subordinates',{
|
||
headers: {
|
||
'Authorization': 'Bearer ' + localStorage.getItem('token')
|
||
}
|
||
});
|
||
assigneeOptions.value = res.data.data;
|
||
|
||
console.log('assigneeOptions设置后:', assigneeOptions.value);
|
||
} catch (error) {
|
||
console.error('获取下属人员列表失败:', error);
|
||
}
|
||
}
|
||
// 计算属性
|
||
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);
|
||
// 根据时间范围重新获取转化对比数据
|
||
getConversionComparison(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) => {
|
||
if (!dateString) return '';
|
||
// 处理 "2025-08-21 11:58:10" 格式的时间字符串
|
||
try {
|
||
const date = new Date(dateString.replace(' ', 'T'));
|
||
if (isNaN(date.getTime())) {
|
||
return dateString; // 如果解析失败,返回原字符串
|
||
}
|
||
return date.toLocaleDateString("zh-CN");
|
||
} catch (error) {
|
||
return dateString;
|
||
}
|
||
};
|
||
|
||
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 = async () => {
|
||
if (!newTask.title || !newTask.assignee || !newTask.deadline) {
|
||
alert("请填写完整信息");
|
||
return;
|
||
}
|
||
|
||
try {
|
||
// 构造API请求参数
|
||
const params = {
|
||
task_title: newTask.title,
|
||
task_assignee: [newTask.assignee], // 转换为数组格式
|
||
expiration_date: newTask.deadline.replace(/-/g, ''), // 移除日期中的横线
|
||
task_content: newTask.description || newTask.title
|
||
};
|
||
|
||
// 调用API
|
||
const response = await assignTasks(params);
|
||
console.log('任务创建成功:', response);
|
||
|
||
// 创建本地任务对象用于显示
|
||
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;
|
||
alert('任务创建成功!');
|
||
} catch (error) {
|
||
console.error('创建任务失败:', error);
|
||
alert('创建任务失败,请重试');
|
||
}
|
||
};
|
||
|
||
// 核心数据
|
||
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={
|
||
totalDeal:res1.data, //总成交单数
|
||
DingconversionRate:res2.data, //定金转化率
|
||
totalCallCount:res3.data, // 总通话
|
||
newCustomer:res4.data, //新客户
|
||
conversionRate:res5.data,//转化率
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error("获取总成交金额失败:", error);
|
||
}
|
||
}
|
||
// 实时进度
|
||
const realTimeProgress = ref({});
|
||
async function getRealTimeProgress() {
|
||
try {
|
||
const res = await getCompanyRealTimeProgress()
|
||
// console.log(111111,res)
|
||
realTimeProgress.value = res.data
|
||
} catch (error) {
|
||
console.error("获取实时进度失败:", error);
|
||
}
|
||
}
|
||
// 转化对比
|
||
const conversionComparison = ref({});
|
||
|
||
// 计算属性:转换 conversionComparison 数据为 funnelData 格式
|
||
const formattedFunnelData = computed(() => {
|
||
if (!conversionComparison.value || !conversionComparison.value.company_current_rate) {
|
||
return []; // 返回空数组,避免数据格式不匹配
|
||
}
|
||
|
||
const currentData = conversionComparison.value.company_current_rate;
|
||
const stageOrder = ['线索总数', '加微', '到课', '付定金', '成交'];
|
||
const colors = ['#4CAF50', '#2196F3', '#FF9800', '#9C27B0', '#F44336'];
|
||
|
||
|
||
|
||
return stageOrder.map((stageName, index) => {
|
||
const count = currentData[stageName] || 0;
|
||
const totalCount = currentData['线索总数'] || 1;
|
||
const percentage = totalCount > 0 ? Math.round((count / totalCount) * 100) : 0;
|
||
|
||
return {
|
||
name: stageName,
|
||
count: count,
|
||
percentage: percentage,
|
||
color: colors[index]
|
||
};
|
||
});
|
||
});
|
||
|
||
// 计算属性:转换 conversionComparison 数据格式以适配 FunnelChart 组件
|
||
const formattedComparisonData = computed(() => {
|
||
if (!conversionComparison.value || !conversionComparison.value.company_current_rate) {
|
||
return {};
|
||
}
|
||
|
||
const currentData = conversionComparison.value.company_current_rate;
|
||
const lastData = conversionComparison.value.company_last_rate;
|
||
const checkType = conversionComparison.value.check_type;
|
||
|
||
|
||
|
||
// 确保lastData存在
|
||
if (!lastData) {
|
||
|
||
return {};
|
||
}
|
||
|
||
// 根据 check_type 确定时间范围键
|
||
const timeRangeKey = checkType === 'month' ? 'month' : 'periods';
|
||
const stageOrder = ['线索总数', '加微', '到课', '付定金', '成交'];
|
||
|
||
const comparisonArray = stageOrder.map(stageName => ({
|
||
name: stageName,
|
||
count: lastData[stageName] || 0
|
||
}));
|
||
|
||
|
||
|
||
// 同时返回period和month两个键,确保组件能找到对应数据
|
||
const result = {
|
||
periods: comparisonArray,
|
||
month: comparisonArray
|
||
};
|
||
|
||
|
||
return result;
|
||
});
|
||
|
||
async function getConversionComparison(data) {
|
||
const params={
|
||
check_type:data //month periods
|
||
}
|
||
try {
|
||
const res = await getCompanyConversionRateVsLast(params)
|
||
console.log(111111,res)
|
||
conversionComparison.value = res.data
|
||
} catch (error) {
|
||
console.error("获取转化对比失败:", error);
|
||
}
|
||
}
|
||
|
||
// 获取全公司销售月度业绩红黑榜 params:{"rank_type": "red" // "rank_type": "black"}
|
||
const companySalesRank = ref({});
|
||
|
||
// 计算属性:转换 companySalesRank 数据格式以适配 PersonalSalesRanking 组件
|
||
const formattedSalesRankingData = computed(() => {
|
||
if (!companySalesRank.value) {
|
||
return [];
|
||
}
|
||
|
||
// 根据 rank_type 选择对应的数据
|
||
const rankType = companySalesRank.value.rank_type;
|
||
const rankList = rankType === 'red'
|
||
? companySalesRank.value.sales_monthly_performance_red
|
||
: companySalesRank.value.sales_monthly_performance_black;
|
||
|
||
if (!rankList) {
|
||
return [];
|
||
}
|
||
|
||
return rankList.map((item, index) => ({
|
||
id: index + 1,
|
||
name: item.name,
|
||
department: item.department,
|
||
performance: item.deal_count, // 假设每单10000元,可根据实际情况调整
|
||
deals: item.deal_count,
|
||
conversionRate: parseFloat(item.conversion_rate.replace('%', '')),
|
||
trend: rankType === 'red' ? 'up' : 'down', // 红榜为上升趋势,黑榜为下降趋势
|
||
growth: rankType === 'red' ? Math.random() * 20 : -(Math.random() * 20), // 红榜正增长,黑榜负增长
|
||
avatar: '/default-avatar.svg'
|
||
}));
|
||
});
|
||
|
||
// 处理销售排行榜期间变化
|
||
const handleRankingPeriodChange = (periods) => {
|
||
// 根据期间参数调用相应的函数,这里默认调用红榜数据
|
||
getCompanySalesRank('red');
|
||
};
|
||
|
||
async function getCompanySalesRank(Rank) {
|
||
const params={
|
||
rank_type:Rank,
|
||
}
|
||
try {
|
||
const res = await getSalesMonthlyPerformance(params)
|
||
companySalesRank.value = res.data
|
||
} catch (error) {
|
||
console.error("获取销售月度业绩红黑榜失败:", error);
|
||
}
|
||
}
|
||
// 获取全中心业绩排行榜逻辑已移至 RankingList 组件
|
||
// 客户类型占比
|
||
const customerTypeRatio = ref({});
|
||
async function getCustomerTypeRatio(data) {
|
||
const params={
|
||
distribution_type:data // child_education territory occupation
|
||
}
|
||
try {
|
||
const res = await getCustomerTypeDistribution(params)
|
||
customerTypeRatio.value = res.data
|
||
} catch (error) {
|
||
console.error("获取客户类型占比失败:", error);
|
||
}
|
||
}
|
||
// 客户迫切解决的问题排行榜
|
||
const customerUrgency = ref({});
|
||
const problemRankingData = ref([]);
|
||
|
||
async function getCustomerUrgency() {
|
||
try {
|
||
const res = await getUrgentNeedToAddress()
|
||
customerUrgency.value = res.data
|
||
|
||
// 将API返回的数据转换为ProblemRanking组件需要的格式
|
||
if (res.data && res.data.company_urgent_issue_ratio) {
|
||
problemRankingData.value = Object.entries(res.data.company_urgent_issue_ratio).map(([name, value]) => ({
|
||
name,
|
||
value
|
||
}));
|
||
}
|
||
} catch (error) {
|
||
console.error("获取客户迫切解决的问题排行榜失败:", error);
|
||
}
|
||
}
|
||
// 获取级别树
|
||
const levelTree = ref({});
|
||
async function CusotomGetLevelTree() {
|
||
try {
|
||
const res = await getLevelTree()
|
||
levelTree.value = res.data
|
||
} catch (error) {
|
||
console.error("获取级别树失败:", error);
|
||
}
|
||
}
|
||
// 获取详细数据表格
|
||
const detailData = ref({});
|
||
async function getDetailData(params) {
|
||
if(params?.center_leader){
|
||
try {
|
||
const res = await getDetailedDataTable(params)
|
||
detailData.value = res.data
|
||
} catch (error) {
|
||
console.error("获取详细数据表格失败:", error);
|
||
}
|
||
}else{
|
||
try {
|
||
const res = await getDetailedDataTable()
|
||
detailData.value = res.data
|
||
} catch (error) {
|
||
console.error("获取详细数据表格失败:", error);
|
||
}
|
||
}
|
||
|
||
}
|
||
|
||
// 处理筛选器变化
|
||
const handleFilterChange = (filterParams) => {
|
||
console.log('筛选器变化:', filterParams)
|
||
getDetailData(filterParams)
|
||
}
|
||
|
||
onMounted(async() => {
|
||
// 页面初始化逻辑
|
||
await getRealTimeProgress()
|
||
await getTotalDeals()
|
||
await getTaskList()
|
||
await getConversionComparison('month')
|
||
await getCompanySalesRank('red')
|
||
await getCustomerTypeRatio('child_education')
|
||
await getCustomerUrgency()
|
||
await CusotomGetLevelTree()
|
||
await getDetailData()
|
||
await name() // 获取下属人员列表
|
||
});
|
||
</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-periods {
|
||
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;
|
||
}
|
||
|
||
.periods-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>
|