Browse Source

修改 bug

master
王泽彦 7 months ago
parent
commit
b9879d7249
  1. 762
      niucloud/app/api/controller/Dashboard.php
  2. 202
      niucloud/app/api/controller/Dashboard.php.backup
  3. 827
      niucloud/app/api/controller/Dashboard_new.php
  4. 423
      niucloud/app/api/controller/MarketStats.php
  5. 7
      niucloud/app/api/route/route.php
  6. 590
      niucloud/app/view/dashboard/main.html
  7. 569
      niucloud/public/static/css/dashboard.css

762
niucloud/app/api/controller/Dashboard.php

@ -5,9 +5,10 @@ namespace app\api\controller;
use app\service\api\member\MemberService; use app\service\api\member\MemberService;
use core\base\BaseApiController; use core\base\BaseApiController;
use think\facade\View; use think\facade\View;
use think\facade\Db;
/** /**
* Dashboard WebView 控制器 * Dashboard WebView 控制器 - 市场人员业绩管理系统
*/ */
class Dashboard extends BaseApiController class Dashboard extends BaseApiController
{ {
@ -26,8 +27,8 @@ class Dashboard extends BaseApiController
} }
try { try {
// 这里应该验证token,暂时跳过验证用于测试 // 验证token并获取用户信息
$userInfo = $this->getMockUserInfo($type); $userInfo = $this->getUserInfo($token);
// 根据页面类型渲染不同内容 // 根据页面类型渲染不同内容
$htmlContent = $this->renderDashboardPage($type, $userInfo, $platform); $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页面 * 渲染Dashboard页面
*/ */
@ -56,6 +75,7 @@ class Dashboard extends BaseApiController
// 页面标题映射 // 页面标题映射
$titleMap = [ $titleMap = [
'my_data' => '我的数据', 'my_data' => '我的数据',
'team_data' => '团队数据',
'dept_data' => '部门数据', 'dept_data' => '部门数据',
'campus_data' => '校区数据' 'campus_data' => '校区数据'
]; ];
@ -79,6 +99,8 @@ class Dashboard extends BaseApiController
switch ($type) { switch ($type) {
case 'my_data': case 'my_data':
return $this->getMyData($userInfo); return $this->getMyData($userInfo);
case 'team_data':
return $this->getTeamData($userInfo);
case 'dept_data': case 'dept_data':
return $this->getDeptData($userInfo); return $this->getDeptData($userInfo);
case 'campus_data': case 'campus_data':
@ -89,58 +111,104 @@ class Dashboard extends BaseApiController
} }
/** /**
* 获取我的数据 * 获取我的数据(市场人员)
*/ */
private function getMyData($userInfo) private function getMyData($userInfo)
{ {
$staffId = $userInfo['staff_id'];
$currentMonth = date('Y-m-01');
return [ return [
// 核心统计指标
'stats' => [ 'stats' => [
['label' => '本月签约客户', 'value' => 12, 'unit' => '人', 'trend' => '+15%'], [
['label' => '本月完成业绩', 'value' => 85000, 'unit' => '元', 'trend' => '+8%'], 'label' => '本月新增资源',
['label' => '跟进客户数', 'value' => 45, 'unit' => '人', 'trend' => '+5%'], 'value' => $this->getResourceCount($staffId, $currentMonth),
['label' => '转化率', 'value' => 26.7, 'unit' => '%', 'trend' => '+2.3%'] 'unit' => '个',
'trend' => $this->getResourceTrend($staffId)
], ],
'charts' => [ [
'monthly_trend' => [ 'label' => '本月成交客户',
'title' => '月度业绩趋势', 'value' => $this->getConvertedCount($staffId, $currentMonth),
'data' => [65000, 72000, 68000, 75000, 82000, 85000] 'unit' => '人',
'trend' => $this->getConversionTrend($staffId)
], ],
'customer_source' => [ [
'title' => '客户来源分布', 'label' => '本月业绩',
'data' => [ 'value' => $this->getPerformance($staffId, $currentMonth),
['name' => '线上推广', 'value' => 35], 'unit' => '元',
['name' => '转介绍', 'value' => 28], 'trend' => $this->getPerformanceTrend($staffId)
['name' => '电话营销', 'value' => 22], ],
['name' => '其他', 'value' => 15] [
] '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) private function getDeptData($userInfo)
{ {
$campusId = $userInfo['campus_id'];
$currentMonth = date('Y-m-01');
return [ return [
'stats' => [ // 部门总览
['label' => '部门总业绩', 'value' => 520000, 'unit' => '元', 'trend' => '+12%'], 'dept_overview' => [
['label' => '团队人数', 'value' => 8, 'unit' => '人', 'trend' => '0%'], 'total_depts' => $this->getDeptCount($campusId),
['label' => '平均业绩', 'value' => 65000, 'unit' => '元', 'trend' => '+12%'], 'total_resources' => $this->getDeptResourceCount($campusId, $currentMonth),
['label' => '部门排名', 'value' => 2, 'unit' => '名', 'trend' => '+1'] 'total_performance' => $this->getDeptPerformance($campusId, $currentMonth),
'conversion_rate' => $this->getDeptConversionRate($campusId, $currentMonth)
], ],
'charts' => [
'team_performance' => [ // 部门排名
'title' => '团队成员业绩排行', 'dept_ranking' => $this->getDeptRanking($campusId, $currentMonth)
'data' => [
['name' => '张三', 'value' => 85000],
['name' => '李四', 'value' => 72000],
['name' => '王五', 'value' => 68000],
['name' => '赵六', 'value' => 65000]
]
]
]
]; ];
} }
@ -149,42 +217,616 @@ class Dashboard extends BaseApiController
*/ */
private function getCampusData($userInfo) private function getCampusData($userInfo)
{ {
$campusId = $userInfo['campus_id'];
$currentMonth = date('Y-m-01');
return [ return [
'stats' => [ // 校区总览
['label' => '校区总业绩', 'value' => 1200000, 'unit' => '元', 'trend' => '+18%'], 'campus_overview' => [
['label' => '部门数量', 'value' => 5, 'unit' => '个', 'trend' => '0%'], 'total_performance' => $this->getCampusPerformance($campusId, $currentMonth),
['label' => '员工总数', 'value' => 32, 'unit' => '人', 'trend' => '+3'], 'total_resources' => $this->getCampusResourceCount($campusId, $currentMonth),
['label' => '客户总数', 'value' => 245, 'unit' => '人', 'trend' => '+25'] 'total_converted' => $this->getCampusConvertedCount($campusId, $currentMonth),
'total_staff' => $this->getCampusStaffCount($campusId)
], ],
'charts' => [
'dept_performance' => [ // 校区部门对比
'title' => '部门业绩对比', 'dept_comparison' => $this->getCampusDeptComparison($campusId, $currentMonth)
'data' => [
['name' => '销售一部', 'value' => 320000],
['name' => '销售二部', 'value' => 280000],
['name' => '销售三部', 'value' => 260000],
['name' => '客服部', 'value' => 180000],
['name' => '行政部', 'value' => 160000]
]
]
]
]; ];
} }
// ========== 数据查询方法 ==========
/**
* 获取资源数量
*/
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 getMockUserInfo($type) 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 [ return [
'id' => 1, ['stage' => '新增资源', 'count' => $totalResources, 'rate' => 100],
'name' => '测试员工', ['stage' => '有效联系', 'count' => $contactedResources, 'rate' => round($contactedResources * 100 / $totalResources, 1)],
'department' => '销售部', ['stage' => '意向客户', 'count' => $intentionResources, 'rate' => round($intentionResources * 100 / $totalResources, 1)],
'campus' => '总校区', ['stage' => '成交客户', 'count' => $convertedResources, 'rate' => round($convertedResources * 100 / $totalResources, 1)]
'role' => 'staff' ];
}
/**
* 获取月度趋势
*/
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%';
}
/** /**
* 渲染错误页面 * 渲染错误页面

202
niucloud/app/api/controller/Dashboard.php.backup

@ -0,0 +1,202 @@
<?php
namespace app\api\controller;
use app\service\api\member\MemberService;
use core\base\BaseApiController;
use think\facade\View;
/**
* Dashboard WebView 控制器
*/
class Dashboard extends BaseApiController
{
/**
* WebView 页面渲染
*/
public function webview()
{
$type = $this->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'
]);
}
}

827
niucloud/app/api/controller/Dashboard_new.php

@ -0,0 +1,827 @@
<?php
namespace app\api\controller;
use app\service\api\member\MemberService;
use core\base\BaseApiController;
use think\facade\View;
use think\facade\Db;
/**
* Dashboard WebView 控制器 - 市场人员业绩管理系统
*/
class Dashboard extends BaseApiController
{
/**
* WebView 页面渲染
*/
public function webview()
{
$type = $this->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'
]);
}
}

423
niucloud/app/api/controller/MarketStats.php

@ -0,0 +1,423 @@
<?php
namespace app\api\controller;
use core\base\BaseApiController;
use think\facade\Db;
/**
* 市场统计数据API控制器
*/
class MarketStats extends BaseApiController
{
/**
* 获取个人统计数据
*/
public function getPersonalStats()
{
$staffId = $this->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;
}
}

7
niucloud/app/api/route/route.php

@ -446,6 +446,13 @@ Route::group(function () {
Route::get('statistics/getStaffStatistics', 'apiController.Statistics/getStaffStatistics'); 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::get('personnel/reimbursement_list', 'apiController.Personnel/reimbursement_list');
Route::post('personnel/reimbursement_add', 'apiController.Personnel/reimbursement_add'); Route::post('personnel/reimbursement_add', 'apiController.Personnel/reimbursement_add');

590
niucloud/app/view/dashboard/main.html

@ -0,0 +1,590 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{$pageTitle}</title>
<link rel="stylesheet" href="/static/css/dashboard.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vue@3/dist/vue.global.js"></script>
</head>
<body>
<div id="app">
<!-- 页面头部 -->
<div class="header">
<h1><i class="fas fa-chart-line"></i> {$pageTitle}</h1>
<div class="user-info">
<span><i class="fas fa-user"></i> 欢迎,{$userInfo.name}</span>
<span class="role-badge">
<i class="fas fa-briefcase"></i>
{{ userInfo.role === 'market_staff' ? '市场人员' : '市场经理' }}
</span>
</div>
</div>
<!-- 数据筛选器 -->
<div class="filter-container">
<div class="filter-item">
<label>时间范围:</label>
<select v-model="timeRange" @change="updateData">
<option value="current_month">本月</option>
<option value="last_month">上月</option>
<option value="current_quarter">本季度</option>
<option value="current_year">本年</option>
</select>
</div>
<div class="filter-item">
<label>数据类型:</label>
<select v-model="dataType" @change="updateData">
<option value="all">全部数据</option>
<option value="performance">业绩数据</option>
<option value="resource">资源数据</option>
<option value="income">收益数据</option>
</select>
</div>
<div class="filter-item">
<button @click="exportData" class="btn-export">
<i class="fas fa-download"></i> 导出数据
</button>
</div>
</div>
<!-- 统计卡片 -->
<div class="stats-container">
<div v-for="stat in pageData.stats" :key="stat.label" class="stat-card">
<div class="stat-icon">
<i :class="getIconClass(stat.label)"></i>
</div>
<div class="stat-content">
<div class="stat-value">{{ formatValue(stat.value) }}{{ stat.unit }}</div>
<div class="stat-label">{{ stat.label }}</div>
<div class="stat-trend" :class="getTrendClass(stat.trend)">
<i :class="getTrendIcon(stat.trend)"></i>
{{ stat.trend }}
</div>
</div>
</div>
</div>
<!-- 资源分析 -->
<div v-if="pageData.resource_analysis" class="section">
<h2><i class="fas fa-users"></i> 资源分析</h2>
<div class="charts-container">
<div class="chart-card">
<h3><i class="fas fa-chart-pie"></i> 渠道分布</h3>
<canvas id="channelChart" width="400" height="300"></canvas>
</div>
<div class="chart-card">
<h3><i class="fas fa-bullseye"></i> 来源分布</h3>
<canvas id="sourceChart" width="400" height="300"></canvas>
</div>
<div class="chart-card">
<h3><i class="fas fa-filter"></i> 转化漏斗</h3>
<canvas id="funnelChart" width="400" height="300"></canvas>
</div>
<div class="chart-card">
<h3><i class="fas fa-chart-line"></i> 月度趋势</h3>
<canvas id="trendChart" width="400" height="300"></canvas>
</div>
</div>
</div>
<!-- 收益分析 -->
<div v-if="pageData.income_analysis" class="section">
<h2><i class="fas fa-dollar-sign"></i> 收益分析</h2>
<div class="charts-container">
<div class="chart-card">
<h3><i class="fas fa-chart-bar"></i> 提成明细</h3>
<canvas id="commissionChart" width="400" height="300"></canvas>
</div>
<div class="chart-card">
<h3><i class="fas fa-gift"></i> 奖励历史</h3>
<canvas id="bonusChart" width="400" height="300"></canvas>
</div>
</div>
</div>
<!-- 团队数据(经理权限) -->
<div v-if="pageData.team_overview" class="section">
<h2><i class="fas fa-users-cog"></i> 团队数据</h2>
<div class="team-overview">
<div class="overview-card">
<h3><i class="fas fa-users"></i> 团队成员</h3>
<div class="overview-value">{{ pageData.team_overview.total_members }}</div>
<div class="overview-desc">活跃成员</div>
</div>
<div class="overview-card">
<h3><i class="fas fa-user-plus"></i> 团队资源</h3>
<div class="overview-value">{{ pageData.team_overview.total_resources }}</div>
<div class="overview-desc">本月新增</div>
</div>
<div class="overview-card">
<h3><i class="fas fa-user-check"></i> 团队成交</h3>
<div class="overview-value">{{ pageData.team_overview.total_converted }}</div>
<div class="overview-desc">本月成交</div>
</div>
<div class="overview-card">
<h3><i class="fas fa-chart-line"></i> 团队业绩</h3>
<div class="overview-value">{{ formatValue(pageData.team_overview.total_performance) }}</div>
<div class="overview-desc">本月业绩</div>
</div>
</div>
<div class="ranking-section">
<h3><i class="fas fa-trophy"></i> 团队成员排名</h3>
<div class="ranking-table">
<table>
<thead>
<tr>
<th>排名</th>
<th>姓名</th>
<th>资源数</th>
<th>成交数</th>
<th>业绩</th>
<th>提成</th>
</tr>
</thead>
<tbody>
<tr v-for="(member, index) in pageData.member_ranking" :key="member.staff_id">
<td>
<span class="rank-badge" :class="getRankClass(index)">
{{ index + 1 }}
</span>
</td>
<td>{{ member.staff_name }}</td>
<td>{{ member.resource_count }}</td>
<td>{{ member.converted_count }}</td>
<td>{{ formatValue(member.performance) }}</td>
<td>{{ formatValue(member.commission) }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 部门数据 -->
<div v-if="pageData.dept_overview" class="section">
<h2><i class="fas fa-building"></i> 部门数据</h2>
<div class="team-overview">
<div class="overview-card">
<h3><i class="fas fa-sitemap"></i> 部门总数</h3>
<div class="overview-value">{{ pageData.dept_overview.total_depts }}</div>
<div class="overview-desc">活跃部门</div>
</div>
<div class="overview-card">
<h3><i class="fas fa-users"></i> 部门资源</h3>
<div class="overview-value">{{ pageData.dept_overview.total_resources }}</div>
<div class="overview-desc">本月新增</div>
</div>
<div class="overview-card">
<h3><i class="fas fa-chart-line"></i> 部门业绩</h3>
<div class="overview-value">{{ formatValue(pageData.dept_overview.total_performance) }}</div>
<div class="overview-desc">本月业绩</div>
</div>
<div class="overview-card">
<h3><i class="fas fa-percentage"></i> 转化率</h3>
<div class="overview-value">{{ pageData.dept_overview.conversion_rate }}%</div>
<div class="overview-desc">平均转化</div>
</div>
</div>
<div class="ranking-section">
<h3><i class="fas fa-chart-bar"></i> 部门业绩排名</h3>
<div class="ranking-table">
<table>
<thead>
<tr>
<th>排名</th>
<th>部门名称</th>
<th>业绩</th>
<th>占比</th>
</tr>
</thead>
<tbody>
<tr v-for="(dept, index) in pageData.dept_ranking" :key="dept.dept_id">
<td>
<span class="rank-badge" :class="getRankClass(index)">
{{ index + 1 }}
</span>
</td>
<td>{{ dept.dept_name }}</td>
<td>{{ formatValue(dept.performance) }}</td>
<td>{{ getPercentage(dept.performance, pageData.dept_overview.total_performance) }}%</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 加载提示 -->
<div v-if="loading" class="loading-overlay">
<div class="loading-spinner">
<i class="fas fa-spinner fa-spin"></i>
<p>数据加载中...</p>
</div>
</div>
</div>
<script>
const { createApp } = Vue;
createApp({
data() {
return {
pageData: <?= json_encode($pageData) ?>,
userInfo: <?= json_encode($userInfo) ?>,
loading: false,
timeRange: 'current_month',
dataType: 'all'
};
},
mounted() {
this.initCharts();
this.initEventListeners();
},
methods: {
formatValue(value) {
if (typeof value === 'number') {
return value.toLocaleString();
}
return value;
},
getIconClass(label) {
const iconMap = {
'本月新增资源': 'fas fa-user-plus',
'本月成交客户': 'fas fa-user-check',
'本月业绩': 'fas fa-dollar-sign',
'本月提成': 'fas fa-gift'
};
return iconMap[label] || 'fas fa-chart-bar';
},
getTrendClass(trend) {
return trend.startsWith('+') ? 'trend-up' : 'trend-down';
},
getTrendIcon(trend) {
return trend.startsWith('+') ? 'fas fa-arrow-up' : 'fas fa-arrow-down';
},
getRankClass(index) {
const classes = ['rank-first', 'rank-second', 'rank-third'];
return classes[index] || 'rank-other';
},
getPercentage(value, total) {
if (total === 0) return 0;
return Math.round((value / total) * 100);
},
updateData() {
this.loading = true;
// 模拟数据更新
setTimeout(() => {
this.loading = false;
this.showNotification('数据已更新', 'success');
}, 1000);
},
exportData() {
this.showNotification('数据导出功能开发中...', 'info');
},
showNotification(message, type = 'info') {
// 创建通知元素
const notification = document.createElement('div');
notification.className = `notification notification-${type}`;
notification.innerHTML = `
<i class="fas fa-${type === 'success' ? 'check-circle' : 'info-circle'}"></i>
${message}
`;
document.body.appendChild(notification);
// 3秒后移除
setTimeout(() => {
notification.remove();
}, 3000);
},
initEventListeners() {
// 响应式处理
window.addEventListener('resize', () => {
this.resizeCharts();
});
},
initCharts() {
// 渠道分布图
if (this.pageData.resource_analysis && this.pageData.resource_analysis.channel_distribution) {
this.createPieChart('channelChart', this.pageData.resource_analysis.channel_distribution);
}
// 来源分布图
if (this.pageData.resource_analysis && this.pageData.resource_analysis.source_distribution) {
this.createPieChart('sourceChart', this.pageData.resource_analysis.source_distribution);
}
// 转化漏斗图
if (this.pageData.resource_analysis && this.pageData.resource_analysis.conversion_funnel) {
this.createFunnelChart('funnelChart', this.pageData.resource_analysis.conversion_funnel);
}
// 月度趋势图
if (this.pageData.resource_analysis && this.pageData.resource_analysis.monthly_trend) {
this.createLineChart('trendChart', this.pageData.resource_analysis.monthly_trend);
}
// 提成明细图
if (this.pageData.income_analysis && this.pageData.income_analysis.commission_breakdown) {
this.createBarChart('commissionChart', this.pageData.income_analysis.commission_breakdown);
}
// 奖励历史图
if (this.pageData.income_analysis && this.pageData.income_analysis.bonus_history) {
this.createBarChart('bonusChart', this.pageData.income_analysis.bonus_history);
}
},
createPieChart(canvasId, data) {
const ctx = document.getElementById(canvasId);
if (!ctx) return;
new Chart(ctx, {
type: 'pie',
data: {
labels: data.map(item => item.name),
datasets: [{
data: data.map(item => item.value),
backgroundColor: [
'#FF6384',
'#36A2EB',
'#FFCE56',
'#4BC0C0',
'#9966FF',
'#FF9F40'
]
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: {
position: 'bottom',
labels: {
padding: 20,
usePointStyle: true
}
},
tooltip: {
callbacks: {
label: function(context) {
const label = context.label || '';
const value = context.parsed;
const total = context.dataset.data.reduce((a, b) => a + b, 0);
const percentage = ((value / total) * 100).toFixed(1);
return `${label}: ${value} (${percentage}%)`;
}
}
}
}
}
});
},
createFunnelChart(canvasId, data) {
const ctx = document.getElementById(canvasId);
if (!ctx) return;
new Chart(ctx, {
type: 'bar',
data: {
labels: data.map(item => item.stage),
datasets: [{
label: '转化率',
data: data.map(item => item.rate),
backgroundColor: '#36A2EB',
borderColor: '#1E88E5',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
max: 100,
ticks: {
callback: function(value) {
return value + '%';
}
}
}
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
return `转化率: ${context.parsed.y}%`;
}
}
}
}
}
});
},
createLineChart(canvasId, data) {
const ctx = document.getElementById(canvasId);
if (!ctx) return;
new Chart(ctx, {
type: 'line',
data: {
labels: data.map(item => item.month),
datasets: [{
label: '资源数量',
data: data.map(item => item.count),
borderColor: '#36A2EB',
backgroundColor: 'rgba(54, 162, 235, 0.1)',
fill: true,
tension: 0.4
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: {
stepSize: 1
}
}
},
plugins: {
legend: {
display: false
}
}
}
});
},
createBarChart(canvasId, data) {
const ctx = document.getElementById(canvasId);
if (!ctx) return;
new Chart(ctx, {
type: 'bar',
data: {
labels: data.map(item => item.type || item.month),
datasets: [{
label: '金额',
data: data.map(item => item.amount),
backgroundColor: '#36A2EB',
borderColor: '#1E88E5',
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
scales: {
y: {
beginAtZero: true,
ticks: {
callback: function(value) {
return '¥' + value.toLocaleString();
}
}
}
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function(context) {
return '金额: ¥' + context.parsed.y.toLocaleString();
}
}
}
}
}
});
},
resizeCharts() {
// 重新调整图表大小
Chart.instances.forEach(chart => {
chart.resize();
});
}
}
}).mount('#app');
</script>
<style>
/* 通知样式 */
.notification {
position: fixed;
top: 20px;
right: 20px;
padding: 15px 20px;
border-radius: 8px;
color: white;
font-weight: 500;
z-index: 1000;
animation: slideIn 0.3s ease-out;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.notification-success {
background: linear-gradient(135deg, #10b981, #059669);
}
.notification-info {
background: linear-gradient(135deg, #3b82f6, #1d4ed8);
}
.notification i {
margin-right: 8px;
}
@keyframes slideIn {
from {
transform: translateX(100%);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
/* 加载动画 */
.loading-overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 9999;
}
.loading-spinner {
text-align: center;
color: white;
}
.loading-spinner i {
font-size: 48px;
margin-bottom: 20px;
}
.loading-spinner p {
font-size: 18px;
font-weight: 500;
}
</style>
</body>
</html>

569
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;
}
Loading…
Cancel
Save