From 726c14bae0904109aae642490bc2f22e32796460 Mon Sep 17 00:00:00 2001 From: hant Date: Mon, 9 Jun 2025 23:16:58 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- application/admin/addresmart/Address.php | 46 +++++- application/admin/command/Test.php | 35 +---- .../controller/CustomerInfoExtractor.php | 91 ++++++++++++ application/admin/controller/Order.php | 8 +- application/admin/validate/Order.php | 3 +- application/admin/view/order/add.html | 6 +- application/admin/view/order/copy.html | 4 +- application/admin/view/order/edit.html | 4 +- public/assets/js/backend/order.js | 132 +++++++++--------- public/assets/js/cascader.js | 5 +- 10 files changed, 225 insertions(+), 109 deletions(-) create mode 100644 application/admin/controller/CustomerInfoExtractor.php diff --git a/application/admin/addresmart/Address.php b/application/admin/addresmart/Address.php index 7ecbfb4..385d78b 100644 --- a/application/admin/addresmart/Address.php +++ b/application/admin/addresmart/Address.php @@ -18,7 +18,11 @@ class Address $name_items [] = $item->title; $name_items_map [$item->title] = $item->id; } - $type = self::findMostSimilar($string,$name_items); + + $titles = Item::where('status',1)->column('title'); + $type = self::extractServiceTypes($string,$titles)[0] ?? ''; + + $type_arr = explode('__',$type); $type = $type_arr[0] ?? ''; $str = $type_arr[1] ?? ''; @@ -34,9 +38,9 @@ class Address $fuzz = self::fuzz($re['addr']); $parse = self::parse($fuzz['a1'], $fuzz['a2'], $fuzz['a3']); - $re['province'] = $parse['province']; - $re['city'] = $parse['city']; - $re['region'] = $parse['region']; + $re['province'] = $parse['province'] ?? ''; + $re['city'] = $parse['city'] ?? ''; + $re['region'] = $parse['region'] ?? ''; $re['item'] = [ 'id'=> $name_items_map[$type] ?? 0, 'item' => $type ?? '' @@ -305,4 +309,38 @@ class Address return $r; } + + + /** + * 从聊天内容中提取匹配的服务类型 + * + * @param string $chatText 聊天内容 + * @param array $serviceTypes 服务类型数组 + * @param bool $returnAll 是否返回全部匹配,false 时只返回第一个匹配 + * @return array|string|null 匹配的服务类型(数组或单个字符串) + */ + static function extractServiceTypes(string $chatText, array $serviceTypes, bool $returnAll = true): array|string|null + { + // 去重 + 去空 + $cleaned = array_filter(array_map('trim', $serviceTypes)); + + // 优先匹配更长的词 + usort($cleaned, fn($a, $b) => mb_strlen($b, 'UTF-8') - mb_strlen($a, 'UTF-8')); + + $matched = []; + + foreach ($cleaned as $service) { + if (mb_stripos($chatText, $service) !== false) { + if ($returnAll) { + $matched[] = $service; + } else { + return $service; + } + } + } + + return $returnAll ? $matched : null; + } + + } diff --git a/application/admin/command/Test.php b/application/admin/command/Test.php index ef5fc4e..f97508a 100644 --- a/application/admin/command/Test.php +++ b/application/admin/command/Test.php @@ -34,37 +34,12 @@ class Test extends Command protected function execute(Input $input, Output $output) { - dd(config('system_id')); - - $order = Order::where('id',140)->find(); - AutoDispatchLogic::autoDispatch($order); - - - $hookParams = [ - 'dispatch' => (new OrderDispatch())->where('id', 144)->find(), - 'remark' => '系统自动派单给师傅:'. '时间嗯' .'('.'12312'.')', + $dispatch = OrderDispatch::where('id',177)->get(); + $hookParams2 = [ + 'dispatch' => $dispatch, + 'remark' => '手动派单给师傅:' . '老师傅' .'(123)', ]; - - Lang::load(APP_PATH . 'admin/lang/zh-cn/orders/dispatch2.php'); - - $Model = new \app\admin\model\OrderDispatch(); - $statusList = $Model->getStatusList(); - $dispatch = $hookParams['dispatch']; //订单对象 - $remark = $hookParams['remark'] ?? ''; //备注 - $data = [ - 'dispatch_id' => $dispatch->id, - 'order_id' => $dispatch->order_id, - 'worker_id' => $dispatch->worker_id, - 'status' => $dispatch->status, - 'status_text' => $statusList[$dispatch->status], - 'remark' => $remark, - 'admin_user' => $dispatch->admin_user??'sys', - ]; - dd($data); - - - dd($res); - + Hook::listen('order_dispatch_change', $hookParams2); } diff --git a/application/admin/controller/CustomerInfoExtractor.php b/application/admin/controller/CustomerInfoExtractor.php new file mode 100644 index 0000000..3329cf1 --- /dev/null +++ b/application/admin/controller/CustomerInfoExtractor.php @@ -0,0 +1,91 @@ + $this->extractNickname($chatText), + 'city' => $this->extractCity($chatText), + 'district' => $this->extractDistrict($chatText), + 'address' => $this->extractAddress($chatText), + 'phone' => $this->extractPhone($chatText), + 'remark' => $this->extractRemark($chatText), + 'services' => $this->extractServices($chatText, $serviceTypes), + ]; + } + + protected function extractNickname(string $text): ?string + { + if (preg_match('/^(.*?)\s*-->/u', $text, $match)) { + return trim($match[1]); + } + return null; + } + + protected function extractCity(string $text): ?string + { + if (preg_match('/(北京|上海|广州|深圳|武汉|成都|重庆|杭州|南京|天津|西安|苏州|郑州|长沙|青岛|合肥|福州|厦门|南昌|昆明|大连|宁波|无锡|哈尔滨|长春|石家庄|南宁|贵阳|兰州|呼和浩特|乌鲁木齐)/u', $text, $match)) { + return $match[1]; + } + return null; + } + + protected function extractDistrict(string $text): ?string + { + if (preg_match('/([\p{Han}]{1,10}区)/u', $text, $match)) { + return $match[1]; + } + return null; + } + + protected function extractAddress(string $text): ?string + { + if (preg_match('/(湖北省|四川省|北京市|上海市|重庆市|[\p{Han}]+省)?[\p{Han}]+市\s*[\p{Han}]+区.*?(\d+栋.*?室)/u', $text, $match)) { + return $match[0]; + } + return null; + } + + protected function extractPhone(string $text): ?string + { + if (preg_match('/1[3-9]\d{9}/', $text, $match)) { + return $match[0]; + } + return null; + } + + protected function extractRemark(string $text): ?string + { + if (preg_match_all('/https?:\/\/[^\s]+/i', $text, $matches)) { + return implode(', ', $matches[0]); + } + + // 其他软件/售后/推广语也可加关键词检测 + if (str_contains($text, '软件下载') || str_contains($text, '自动发货')) { + return '可能包含软件下载或推广信息'; + } + + return null; + } + + protected function extractServices(string $text, array $serviceTypes): array + { + $cleaned = array_filter(array_map('trim', $serviceTypes)); + usort($cleaned, fn($a, $b) => mb_strlen($b, 'UTF-8') - mb_strlen($a, 'UTF-8')); + + $matched = []; + foreach ($cleaned as $service) { + if (mb_stripos($text, $service) !== false) { + $matched[] = $service; + } + } + return $matched; + } +} \ No newline at end of file diff --git a/application/admin/controller/Order.php b/application/admin/controller/Order.php index be5da90..7528246 100644 --- a/application/admin/controller/Order.php +++ b/application/admin/controller/Order.php @@ -5,6 +5,7 @@ namespace app\admin\controller; use app\admin\addresmart\Address; use app\admin\controller\orders\DispatchLogic; use app\admin\model\Admin; +use app\admin\model\Item; use app\admin\model\Message; use app\admin\model\order\Invoice; use app\admin\model\OrderDispatch; @@ -30,6 +31,7 @@ use function Symfony\Component\Clock\now; */ class Order extends Backend { + use CustomerInfoExtractor; /** * Order模型对象 * @var \app\admin\model\Order @@ -389,7 +391,11 @@ class Order extends Backend public function smart() { - $this->success(data: Address::smart(request()->get('str'))); +// $titles = Item::where('status',1)->column('title'); +// $res = $this->extractCustomerInfo(request()->post('str'),$titles); +// dd($res); + + $this->success(data: Address::smart(request()->post('str'))); } diff --git a/application/admin/validate/Order.php b/application/admin/validate/Order.php index 353541e..e4286fa 100644 --- a/application/admin/validate/Order.php +++ b/application/admin/validate/Order.php @@ -13,7 +13,7 @@ class Order extends Validate 'source' => 'require', 'item_id' => 'require', 'customer' => 'require|max:32', - 'tel' => 'require|number|max:32', + 'tel' => 'require|number|max:32|regex:/^1[3-9]\d{9}$/', 'area_id' => 'require', 'address' => 'require|max:255', 'lng' => 'require', @@ -32,6 +32,7 @@ class Order extends Validate 'customer.max' => '客户昵称不能超过 32 个字符', 'tel.require' => '请输入客户电话', + 'tel.regex' => '电话号码格式不正确', 'area_id.require' => '请选择地区', 'address.require' => '请选择详细地址', diff --git a/application/admin/view/order/add.html b/application/admin/view/order/add.html index 8ba7c27..23a4058 100644 --- a/application/admin/view/order/add.html +++ b/application/admin/view/order/add.html @@ -71,7 +71,7 @@
收款方式:
- @@ -84,7 +84,7 @@
-
+
优惠:
@@ -102,7 +102,7 @@
上门时间:
- {:build_radios('row[set_time]', ['1'=>'有', '0'=>'无'], 1)} + {:build_radios('row[set_time]', ['1'=>'有', '0'=>'无'])}
diff --git a/application/admin/view/order/copy.html b/application/admin/view/order/copy.html index 74ebae4..785dc42 100644 --- a/application/admin/view/order/copy.html +++ b/application/admin/view/order/copy.html @@ -71,7 +71,7 @@
收款方式:
- @@ -84,7 +84,7 @@
-
+
优惠:
diff --git a/application/admin/view/order/edit.html b/application/admin/view/order/edit.html index c483a75..18d7ddc 100644 --- a/application/admin/view/order/edit.html +++ b/application/admin/view/order/edit.html @@ -70,7 +70,7 @@
收款方式:
- @@ -83,7 +83,7 @@
-
+
优惠:
diff --git a/public/assets/js/backend/order.js b/public/assets/js/backend/order.js index 335a656..082a501 100644 --- a/public/assets/js/backend/order.js +++ b/public/assets/js/backend/order.js @@ -4,6 +4,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function $('[name^="row["]').val(''); $("#c-city").citypicker('reset'); $("#item_id").val(''); + $('.zd-cascader-menu').find('li.in-active-path').removeClass('in-active-path'); + $('.zd-cascader-panel').find('.is-selected-icon').remove(); $("#item_id_value").val(''); $(".selectpicker").val('').selectpicker('refresh'); } @@ -26,23 +28,16 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function // 拼装文本 function assembleOrderMessage(data) { - const message = ` -【订单详情】 -录单员: ${data.user.nickname} -订单编号: ${data.order_no} + const message = `订单编号: ${data.order_no} 服务名称: ${data.item_title} 客户姓名: ${data.customer} 客户电话: ${data.tel} -上门时间: ${data.plan_time} -优惠码: ${data.coupon?.description||'无'} -订单状态: ${data.status_text} +上门时间: ${data.plan_time || '无'} +优惠码: ${data.coupon?.description || '无'} 详细地址: ${data.address} -订单详情: ${data.detail} +订单详情: ${data.detail || '无'} 订单备注: ${data.remark} -派单方式: ${data.dispatch_type === 1 ? '手动派单' : '自动派单'} -收款方式: ${data.receive_type === 1 ? '已收定金' : '已收全款'} - -请查收以上订单信息。`; +收款方式: ${data.receive_type === 1 ? '已收定金' : '已收全款'}`; return message; } @@ -74,9 +69,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function fixedRightNumber: 1, fixedNumber: 3, fixedColumns: true, - renderDefault:true, - searchFormVisible:true, - search:false, + renderDefault: true, + searchFormVisible: true, + search: false, columns: [ [ {checkbox: true}, @@ -84,7 +79,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function { field: 'status', title: __('Status'), - fixed:true, + fixed: true, searchList: { "0": __('Status 0'), "10": __('Status 10'), @@ -99,10 +94,10 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function /*"-20": __('Status -20'), "-30": __('Status -30')*/ }, - defaultValue:10, + defaultValue: 10, formatter: Table.api.formatter.status, - custom:{ - "10":"my_dispatch" + custom: { + "10": "my_dispatch" } }, { @@ -121,7 +116,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function {field: 'order_no', title: __('Order_no'), operate: 'LIKE'}, {field: 'customer', title: __('Customer'), operate: 'LIKE'}, {field: 'tel', title: __('Tel'), operate: 'LIKE'}, - {field: 'area.merge_name', title: __('Area_id'),searchable:false}, + {field: 'area.merge_name', title: __('Area_id'), searchable: false}, { field: 'address', title: __('Address'), @@ -203,7 +198,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function extend: 'data-toggle="tooltip" data-container="body"', classname: 'btn btn-xs btn-info btn-editone', visible: function (row) { - if (row.status != 60 && row.status != 70) { + if (row.status != 60 && row.status != 70) { return true; } return false; @@ -275,7 +270,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function } return false; }, - callback:function (){ + callback: function () { table.bootstrapTable('refresh'); } }, @@ -336,17 +331,17 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function }, }, { - name:"error", - text:"订单报错", - title:"订单报错", + name: "error", + text: "订单报错", + title: "订单报错", extend: 'data-toggle="tooltip" data-container="body"', classname: 'btn btn-dialog', icon: 'fa fa-bolt', url: 'order/addAbnormal', - refresh:true, + refresh: true, dropdown: "更多", visible: function (row) { - if (row.status != 60 && row.status != 70) { + if (row.status != 60 && row.status != 70) { return true; } return false; @@ -413,12 +408,12 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function }); const timer = setInterval(function () { table.bootstrapTable('refresh', {}); - },1000 * 120); + }, 1000 * 120); }, add: function () { $("#mybuttom").on("click", function () { const res = $("form[role=form]").isValid(); - if (res){ + if (res) { Form.api.submit($("form[role=form]")); // Toastr.success('录入成功'); } @@ -427,9 +422,12 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function $("#mysubmit").on("click", function () { const res = $("form[role=form]").isValid(); - if (res){ - Form.api.submit($("form[role=form]")); - clearInfo(); + console.log('form', res); + if (res) { + Form.api.submit($("form[role=form]"), function (data, ret) { + clearInfo(); + return false; + }); // Toastr.success('录入成功'); } return false; @@ -438,11 +436,12 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function $("#smart").on("click", function () { $.ajax({ url: "order/smart", // 你的 API 地址 - type: "GET", + type: "post", + contentType: 'application/json', dataType: "json", - data: { + data: JSON.stringify({ str: $('#smart_text').val() - }, + }), success: function (data) { if (data.code === 1) { data = data.data; @@ -483,26 +482,10 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function Controller.api.map(); }, edit: function () { - // 未选择上门时间时,隐藏时间选择框 - var planTime = $('input[name="row[plan_time]"]').val() - if (planTime === '' || planTime === 'null') { - $('#set-time').hide(); - } else { - $('#set-time').show(); - } - Controller.api.bindevent(); Controller.api.map(); }, copy: function () { - // 未选择上门时间时,隐藏时间选择框 - var planTime = $('input[name="row[plan_time]"]').val() - if (planTime === '' || planTime === 'null') { - $('#set-time').hide(); - } else { - $('#set-time').show(); - } - Controller.api.bindevent(); Controller.api.map(); }, @@ -537,11 +520,12 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function $('#c-bank_account').closest('.form-group').hide(); } } + // 初始化时执行一次 toggleInvoiceFields(); // 监听 select 改变 - $('#c-source').on('change',function () { + $('#c-source').on('change', function () { toggleInvoiceFields(); }); Form.api.bindevent($("form[role=form]")); @@ -549,17 +533,8 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function api: { bindevent: function () { Form.api.bindevent($("form[role=form]")); - - $('input[name="row[set_time]"]').on('change', function () { - var val = $(this).val(); - if (val == 1) { - $('#set-time').show(); - } else { - $('#set-time').hide(); - } - }); }, - map:function () { + map: function () { $("#c-city").on("cp:updated", function () { var citypicker = $(this).data("citypicker"); var code = citypicker.getCode("district") || citypicker.getCode("city") || citypicker.getCode("province"); @@ -572,7 +547,7 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function // }); $(document).on('click', "#area_map", function (e) { const data = $("#c-city").val(); - if (!data){ + if (!data) { Toastr.error('请先选择区域'); return false; } @@ -590,9 +565,9 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function var url = "/addons/address/index/select?a=1"; url += (lat && lng) ? 'lat=' + lat + '&lng=' + lng + (input_id ? "&address=" + $("#" + input_id).val() : "") - +(zoom ? "&zoom=" + zoom : "") : '' + + (zoom ? "&zoom=" + zoom : "") : '' ; - if (city_code){ + if (city_code) { url += city_code ? "&city_code=" + city_code : ""; } // console.log(url); @@ -626,6 +601,33 @@ define(['jquery', 'bootstrap', 'backend', 'table', 'form', 'cascader'], function } }); $('#item_id').val($('#item_id').data('value')).focus(); + + const mainSelect = document.getElementById('receive_type'); + const otherSelect = document.getElementById('coupon'); + + function toggleOtherSelect() { + if (mainSelect.value === '2') { + otherSelect.style.display = 'none'; + } else { + otherSelect.style.display = ''; + } + } + + mainSelect.addEventListener('change', toggleOtherSelect); + toggleOtherSelect(); + + $('input[name="row[set_time]"]').on('change', toggleTime); + + function toggleTime() { + var val = $('input[name="row[set_time]"]:checked').val(); + if (val == 1) { + $('#set-time').show(); + } else { + $('#set-time').hide(); + } + } + + toggleTime(); } } }; diff --git a/public/assets/js/cascader.js b/public/assets/js/cascader.js index 3b82dc2..2ed59c6 100644 --- a/public/assets/js/cascader.js +++ b/public/assets/js/cascader.js @@ -132,6 +132,10 @@ this.search(this.$el.val()); }, this)); } + ZdCascader.prototype.clear_path_class = function () { + this.$dropdownWrap.find('li.' + this.CLASS.checkClass.nodeAnchor).removeClass(this.CLASS + .checkClass.nodeAnchor); + } ZdCascader.prototype._wrapClick = function () { event.stopPropagation(); this.$el.focus(); @@ -318,7 +322,6 @@ }).sort(function(a, b) { return b.num - a.num }).slice(0, 10) - console.log(data); this.reload(data, true) } //关键词筛选数据(暂不用)