From c6979b7129b87b245bbba75028518a2ff4460f1d Mon Sep 17 00:00:00 2001 From: hant Date: Sat, 24 May 2025 19:07:07 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B4=BE=E5=8D=95=E5=9C=B0=E5=9B=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/controller/orders/Dispatch.php | 47 ++- .../admin/controller/workers/Worker.php | 88 ++++-- .../admin/view/orders/dispatch/map.html | 293 +++++++++++++----- public/assets/img/worker.png | Bin 0 -> 459 bytes public/assets/js/backend/order.js | 4 +- public/assets/js/backend/orders/dispatch.js | 2 +- 6 files changed, 315 insertions(+), 119 deletions(-) create mode 100644 public/assets/img/worker.png diff --git a/application/admin/controller/orders/Dispatch.php b/application/admin/controller/orders/Dispatch.php index d0ad64f..0e521d7 100644 --- a/application/admin/controller/orders/Dispatch.php +++ b/application/admin/controller/orders/Dispatch.php @@ -74,7 +74,7 @@ class Dispatch extends Backend $row->btn_edit = (in_array($row->status, $this->model->btnActiveStatusList('btn_edit'))) ? true : false; $row->btn_cancel = (in_array($row->status, $this->model->btnActiveStatusList('btn_cancel'))) ? true : false; $row->btn_abnormal = (in_array($row->status, $this->model->btnActiveStatusList('btn_abnormal'))) ? true : false; - // $row->btn_income = (in_array($row->status, $this->model->btnActiveStatusList('btn_income')) && in_array($row->order->status, $orderModel->incomeBtnStatus())) ? true : false; + // $row->btn_income = (in_array($row->status, $this->model->btnActiveStatusList('btn_income')) && in_array($row->order->status, $orderModel->incomeBtnStatus())) ? true : false; } $result = array("total" => $list->total(), "rows" => $list->items()); @@ -99,26 +99,30 @@ class Dispatch extends Backend $this->error(__('No results were found')); } - $items = Db::name('item') - ->where('status',1) - ->field(['id','title','key_word','pid']) - ->order('pid','asc') - ->order('sort','desc') + $items = Db::name('item') + ->where('status', 1) + ->field(['id', 'title', 'key_word', 'pid']) + ->order('pid', 'asc') + ->order('sort', 'desc') ->select(); $tree = $this->buildTree($items); $formattedTree = $this->formatTree($tree); $area_name = model('area')->getNameByCode($order->area_id); - $order->area_name = str_replace(',','/',$area_name); + $order->area_name = str_replace(',', '/', $area_name); $this->view->assign('items', $formattedTree); $this->view->assign('row', $order); return $this->view->fetch(); } $params = $this->request->post('row/a'); +// $this->success(); if (empty($params)) { - $this->error(__('Parameter %s can not be empty', '')); + $params = $this->request->post(); + if (empty($params)) { + $this->error(__('Parameter %s can not be empty', '')); + } } $params = $this->preExcludeFields($params); @@ -134,8 +138,8 @@ class Dispatch extends Backend $validate = is_bool($this->modelValidate) ? ($this->modelSceneValidate ? $name . '.add' : $name) : $this->modelValidate; $this->model->validateFailException()->validate($validate); } - $order = model('order')->where('id',$params['order_id'])->find(); - if (!$order){ + $order = model('order')->where('id', $params['order_id'])->find(); + if (!$order) { $this->error(__('No results were found')); } $insert = [ @@ -148,7 +152,7 @@ class Dispatch extends Backend 'is_receipt' => $order['receive_type'] == 1 ]; - $worker = model('worker')->where('id',$params['worker_id'])->find(); + $worker = model('worker')->where('id', $params['worker_id'])->find(); $insert ['worker_name'] = $worker->name; $insert ['worker_tel'] = $worker->tel; @@ -168,8 +172,8 @@ class Dispatch extends Backend //dispatch log $hookParams2 = [ - 'dispatch' => $this->model->get($this->model->id), - 'remark' => '手动派单,操作人:'.$this->auth->nickname, + 'dispatch' => $this->model->get($this->model->id), + 'remark' => '手动派单,操作人:' . $this->auth->nickname, ]; Hook::listen('order_dispatch_change', $hookParams2); @@ -296,16 +300,27 @@ class Dispatch extends Backend private function getWorkers($workers) { - foreach ($workers as $worker){ + foreach ($workers as $worker) { $worker->dist = '100m'; } return $workers; } - public function map(){ + public function map() + { // 配置信息 $config = get_addon_config('address'); - $this->view->assign('mapkey',$config); + $order_id = request()->get('order_id'); + $order = (new Order())->where('id', $order_id)->select(); + $order = $order[0] ?? false; + if (!$order) { + abort(404); + } + $center = [ + $order->lng, $order->lat + ]; + $this->view->assign('mapkey', $config); + $this->view->assign('center', $center); return $this->fetch(); } diff --git a/application/admin/controller/workers/Worker.php b/application/admin/controller/workers/Worker.php index 7fd53be..3f59616 100644 --- a/application/admin/controller/workers/Worker.php +++ b/application/admin/controller/workers/Worker.php @@ -308,26 +308,78 @@ class Worker extends Backend public function dispatchMapList() { $order_id = request()->get('order_id'); - if ($order_id) { - $order = (new Order())->where('id',$order_id)->select(); + $search = request()->get('search'); + list($where, $sort, $order, $offset, $limit) = $this->buildparams(); + $order = (new Order())->where('id',$order_id)->field(['id','lng','lat'])->select(); - if (!empty($order)) { - $order = $order[0]; - } - - $worker_info = Db::query("SELECT id,name, tel, distance,star,lng, lat -FROM ( - SELECT id, `name`, tel, lng, lat,star, - ST_Distance_Sphere( - point(lng, lat), - point(?, ?) - ) AS distance - FROM fa_worker -) AS t -WHERE distance < 50000 -ORDER BY distance;",[$order->lng,$order->lat]); - dd($worker_info); + if (!empty($order)) { + $order = $order[0]; } + + $worker_distance = Db::query("SELECT id +FROM ( +SELECT id, + ST_Distance_Sphere( + point(lng, lat), + point(?, ?) + ) AS distance +FROM fa_worker where status = 1 +) AS t +WHERE distance < 50000 +ORDER BY distance;",[$order->lng,$order->lat]); + + $worker_ids = array_column($worker_distance,'id'); + + $build = model('worker') + ->where('status', 1) + ->whereIn('id', $worker_ids); + + + if ($search){ + $build->where(function ($q)use($search){ + $q->where('name','like','%'.$search.'%') + ->whereor('tel','like','%'.$search.'%'); + }); + } + + $build->withCount(['myorder' => function ($query) { + $query->where('status', Order::STATUS_FINISHED); + return 'finish_order'; + },]) + ->withCount(['myorder' => function ($query) { + $query->where('status', '<', Order::STATUS_FINISHED) + ->where('status', '>=', Order::STATUS_DRAFT); + return 'doing_order'; + }]) + ->field( + ['id', 'name', 'tel', 'area_id', 'lng', 'lat','ST_Distance_Sphere( + point(lng, lat), + point('.$order->lng.','.$order->lat.') + ) AS distance'] + )->order('distance'); + $list = $build + ->paginate($limit); + + $data = []; + + foreach ($list->items() as $item){ + $data [] = [ + 'id' => $item->id, + 'name' => $item->name, + 'tel' => $item->tel, + 'lat' => $item->lat, + 'lng' => $item->lng, + 'distance' => $item->distance, + 'doing_order' => $item->doing_order, + 'finish_order' => $item->finish_order, + 'star' => $item->star, + ]; + } + + $result = array("total" => $list->total(), "rows" => $data,'order' => $order); + + return $result; + } } diff --git a/application/admin/view/orders/dispatch/map.html b/application/admin/view/orders/dispatch/map.html index 3d0fa62..041aa05 100644 --- a/application/admin/view/orders/dispatch/map.html +++ b/application/admin/view/orders/dispatch/map.html @@ -12,59 +12,54 @@ font-size: 14px; line-height: 1.6; } + .worker-search-bar { + display: flex; + gap: 8px; /* 输入框和按钮的间距 */ + margin-bottom: 10px; + } + + .worker-input { + flex: 1; + padding: 6px 10px; + font-size: 14px; + border: 1px solid #ccc; + border-radius: 4px; + line-height: 14px; + } + + .worker-btn { + padding: 6px 12px; + font-size: 14px; + background-color: #007bff; + color: #fff; + border: 1px solid #007bff; + border-radius: 4px; + cursor: pointer; + line-height: 14px; + } + + .worker-btn:hover { + background-color: #0056b3; + border-color: #0056b3; + } + .worker-card{ + background-color: #c9c5c542; + margin-bottom: 10px; + box-shadow: 0 2px 6px rgba(12, 72, 128, .1); + border-radius: 5px; + } -
+
-
- -
- -
-
-
- 张三 - -
-
-
- 电话:138****1234 - 星级: - ★★★★★ - -
-
- 距离:2.5 公里 - 订单数:3 -
-
-
-
- - -
-
-
- 李四 - -
-
-
- 电话:137****5678 - 星级: - ★★★★☆ - -
-
- 距离:4.1 公里 - 订单数:1 -
-
-
+ +
@@ -75,13 +70,8 @@ securityJsCode: "{$mapkey.amapsecurityjscode|default=''}", } - const taskLocation = [104.065735, 30.657201]; // 天府广场 - - const workers = [ - { name: '张三', phone: '138****1234', position: [104.066, 30.658] }, - { name: '李四', phone: '139****5678', position: [104.064, 30.656] }, - { name: '王五', phone: '136****9876', position: [104.067, 30.659] }, - ]; + const taskLocation = {:json_encode($center); }; + let workerMarkers = []; const map = new AMap.Map('map', { zoom: 16, @@ -95,51 +85,188 @@ icon: '//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-red.png', label: { content: '任务点', - offset: new AMap.Pixel(20, -10) + offset: new AMap.Pixel(10, -10) } }); map.add(taskMarker); + function drawWorker(workers) { + // 清除旧的工人标记 + if (workerMarkers.length > 0) { + map.remove(workerMarkers); + workerMarkers = []; + } - // 添加工人标记(蓝色) - workers.forEach(worker => { - const marker = new AMap.Marker({ - position: worker.position, - title: worker.name, - icon: '//a.amap.com/jsapi_demos/static/demo-center/icons/poi-marker-blue.png', - label: { - content: `${worker.name}`, - offset: new AMap.Pixel(20, -10) + // 添加新的工人标记 + workers.forEach((worker, index) => { + const position = addSlightOffset(worker.lat, worker.lng, index); + + const marker = new AMap.Marker({ + position, + title: worker.name, + icon: new AMap.Icon({ + size: new AMap.Size(32, 32), + image: '/assets/img/worker.png', + imageSize: new AMap.Size(32, 32) + }), + label: { + content: `${worker.name}`, + offset: new AMap.Pixel(10, -10) + } + }); + + const infoWindow = new AMap.InfoWindow({ + content: ` +
+ ${worker.name}
+ 电话:${worker.tel}
+ 距离任务:${worker.distance.toFixed(2)} 米
+ 正在进行订单数:${worker.doing_order}
+ 完成订单数:${worker.finish_order}
+ 星级:${'★'.repeat(worker.star)}
+ +
`, + offset: new AMap.Pixel(0, -10) + }); + + marker.on('click', () => { + infoWindow.open(map, marker.getPosition()); + }); + + map.add(marker); + workerMarkers.push(marker); // 存储到全局数组 + }); + } + //点击确定后执行回调赋值 + var close = function (data) { + var index = parent.Layer.getFrameIndex(window.name); + var callback = parent.$("#layui-layer" + index).data("callback"); + //再执行关闭 + parent.Layer.close(index); + //再调用回传函数 + if (typeof callback === 'function') { + callback.call(undefined, data); + } + }; + + function renderWorkerCards(workers) { + const listContainer = document.getElementById('worker-list'); + listContainer.innerHTML = ''; // 清空旧内容 + if (!workers || workers.length === 0) { + listContainer.innerHTML = ` +
+ +
`; + return; + } + workers.forEach(worker => { + const maskedPhone = worker.tel.replace(/^(\d{3})\d{4}(\d{4})$/, '$1****$2'); + const starHtml = '★'.repeat(worker.star).padEnd(5, '☆'); + const distanceKm = (worker.distance / 1000).toFixed(2); + + const cardHtml = ` +
+
+
+
+ ${worker.name} + +
+
+
+ 电话:${maskedPhone} + 星级: + ${starHtml} + +
+
+ 距离:${distanceKm} 公里 + 完成订单数:${worker.finish_order} +
+ 进行中订单数:${worker.doing_order} +
+
+
+
+
`; + + listContainer.insertAdjacentHTML('beforeend', cardHtml); + }); + } + + function assignOrder(worker_id) { + // 可发起请求 + const params = new URLSearchParams(window.location.search); + const order_id = params.get('order_id'); + $.ajax({ + url: '/admin/orders/dispatch/add', + method: 'POST', + contentType: 'application/json', + data: JSON.stringify({ worker_id,order_id }), + success: function (res) { + Toastr.info('派单成功'); + close() + }, + error: function () { + Toastr.info('派单失败,请重试。'); } }); + } + function reload(){ - const infoWindow = new AMap.InfoWindow({ - content: ` -
- ${worker.name}
- 电话:${worker.phone}
- -
`, - offset: new AMap.Pixel(0, -30) - }); + const params = new URLSearchParams(window.location.search); + const orderId = params.get('order_id'); + const keyword = $('#worker-search').val().trim(); - marker.on('click', () => { - infoWindow.open(map, marker.getPosition()); - }); + // 构造请求参数对象 + const queryParams = new URLSearchParams(); + if(orderId) queryParams.append('order_id', orderId); + if(keyword) queryParams.append('search', keyword); // 传给后端的搜索关键词参数名,跟后端约定 - map.add(marker); - }); - const params = new URLSearchParams(window.location.search); - const orderId = params.get('order_id'); - - $('#reload').click(function () { $.ajax({ - url: "workers/worker/dispatchMapList?order_id=" + orderId, + url: "/admin/workers/worker/dispatchMapList?" + queryParams.toString(), type: 'get', dataType: 'json', success: function (ret) { - console.log(ret); + drawWorker(ret.rows); + renderWorkerCards(ret.rows); } }); + } + + reload(); + + function addSlightOffset(lat, lng, index) { + const offset = 0.00005; // 约5米 + return [ + parseFloat(lng) + (index % 3 - 1) * offset, + parseFloat(lat) + (Math.floor(index / 3) - 1) * offset + ]; + } +// 委托方式绑定事件:监听 .assign-btn 点击 + $(document).on('click', '.assign-btn', function () { + const worker_id = $(this).data('id'); + assignOrder(worker_id); + }); +// 事件委托,绑定点击工人卡片的事件 + $(document).on('click', '.worker-card', function (e) { + // 避免点击“派单”按钮时触发此事件 + if ($(e.target).closest('.assign-btn').length) { + return; + } + + const lat = parseFloat($(this).data('lat')); + const lng = parseFloat($(this).data('lng')); + + if (!isNaN(lat) && !isNaN(lng)) { + map.setCenter([lng, lat]); // 高德地图的坐标格式是 [经度, 纬度] + map.setZoom(17); // 可选:放大地图 + } + }); + + $('#reload').click(function () { + reload(); }); }) diff --git a/public/assets/img/worker.png b/public/assets/img/worker.png new file mode 100644 index 0000000000000000000000000000000000000000..8f7d135eb4a17f4e50b964a101c240323baaae2f GIT binary patch literal 459 zcmV;+0W|)JP)Px$g-Jv~R9Hvtm&}orgciX3lpg@$RTq}qA(3}smsAC!1Q-(h3QBM+_s&5EE(PGS zGn$=EGVj~gSS@8LNC6&#JWA;43ZuWN!t6+q{AhOqhYo%EKi5MWg8dy#lU ze(v4a2*8H?*MLZI>ARj0z&yxFM_xS}D$1R^Gz7@bz$c!SGXTJ53HVL;+Bq9D1;Q)> z!mS+v!~;oq3!0^a7})?AbPIUkd0?Xt#Px4>hAhK&6yGIdno%}{lmLwi12&