文件
mashangyoujiu/Application/Retail/Controller/RiderMonitorController.class.php

508 行
18 KiB
PHP

<?php
namespace Retail\Controller;
use Think\Controller;
/**
* 骑手工作状态监控控制器
*/
class RiderMonitorController extends Controller {
/**
* 监控首页
*/
public function index() {
$this->display();
}
/**
* 获取在线骑手列表
*/
public function getOnlineRiders() {
$page = I('page', 1, 'intval');
$pageSize = I('pageSize', 20, 'intval');
$keyword = I('keyword', '', 'trim');
$area_id = I('area_id', 0, 'intval');
$work_status = I('work_status', '', 'trim');
$where = array('u.user_type' => 'rider', 'u.status' => 1);
// 关键词搜索
if (!empty($keyword)) {
$where['_string'] = "(u.nickname LIKE '%{$keyword}%' OR u.phone LIKE '%{$keyword}%')";
}
// 工作状态筛选
if ($work_status !== '') {
$where['rs.work_status'] = $work_status;
}
// 区域筛选
if ($area_id > 0) {
$where['ra.area_id'] = $area_id;
}
$model = M('users');
$total = $model->alias('u')
->join('ls_rider_status rs ON u.id = rs.rider_id', 'LEFT')
->join('ls_rider_area ra ON u.id = ra.rider_id', 'LEFT')
->where($where)
->count('DISTINCT u.id');
$list = $model->alias('u')
->field('u.*, rs.work_status, rs.current_lat, rs.current_lng, rs.last_update_time, rs.order_count_today')
->join('ls_rider_status rs ON u.id = rs.rider_id', 'LEFT')
->join('ls_rider_area ra ON u.id = ra.rider_id', 'LEFT')
->where($where)
->group('u.id')
->order('rs.last_update_time desc')
->page($page, $pageSize)
->select();
// 格式化数据
foreach ($list as &$item) {
$item['work_status_text'] = $this->getWorkStatusText($item['work_status']);
$item['last_update_time_format'] = $item['last_update_time'] ? date('Y-m-d H:i:s', $item['last_update_time']) : '';
// 计算在线时长
if ($item['work_status'] == 'online') {
$online_duration = time() - strtotime(date('Y-m-d 00:00:00'));
$item['online_duration'] = $this->formatDuration($online_duration);
} else {
$item['online_duration'] = '0分钟';
}
// 获取当前订单信息
$current_order = M('order')->where(array(
'rider_id' => $item['id'],
'status' => array('in', array('accepted', 'picked_up', 'delivering'))
))->find();
$item['current_order'] = $current_order;
// 获取配送区域
$areas = M('rider_area')->alias('ra')
->field('da.area_name')
->join('ls_delivery_area da ON ra.area_id = da.id')
->where(array('ra.rider_id' => $item['id']))
->select();
$item['areas'] = $areas;
}
$this->ajaxReturn(array(
'code' => 0,
'msg' => 'success',
'data' => array(
'list' => $list,
'total' => $total
)
));
}
/**
* 获取骑手实时位置
*/
public function getRiderLocation() {
$rider_id = I('rider_id', 0, 'intval');
if (empty($rider_id)) {
$this->ajaxReturn(array('code' => 1, 'msg' => '参数错误'));
}
$location = M('rider_status')->where(array('rider_id' => $rider_id))->find();
if (empty($location)) {
$this->ajaxReturn(array('code' => 1, 'msg' => '未找到位置信息'));
}
// 获取骑手信息
$rider = M('users')->where(array('id' => $rider_id))->find();
$data = array(
'rider_id' => $rider_id,
'nickname' => $rider['nickname'],
'phone' => $rider['phone'],
'current_lat' => floatval($location['current_lat']),
'current_lng' => floatval($location['current_lng']),
'work_status' => $location['work_status'],
'work_status_text' => $this->getWorkStatusText($location['work_status']),
'last_update_time' => date('Y-m-d H:i:s', $location['last_update_time'])
);
// 获取最近的位置轨迹
$tracks = M('rider_location_track')->where(array(
'rider_id' => $rider_id,
'create_time' => array('egt', time() - 3600) // 最近1小时
))->order('create_time desc')->limit(50)->select();
$track_points = array();
foreach ($tracks as $track) {
$track_points[] = array(
'lat' => floatval($track['latitude']),
'lng' => floatval($track['longitude']),
'time' => date('H:i:s', $track['create_time'])
);
}
$data['track_points'] = $track_points;
$this->ajaxReturn(array(
'code' => 0,
'msg' => 'success',
'data' => $data
));
}
/**
* 获取骑手工作统计
*/
public function getRiderWorkStats() {
$rider_id = I('rider_id', 0, 'intval');
$date = I('date', date('Y-m-d'), 'trim');
if (empty($rider_id)) {
$this->ajaxReturn(array('code' => 1, 'msg' => '参数错误'));
}
$start_time = strtotime($date . ' 00:00:00');
$end_time = strtotime($date . ' 23:59:59');
// 基础统计
$stats = array();
// 订单统计
$order_where = array(
'rider_id' => $rider_id,
'create_time' => array('between', array($start_time, $end_time))
);
$stats['order_count'] = M('order')->where($order_where)->count();
$stats['completed_count'] = M('order')->where(array_merge($order_where, array('status' => 'completed')))->count();
$stats['cancelled_count'] = M('order')->where(array_merge($order_where, array('status' => 'cancelled')))->count();
// 收入统计
$stats['total_income'] = M('order')->where(array_merge($order_where, array('status' => 'completed')))->sum('delivery_fee') ?: 0;
$stats['total_income_format'] = number_format($stats['total_income'], 2);
// 在线时长统计
$online_logs = M('rider_online_log')->where(array(
'rider_id' => $rider_id,
'date' => $date
))->find();
$stats['online_duration'] = $online_logs ? $online_logs['online_duration'] : 0;
$stats['online_duration_format'] = $this->formatDuration($stats['online_duration']);
// 平均配送时间
$avg_delivery_time = M('order')->where(array_merge($order_where, array('status' => 'completed')))
->avg('delivery_duration');
$stats['avg_delivery_time'] = $avg_delivery_time ? round($avg_delivery_time / 60, 1) : 0;
// 准时率
$timeout_count = M('order')->where(array_merge($order_where, array(
'status' => 'completed',
'is_timeout' => 1
)))->count();
$stats['punctuality_rate'] = $stats['completed_count'] > 0 ?
round((($stats['completed_count'] - $timeout_count) / $stats['completed_count']) * 100, 2) : 0;
$this->ajaxReturn(array(
'code' => 0,
'msg' => 'success',
'data' => $stats
));
}
/**
* 获取实时监控数据
*/
public function getRealtimeData() {
$area_id = I('area_id', 0, 'intval');
$where = array('u.user_type' => 'rider', 'u.status' => 1);
if ($area_id > 0) {
$where['ra.area_id'] = $area_id;
}
// 在线骑手统计
$online_count = M('users')->alias('u')
->join('ls_rider_status rs ON u.id = rs.rider_id', 'LEFT')
->join('ls_rider_area ra ON u.id = ra.rider_id', 'LEFT')
->where(array_merge($where, array('rs.work_status' => 'online')))
->count('DISTINCT u.id');
// 忙碌骑手统计
$busy_count = M('users')->alias('u')
->join('ls_rider_status rs ON u.id = rs.rider_id', 'LEFT')
->join('ls_rider_area ra ON u.id = ra.rider_id', 'LEFT')
->where(array_merge($where, array('rs.work_status' => 'busy')))
->count('DISTINCT u.id');
// 离线骑手统计
$offline_count = M('users')->alias('u')
->join('ls_rider_status rs ON u.id = rs.rider_id', 'LEFT')
->join('ls_rider_area ra ON u.id = ra.rider_id', 'LEFT')
->where(array_merge($where, array('rs.work_status' => array('in', array('offline', '')))))
->count('DISTINCT u.id');
// 今日订单统计
$today_start = strtotime(date('Y-m-d 00:00:00'));
$today_end = strtotime(date('Y-m-d 23:59:59'));
$order_where = array('create_time' => array('between', array($today_start, $today_end)));
if ($area_id > 0) {
$order_where['delivery_area_id'] = $area_id;
}
$today_orders = M('order')->where($order_where)->count();
$completed_orders = M('order')->where(array_merge($order_where, array('status' => 'completed')))->count();
$pending_orders = M('order')->where(array_merge($order_where, array('status' => 'pending')))->count();
$data = array(
'rider_stats' => array(
'online_count' => $online_count,
'busy_count' => $busy_count,
'offline_count' => $offline_count,
'total_count' => $online_count + $busy_count + $offline_count
),
'order_stats' => array(
'today_orders' => $today_orders,
'completed_orders' => $completed_orders,
'pending_orders' => $pending_orders,
'completion_rate' => $today_orders > 0 ? round(($completed_orders / $today_orders) * 100, 2) : 0
)
);
$this->ajaxReturn(array(
'code' => 0,
'msg' => 'success',
'data' => $data
));
}
/**
* 获取骑手分布地图数据
*/
public function getRiderMapData() {
$area_id = I('area_id', 0, 'intval');
$where = array(
'u.user_type' => 'rider',
'u.status' => 1,
'rs.work_status' => array('in', array('online', 'busy'))
);
if ($area_id > 0) {
$where['ra.area_id'] = $area_id;
}
$riders = M('users')->alias('u')
->field('u.id, u.nickname, u.phone, rs.current_lat, rs.current_lng, rs.work_status')
->join('ls_rider_status rs ON u.id = rs.rider_id')
->join('ls_rider_area ra ON u.id = ra.rider_id', 'LEFT')
->where($where)
->group('u.id')
->select();
$map_data = array();
foreach ($riders as $rider) {
if (empty($rider['current_lat']) || empty($rider['current_lng'])) {
continue;
}
$map_data[] = array(
'rider_id' => $rider['id'],
'nickname' => $rider['nickname'],
'phone' => $rider['phone'],
'lat' => floatval($rider['current_lat']),
'lng' => floatval($rider['current_lng']),
'work_status' => $rider['work_status'],
'work_status_text' => $this->getWorkStatusText($rider['work_status'])
);
}
$this->ajaxReturn(array(
'code' => 0,
'msg' => 'success',
'data' => $map_data
));
}
/**
* 强制下线骑手
*/
public function forceOffline() {
$rider_id = I('rider_id', 0, 'intval');
$reason = I('reason', '', 'trim');
if (empty($rider_id)) {
$this->ajaxReturn(array('code' => 1, 'msg' => '参数错误'));
}
if (empty($reason)) {
$this->ajaxReturn(array('code' => 1, 'msg' => '请填写下线原因'));
}
// 检查骑手是否有进行中的订单
$active_order = M('order')->where(array(
'rider_id' => $rider_id,
'status' => array('in', array('accepted', 'picked_up', 'delivering'))
))->find();
if ($active_order) {
$this->ajaxReturn(array('code' => 1, 'msg' => '该骑手有进行中的订单,无法强制下线'));
}
// 更新骑手状态
$result = M('rider_status')->where(array('rider_id' => $rider_id))->save(array(
'work_status' => 'offline',
'last_update_time' => time()
));
if ($result !== false) {
// 记录操作日志
M('rider_operation_log')->add(array(
'rider_id' => $rider_id,
'operator_id' => session('admin_id'),
'action' => 'force_offline',
'reason' => $reason,
'create_time' => time()
));
$this->ajaxReturn(array('code' => 0, 'msg' => '操作成功'));
} else {
$this->ajaxReturn(array('code' => 1, 'msg' => '操作失败'));
}
}
/**
* 发送通知给骑手
*/
public function sendNotification() {
$rider_id = I('rider_id', 0, 'intval');
$message = I('message', '', 'trim');
if (empty($rider_id)) {
$this->ajaxReturn(array('code' => 1, 'msg' => '参数错误'));
}
if (empty($message)) {
$this->ajaxReturn(array('code' => 1, 'msg' => '请输入通知内容'));
}
// 这里可以集成推送服务
// 示例:调用推送接口发送通知
// 记录通知日志
M('rider_notification_log')->add(array(
'rider_id' => $rider_id,
'sender_id' => session('admin_id'),
'message' => $message,
'create_time' => time()
));
$this->ajaxReturn(array('code' => 0, 'msg' => '通知发送成功'));
}
/**
* 导出监控数据
*/
public function exportMonitorData() {
$date = I('date', date('Y-m-d'), 'trim');
$area_id = I('area_id', 0, 'intval');
$start_time = strtotime($date . ' 00:00:00');
$end_time = strtotime($date . ' 23:59:59');
$where = array('u.user_type' => 'rider', 'u.status' => 1);
if ($area_id > 0) {
$where['ra.area_id'] = $area_id;
}
$list = M('users')->alias('u')
->field('u.*, rs.work_status, rs.last_update_time, rs.order_count_today')
->join('ls_rider_status rs ON u.id = rs.rider_id', 'LEFT')
->join('ls_rider_area ra ON u.id = ra.rider_id', 'LEFT')
->where($where)
->group('u.id')
->select();
// 导出Excel
vendor('PHPExcel.PHPExcel');
$objPHPExcel = new \PHPExcel();
$objPHPExcel->setActiveSheetIndex(0);
$objActSheet = $objPHPExcel->getActiveSheet();
// 设置表头
$objActSheet->setCellValue('A1', '骑手ID');
$objActSheet->setCellValue('B1', '姓名');
$objActSheet->setCellValue('C1', '手机号');
$objActSheet->setCellValue('D1', '工作状态');
$objActSheet->setCellValue('E1', '今日订单数');
$objActSheet->setCellValue('F1', '今日收入');
$objActSheet->setCellValue('G1', '在线时长');
$objActSheet->setCellValue('H1', '最后更新时间');
// 填充数据
$row = 2;
foreach ($list as $item) {
// 计算今日收入
$today_income = M('order')->where(array(
'rider_id' => $item['id'],
'status' => 'completed',
'create_time' => array('between', array($start_time, $end_time))
))->sum('delivery_fee') ?: 0;
// 获取在线时长
$online_log = M('rider_online_log')->where(array(
'rider_id' => $item['id'],
'date' => $date
))->find();
$online_duration = $online_log ? $this->formatDuration($online_log['online_duration']) : '0分钟';
$objActSheet->setCellValue('A' . $row, $item['id']);
$objActSheet->setCellValue('B' . $row, $item['nickname']);
$objActSheet->setCellValue('C' . $row, $item['phone']);
$objActSheet->setCellValue('D' . $row, $this->getWorkStatusText($item['work_status']));
$objActSheet->setCellValue('E' . $row, $item['order_count_today'] ?: 0);
$objActSheet->setCellValue('F' . $row, number_format($today_income, 2));
$objActSheet->setCellValue('G' . $row, $online_duration);
$objActSheet->setCellValue('H' . $row, $item['last_update_time'] ? date('Y-m-d H:i:s', $item['last_update_time']) : '');
$row++;
}
$filename = '骑手监控数据_' . $date . '_' . date('His') . '.xlsx';
header('Content-Type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
header('Content-Disposition: attachment;filename="' . $filename . '"');
header('Cache-Control: max-age=0');
$objWriter = \PHPExcel_IOFactory::createWriter($objPHPExcel, 'Excel2007');
$objWriter->save('php://output');
exit;
}
/**
* 格式化时长
*/
private function formatDuration($seconds) {
$hours = floor($seconds / 3600);
$minutes = floor(($seconds % 3600) / 60);
if ($hours > 0) {
return $hours . '小时' . $minutes . '分钟';
} else {
return $minutes . '分钟';
}
}
/**
* 获取工作状态文本
*/
private function getWorkStatusText($status) {
$statusMap = array(
'online' => '在线空闲',
'busy' => '配送中',
'offline' => '离线',
'rest' => '休息中'
);
return isset($statusMap[$status]) ? $statusMap[$status] : '未知';
}
}
?>