This commit is contained in:
hant 2025-06-09 23:16:58 +08:00
parent 7904e9abeb
commit 726c14bae0
10 changed files with 225 additions and 109 deletions

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -0,0 +1,91 @@
<?php
namespace app\admin\controller;
trait CustomerInfoExtractor
{
/**
* 提取完整客户信息
*/
public function extractCustomerInfo(string $chatText, array $serviceTypes): array
{
return [
'nickname' => $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;
}
}

View File

@ -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')));
}

View File

@ -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' => '请选择详细地址',

View File

@ -71,7 +71,7 @@
<div class="item flex-sb">
<div class="title flex-c">收款方式:</div>
<div class="value flex-c">
<select name="row[receive_type]" class="form-control selectpicker">
<select name="row[receive_type]" id="receive_type" class="form-control selectpicker">
<option selected value="1">已收定金</option>
<option value="2">已收全款</option>
</select>
@ -84,7 +84,7 @@
</div>
</div>
</div>
<div class="line flex-sb bt-40">
<div class="line flex-sb bt-40" id="coupon">
<div class="item flex-sb">
<div class="title flex-c">优惠:</div>
<div class="value flex-c">
@ -102,7 +102,7 @@
<div class="item flex-sb">
<div class="title flex-c">上门时间:</div>
<div class="value flex-l">
{:build_radios('row[set_time]', ['1'=>'有', '0'=>'无'], 1)}
{:build_radios('row[set_time]', ['1'=>'有', '0'=>'无'])}
</div>
</div>
<div class="item flex-sb" id="set-time">

View File

@ -71,7 +71,7 @@
<div class="item flex-sb">
<div class="title flex-c">收款方式:</div>
<div class="value flex-c">
<select name="row[receive_type]" class="form-control selectpicker">
<select name="row[receive_type]" class="form-control selectpicker" id="receive_type">
<option {if 1 == $row.receive_type} selected {/if} value="1">已收定金</option>
<option {if 2 == $row.receive_type} selected {/if} value="2">已收全款</option>
</select>
@ -84,7 +84,7 @@
</div>
</div>
</div>
<div class="line flex-sb bt-40">
<div class="line flex-sb bt-40" id="coupon">
<div class="item flex-sb">
<div class="title flex-c">优惠:</div>
<div class="value flex-c">

View File

@ -70,7 +70,7 @@
<div class="item flex-sb">
<div class="title flex-c">收款方式:</div>
<div class="value flex-c">
<select name="row[receive_type]" class="form-control selectpicker">
<select name="row[receive_type]" id="receive_type" class="form-control selectpicker">
<option {if 1 == $row.receive_type} selected {/if} value="1">已收定金</option>
<option {if 2 == $row.receive_type} selected {/if} value="2">已收全款</option>
</select>
@ -83,7 +83,7 @@
</div>
</div>
</div>
<div class="line flex-sb bt-40">
<div class="line flex-sb bt-40" id="coupon">
<div class="item flex-sb">
<div class="title flex-c">优惠:</div>
<div class="value flex-c">

View File

@ -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();
}
}
};

View File

@ -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)
}
//关键词筛选数据(暂不用)