Files
duolamaojiazhen/application/admin/controller/BulletinBoard.php
2026-01-30 18:48:13 +08:00

485 lines
19 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\admin\controller;
use app\admin\model\OrderGoods;
use app\admin\model\OrderRecharge;
use app\admin\model\OrderYearcard;
use app\admin\model\User;
use app\common\server\UrlServer;
use think\Db;
use think\facade\Url;
use app\admin\model\Order;
class BulletinBoard extends AdminBase
{
//看板统计
public function index()
{
// 本月与上月时间范围
$monthStart = date('Y-m-01 00:00:00');
$monthEnd = date('Y-m-t 23:59:59');
$lastMonthStart = date('Y-m-01 00:00:00', strtotime('-1 month'));
$lastMonthEnd = date('Y-m-t 23:59:59', strtotime('-1 month'));
$monthStartTs = strtotime($monthStart);
$monthEndTs = strtotime($monthEnd);
//查询商品订单
$orderMoney = Order::where('status','<>', 0)->sum('order_amount');
//年卡订单
$yearCardOrderMoney = OrderYearcard::where('status', 1)->sum('pay_fee');
//订单管理
$orderGoods = OrderGoods::where('status', 1)->sum('total_fee');
//充值订单
$rechargeOrderMoney = OrderRecharge::where('pay_status', 1)->sum('order_amount');
// 计算月销售总额(所有订单类型的总和)
$monthlySales = ($orderMoney ?: 0) + ($yearCardOrderMoney ?: 0) + ($orderGoods ?: 0) + ($rechargeOrderMoney ?: 0);
// 计算月订单量(本月订单数量)
$monthlyOrders = Order::where('status','<>', 0)
->whereTime('create_time', 'month')
->count();
$monthlyOrders += OrderYearcard::where('status', 1)
->whereTime('createtime', 'month')
->count();
$monthlyOrders += OrderGoods::where('status', 1)
->whereTime('createtime', 'month')
->count();
$monthlyOrders += OrderRecharge::where('pay_status', 1)
->whereTime('create_time', 'month')
->count();
// 计算月客户数(本月新增用户)
$monthlyCustomers = User::whereTime('create_time', 'month')
->where('del', 0)
->count();
// 计算同比(上个月的数据)
$lastMonthSales = Order::where('status','<>', 0)
->whereTime('create_time', 'last month')
->sum('order_amount');
$lastMonthSales += OrderYearcard::where('status', 1)
->whereTime('createtime', 'last month')
->sum('pay_fee');
$lastMonthSales += OrderGoods::where('status', 1)
->whereTime('createtime', 'last month')
->sum('total_fee');
$lastMonthSales += OrderRecharge::where('pay_status', 1)
->whereTime('create_time', 'last month')
->sum('order_amount');
$lastMonthOrders = Order::where('status','<>', 0)
->whereTime('create_time', 'last month')
->count();
$lastMonthOrders += OrderYearcard::where('status', 1)
->whereTime('createtime', 'last month')
->count();
$lastMonthOrders += OrderGoods::where('status', 1)
->whereTime('createtime', 'last month')
->count();
$lastMonthOrders += OrderRecharge::where('pay_status', 1)
->whereTime('create_time', 'last month')
->count();
$lastMonthCustomers = User::whereTime('create_time', 'last month')
->where('del', 0)
->count();
// 计算同比变化率
$salesCompare = $lastMonthSales > 0 ? round((($monthlySales - $lastMonthSales) / $lastMonthSales) * 100, 2) : 0;
$ordersCompare = $lastMonthOrders > 0 ? round((($monthlyOrders - $lastMonthOrders) / $lastMonthOrders) * 100, 2) : 0;
$customersCompare = $lastMonthCustomers > 0 ? round((($monthlyCustomers - $lastMonthCustomers) / $lastMonthCustomers) * 100, 2) : 0;
// 统计本月客户分析(老客户 vs 新客户)
// 本月所有用户ID列表
$monthUsers = User::whereTime('create_time', 'month')
->where('del', 0)
->column('id');
// 如果本月有用户,查询这些用户中哪些有订单(老客户)
$oldCustomerCount = 0;
$newCustomerCount = 0;
if (!empty($monthUsers)) {
// 查询本月注册的用户中在Order表中有订单的用户老客户
// 只要有订单就算老客户,不限制订单时间
$oldCustomerIds = Order::where('status','<>', 0)
->where('user_id', 'in', $monthUsers)
->where('user_id', '>', 0)
->distinct(true)
->column('user_id');
$oldCustomerCount = count($oldCustomerIds);
// 本月注册但没下过单的(新客户)
$newCustomerCount = count($monthUsers) - $oldCustomerCount;
}
// ========== 目标完成率与客服进度 ==========
// 销售目标(可以后续做成后台可配置,这里先写死为 100000
$monthlyTarget = 100000;
$overallCompletion = $monthlyTarget > 0 ? round(($monthlySales / $monthlyTarget) * 100, 2) : 0;
// 按客服统计本月与上月销售额
$staffCurrent = Db::name('order')
->alias('o')
->join('admin a', 'o.admin_id = a.id', 'LEFT')
->where('o.status', '<>', 0)
->whereTime('o.create_time', 'between', [$monthStart, $monthEnd])
->field('o.admin_id,a.name as staff_name,SUM(o.order_amount) as total')
->group('o.admin_id')
->select();
$staffLast = Db::name('order')
->alias('o')
->join('admin a', 'o.admin_id = a.id', 'LEFT')
->where('o.status', '<>', 0)
->whereTime('o.create_time', 'between', [$lastMonthStart, $lastMonthEnd])
->field('o.admin_id,SUM(o.order_amount) as total')
->group('o.admin_id')
->select();
$staffLastMap = [];
foreach ($staffLast as $row) {
$staffLastMap[$row['admin_id']] = $row['total'];
}
$staffProgress = [];
foreach ($staffCurrent as $row) {
$adminId = $row['admin_id'];
$staffSales = (float)$row['total'];
$lastSales = isset($staffLastMap[$adminId]) ? (float)$staffLastMap[$adminId] : 0;
$percent = $monthlySales > 0 ? round(($staffSales / $monthlySales) * 100, 2) : 0;
$yoy = $lastSales > 0 ? round((($staffSales - $lastSales) / $lastSales) * 100, 2) : 0;
$staffProgress[] = [
'name' => $row['staff_name'] ?: '未分配',
'sales' => $staffSales,
'percent' => $percent,
'yoy' => $yoy,
];
}
// ========== 渠道分析(当月订单数按渠道) ==========
$channelRows = Db::name('order')
->alias('o')
->join('orderchannel c', 'o.channel_id = c.id', 'LEFT')
->where('o.status', '<>', 0)
->whereTime('o.create_time', 'month')
->field('c.name as channel_name, COUNT(*) as total')
->group('o.channel_id')
->select();
$channelPie = [];
foreach ($channelRows as $row) {
$name = $row['channel_name'] ?: '其他';
$value = (int)$row['total'];
$channelPie[] = [
'name' => $name,
'value' => $value,
];
}
// ========== 每日销售趋势(仅商品订单,按天汇总) ==========
$trendRows = Db::name('order')
->where('status', '<>', 0)
->whereTime('create_time', 'between', [$monthStart, $monthEnd])
->field("FROM_UNIXTIME(create_time,'%Y-%m-%d') as day, SUM(order_amount) as total")
->group('day')
->order('day asc')
->select();
$trendDates = [];
$trendValues = [];
$trendTotal = 0;
foreach ($trendRows as $row) {
$trendDates[] = $row['day'];
$amount = (float)$row['total'];
$trendValues[] = $amount;
$trendTotal += $amount;
}
// 上月总销售额(用于计算趋势图上的“上月同比”)
$lastTrendTotal = Db::name('order')
->where('status', '<>', 0)
->whereTime('create_time', 'between', [$lastMonthStart, $lastMonthEnd])
->sum('order_amount');
$trendCompare = $lastTrendTotal > 0 ? round((($trendTotal - $lastTrendTotal) / $lastTrendTotal) * 100, 2) : 0;
// ========== 区域月销售额(通过地址模糊匹配简单统计) ==========
$regions = ['南明区', '云岩区', '白云区', '乌当区', '花溪区', '龙里'];
$regionTotals = [];
foreach ($regions as $regionName) {
$total = Db::name('order')
->where('status', '<>', 0)
->whereTime('create_time', 'month')
->whereLike('address', '%' . $regionName . '%')
->sum('order_amount');
$regionTotals[] = (float)$total;
}
// ========== 本月销售情况(单次/年卡/次卡/其它服务) ==========
// 复用统计图表控制器中的规则:根据 number 字段区分不同卡类型
$salesSituation = [
['value' => 0, 'name' => '单次服务'],
['value' => 0, 'name' => '年卡销量'],
['value' => 0, 'name' => '次卡销量'],
['value' => 0, 'name' => '其它服务'],
];
$kaWhere = [
['del', '=', 0],
['create_time', '>=', $monthStartTs],
['create_time', '<=', $monthEndTs],
];
// 单次服务number = 1
$salesSituation[0]['value'] = Db::name('order')
->where($kaWhere)
->where('number', '=', 1)
->sum('total_amount');
// 年卡number > 6
$salesSituation[1]['value'] = Db::name('order')
->where($kaWhere)
->where('number', '>', 6)
->sum('total_amount');
// 次卡2 <= number <= 6
$salesSituation[2]['value'] = Db::name('order')
->where($kaWhere)
->where('number', '>=', 2)
->where('number', '<=', 6)
->sum('total_amount');
// 其它服务number = 0
$salesSituation[3]['value'] = Db::name('order')
->where($kaWhere)
->where('number', '=', 0)
->sum('total_amount');
// ========== 渠道 & 区域详细拆分(总额 / 年卡 / 次卡 / 单次 / 其它) ==========
$serviceTypes = [
'total' => [],
'year' => [['number', '>', 6]],
'times' => [['number', '>=', 2], ['number', '<=', 6]],
'single'=> [['number', '=', 1]],
'other' => [['number', '=', 0]],
];
// 渠道关键字到列 key 的映射
$channelBuckets = [
'meituan' => '美团',
'gzh' => '公众号',
'douyin' => '抖音',
'staff' => '员工',
'yeye' => '异业',
'other' => '渠道',
];
$channelDetail = [];
foreach ($serviceTypes as $typeKey => $extraWhere) {
$totals = [
'meituan' => 0,
'gzh' => 0,
'douyin' => 0,
'staff' => 0,
'yeye' => 0,
'other' => 0,
];
$query = Db::name('order')
->alias('o')
->join('orderchannel c', 'o.channel_id = c.id', 'LEFT')
->where('o.status', '<>', 0)
->whereBetween('o.create_time', [$monthStartTs, $monthEndTs]);
if (!empty($extraWhere)) {
$query->where($extraWhere);
}
$rows = $query
->field('c.name as channel_name, SUM(o.order_amount) as total')
->group('o.channel_id')
->select();
foreach ($rows as $row) {
$name = $row['channel_name'] ?? '';
$value = (float)($row['total'] ?? 0);
$bucket = 'other';
if (mb_strpos($name, '美团') !== false) {
$bucket = 'meituan';
} elseif (mb_strpos($name, '公众号') !== false || mb_strpos($name, '公') !== false) {
$bucket = 'gzh';
} elseif (mb_strpos($name, '抖音') !== false) {
$bucket = 'douyin';
} elseif (mb_strpos($name, '员工') !== false) {
$bucket = 'staff';
} elseif (mb_strpos($name, '异业') !== false) {
$bucket = 'yeye';
}
$totals[$bucket] += $value;
}
$channelDetail[$typeKey] = $totals;
}
// 区域明细:每种服务类型在各区域的销售额
$regionDetail = [];
foreach ($serviceTypes as $typeKey => $extraWhere) {
$regionDetail[$typeKey] = [];
foreach ($regions as $regionName) {
$q = Db::name('order')
->where('status', '<>', 0)
->whereBetween('create_time', [$monthStartTs, $monthEndTs])
->whereLike('address', '%' . $regionName . '%');
if (!empty($extraWhere)) {
$q->where($extraWhere);
}
$regionDetail[$typeKey][$regionName] = (float)$q->sum('order_amount');
}
}
// 汇总仪表盘数据供前端使用
$dashboardData = [
'monthlyTarget' => $monthlyTarget,
'overallCompletion' => $overallCompletion,
'staffProgress' => $staffProgress,
'channelPie' => $channelPie,
'trendDates' => $trendDates,
'trendValues' => $trendValues,
'trendCompare' => $trendCompare,
'regions' => $regions,
'regionTotals' => $regionTotals,
'salesSituation' => $salesSituation,
'channelDetail' => $channelDetail,
'regionDetail' => $regionDetail,
];
// 传递数据到视图
$this->assign([
'monthlySales' => $monthlySales,
'monthlyOrders' => $monthlyOrders,
'monthlyCustomers' => $monthlyCustomers,
'salesCompare' => $salesCompare,
'ordersCompare' => $ordersCompare,
'customersCompare' => $customersCompare,
'oldCustomers' => $oldCustomerCount, // 老客户数量(本月注册且下过单的)
'newCustomers' => $newCustomerCount, // 新客户数量(本月注册但没下过单的)
'dashboardData' => json_encode($dashboardData, JSON_UNESCAPED_UNICODE),
]);
return $this->fetch();
}
//地图上显示注册用户位置
public function map()
{
//查询商品订单
$orderMoney = Order::where('status','<>', 0)->sum('order_amount');
//年卡订单
$yearCardOrderMoney = OrderYearcard::where('status', 1)->sum('pay_fee');
//订单管理
$orderGoods = OrderGoods::where('status', 1)->sum('total_fee');
//充值订单
$rechargeOrderMoney = OrderRecharge::where('pay_status', 1)->sum('order_amount');
return $this->fetch();
}
public function getUserMap()
{
// 查询有经纬度的用户
$userArray = User::where('latitude', '<>', '')
->where('longitude', '<>', '')
->field('id,nickname,mobile,avatar,longitude,latitude')
->select();
// 格式化数据,转换为前端期望的字段名
$result = [];
foreach ($userArray as $user) {
// 处理头像URL使用代理接口避免CORS问题
$avatar = '';
if (!empty($user['avatar'])) {
$originalUrl = UrlServer::getFileUrl($user['avatar']);
// 去掉JSON编码时产生的转义反斜杠
$originalUrl = str_replace('\\/', '/', $originalUrl);
// 如果URL是跨域的使用代理接口
$currentDomain = $this->request->domain();
if (strpos($originalUrl, $currentDomain) === false) {
// 跨域使用代理手动构建URL避免双重编码
$avatar = $this->request->domain() . '/admin/bulletin_board/proxyImage?url=' . rawurlencode($originalUrl);
} else {
// 同域,直接使用
$avatar = $originalUrl;
}
}
$result[] = [
'id' => $user['id'] ?? 0,
'lng' => floatval($user['longitude'] ?? 0),
'lat' => floatval($user['latitude'] ?? 0),
'name' => $user['nickname'] ?? '',
'contact' => $user['nickname'] ?? '', // 兼容两种字段名
'mobile' => $user['mobile'] ?? '',
'phone' => $user['mobile'] ?? '', // 兼容 telephone/phone
'telephone' => $user['mobile'] ?? '',
'avatar' => $avatar
];
}
return $this->_success('获取成功', $result);
}
/**
* 图片代理接口解决CORS跨域问题
*/
public function proxyImage()
{
$url = $this->request->get('url', '');
if (empty($url)) {
header('HTTP/1.1 404 Not Found');
exit;
}
// 解码URL
$url = urldecode($url);
// 验证URL格式
if (!filter_var($url, FILTER_VALIDATE_URL)) {
header('HTTP/1.1 400 Bad Request');
exit;
}
// 只允许图片格式
$allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
$extension = strtolower(pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION));
if (!in_array($extension, $allowedExtensions)) {
header('HTTP/1.1 403 Forbidden');
exit;
}
// 获取图片
$context = stream_context_create([
'http' => [
'method' => 'GET',
'timeout' => 10,
'header' => [
'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
]
]
]);
$imageData = @file_get_contents($url, false, $context);
if ($imageData === false) {
header('HTTP/1.1 404 Not Found');
exit;
}
// 设置响应头
header('Content-Type: image/' . ($extension === 'jpg' ? 'jpeg' : $extension));
header('Cache-Control: public, max-age=3600');
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Methods: GET');
// 输出图片
echo $imageData;
exit;
}
}