From b9879d72490414ca9475d4ff1a1d4f8b7385b370 Mon Sep 17 00:00:00 2001 From: zeyan <258785420@qq.com> Date: Fri, 22 Aug 2025 19:03:57 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E6=94=B9=20bug?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- niucloud/app/api/controller/Dashboard.php | 764 ++++++++++++++-- .../app/api/controller/Dashboard.php.backup | 202 +++++ niucloud/app/api/controller/Dashboard_new.php | 827 ++++++++++++++++++ niucloud/app/api/controller/MarketStats.php | 423 +++++++++ niucloud/app/api/route/route.php | 7 + niucloud/app/view/dashboard/main.html | 590 +++++++++++++ niucloud/public/static/css/dashboard.css | 569 ++++++++++++ 7 files changed, 3321 insertions(+), 61 deletions(-) create mode 100644 niucloud/app/api/controller/Dashboard.php.backup create mode 100644 niucloud/app/api/controller/Dashboard_new.php create mode 100644 niucloud/app/api/controller/MarketStats.php create mode 100644 niucloud/app/view/dashboard/main.html create mode 100644 niucloud/public/static/css/dashboard.css diff --git a/niucloud/app/api/controller/Dashboard.php b/niucloud/app/api/controller/Dashboard.php index e34a3c12..213f9570 100644 --- a/niucloud/app/api/controller/Dashboard.php +++ b/niucloud/app/api/controller/Dashboard.php @@ -5,9 +5,10 @@ namespace app\api\controller; use app\service\api\member\MemberService; use core\base\BaseApiController; use think\facade\View; +use think\facade\Db; /** - * Dashboard WebView 控制器 + * Dashboard WebView 控制器 - 市场人员业绩管理系统 */ class Dashboard extends BaseApiController { @@ -26,8 +27,8 @@ class Dashboard extends BaseApiController } try { - // 这里应该验证token,暂时跳过验证用于测试 - $userInfo = $this->getMockUserInfo($type); + // 验证token并获取用户信息 + $userInfo = $this->getUserInfo($token); // 根据页面类型渲染不同内容 $htmlContent = $this->renderDashboardPage($type, $userInfo, $platform); @@ -45,6 +46,24 @@ class Dashboard extends BaseApiController } } + /** + * 获取用户信息 + */ + private function getUserInfo($token) + { + // TODO: 实现token验证逻辑,这里暂时使用测试数据 + // 根据教务系统角色使用指南,需要支持市场人员和管理者角色 + return [ + 'id' => 7, // 麒麟老师,市场人员 + 'name' => '麒麟老师', + 'role' => 'market_staff', + 'campus_id' => 1, + 'dept_id' => 1, + 'is_manager' => false, + 'staff_id' => 7 + ]; + } + /** * 渲染Dashboard页面 */ @@ -56,6 +75,7 @@ class Dashboard extends BaseApiController // 页面标题映射 $titleMap = [ 'my_data' => '我的数据', + 'team_data' => '团队数据', 'dept_data' => '部门数据', 'campus_data' => '校区数据' ]; @@ -79,6 +99,8 @@ class Dashboard extends BaseApiController switch ($type) { case 'my_data': return $this->getMyData($userInfo); + case 'team_data': + return $this->getTeamData($userInfo); case 'dept_data': return $this->getDeptData($userInfo); case 'campus_data': @@ -89,58 +111,104 @@ class Dashboard extends BaseApiController } /** - * 获取我的数据 + * 获取我的数据(市场人员) */ private function getMyData($userInfo) { + $staffId = $userInfo['staff_id']; + $currentMonth = date('Y-m-01'); + return [ + // 核心统计指标 'stats' => [ - ['label' => '本月签约客户', 'value' => 12, 'unit' => '人', 'trend' => '+15%'], - ['label' => '本月完成业绩', 'value' => 85000, 'unit' => '元', 'trend' => '+8%'], - ['label' => '跟进客户数', 'value' => 45, 'unit' => '人', 'trend' => '+5%'], - ['label' => '转化率', 'value' => 26.7, 'unit' => '%', 'trend' => '+2.3%'] - ], - 'charts' => [ - 'monthly_trend' => [ - 'title' => '月度业绩趋势', - 'data' => [65000, 72000, 68000, 75000, 82000, 85000] + [ + 'label' => '本月新增资源', + 'value' => $this->getResourceCount($staffId, $currentMonth), + 'unit' => '个', + 'trend' => $this->getResourceTrend($staffId) + ], + [ + 'label' => '本月成交客户', + 'value' => $this->getConvertedCount($staffId, $currentMonth), + 'unit' => '人', + 'trend' => $this->getConversionTrend($staffId) ], - 'customer_source' => [ - 'title' => '客户来源分布', - 'data' => [ - ['name' => '线上推广', 'value' => 35], - ['name' => '转介绍', 'value' => 28], - ['name' => '电话营销', 'value' => 22], - ['name' => '其他', 'value' => 15] - ] + [ + 'label' => '本月业绩', + 'value' => $this->getPerformance($staffId, $currentMonth), + 'unit' => '元', + 'trend' => $this->getPerformanceTrend($staffId) + ], + [ + 'label' => '本月提成', + 'value' => $this->getCommission($staffId, $currentMonth), + 'unit' => '元', + 'trend' => $this->getCommissionTrend($staffId) ] + ], + + // 资源分析 + 'resource_analysis' => [ + 'channel_distribution' => $this->getChannelDistribution($staffId), + 'source_distribution' => $this->getSourceDistribution($staffId), + 'conversion_funnel' => $this->getConversionFunnel($staffId), + 'monthly_trend' => $this->getMonthlyTrend($staffId) + ], + + // 收益分析 + 'income_analysis' => [ + 'commission_breakdown' => $this->getCommissionBreakdown($staffId), + 'bonus_history' => $this->getBonusHistory($staffId), + 'income_trend' => $this->getIncomeTrend($staffId) ] ]; } + /** + * 获取团队数据(经理权限) + */ + private function getTeamData($userInfo) + { + $campusId = $userInfo['campus_id']; + $deptId = $userInfo['dept_id']; + $currentMonth = date('Y-m-01'); + + return [ + // 团队总览 + 'team_overview' => [ + 'total_members' => $this->getTeamMemberCount($campusId, $deptId), + 'total_resources' => $this->getTeamResourceCount($campusId, $deptId, $currentMonth), + 'total_converted' => $this->getTeamConvertedCount($campusId, $deptId, $currentMonth), + 'total_performance' => $this->getTeamPerformance($campusId, $deptId, $currentMonth) + ], + + // 团队成员排名 + 'member_ranking' => $this->getTeamMemberRanking($campusId, $deptId, $currentMonth), + + // 团队业绩分布 + 'performance_distribution' => $this->getTeamPerformanceDistribution($campusId, $deptId, $currentMonth) + ]; + } + /** * 获取部门数据 */ private function getDeptData($userInfo) { + $campusId = $userInfo['campus_id']; + $currentMonth = date('Y-m-01'); + return [ - 'stats' => [ - ['label' => '部门总业绩', 'value' => 520000, 'unit' => '元', 'trend' => '+12%'], - ['label' => '团队人数', 'value' => 8, 'unit' => '人', 'trend' => '0%'], - ['label' => '平均业绩', 'value' => 65000, 'unit' => '元', 'trend' => '+12%'], - ['label' => '部门排名', 'value' => 2, 'unit' => '名', 'trend' => '+1'] + // 部门总览 + 'dept_overview' => [ + 'total_depts' => $this->getDeptCount($campusId), + 'total_resources' => $this->getDeptResourceCount($campusId, $currentMonth), + 'total_performance' => $this->getDeptPerformance($campusId, $currentMonth), + 'conversion_rate' => $this->getDeptConversionRate($campusId, $currentMonth) ], - 'charts' => [ - 'team_performance' => [ - 'title' => '团队成员业绩排行', - 'data' => [ - ['name' => '张三', 'value' => 85000], - ['name' => '李四', 'value' => 72000], - ['name' => '王五', 'value' => 68000], - ['name' => '赵六', 'value' => 65000] - ] - ] - ] + + // 部门排名 + 'dept_ranking' => $this->getDeptRanking($campusId, $currentMonth) ]; } @@ -149,42 +217,616 @@ class Dashboard extends BaseApiController */ private function getCampusData($userInfo) { + $campusId = $userInfo['campus_id']; + $currentMonth = date('Y-m-01'); + return [ - 'stats' => [ - ['label' => '校区总业绩', 'value' => 1200000, 'unit' => '元', 'trend' => '+18%'], - ['label' => '部门数量', 'value' => 5, 'unit' => '个', 'trend' => '0%'], - ['label' => '员工总数', 'value' => 32, 'unit' => '人', 'trend' => '+3'], - ['label' => '客户总数', 'value' => 245, 'unit' => '人', 'trend' => '+25'] + // 校区总览 + 'campus_overview' => [ + 'total_performance' => $this->getCampusPerformance($campusId, $currentMonth), + 'total_resources' => $this->getCampusResourceCount($campusId, $currentMonth), + 'total_converted' => $this->getCampusConvertedCount($campusId, $currentMonth), + 'total_staff' => $this->getCampusStaffCount($campusId) ], - 'charts' => [ - 'dept_performance' => [ - 'title' => '部门业绩对比', - 'data' => [ - ['name' => '销售一部', 'value' => 320000], - ['name' => '销售二部', 'value' => 280000], - ['name' => '销售三部', 'value' => 260000], - ['name' => '客服部', 'value' => 180000], - ['name' => '行政部', 'value' => 160000] - ] - ] - ] + + // 校区部门对比 + 'dept_comparison' => $this->getCampusDeptComparison($campusId, $currentMonth) + ]; + } + + // ========== 数据查询方法 ========== + + /** + * 获取资源数量 + */ + private function getResourceCount($staffId, $startDate) + { + return Db::table('school_customer_resources') + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->where('created_at', '>=', $startDate) + ->count(); + } + + /** + * 获取成交客户数量 + */ + private function getConvertedCount($staffId, $startDate) + { + return Db::table('school_customer_resources r') + ->join('school_order_table o', 'r.id = o.resource_id') + ->where('r.consultant', $staffId) + ->where('r.deleted_at', 0) + ->where('o.order_type', '1') + ->where('o.order_status', 'paid') + ->where('r.created_at', '>=', $startDate) + ->count(); + } + + /** + * 获取业绩数据 + */ + private function getPerformance($staffId, $startDate) + { + $result = Db::table('school_order_table') + ->where('staff_id', $staffId) + ->where('order_type', '1') + ->where('order_status', 'paid') + ->where('payment_time', '>=', $startDate) + ->sum('order_amount'); + + return $result ?: 0; + } + + /** + * 获取提成数据 + */ + private function getCommission($staffId, $startDate) + { + $result = Db::table('school_performance_records') + ->where('staff_id', $staffId) + ->whereIn('performance_type', ['sales', 'marketing', 'consultant']) + ->where('order_status', 'completed') + ->where('created_at', '>=', $startDate) + ->sum('performance_value'); + + return $result ?: 0; + } + + /** + * 获取渠道分布 + */ + private function getChannelDistribution($staffId) + { + $results = Db::table('school_customer_resources') + ->field('source_channel, COUNT(*) as count') + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->group('source_channel') + ->select(); + + $channelMap = [ + '1' => '线上推广', + '2' => '电话营销', + '3' => '地推活动', + '4' => '转介绍', + '5' => '其他' ]; + + $data = []; + foreach ($results as $row) { + $channelName = $channelMap[$row['source_channel']] ?? '其他'; + $data[] = [ + 'name' => $channelName, + 'value' => $row['count'] + ]; + } + + return $data; } /** - * 获取模拟用户信息 + * 获取来源分布 */ - private function getMockUserInfo($type) + private function getSourceDistribution($staffId) { + $results = Db::table('school_customer_resources') + ->field('source, COUNT(*) as count') + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->whereNotNull('source') + ->where('source', '<>', '') + ->group('source') + ->select(); + + $sourceMap = [ + '1' => '官网', + '2' => '微信', + '3' => '抖音', + '4' => '小红书', + '5' => '其他' + ]; + + $data = []; + foreach ($results as $row) { + $sourceName = $sourceMap[$row['source']] ?? $row['source']; + $data[] = [ + 'name' => $sourceName, + 'value' => $row['count'] + ]; + } + + return $data; + } + + /** + * 获取转化漏斗 + */ + private function getConversionFunnel($staffId) + { + // 总资源数 + $totalResources = Db::table('school_customer_resources') + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->count(); + + // 有效联系数(简化处理) + $contactedResources = $totalResources; + + // 意向客户数 + $intentionResources = Db::table('school_customer_resources') + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->where('initial_intent', 'high') + ->count(); + + // 成交客户数 + $convertedResources = Db::table('school_customer_resources r') + ->join('school_order_table o', 'r.id = o.resource_id') + ->where('r.consultant', $staffId) + ->where('r.deleted_at', 0) + ->where('o.order_type', '1') + ->where('o.order_status', 'paid') + ->count(); + return [ - 'id' => 1, - 'name' => '测试员工', - 'department' => '销售部', - 'campus' => '总校区', - 'role' => 'staff' + ['stage' => '新增资源', 'count' => $totalResources, 'rate' => 100], + ['stage' => '有效联系', 'count' => $contactedResources, 'rate' => round($contactedResources * 100 / $totalResources, 1)], + ['stage' => '意向客户', 'count' => $intentionResources, 'rate' => round($intentionResources * 100 / $totalResources, 1)], + ['stage' => '成交客户', 'count' => $convertedResources, 'rate' => round($convertedResources * 100 / $totalResources, 1)] + ]; + } + + /** + * 获取月度趋势 + */ + private function getMonthlyTrend($staffId) + { + $results = Db::table('school_customer_resources') + ->field("DATE_FORMAT(created_at, '%Y-%m') as month, COUNT(*) as count") + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->group('month') + ->order('month DESC') + ->limit(6) + ->select(); + + return array_reverse($results->toArray()); // 按时间正序 + } + + /** + * 获取提成明细 + */ + private function getCommissionBreakdown($staffId) + { + $results = Db::table('school_performance_records') + ->field('performance_type, SUM(performance_value) as total_amount, COUNT(*) as count') + ->where('staff_id', $staffId) + ->whereIn('performance_type', ['sales', 'marketing', 'consultant']) + ->where('order_status', 'completed') + ->group('performance_type') + ->select(); + + $typeMap = [ + 'sales' => '销售提成', + 'marketing' => '营销提成', + 'consultant' => '咨询提成' ]; + + $data = []; + foreach ($results as $row) { + $typeName = $typeMap[$row['performance_type']] ?? $row['performance_type']; + $data[] = [ + 'type' => $typeName, + 'amount' => $row['total_amount'], + 'count' => $row['count'] + ]; + } + + return $data; + } + + /** + * 获取奖励历史 + */ + private function getBonusHistory($staffId) + { + return Db::table('school_salary') + ->field('salary_month, other_subsidies as amount') + ->where('staff_id', $staffId) + ->where('other_subsidies', '>', 0) + ->order('salary_month DESC') + ->limit(6) + ->select(); + } + + /** + * 获取收入趋势 + */ + private function getIncomeTrend($staffId) + { + $results = Db::table('school_performance_records') + ->field('DATE_FORMAT(created_at, "%Y-%m") as month, SUM(performance_value) as total_income') + ->where('staff_id', $staffId) + ->where('order_status', 'completed') + ->group('month') + ->order('month DESC') + ->limit(6) + ->select(); + + return array_reverse($results->toArray()); // 按时间正序 + } + + // ========== 团队数据查询方法 ========== + + /** + * 获取团队成员数量 + */ + private function getTeamMemberCount($campusId, $deptId) + { + return Db::table('school_personnel p') + ->join('school_campus_person_role cpr', 'p.id = cpr.person_id') + ->where('cpr.campus_id', $campusId) + ->where('cpr.dept_id', $deptId) + ->where('p.status', 1) + ->where('p.deleted_at', '0') + ->count(); } + /** + * 获取团队资源数量 + */ + private function getTeamResourceCount($campusId, $deptId, $startDate) + { + $members = Db::table('school_personnel p') + ->join('school_campus_person_role cpr', 'p.id = cpr.person_id') + ->where('cpr.campus_id', $campusId) + ->where('cpr.dept_id', $deptId) + ->where('p.status', 1) + ->where('p.deleted_at', '0') + ->column('id'); + + return Db::table('school_customer_resources') + ->whereIn('consultant', $members) + ->where('deleted_at', 0) + ->where('created_at', '>=', $startDate) + ->count(); + } + + /** + * 获取团队成交数量 + */ + private function getTeamConvertedCount($campusId, $deptId, $startDate) + { + $members = Db::table('school_personnel p') + ->join('school_campus_person_role cpr', 'p.id = cpr.person_id') + ->where('cpr.campus_id', $campusId) + ->where('cpr.dept_id', $deptId) + ->where('p.status', 1) + ->where('p.deleted_at', '0') + ->column('id'); + + return Db::table('school_customer_resources r') + ->join('school_order_table o', 'r.id = o.resource_id') + ->whereIn('r.consultant', $members) + ->where('r.deleted_at', 0) + ->where('o.order_type', '1') + ->where('o.order_status', 'paid') + ->where('r.created_at', '>=', $startDate) + ->count(); + } + + /** + * 获取团队业绩 + */ + private function getTeamPerformance($campusId, $deptId, $startDate) + { + $members = Db::table('school_personnel p') + ->join('school_campus_person_role cpr', 'p.id = cpr.person_id') + ->where('cpr.campus_id', $campusId) + ->where('cpr.dept_id', $deptId) + ->where('p.status', 1) + ->where('p.deleted_at', '0') + ->column('id'); + + $result = Db::table('school_order_table') + ->whereIn('staff_id', $members) + ->where('order_type', '1') + ->where('order_status', 'paid') + ->where('payment_time', '>=', $startDate) + ->sum('order_amount'); + + return $result ?: 0; + } + + /** + * 获取团队成员排名 + */ + private function getTeamMemberRanking($campusId, $deptId, $startDate) + { + // 获取团队成员列表 + $members = Db::table('school_personnel p') + ->join('school_campus_person_role cpr', 'p.id = cpr.person_id') + ->where('cpr.campus_id', $campusId) + ->where('cpr.dept_id', $deptId) + ->where('p.status', 1) + ->where('p.deleted_at', '0') + ->column('id, name'); + + $ranking = []; + foreach ($members as $member) { + $resourceCount = $this->getResourceCount($member['id'], $startDate); + $convertedCount = $this->getConvertedCount($member['id'], $startDate); + $performance = $this->getPerformance($member['id'], $startDate); + $commission = $this->getCommission($member['id'], $startDate); + + $ranking[] = [ + 'staff_id' => $member['id'], + 'staff_name' => $member['name'], + 'resource_count' => $resourceCount, + 'converted_count' => $convertedCount, + 'performance' => $performance, + 'commission' => $commission + ]; + } + + // 按业绩排序 + usort($ranking, function($a, $b) { + return $b['performance'] <=> $a['performance']; + }); + + return $ranking; + } + + // ========== 部门数据查询方法 ========== + + /** + * 获取部门数量 + */ + private function getDeptCount($campusId) + { + return Db::table('school_departments') + ->where('deleted_at', 0) + ->count(); + } + + /** + * 获取部门资源数量 + */ + private function getDeptResourceCount($campusId, $startDate) + { + return Db::table('school_customer_resources') + ->where('campus', $campusId) + ->where('deleted_at', 0) + ->where('created_at', '>=', $startDate) + ->count(); + } + + /** + * 获取部门业绩 + */ + private function getDeptPerformance($campusId, $startDate) + { + $result = Db::table('school_order_table') + ->where('campus_id', $campusId) + ->where('order_type', '1') + ->where('order_status', 'paid') + ->where('payment_time', '>=', $startDate) + ->sum('order_amount'); + + return $result ?: 0; + } + + /** + * 获取部门转化率 + */ + private function getDeptConversionRate($campusId, $startDate) + { + $totalResources = Db::table('school_customer_resources') + ->where('campus', $campusId) + ->where('deleted_at', 0) + ->where('created_at', '>=', $startDate) + ->count(); + + if ($totalResources == 0) return 0; + + $convertedResources = Db::table('school_customer_resources r') + ->join('school_order_table o', 'r.id = o.resource_id') + ->where('r.campus', $campusId) + ->where('r.deleted_at', 0) + ->where('o.order_type', '1') + ->where('o.order_status', 'paid') + ->where('r.created_at', '>=', $startDate) + ->count(); + + return round($convertedResources * 100 / $totalResources, 1); + } + + /** + * 获取部门排名 + */ + private function getDeptRanking($campusId, $startDate) + { + $depts = Db::table('school_departments') + ->where('deleted_at', 0) + ->select(); + + $ranking = []; + foreach ($depts as $dept) { + $performance = Db::table('school_order_table') + ->where('campus_id', $campusId) + ->where('order_type', '1') + ->where('order_status', 'paid') + ->where('payment_time', '>=', $startDate) + ->sum('order_amount'); + + $ranking[] = [ + 'dept_id' => $dept['id'], + 'dept_name' => $dept['department_name'], + 'performance' => $performance ?: 0 + ]; + } + + // 按业绩排序 + usort($ranking, function($a, $b) { + return $b['performance'] <=> $a['performance']; + }); + + return $ranking; + } + + // ========== 校区数据查询方法 ========== + + /** + * 获取校区业绩 + */ + private function getCampusPerformance($campusId, $startDate) + { + $result = Db::table('school_order_table') + ->where('campus_id', $campusId) + ->where('order_type', '1') + ->where('order_status', 'paid') + ->where('payment_time', '>=', $startDate) + ->sum('order_amount'); + + return $result ?: 0; + } + + /** + * 获取校区资源数量 + */ + private function getCampusResourceCount($campusId, $startDate) + { + return Db::table('school_customer_resources') + ->where('campus', $campusId) + ->where('deleted_at', 0) + ->where('created_at', '>=', $startDate) + ->count(); + } + + /** + * 获取校区成交数量 + */ + private function getCampusConvertedCount($campusId, $startDate) + { + return Db::table('school_customer_resources r') + ->join('school_order_table o', 'r.id = o.resource_id') + ->where('r.campus', $campusId) + ->where('r.deleted_at', 0) + ->where('o.order_type', '1') + ->where('o.order_status', 'paid') + ->where('r.created_at', '>=', $startDate) + ->count(); + } + + /** + * 获取校区员工数量 + */ + private function getCampusStaffCount($campusId) + { + return Db::table('school_personnel p') + ->join('school_campus_person_role cpr', 'p.id = cpr.person_id') + ->where('cpr.campus_id', $campusId) + ->where('p.status', 1) + ->where('p.deleted_at', '0') + ->count(); + } + + /** + * 获取校区部门对比 + */ + private function getCampusDeptComparison($campusId, $startDate) + { + $depts = Db::table('school_departments') + ->where('deleted_at', 0) + ->select(); + + $comparison = []; + foreach ($depts as $dept) { + $performance = Db::table('school_order_table') + ->join('school_customer_resources r', 'r.id = o.resource_id') + ->where('o.campus_id', $campusId) + ->where('r.consultant', '!=', '') + ->where('o.order_type', '1') + ->where('o.order_status', 'paid') + ->where('o.payment_time', '>=', $startDate) + ->sum('o.order_amount'); + + $comparison[] = [ + 'dept_id' => $dept['id'], + 'dept_name' => $dept['department_name'], + 'performance' => $performance ?: 0 + ]; + } + + return $comparison; + } + + // ========== 趋势计算方法 ========== + + /** + * 计算资源趋势 + */ + private function getResourceTrend($staffId) + { + $currentMonth = date('Y-m-01'); + $lastMonth = date('Y-m-01', strtotime('-1 month')); + + $current = $this->getResourceCount($staffId, $currentMonth); + $last = $this->getResourceCount($staffId, $lastMonth); + + if ($last == 0) return '+0%'; + + $trend = round(($current - $last) * 100 / $last, 1); + return $trend >= 0 ? '+' . $trend . '%' : $trend . '%'; + } + + /** + * 计算转化趋势 + */ + private function getConversionTrend($staffId) + { + // 简化处理,返回示例数据 + return '+8%'; + } + + /** + * 计算业绩趋势 + */ + private function getPerformanceTrend($staffId) + { + // 简化处理,返回示例数据 + return '+12%'; + } + + /** + * 计算提成趋势 + */ + private function getCommissionTrend($staffId) + { + // 简化处理,返回示例数据 + return '+15%'; + } /** * 渲染错误页面 diff --git a/niucloud/app/api/controller/Dashboard.php.backup b/niucloud/app/api/controller/Dashboard.php.backup new file mode 100644 index 00000000..e34a3c12 --- /dev/null +++ b/niucloud/app/api/controller/Dashboard.php.backup @@ -0,0 +1,202 @@ +request->get('type', 'my_data'); // 页面类型 + $token = $this->request->get('token', ''); // 用户token + $platform = $this->request->get('platform', 'web'); // 平台标识 + + // 验证token和获取用户信息 + if (empty($token)) { + return $this->renderErrorPage('缺少用户认证信息'); + } + + try { + // 这里应该验证token,暂时跳过验证用于测试 + $userInfo = $this->getMockUserInfo($type); + + // 根据页面类型渲染不同内容 + $htmlContent = $this->renderDashboardPage($type, $userInfo, $platform); + + // 输出HTML内容 + return response($htmlContent)->header([ + 'Content-Type' => 'text/html; charset=utf-8', + 'Cache-Control' => 'no-cache, no-store, must-revalidate', + 'Pragma' => 'no-cache', + 'Expires' => '0' + ]); + + } catch (\Exception $e) { + return $this->renderErrorPage('页面加载失败: ' . $e->getMessage()); + } + } + + /** + * 渲染Dashboard页面 + */ + private function renderDashboardPage($type, $userInfo, $platform) + { + // 获取页面数据 + $pageData = $this->getPageData($type, $userInfo); + + // 页面标题映射 + $titleMap = [ + 'my_data' => '我的数据', + 'dept_data' => '部门数据', + 'campus_data' => '校区数据' + ]; + + $pageTitle = $titleMap[$type] ?? '数据统计'; + + // 使用视图模板渲染页面 + return View::fetch('dashboard/main', [ + 'pageTitle' => $pageTitle, + 'pageData' => $pageData, + 'platform' => $platform, + 'userInfo' => $userInfo + ]); + } + + /** + * 获取页面数据 + */ + private function getPageData($type, $userInfo) + { + switch ($type) { + case 'my_data': + return $this->getMyData($userInfo); + case 'dept_data': + return $this->getDeptData($userInfo); + case 'campus_data': + return $this->getCampusData($userInfo); + default: + return []; + } + } + + /** + * 获取我的数据 + */ + private function getMyData($userInfo) + { + return [ + 'stats' => [ + ['label' => '本月签约客户', 'value' => 12, 'unit' => '人', 'trend' => '+15%'], + ['label' => '本月完成业绩', 'value' => 85000, 'unit' => '元', 'trend' => '+8%'], + ['label' => '跟进客户数', 'value' => 45, 'unit' => '人', 'trend' => '+5%'], + ['label' => '转化率', 'value' => 26.7, 'unit' => '%', 'trend' => '+2.3%'] + ], + 'charts' => [ + 'monthly_trend' => [ + 'title' => '月度业绩趋势', + 'data' => [65000, 72000, 68000, 75000, 82000, 85000] + ], + 'customer_source' => [ + 'title' => '客户来源分布', + 'data' => [ + ['name' => '线上推广', 'value' => 35], + ['name' => '转介绍', 'value' => 28], + ['name' => '电话营销', 'value' => 22], + ['name' => '其他', 'value' => 15] + ] + ] + ] + ]; + } + + /** + * 获取部门数据 + */ + private function getDeptData($userInfo) + { + return [ + 'stats' => [ + ['label' => '部门总业绩', 'value' => 520000, 'unit' => '元', 'trend' => '+12%'], + ['label' => '团队人数', 'value' => 8, 'unit' => '人', 'trend' => '0%'], + ['label' => '平均业绩', 'value' => 65000, 'unit' => '元', 'trend' => '+12%'], + ['label' => '部门排名', 'value' => 2, 'unit' => '名', 'trend' => '+1'] + ], + 'charts' => [ + 'team_performance' => [ + 'title' => '团队成员业绩排行', + 'data' => [ + ['name' => '张三', 'value' => 85000], + ['name' => '李四', 'value' => 72000], + ['name' => '王五', 'value' => 68000], + ['name' => '赵六', 'value' => 65000] + ] + ] + ] + ]; + } + + /** + * 获取校区数据 + */ + private function getCampusData($userInfo) + { + return [ + 'stats' => [ + ['label' => '校区总业绩', 'value' => 1200000, 'unit' => '元', 'trend' => '+18%'], + ['label' => '部门数量', 'value' => 5, 'unit' => '个', 'trend' => '0%'], + ['label' => '员工总数', 'value' => 32, 'unit' => '人', 'trend' => '+3'], + ['label' => '客户总数', 'value' => 245, 'unit' => '人', 'trend' => '+25'] + ], + 'charts' => [ + 'dept_performance' => [ + 'title' => '部门业绩对比', + 'data' => [ + ['name' => '销售一部', 'value' => 320000], + ['name' => '销售二部', 'value' => 280000], + ['name' => '销售三部', 'value' => 260000], + ['name' => '客服部', 'value' => 180000], + ['name' => '行政部', 'value' => 160000] + ] + ] + ] + ]; + } + + /** + * 获取模拟用户信息 + */ + private function getMockUserInfo($type) + { + return [ + 'id' => 1, + 'name' => '测试员工', + 'department' => '销售部', + 'campus' => '总校区', + 'role' => 'staff' + ]; + } + + + /** + * 渲染错误页面 + */ + private function renderErrorPage($message) + { + $errorHtml = View::fetch('dashboard/error', [ + 'message' => $message + ]); + + return response($errorHtml)->header([ + 'Content-Type' => 'text/html; charset=utf-8' + ]); + } +} \ No newline at end of file diff --git a/niucloud/app/api/controller/Dashboard_new.php b/niucloud/app/api/controller/Dashboard_new.php new file mode 100644 index 00000000..3bbcbc62 --- /dev/null +++ b/niucloud/app/api/controller/Dashboard_new.php @@ -0,0 +1,827 @@ +request->get('type', 'my_data'); // 页面类型 + $token = $this->request->get('token', ''); // 用户token + $platform = $this->request->get('platform', 'web'); // 平台标识 + + // 验证token和获取用户信息 + if (empty($token)) { + return $this->renderErrorPage('缺少用户认证信息'); + } + + try { + // 验证token并获取用户信息 + $userInfo = $this->getUserInfo($token); + + // 根据页面类型渲染不同内容 + $htmlContent = $this->renderDashboardPage($type, $userInfo, $platform); + + // 输出HTML内容 + return response($htmlContent)->header([ + 'Content-Type' => 'text/html; charset=utf-8', + 'Cache-Control' => 'no-cache, no-store, must-revalidate', + 'Pragma' => 'no-cache', + 'Expires' => '0' + ]); + + } catch (\Exception $e) { + return $this->renderErrorPage('页面加载失败: ' . $e->getMessage()); + } + } + + /** + * 获取用户信息 + */ + private function getUserInfo($token) + { + // TODO: 实现token验证逻辑,这里暂时使用测试数据 + // 根据教务系统角色使用指南,需要支持市场人员和管理者角色 + return [ + 'id' => 7, // 麒麟老师,市场人员 + 'name' => '麒麟老师', + 'role' => 'market_staff', + 'campus_id' => 1, + 'dept_id' => 1, + 'is_manager' => false, + 'staff_id' => 7 + ]; + } + + /** + * 渲染Dashboard页面 + */ + private function renderDashboardPage($type, $userInfo, $platform) + { + // 获取页面数据 + $pageData = $this->getPageData($type, $userInfo); + + // 页面标题映射 + $titleMap = [ + 'my_data' => '我的数据', + 'team_data' => '团队数据', + 'dept_data' => '部门数据', + 'campus_data' => '校区数据' + ]; + + $pageTitle = $titleMap[$type] ?? '数据统计'; + + // 使用视图模板渲染页面 + return View::fetch('dashboard/main', [ + 'pageTitle' => $pageTitle, + 'pageData' => $pageData, + 'platform' => $platform, + 'userInfo' => $userInfo + ]); + } + + /** + * 获取页面数据 + */ + private function getPageData($type, $userInfo) + { + switch ($type) { + case 'my_data': + return $this->getMyData($userInfo); + case 'team_data': + return $this->getTeamData($userInfo); + case 'dept_data': + return $this->getDeptData($userInfo); + case 'campus_data': + return $this->getCampusData($userInfo); + default: + return []; + } + } + + /** + * 获取我的数据(市场人员) + */ + private function getMyData($userInfo) + { + $staffId = $userInfo['staff_id']; + $currentMonth = date('Y-m-01'); + + return [ + // 核心统计指标 + 'stats' => [ + [ + 'label' => '本月新增资源', + 'value' => $this->getResourceCount($staffId, $currentMonth), + 'unit' => '个', + 'trend' => $this->getResourceTrend($staffId) + ], + [ + 'label' => '本月成交客户', + 'value' => $this->getConvertedCount($staffId, $currentMonth), + 'unit' => '人', + 'trend' => $this->getConversionTrend($staffId) + ], + [ + 'label' => '本月业绩', + 'value' => $this->getPerformance($staffId, $currentMonth), + 'unit' => '元', + 'trend' => $this->getPerformanceTrend($staffId) + ], + [ + 'label' => '本月提成', + 'value' => $this->getCommission($staffId, $currentMonth), + 'unit' => '元', + 'trend' => $this->getCommissionTrend($staffId) + ] + ], + + // 资源分析 + 'resource_analysis' => [ + 'channel_distribution' => $this->getChannelDistribution($staffId), + 'source_distribution' => $this->getSourceDistribution($staffId), + 'conversion_funnel' => $this->getConversionFunnel($staffId), + 'monthly_trend' => $this->getMonthlyTrend($staffId) + ], + + // 收益分析 + 'income_analysis' => [ + 'commission_breakdown' => $this->getCommissionBreakdown($staffId), + 'bonus_history' => $this->getBonusHistory($staffId), + 'income_trend' => $this->getIncomeTrend($staffId) + ] + ]; + } + + /** + * 获取团队数据(经理权限) + */ + private function getTeamData($userInfo) + { + $campusId = $userInfo['campus_id']; + $deptId = $userInfo['dept_id']; + $currentMonth = date('Y-m-01'); + + return [ + // 团队总览 + 'team_overview' => [ + 'total_members' => $this->getTeamMemberCount($campusId, $deptId), + 'total_resources' => $this->getTeamResourceCount($campusId, $deptId, $currentMonth), + 'total_converted' => $this->getTeamConvertedCount($campusId, $deptId, $currentMonth), + 'total_performance' => $this->getTeamPerformance($campusId, $deptId, $currentMonth) + ], + + // 团队成员排名 + 'member_ranking' => $this->getTeamMemberRanking($campusId, $deptId, $currentMonth), + + // 团队业绩分布 + 'performance_distribution' => $this->getTeamPerformanceDistribution($campusId, $deptId, $currentMonth) + ]; + } + + /** + * 获取部门数据 + */ + private function getDeptData($userInfo) + { + $campusId = $userInfo['campus_id']; + $currentMonth = date('Y-m-01'); + + return [ + // 部门总览 + 'dept_overview' => [ + 'total_depts' => $this->getDeptCount($campusId), + 'total_resources' => $this->getDeptResourceCount($campusId, $currentMonth), + 'total_performance' => $this->getDeptPerformance($campusId, $currentMonth), + 'conversion_rate' => $this->getDeptConversionRate($campusId, $currentMonth) + ], + + // 部门排名 + 'dept_ranking' => $this->getDeptRanking($campusId, $currentMonth) + ]; + } + + /** + * 获取校区数据 + */ + private function getCampusData($userInfo) + { + $campusId = $userInfo['campus_id']; + $currentMonth = date('Y-m-01'); + + return [ + // 校区总览 + 'campus_overview' => [ + 'total_performance' => $this->getCampusPerformance($campusId, $currentMonth), + 'total_resources' => $this->getCampusResourceCount($campusId, $currentMonth), + 'total_converted' => $this->getCampusConvertedCount($campusId, $currentMonth), + 'total_staff' => $this->getCampusStaffCount($campusId) + ], + + // 校区部门对比 + 'dept_comparison' => $this->getCampusDeptComparison($campusId, $currentMonth) + ]; + } + + // ========== 数据查询方法 ========== + + /** + * 获取资源数量 + */ + private function getResourceCount($staffId, $startDate) + { + return Db::table('school_customer_resources') + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->where('created_at', '>=', $startDate) + ->count(); + } + + /** + * 获取成交客户数量 + */ + private function getConvertedCount($staffId, $startDate) + { + return Db::table('school_customer_resources r') + ->join('school_order_table o', 'r.id = o.resource_id') + ->where('r.consultant', $staffId) + ->where('r.deleted_at', 0) + ->where('o.order_type', '1') + ->where('o.order_status', 'paid') + ->where('r.created_at', '>=', $startDate) + ->count(); + } + + /** + * 获取业绩数据 + */ + private function getPerformance($staffId, $startDate) + { + $result = Db::table('school_order_table') + ->where('staff_id', $staffId) + ->where('order_type', '1') + ->where('order_status', 'paid') + ->where('payment_time', '>=', $startDate) + ->sum('order_amount'); + + return $result ?: 0; + } + + /** + * 获取提成数据 + */ + private function getCommission($staffId, $startDate) + { + $result = Db::table('school_performance_records') + ->where('staff_id', $staffId) + ->whereIn('performance_type', ['sales', 'marketing', 'consultant']) + ->where('order_status', 'completed') + ->where('created_at', '>=', $startDate) + ->sum('performance_value'); + + return $result ?: 0; + } + + /** + * 获取渠道分布 + */ + private function getChannelDistribution($staffId) + { + $results = Db::table('school_customer_resources') + ->field('source_channel, COUNT(*) as count') + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->group('source_channel') + ->select(); + + $channelMap = [ + '1' => '线上推广', + '2' => '电话营销', + '3' => '地推活动', + '4' => '转介绍', + '5' => '其他' + ]; + + $data = []; + foreach ($results as $row) { + $channelName = $channelMap[$row['source_channel']] ?? '其他'; + $data[] = [ + 'name' => $channelName, + 'value' => $row['count'] + ]; + } + + return $data; + } + + /** + * 获取来源分布 + */ + private function getSourceDistribution($staffId) + { + $results = Db::table('school_customer_resources') + ->field('source, COUNT(*) as count') + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->whereNotNull('source') + ->where('source', '<>', '') + ->group('source') + ->select(); + + $sourceMap = [ + '1' => '官网', + '2' => '微信', + '3' => '抖音', + '4' => '小红书', + '5' => '其他' + ]; + + $data = []; + foreach ($results as $row) { + $sourceName = $sourceMap[$row['source']] ?? $row['source']; + $data[] = [ + 'name' => $sourceName, + 'value' => $row['count'] + ]; + } + + return $data; + } + + /** + * 获取转化漏斗 + */ + private function getConversionFunnel($staffId) + { + // 总资源数 + $totalResources = Db::table('school_customer_resources') + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->count(); + + // 有效联系数(简化处理) + $contactedResources = $totalResources; + + // 意向客户数 + $intentionResources = Db::table('school_customer_resources') + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->where('initial_intent', 'high') + ->count(); + + // 成交客户数 + $convertedResources = Db::table('school_customer_resources r') + ->join('school_order_table o', 'r.id = o.resource_id') + ->where('r.consultant', $staffId) + ->where('r.deleted_at', 0) + ->where('o.order_type', '1') + ->where('o.order_status', 'paid') + ->count(); + + return [ + ['stage' => '新增资源', 'count' => $totalResources, 'rate' => 100], + ['stage' => '有效联系', 'count' => $contactedResources, 'rate' => round($contactedResources * 100 / $totalResources, 1)], + ['stage' => '意向客户', 'count' => $intentionResources, 'rate' => round($intentionResources * 100 / $totalResources, 1)], + ['stage' => '成交客户', 'count' => $convertedResources, 'rate' => round($convertedResources * 100 / $totalResources, 1)] + ]; + } + + /** + * 获取月度趋势 + */ + private function getMonthlyTrend($staffId) + { + $results = Db::table('school_customer_resources') + ->field("DATE_FORMAT(created_at, '%Y-%m') as month, COUNT(*) as count") + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->group('month') + ->order('month DESC') + ->limit(6) + ->select(); + + return array_reverse($results); // 按时间正序 + } + + /** + * 获取提成明细 + */ + private function getCommissionBreakdown($staffId) + { + $results = Db::table('school_performance_records') + ->field('performance_type, SUM(performance_value) as total_amount, COUNT(*) as count') + ->where('staff_id', $staffId) + ->whereIn('performance_type', ['sales', 'marketing', 'consultant']) + ->where('order_status', 'completed') + ->group('performance_type') + ->select(); + + $typeMap = [ + 'sales' => '销售提成', + 'marketing' => '营销提成', + 'consultant' => '咨询提成' + ]; + + $data = []; + foreach ($results as $row) { + $typeName = $typeMap[$row['performance_type']] ?? $row['performance_type']; + $data[] = [ + 'type' => $typeName, + 'amount' => $row['total_amount'], + 'count' => $row['count'] + ]; + } + + return $data; + } + + /** + * 获取奖励历史 + */ + private function getBonusHistory($staffId) + { + return Db::table('school_salary') + ->field('salary_month, other_subsidies as amount') + ->where('staff_id', $staffId) + ->where('other_subsidies', '>', 0) + ->order('salary_month DESC') + ->limit(6) + ->select(); + } + + // ========== 团队数据查询方法 ========== + + /** + * 获取团队成员数量 + */ + private function getTeamMemberCount($campusId, $deptId) + { + return Db::table('school_personnel p') + ->join('school_campus_person_role cpr', 'p.id = cpr.person_id') + ->where('cpr.campus_id', $campusId) + ->where('cpr.dept_id', $deptId) + ->where('p.status', 1) + ->where('p.deleted_at', '0') + ->count(); + } + + /** + * 获取团队资源数量 + */ + private function getTeamResourceCount($campusId, $deptId, $startDate) + { + $members = Db::table('school_personnel p') + ->join('school_campus_person_role cpr', 'p.id = cpr.person_id') + ->where('cpr.campus_id', $campusId) + ->where('cpr.dept_id', $deptId) + ->where('p.status', 1) + ->where('p.deleted_at', '0') + ->column('id'); + + return Db::table('school_customer_resources') + ->whereIn('consultant', $members) + ->where('deleted_at', 0) + ->where('created_at', '>=', $startDate) + ->count(); + } + + /** + * 获取团队成交数量 + */ + private function getTeamConvertedCount($campusId, $deptId, $startDate) + { + $members = Db::table('school_personnel p') + ->join('school_campus_person_role cpr', 'p.id = cpr.person_id') + ->where('cpr.campus_id', $campusId) + ->where('cpr.dept_id', $deptId) + ->where('p.status', 1) + ->where('p.deleted_at', '0') + ->column('id'); + + return Db::table('school_customer_resources r') + ->join('school_order_table o', 'r.id = o.resource_id') + ->whereIn('r.consultant', $members) + ->where('r.deleted_at', 0) + ->where('o.order_type', '1') + ->where('o.order_status', 'paid') + ->where('r.created_at', '>=', $startDate) + ->count(); + } + + /** + * 获取团队业绩 + */ + private function getTeamPerformance($campusId, $deptId, $startDate) + { + $members = Db::table('school_personnel p') + ->join('school_campus_person_role cpr', 'p.id = cpr.person_id') + ->where('cpr.campus_id', $campusId) + ->where('cpr.dept_id', $deptId) + ->where('p.status', 1) + ->where('p.deleted_at', '0') + ->column('id'); + + $result = Db::table('school_order_table') + ->whereIn('staff_id', $members) + ->where('order_type', '1') + ->where('order_status', 'paid') + ->where('payment_time', '>=', $startDate) + ->sum('order_amount'); + + return $result ?: 0; + } + + /** + * 获取团队成员排名 + */ + private function getTeamMemberRanking($campusId, $deptId, $startDate) + { + // 获取团队成员列表 + $members = Db::table('school_personnel p') + ->join('school_campus_person_role cpr', 'p.id = cpr.person_id') + ->where('cpr.campus_id', $campusId) + ->where('cpr.dept_id', $deptId) + ->where('p.status', 1) + ->where('p.deleted_at', '0') + ->column('id, name'); + + $ranking = []; + foreach ($members as $member) { + $resourceCount = $this->getResourceCount($member['id'], $startDate); + $convertedCount = $this->getConvertedCount($member['id'], $startDate); + $performance = $this->getPerformance($member['id'], $startDate); + $commission = $this->getCommission($member['id'], $startDate); + + $ranking[] = [ + 'staff_id' => $member['id'], + 'staff_name' => $member['name'], + 'resource_count' => $resourceCount, + 'converted_count' => $convertedCount, + 'performance' => $performance, + 'commission' => $commission + ]; + } + + // 按业绩排序 + usort($ranking, function($a, $b) { + return $b['performance'] <=> $a['performance']; + }); + + return $ranking; + } + + // ========== 部门数据查询方法 ========== + + /** + * 获取部门数量 + */ + private function getDeptCount($campusId) + { + return Db::table('school_departments') + ->where('deleted_at', 0) + ->count(); + } + + /** + * 获取部门资源数量 + */ + private function getDeptResourceCount($campusId, $startDate) + { + return Db::table('school_customer_resources') + ->where('campus', $campusId) + ->where('deleted_at', 0) + ->where('created_at', '>=', $startDate) + ->count(); + } + + /** + * 获取部门业绩 + */ + private function getDeptPerformance($campusId, $startDate) + { + $result = Db::table('school_order_table') + ->where('campus_id', $campusId) + ->where('order_type', '1') + ->where('order_status', 'paid') + ->where('payment_time', '>=', $startDate) + ->sum('order_amount'); + + return $result ?: 0; + } + + /** + * 获取部门转化率 + */ + private function getDeptConversionRate($campusId, $startDate) + { + $totalResources = Db::table('school_customer_resources') + ->where('campus', $campusId) + ->where('deleted_at', 0) + ->where('created_at', '>=', $startDate) + ->count(); + + if ($totalResources == 0) return 0; + + $convertedResources = Db::table('school_customer_resources r') + ->join('school_order_table o', 'r.id = o.resource_id') + ->where('r.campus', $campusId) + ->where('r.deleted_at', 0) + ->where('o.order_type', '1') + ->where('o.order_status', 'paid') + ->where('r.created_at', '>=', $startDate) + ->count(); + + return round($convertedResources * 100 / $totalResources, 1); + } + + /** + * 获取部门排名 + */ + private function getDeptRanking($campusId, $startDate) + { + $depts = Db::table('school_departments') + ->where('deleted_at', 0) + ->select(); + + $ranking = []; + foreach ($depts as $dept) { + $performance = Db::table('school_order_table') + ->where('campus_id', $campusId) + ->where('order_type', '1') + ->where('order_status', 'paid') + ->where('payment_time', '>=', $startDate) + ->sum('order_amount'); + + $ranking[] = [ + 'dept_id' => $dept['id'], + 'dept_name' => $dept['department_name'], + 'performance' => $performance ?: 0 + ]; + } + + // 按业绩排序 + usort($ranking, function($a, $b) { + return $b['performance'] <=> $a['performance']; + }); + + return $ranking; + } + + // ========== 校区数据查询方法 ========== + + /** + * 获取校区业绩 + */ + private function getCampusPerformance($campusId, $startDate) + { + $result = Db::table('school_order_table') + ->where('campus_id', $campusId) + ->where('order_type', '1') + ->where('order_status', 'paid') + ->where('payment_time', '>=', $startDate) + ->sum('order_amount'); + + return $result ?: 0; + } + + /** + * 获取校区资源数量 + */ + private function getCampusResourceCount($campusId, $startDate) + { + return Db::table('school_customer_resources') + ->where('campus', $campusId) + ->where('deleted_at', 0) + ->where('created_at', '>=', $startDate) + ->count(); + } + + /** + * 获取校区成交数量 + */ + private function getCampusConvertedCount($campusId, $startDate) + { + return Db::table('school_customer_resources r') + ->join('school_order_table o', 'r.id = o.resource_id') + ->where('r.campus', $campusId) + ->where('r.deleted_at', 0) + ->where('o.order_type', '1') + ->where('o.order_status', 'paid') + ->where('r.created_at', '>=', $startDate) + ->count(); + } + + /** + * 获取校区员工数量 + */ + private function getCampusStaffCount($campusId) + { + return Db::table('school_personnel p') + ->join('school_campus_person_role cpr', 'p.id = cpr.person_id') + ->where('cpr.campus_id', $campusId) + ->where('p.status', 1) + ->where('p.deleted_at', '0') + ->count(); + } + + /** + * 获取校区部门对比 + */ + private function getCampusDeptComparison($campusId, $startDate) + { + $depts = Db::table('school_departments') + ->where('deleted_at', 0) + ->select(); + + $comparison = []; + foreach ($depts as $dept) { + $performance = Db::table('school_order_table') + ->join('school_customer_resources r', 'r.id = o.resource_id') + ->where('o.campus_id', $campusId) + ->where('r.consultant', '!=', '') + ->where('o.order_type', '1') + ->where('o.order_status', 'paid') + ->where('o.payment_time', '>=', $startDate) + ->sum('o.order_amount'); + + $comparison[] = [ + 'dept_id' => $dept['id'], + 'dept_name' => $dept['department_name'], + 'performance' => $performance ?: 0 + ]; + } + + return $comparison; + } + + // ========== 趋势计算方法 ========== + + /** + * 计算资源趋势 + */ + private function getResourceTrend($staffId) + { + $currentMonth = date('Y-m-01'); + $lastMonth = date('Y-m-01', strtotime('-1 month')); + + $current = $this->getResourceCount($staffId, $currentMonth); + $last = $this->getResourceCount($staffId, $lastMonth); + + if ($last == 0) return '+0%'; + + $trend = round(($current - $last) * 100 / $last, 1); + return $trend >= 0 ? '+' . $trend . '%' : $trend . '%'; + } + + /** + * 计算转化趋势 + */ + private function getConversionTrend($staffId) + { + // 简化处理,返回示例数据 + return '+8%'; + } + + /** + * 计算业绩趋势 + */ + private function getPerformanceTrend($staffId) + { + // 简化处理,返回示例数据 + return '+12%'; + } + + /** + * 计算提成趋势 + */ + private function getCommissionTrend($staffId) + { + // 简化处理,返回示例数据 + return '+15%'; + } + + /** + * 渲染错误页面 + */ + private function renderErrorPage($message) + { + $errorHtml = View::fetch('dashboard/error', [ + 'message' => $message + ]); + + return response($errorHtml)->header([ + 'Content-Type' => 'text/html; charset=utf-8' + ]); + } +} \ No newline at end of file diff --git a/niucloud/app/api/controller/MarketStats.php b/niucloud/app/api/controller/MarketStats.php new file mode 100644 index 00000000..07b9025c --- /dev/null +++ b/niucloud/app/api/controller/MarketStats.php @@ -0,0 +1,423 @@ +request->post('staff_id'); + $startDate = $this->request->post('start_date'); + $endDate = $this->request->post('end_date'); + + if (empty($staffId)) { + return fail('缺少员工ID'); + } + + try { + $stats = [ + 'resource_count' => $this->getResourceCount($staffId, $startDate, $endDate), + 'converted_count' => $this->getConvertedCount($staffId, $startDate, $endDate), + 'performance' => $this->getPerformance($staffId, $startDate, $endDate), + 'commission' => $this->getCommission($staffId, $startDate, $endDate), + 'bonus' => $this->getBonus($staffId, $startDate, $endDate) + ]; + + return success($stats); + + } catch (\Exception $e) { + return fail($e->getMessage()); + } + } + + /** + * 获取团队统计数据 + */ + public function getTeamStats() + { + $campusId = $this->request->post('campus_id'); + $deptId = $this->request->post('dept_id'); + $startDate = $this->request->post('start_date'); + $endDate = $this->request->post('end_date'); + + if (empty($campusId) || empty($deptId)) { + return fail('缺少校区或部门信息'); + } + + try { + $stats = [ + 'team_members' => $this->getTeamMembers($campusId, $deptId), + 'team_performance' => $this->getTeamPerformance($campusId, $deptId, $startDate, $endDate), + 'ranking' => $this->getTeamRanking($campusId, $deptId, $startDate, $endDate) + ]; + + return success($stats); + + } catch (\Exception $e) { + return fail($e->getMessage()); + } + } + + /** + * 获取渠道分布统计 + */ + public function getChannelDistribution() + { + $staffId = $this->request->post('staff_id'); + $startDate = $this->request->post('start_date'); + $endDate = $this->request->post('end_date'); + + try { + $distribution = Db::table('school_customer_resources') + ->field('source_channel, COUNT(*) as count') + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->when($startDate, function($query) use ($startDate) { + return $query->whereTime('created_at', '>=', $startDate); + }) + ->when($endDate, function($query) use ($endDate) { + return $query->whereTime('created_at', '<=', $endDate); + }) + ->group('source_channel') + ->select(); + + return success($distribution); + + } catch (\Exception $e) { + return fail($e->getMessage()); + } + } + + /** + * 获取来源分布统计 + */ + public function getSourceDistribution() + { + $staffId = $this->request->post('staff_id'); + $startDate = $this->request->post('start_date'); + $endDate = $this->request->post('end_date'); + + try { + $distribution = Db::table('school_customer_resources') + ->field('source, COUNT(*) as count') + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->when($startDate, function($query) use ($startDate) { + return $query->whereTime('created_at', '>=', $startDate); + }) + ->when($endDate, function($query) use ($endDate) { + return $query->whereTime('created_at', '<=', $endDate); + }) + ->whereNotNull('source') + ->where('source', '<>', '') + ->group('source') + ->select(); + + return success($distribution); + + } catch (\Exception $e) { + return fail($e->getMessage()); + } + } + + /** + * 获取转化漏斗数据 + */ + public function getConversionFunnel() + { + $staffId = $this->request->post('staff_id'); + $startDate = $this->request->post('start_date'); + $endDate = $this->request->post('end_date'); + + try { + // 总资源数 + $totalResources = Db::table('school_customer_resources') + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->when($startDate, function($query) use ($startDate) { + return $query->whereTime('created_at', '>=', $startDate); + }) + ->when($endDate, function($query) use ($endDate) { + return $query->whereTime('created_at', '<=', $endDate); + }) + ->count(); + + // 成交客户数 + $convertedResources = Db::table('school_customer_resources r') + ->join('school_order_table o', 'r.id = o.resource_id') + ->where('r.consultant', $staffId) + ->where('r.deleted_at', 0) + ->where('o.order_type', '1') + ->where('o.order_status', 'paid') + ->when($startDate, function($query) use ($startDate) { + return $query->whereTime('r.created_at', '>=', $startDate); + }) + ->when($endDate, function($query) use ($endDate) { + return $query->whereTime('r.created_at', '<=', $endDate); + }) + ->count(); + + $funnel = [ + 'total_resources' => $totalResources, + 'converted_resources' => $convertedResources, + 'conversion_rate' => $totalResources > 0 ? round($convertedResources * 100 / $totalResources, 1) : 0 + ]; + + return success($funnel); + + } catch (\Exception $e) { + return fail($e->getMessage()); + } + } + + /** + * 获取月度趋势数据 + */ + public function getMonthlyTrend() + { + $staffId = $this->request->post('staff_id'); + $months = $this->request->post('months', 6); + + try { + $results = Db::table('school_customer_resources') + ->field("DATE_FORMAT(created_at, '%Y-%m') as month, COUNT(*) as count") + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->group('month') + ->order('month DESC') + ->limit($months) + ->select(); + + return success(array_reverse($results)); + + } catch (\Exception $e) { + return fail($e->getMessage()); + } + } + + /** + * 获取提成明细数据 + */ + public function getCommissionBreakdown() + { + $staffId = $this->request->post('staff_id'); + $startDate = $this->request->post('start_date'); + $endDate = $this->request->post('end_date'); + + try { + $results = Db::table('school_performance_records') + ->field('performance_type, SUM(performance_value) as total_amount, COUNT(*) as count') + ->where('staff_id', $staffId) + ->whereIn('performance_type', ['sales', 'marketing', 'consultant']) + ->where('order_status', 'completed') + ->when($startDate, function($query) use ($startDate) { + return $query->whereTime('created_at', '>=', $startDate); + }) + ->when($endDate, function($query) use ($endDate) { + return $query->whereTime('created_at', '<=', $endDate); + }) + ->group('performance_type') + ->select(); + + return success($results); + + } catch (\Exception $e) { + return fail($e->getMessage()); + } + } + + /** + * 获取奖励历史数据 + */ + public function getBonusHistory() + { + $staffId = $this->request->post('staff_id'); + $months = $this->request->post('months', 6); + + try { + $results = Db::table('school_salary') + ->field('salary_month, other_subsidies as amount') + ->where('staff_id', $staffId) + ->where('other_subsidies', '>', 0) + ->order('salary_month DESC') + ->limit($months) + ->select(); + + return success($results); + + } catch (\Exception $e) { + return fail($e->getMessage()); + } + } + + // ========== 私有查询方法 ========== + + /** + * 获取资源数量 + */ + private function getResourceCount($staffId, $startDate = null, $endDate = null) + { + return Db::table('school_customer_resources') + ->where('consultant', $staffId) + ->where('deleted_at', 0) + ->when($startDate, function($query) use ($startDate) { + return $query->whereTime('created_at', '>=', $startDate); + }) + ->when($endDate, function($query) use ($endDate) { + return $query->whereTime('created_at', '<=', $endDate); + }) + ->count(); + } + + /** + * 获取成交客户数量 + */ + private function getConvertedCount($staffId, $startDate = null, $endDate = null) + { + return Db::table('school_customer_resources r') + ->join('school_order_table o', 'r.id = o.resource_id') + ->where('r.consultant', $staffId) + ->where('r.deleted_at', 0) + ->where('o.order_type', '1') + ->where('o.order_status', 'paid') + ->when($startDate, function($query) use ($startDate) { + return $query->whereTime('r.created_at', '>=', $startDate); + }) + ->when($endDate, function($query) use ($endDate) { + return $query->whereTime('r.created_at', '<=', $endDate); + }) + ->count(); + } + + /** + * 获取业绩数据 + */ + private function getPerformance($staffId, $startDate = null, $endDate = null) + { + $result = Db::table('school_order_table') + ->where('staff_id', $staffId) + ->where('order_type', '1') + ->where('order_status', 'paid') + ->when($startDate, function($query) use ($startDate) { + return $query->whereTime('payment_time', '>=', $startDate); + }) + ->when($endDate, function($query) use ($endDate) { + return $query->whereTime('payment_time', '<=', $endDate); + }) + ->sum('order_amount'); + + return $result ?: 0; + } + + /** + * 获取提成数据 + */ + private function getCommission($staffId, $startDate = null, $endDate = null) + { + $result = Db::table('school_performance_records') + ->where('staff_id', $staffId) + ->whereIn('performance_type', ['sales', 'marketing', 'consultant']) + ->where('order_status', 'completed') + ->when($startDate, function($query) use ($startDate) { + return $query->whereTime('created_at', '>=', $startDate); + }) + ->when($endDate, function($query) use ($endDate) { + return $query->whereTime('created_at', '<=', $endDate); + }) + ->sum('performance_value'); + + return $result ?: 0; + } + + /** + * 获取奖励数据 + */ + private function getBonus($staffId, $startDate = null, $endDate = null) + { + $result = Db::table('school_salary') + ->where('staff_id', $staffId) + ->where('other_subsidies', '>', 0) + ->when($startDate, function($query) use ($startDate) { + return $query->whereTime('salary_month', '>=', $startDate); + }) + ->when($endDate, function($query) use ($endDate) { + return $query->whereTime('salary_month', '<=', $endDate); + }) + ->sum('other_subsidies'); + + return $result ?: 0; + } + + /** + * 获取团队成员 + */ + private function getTeamMembers($campusId, $deptId) + { + return Db::table('school_personnel p') + ->join('school_campus_person_role cpr', 'p.id = cpr.person_id') + ->where('cpr.campus_id', $campusId) + ->where('cpr.dept_id', $deptId) + ->where('p.status', 1) + ->where('p.deleted_at', '0') + ->field('p.id, p.name') + ->select(); + } + + /** + * 获取团队业绩 + */ + private function getTeamPerformance($campusId, $deptId, $startDate = null, $endDate = null) + { + $members = $this->getTeamMembers($campusId, $deptId); + $memberIds = array_column($members, 'id'); + + $result = Db::table('school_order_table') + ->whereIn('staff_id', $memberIds) + ->where('order_type', '1') + ->where('order_status', 'paid') + ->when($startDate, function($query) use ($startDate) { + return $query->whereTime('payment_time', '>=', $startDate); + }) + ->when($endDate, function($query) use ($endDate) { + return $query->whereTime('payment_time', '<=', $endDate); + }) + ->sum('order_amount'); + + return $result ?: 0; + } + + /** + * 获取团队排名 + */ + private function getTeamRanking($campusId, $deptId, $startDate = null, $endDate = null) + { + $members = $this->getTeamMembers($campusId, $deptId); + $memberIds = array_column($members, 'id'); + + $ranking = []; + foreach ($members as $member) { + $performance = $this->getPerformance($member['id'], $startDate, $endDate); + $ranking[] = [ + 'staff_id' => $member['id'], + 'staff_name' => $member['name'], + 'performance' => $performance + ]; + } + + // 按业绩排序 + usort($ranking, function($a, $b) { + return $b['performance'] <=> $a['performance']; + }); + + return $ranking; + } +} \ No newline at end of file diff --git a/niucloud/app/api/route/route.php b/niucloud/app/api/route/route.php index 720e137c..a688a682 100644 --- a/niucloud/app/api/route/route.php +++ b/niucloud/app/api/route/route.php @@ -446,6 +446,13 @@ Route::group(function () { Route::get('statistics/getStaffStatistics', 'apiController.Statistics/getStaffStatistics'); + //市场统计API接口 + Route::post('market_stats/getPersonalStats', 'MarketStats/getPersonalStats'); + Route::post('market_stats/getTeamStats', 'MarketStats/getTeamStats'); + Route::post('market_stats/getChannelDistribution', 'MarketStats/getChannelDistribution'); + Route::post('market_stats/getConversionFunnel', 'MarketStats/getConversionFunnel'); + Route::post('market_stats/getMonthlyTrend', 'MarketStats/getMonthlyTrend'); + //报销 Route::get('personnel/reimbursement_list', 'apiController.Personnel/reimbursement_list'); Route::post('personnel/reimbursement_add', 'apiController.Personnel/reimbursement_add'); diff --git a/niucloud/app/view/dashboard/main.html b/niucloud/app/view/dashboard/main.html new file mode 100644 index 00000000..5586f928 --- /dev/null +++ b/niucloud/app/view/dashboard/main.html @@ -0,0 +1,590 @@ + + + + + + {$pageTitle} + + + + + + +
+ +
+

{$pageTitle}

+ +
+ + +
+
+ + +
+
+ + +
+
+ +
+
+ + +
+
+
+ +
+
+
{{ formatValue(stat.value) }}{{ stat.unit }}
+
{{ stat.label }}
+
+ + {{ stat.trend }} +
+
+
+
+ + +
+

资源分析

+
+
+

渠道分布

+ +
+
+

来源分布

+ +
+
+

转化漏斗

+ +
+
+

月度趋势

+ +
+
+
+ + +
+

收益分析

+
+
+

提成明细

+ +
+
+

奖励历史

+ +
+
+
+ + +
+

团队数据

+
+
+

团队成员

+
{{ pageData.team_overview.total_members }}
+
活跃成员
+
+
+

团队资源

+
{{ pageData.team_overview.total_resources }}
+
本月新增
+
+
+

团队成交

+
{{ pageData.team_overview.total_converted }}
+
本月成交
+
+
+

团队业绩

+
{{ formatValue(pageData.team_overview.total_performance) }}
+
本月业绩
+
+
+ +
+

团队成员排名

+
+ + + + + + + + + + + + + + + + + + + + + +
排名姓名资源数成交数业绩提成
+ + {{ index + 1 }} + + {{ member.staff_name }}{{ member.resource_count }}{{ member.converted_count }}{{ formatValue(member.performance) }}{{ formatValue(member.commission) }}
+
+
+
+ + +
+

部门数据

+
+
+

部门总数

+
{{ pageData.dept_overview.total_depts }}
+
活跃部门
+
+
+

部门资源

+
{{ pageData.dept_overview.total_resources }}
+
本月新增
+
+
+

部门业绩

+
{{ formatValue(pageData.dept_overview.total_performance) }}
+
本月业绩
+
+
+

转化率

+
{{ pageData.dept_overview.conversion_rate }}%
+
平均转化
+
+
+ +
+

部门业绩排名

+
+ + + + + + + + + + + + + + + + + +
排名部门名称业绩占比
+ + {{ index + 1 }} + + {{ dept.dept_name }}{{ formatValue(dept.performance) }}{{ getPercentage(dept.performance, pageData.dept_overview.total_performance) }}%
+
+
+
+ + +
+
+ +

数据加载中...

+
+
+
+ + + + + + \ No newline at end of file diff --git a/niucloud/public/static/css/dashboard.css b/niucloud/public/static/css/dashboard.css new file mode 100644 index 00000000..09cd2706 --- /dev/null +++ b/niucloud/public/static/css/dashboard.css @@ -0,0 +1,569 @@ +/* 市场人员业绩管理系统 - Dashboard样式 */ + +* { + margin: 0; + padding: 0; + box-sizing: border-box; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + min-height: 100vh; + color: #333; +} + +/* 页面头部 */ +.header { + background: linear-gradient(135deg, rgba(102, 126, 234, 0.95) 0%, rgba(118, 75, 162, 0.95) 100%); + color: white; + padding: 20px 30px; + display: flex; + justify-content: space-between; + align-items: center; + box-shadow: 0 2px 20px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(10px); +} + +.header h1 { + font-size: 28px; + font-weight: 700; + display: flex; + align-items: center; + gap: 12px; +} + +.header h1 i { + font-size: 32px; +} + +.user-info { + display: flex; + align-items: center; + gap: 15px; + font-size: 16px; +} + +.role-badge { + background: rgba(255, 255, 255, 0.2); + padding: 8px 16px; + border-radius: 25px; + font-size: 14px; + font-weight: 600; + display: flex; + align-items: center; + gap: 6px; + border: 1px solid rgba(255, 255, 255, 0.3); +} + +.role-badge i { + font-size: 12px; +} + +/* 数据筛选器 */ +.filter-container { + background: rgba(255, 255, 255, 0.95); + margin: 20px; + padding: 20px; + border-radius: 15px; + display: flex; + gap: 20px; + align-items: center; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(10px); +} + +.filter-item { + display: flex; + align-items: center; + gap: 10px; +} + +.filter-item label { + font-weight: 600; + color: #374151; + font-size: 14px; +} + +.filter-item select { + padding: 10px 15px; + border: 2px solid #e5e7eb; + border-radius: 8px; + background: white; + color: #374151; + font-size: 14px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; +} + +.filter-item select:hover { + border-color: #667eea; +} + +.filter-item select:focus { + outline: none; + border-color: #667eea; + box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1); +} + +.btn-export { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + padding: 12px 20px; + border-radius: 8px; + font-size: 14px; + font-weight: 600; + cursor: pointer; + transition: all 0.2s ease; + display: flex; + align-items: center; + gap: 8px; +} + +.btn-export:hover { + transform: translateY(-2px); + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3); +} + +/* 统计卡片容器 */ +.stats-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); + gap: 25px; + padding: 0 20px 20px; +} + +/* 统计卡片 */ +.stat-card { + background: rgba(255, 255, 255, 0.95); + border-radius: 20px; + padding: 25px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + display: flex; + align-items: center; + gap: 20px; + transition: all 0.3s ease; + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.stat-card:hover { + transform: translateY(-5px); + box-shadow: 0 15px 40px rgba(0, 0, 0, 0.15); +} + +.stat-icon { + width: 70px; + height: 70px; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + border-radius: 50%; + display: flex; + align-items: center; + justify-content: center; + color: white; + font-size: 28px; + box-shadow: 0 8px 25px rgba(102, 126, 234, 0.3); +} + +.stat-content { + flex: 1; +} + +.stat-value { + font-size: 32px; + font-weight: 700; + color: #1f2937; + margin-bottom: 5px; +} + +.stat-label { + font-size: 14px; + color: #6b7280; + font-weight: 500; + margin-bottom: 8px; +} + +.stat-trend { + font-size: 13px; + font-weight: 600; + display: flex; + align-items: center; + gap: 4px; +} + +.trend-up { + color: #10b981; +} + +.trend-down { + color: #ef4444; +} + +/* 区域容器 */ +.section { + padding: 20px; +} + +.section h2 { + font-size: 24px; + font-weight: 700; + color: white; + margin-bottom: 25px; + display: flex; + align-items: center; + gap: 12px; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); +} + +.section h2 i { + font-size: 28px; +} + +/* 图表容器 */ +.charts-container { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(400px, 1fr)); + gap: 25px; +} + +.chart-card { + background: rgba(255, 255, 255, 0.95); + border-radius: 20px; + padding: 25px; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.chart-card h3 { + font-size: 16px; + font-weight: 600; + color: #374151; + margin-bottom: 20px; + display: flex; + align-items: center; + gap: 8px; +} + +.chart-card h3 i { + font-size: 18px; + color: #667eea; +} + +/* 团队总览 */ +.team-overview { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +.overview-card { + background: rgba(255, 255, 255, 0.95); + border-radius: 15px; + padding: 20px; + text-align: center; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); + transition: all 0.3s ease; +} + +.overview-card:hover { + transform: translateY(-3px); + box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15); +} + +.overview-card h3 { + font-size: 14px; + color: #6b7280; + margin-bottom: 10px; + font-weight: 600; +} + +.overview-card h3 i { + font-size: 16px; + color: #667eea; + margin-bottom: 5px; +} + +.overview-value { + font-size: 36px; + font-weight: 700; + color: #1f2937; + margin-bottom: 5px; +} + +.overview-desc { + font-size: 12px; + color: #9ca3af; + font-weight: 500; +} + +/* 排名区域 */ +.ranking-section h3 { + font-size: 18px; + font-weight: 600; + color: white; + margin-bottom: 20px; + display: flex; + align-items: center; + gap: 8px; +} + +.ranking-section h3 i { + font-size: 20px; +} + +.ranking-table { + background: rgba(255, 255, 255, 0.95); + border-radius: 15px; + overflow: hidden; + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1); + backdrop-filter: blur(10px); + border: 1px solid rgba(255, 255, 255, 0.2); +} + +.ranking-table table { + width: 100%; + border-collapse: collapse; +} + +.ranking-table th, +.ranking-table td { + padding: 15px 20px; + text-align: left; + border-bottom: 1px solid rgba(0, 0, 0, 0.05); +} + +.ranking-table th { + background: rgba(102, 126, 234, 0.1); + font-weight: 600; + color: #374151; + font-size: 14px; +} + +.ranking-table th i { + margin-right: 5px; + color: #667eea; +} + +.ranking-table td { + font-size: 14px; + color: #374151; + font-weight: 500; +} + +.ranking-table tr:hover { + background: rgba(102, 126, 234, 0.05); +} + +.ranking-table tr:last-child td { + border-bottom: none; +} + +/* 排名徽章 */ +.rank-badge { + display: inline-flex; + align-items: center; + justify-content: center; + width: 28px; + height: 28px; + border-radius: 50%; + font-size: 12px; + font-weight: 700; + color: white; + text-shadow: 0 1px 2px rgba(0, 0, 0, 0.2); +} + +.rank-first { + background: linear-gradient(135deg, #ffd700, #ffed4e); + color: #92400e; +} + +.rank-second { + background: linear-gradient(135deg, #c0c0c0, #e5e5e5); + color: #374151; +} + +.rank-third { + background: linear-gradient(135deg, #cd7f32, #e5a25d); + color: white; +} + +.rank-other { + background: linear-gradient(135deg, #6b7280, #9ca3af); +} + +/* 响应式设计 */ +@media (max-width: 1200px) { + .stats-container { + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + } + + .charts-container { + grid-template-columns: repeat(auto-fit, minmax(350px, 1fr)); + } +} + +@media (max-width: 768px) { + .header { + flex-direction: column; + gap: 15px; + text-align: center; + } + + .filter-container { + flex-direction: column; + align-items: stretch; + gap: 15px; + } + + .stats-container { + grid-template-columns: 1fr; + padding: 0 15px 15px; + } + + .charts-container { + grid-template-columns: 1fr; + } + + .team-overview { + grid-template-columns: repeat(2, 1fr); + } + + .ranking-table { + overflow-x: auto; + } + + .ranking-table table { + min-width: 600px; + } +} + +@media (max-width: 480px) { + .header h1 { + font-size: 24px; + } + + .user-info { + flex-direction: column; + gap: 8px; + } + + .stat-card { + flex-direction: column; + text-align: center; + gap: 15px; + } + + .stat-icon { + width: 60px; + height: 60px; + font-size: 24px; + } + + .team-overview { + grid-template-columns: 1fr; + } + + .overview-value { + font-size: 28px; + } +} + +/* 动画效果 */ +@keyframes fadeInUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.stat-card, +.chart-card, +.overview-card { + animation: fadeInUp 0.6s ease-out; +} + +.chart-card:nth-child(2) { + animation-delay: 0.1s; +} + +.chart-card:nth-child(3) { + animation-delay: 0.2s; +} + +.chart-card:nth-child(4) { + animation-delay: 0.3s; +} + +/* 滚动条样式 */ +::-webkit-scrollbar { + width: 8px; +} + +::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.1); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.3); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.5); +} + +/* 加载动画 */ +.loading-spinner { + text-align: center; +} + +.loading-spinner i { + font-size: 48px; + color: #667eea; + animation: spin 1s linear infinite; +} + +@keyframes spin { + from { + transform: rotate(0deg); + } + to { + transform: rotate(360deg); + } +} + +/* 特殊效果 */ +.stat-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, #667eea, #764ba2, #667eea); + border-radius: 20px 20px 0 0; + opacity: 0.7; +} + +.overview-card::before { + content: ''; + position: absolute; + top: 0; + left: 0; + right: 0; + height: 2px; + background: linear-gradient(90deg, #667eea, #764ba2); + border-radius: 15px 15px 0 0; + opacity: 0.5; +} \ No newline at end of file