客服线路

This commit is contained in:
jianghanbo 2024-10-12 19:16:04 +08:00
parent 1142d443b9
commit 1165277f87
18 changed files with 238 additions and 18 deletions

View File

@ -84,7 +84,7 @@
width="160" width="160"
prop="mobile" prop="mobile"
></el-table-column> ></el-table-column>
<el-table-column width="138px" align="center" label="路线"> <!-- <el-table-column width="138px" align="center" label="路线">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag v-if="scope.row.route_type === 10" type="border-card" <el-tag v-if="scope.row.route_type === 10" type="border-card"
>境内路线</el-tag >境内路线</el-tag
@ -93,7 +93,7 @@
>境外路线</el-tag >境外路线</el-tag
> >
</template> </template>
</el-table-column> </el-table-column> -->
<!-- <el-table-column align="center" label="头像"> <!-- <el-table-column align="center" label="头像">
<template slot-scope="scope"> <template slot-scope="scope">
@ -244,12 +244,12 @@
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> --> </el-form-item> -->
<el-form-item label="路线"> <!-- <el-form-item label="路线">
<el-radio-group v-model="item.route_type"> <el-radio-group v-model="item.route_type">
<el-radio :label="10">境内跟团</el-radio> <el-radio :label="10">境内跟团</el-radio>
<el-radio :label="20">境外跟团</el-radio> <el-radio :label="20">境外跟团</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item> -->
</el-form> </el-form>
<div slot="footer" class="dialog-footer"> <div slot="footer" class="dialog-footer">
<el-button type="primary" @click="onSave(item)"> </el-button> <el-button type="primary" @click="onSave(item)"> </el-button>

View File

@ -40,12 +40,11 @@
<span>{{ scope.row.last_work_time | parseTime('{y}-{m}-{d} {h}:{i}') }}</span> <span>{{ scope.row.last_work_time | parseTime('{y}-{m}-{d} {h}:{i}') }}</span>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column width="138px" align="center" label="线"> <!-- <el-table-column width="138px" align="center" label="线">
<template slot-scope="scope"> <template slot-scope="scope">
<el-tag v-if="scope.row.route_type === 10" type="border-card">境内路线</el-tag> <el-tag type="border-card">{{ scope.row.routes }}</el-tag>
<el-tag v-if="scope.row.route_type === 20" type="success">境外路线</el-tag>
</template> </template>
</el-table-column> </el-table-column> -->
</el-table> </el-table>
</div> </div>
</template> </template>

View File

@ -38,8 +38,8 @@ module.exports = {
}, },
proxy: { proxy: {
'/dev-api': { // 接口地址 以 api开头的都走下面的配置 '/dev-api': { // 接口地址 以 api开头的都走下面的配置
// target: 'https://www.szjinao.cn', // 代理目标地址为后端服务器地址 127.0.0.1 192.168.1.2 target: 'https://www.szjinao.cn', // 代理目标地址为后端服务器地址 127.0.0.1 192.168.1.2
target: 'http://192.168.0.100:8787', // 代理目标地址为后端服务器地址 127.0.0.1 192.168.1.2 // target: 'http://192.168.0.100:8787', // 代理目标地址为后端服务器地址 127.0.0.1 192.168.1.2
ws: true, // 是否支持 websocket 请求 支持 ws: true, // 是否支持 websocket 请求 支持
changeOrigin: true, // 是否启用跨域 changeOrigin: true, // 是否启用跨域
pathRewrite: { pathRewrite: {

View File

@ -4,6 +4,7 @@ namespace app\admin\controller;
use app\model\Admins; use app\model\Admins;
use app\model\Onlines; use app\model\Onlines;
use app\model\Orders; use app\model\Orders;
use app\model\Products;
use support\Log; use support\Log;
use support\Request; use support\Request;
use support\Redis; use support\Redis;
@ -184,6 +185,10 @@ class AdminController extends base
$list[$adminId]['id'] = $adminId; $list[$adminId]['id'] = $adminId;
$list[$adminId]['username'] = $user->username; $list[$adminId]['username'] = $user->username;
$list[$adminId]['route_type'] = $user->route_type; $list[$adminId]['route_type'] = $user->route_type;
$list[$adminId]['routes'] = [];
if ($user->product_ids) {
$list[$adminId]['routes'] = Products::query()->field(['product_name'])->whereIn('id', explode(',', $user->product_ids))->select();
}
} }
return $this->success(array_values($list)); return $this->success(array_values($list));

View File

@ -4,10 +4,12 @@ require_once(__DIR__.'/xlsxwriter.class.php');
use app\model\Admins; use app\model\Admins;
use app\model\Backs; use app\model\Backs;
use app\model\Blacks;
use app\model\Follows; use app\model\Follows;
use app\model\Orders; use app\model\Orders;
use app\model\Logs; use app\model\Logs;
use app\model\ThirdMobileLogs; use app\model\ThirdMobileLogs;
use app\server\SMS;
use app\server\ThirdApiService; use app\server\ThirdApiService;
use app\server\Orders as ServerOrders; use app\server\Orders as ServerOrders;
use stdClass; use stdClass;
@ -389,6 +391,40 @@ class OrderController extends base
return $this->success($backs->append(['order_status_name','os_name']),null, Orders::OSS); return $this->success($backs->append(['order_status_name','os_name']),null, Orders::OSS);
} }
// 批量流转出
public function backBatch(Request $request) {
$sn = $request->post('sn');
if (empty($sn) || !is_array($sn)) {
return $this->error(2001, '请选择流转的订单');
}
$toAdminId = $request->post('to_admin_id');
if (!$toAdminId) {
return $this->error(2001, '请选择流转的客服');
}
// Orders::whereIn('sn', $sn)->update(['admin_id'=> $toAdminId]);
foreach ($sn as $value) {
$item = Orders::where('sn', $value)->find();
if($item->admin_id == $toAdminId) {
continue;
}
Backs::create([
'order_id' => $item->id,
'admin_id' => $toAdminId,
'admin' => $item->admin_id,
'apply_id' => $request->admin->id,
'status' => 1
]);
Orders::where('sn', $value)->update(['admin_id'=> $toAdminId]);
// 发送短信
\app\server\Orders::sendOrderSms($toAdminId, $item);
}
return $this->success(true);
}
//把订单拉回 //把订单拉回
public function back(Request $request) { public function back(Request $request) {
$sn = $request->post('sn'); $sn = $request->post('sn');

View File

@ -0,0 +1,63 @@
<?php
namespace app\admin\controller;
use app\model\Admins;
use app\model\Onlines;
use app\model\Orders;
use app\model\Products;
use support\Log;
use support\Request;
use support\Redis;
class ProductsController extends base {
/**
* 线路列表
* @param Request $request
* @return \support\Response
* @throws \think\db\exception\DbException
*/
public function list(Request $request) {
$query = Products::where('status', 1)->order('id', 'desc');
if($username = $request->get('username')) {
$query->where('username', $username);
}
if($status = $request->get('status')) {
$query->where('status', $status);
}
if($is_order = $request->get('is_order')) {
$query->where('is_order', $is_order);
}
$list = $query->paginate($request->get('limit',1000));
return $this->success($list,null,['oss' => array_values(array_map(function ($os, $k) {
return ['id' => $k, 'os' => $os];
}, Orders::OSS, array_keys(Orders::OSS)))]);
}
public function add(Request $request) {
if (!$request->post('os')) {
return $this->error(2001, '请选择平台.');
}
if (!$request->post('product_name')) {
return $this->error(2001, '线路名称必填');
}
if (!$request->post('third_product_id')) {
return $this->error(2001, '线路id必填');
}
$where = ['os' => $request->get('os'), 'third_product_id' => $request->get('third_product_id')];
$product = (new Products())->where($where)->find();
if (!empty($product)) {
return $this->error(2002, '线路已存在');
}
$product = new Products();
$product->os = $request->post('os');
$product->third_product_id = $request->post('third_product_id');
$product->product_name = $request->post('product_name');
$product->status = 1;
$product->save();
Log::info('product:' . json_encode($product));
return $this->success([]);
}
}

View File

@ -0,0 +1,5 @@
<?php
namespace app\model;
class Products extends base {
}

View File

@ -3,15 +3,19 @@
namespace app\server; namespace app\server;
use app\common\Error; use app\common\Error;
use app\model\Admins;
use app\model\Admins as AdminsModel; use app\model\Admins as AdminsModel;
use app\model\Blacks;
use app\model\FilterMobiles; use app\model\FilterMobiles;
use app\model\Finances as FinancesModel; use app\model\Finances as FinancesModel;
use app\model\LiveRoomWorks; use app\model\LiveRoomWorks;
use app\model\Orders as OrdersModel; use app\model\Orders as OrdersModel;
use app\model\Products;
use support\Log; use support\Log;
use support\Redis; use support\Redis;
class Orders { class Orders {
protected static $redisPool = [];
public static function isDaishiyong(OrdersModel $order): bool public static function isDaishiyong(OrdersModel $order): bool
{ {
// 根据 OrdersModel::AllOssStatusSql[1] 进行判断 // 根据 OrdersModel::AllOssStatusSql[1] 进行判断
@ -237,4 +241,116 @@ class Orders {
return $roomWork; return $roomWork;
} }
/**
* 分配用户
* @param int $status
* @param string $categoryDesc
* @return int|string
* @throws \Exception
*/
public static function poolUser($status, $thirdProductId) {
$status .= $thirdProductId;
if (empty(self::$redisPool[$status])) {
self::$redisPool[$status] = Redis::hGetAll('CRM:Pool:' . $status);
$users = self::users($thirdProductId);
$_users = [];
if (empty(self::$redisPool[$status])) {
foreach ($users as $user) {
$_users[$user->username] = 0;
Redis::hSet('CRM:Pool:' . $status, $user->username, 0);
}
self::$redisPool[$status] = $_users;
} else {
asort(self::$redisPool[$status]);
$key_users = array_keys(self::$redisPool[$status]);
$username = $key_users[0];
$max = self::$redisPool[$status][$username];
$_users = [];
foreach ($users as $user) {
$_users[] = $user->username;
if (!in_array($user->username, $key_users)) {
self::$redisPool[$status][$username] = $max;
Redis::hSet('CRM:Pool:' . $status, $user->username, $max);
}
}
foreach (self::$redisPool[$status] as $username => $val) {
if (!in_array($username, $_users)) {
unset(self::$redisPool[$status][$username]);
Redis::hDel('CRM:Pool:' . $status, $username);
}
}
}
}
$username = null;
try {
$pool = self::$redisPool[$status];
if (empty($pool)) $pool = [];
asort($pool);
$keys = array_keys($pool);
if (empty($keys)) {
throw new \Exception('没有可以分配的用户');
}
$username = $keys[0];
self::$redisPool[$status][$username] += 1;
Redis::hIncrBy('CRM:Pool:' . $status, $username, 1);
} catch (\Exception $e) {
Log::error(dirname(__FILE__) . __LINE__ . '没有可以分配的用户', func_get_args());
throw new \Exception('没有可以分配的用户');
}
return $username;
}
/**
* @param $thirdProductId
* @return array
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
protected static function users($thirdProductId) {
$product = Products::query()->where('third_product_id', $thirdProductId)->find();
if (empty($product)) {
return [];
}
$users = Admins::where('status', 1)->where('is_order', 1)->whereFindInSet('product_ids', $product->id)->select();
if ($thirdProductId == '1128292503') {
Log::info(' 1128292503 find not one...' . json_encode($product));
}
$us = [];
foreach ($users as $u) {
$ru = Redis::get('CRM:USER:ONLINE:' . $u->id);
if (empty($ru)) continue;
$_u = new \stdClass();
$_u->username = $u->id;
$us[] = $_u;
};
return $us;
}
/**
* 发送订单短信
* @param $admin_id
* @param $order
* @throws \think\db\exception\DataNotFoundException
* @throws \think\db\exception\DbException
* @throws \think\db\exception\ModelNotFoundException
*/
public static function sendOrderSms($admin_id, $order)
{
$user = Admins::cache(true)->where('id', $admin_id)->find();
if ((!config('app.debug', true) || config('app.debug', true) === 'false') && (time() * 1000 - $order->create_at) / 1000 < 2 * 24 * 3600) {
$has = Blacks::where('mobile', $order->mobile)->find();
if (empty($has) && !empty($order->mobile)) {
SMS::juhe_sms_send($order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]);
} else {
sleep(10);
}
} else {
Log::info('订单未发送短息:' . json_encode([$order->mobile, 261607, ['title' => $order->product_name, 'mobile' => $user->mobile]]));
}
}
} }

File diff suppressed because one or more lines are too long

View File

@ -1 +0,0 @@
.pagination-container[data-v-6af373ef]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-6af373ef]{display:none}

View File

@ -0,0 +1 @@
.app-container[data-v-7aa35cbf]{position:relative;padding-bottom:60px}.el-table[data-v-7aa35cbf],.filter-container[data-v-7aa35cbf]{padding-bottom:52px}.search[data-v-7aa35cbf]{margin-left:10px}

View File

@ -1 +0,0 @@
.app-container[data-v-7c77c70f]{position:relative;padding-bottom:60px}.el-table[data-v-7c77c70f],.filter-container[data-v-7c77c70f]{padding-bottom:52px}.search[data-v-7c77c70f]{margin-left:10px}

View File

@ -1 +1 @@
.pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.app-container[data-v-414c5a51]{position:relative;padding-bottom:60px}.el-table[data-v-414c5a51],.filter-container[data-v-414c5a51]{padding-bottom:5px} .pagination-container[data-v-28fdfbeb]{padding:32px 16px;position:fixed;bottom:0;left:0;width:100%;background:#fff;padding:40px 280px;-webkit-box-shadow:0 -2px 10px rgba(0,0,0,.1);box-shadow:0 -2px 10px rgba(0,0,0,.1);z-index:100}.pagination-container.hidden[data-v-28fdfbeb]{display:none}.app-container[data-v-4d8f2a06]{position:relative;padding-bottom:60px}.el-table[data-v-4d8f2a06],.filter-container[data-v-4d8f2a06]{padding-bottom:5px}

View File

@ -0,0 +1 @@
.pagination-container[data-v-6af373ef]{background:#fff;padding:32px 16px}.pagination-container.hidden[data-v-6af373ef]{display:none}[data-v-ea07b72c].el-select{width:100%}.addRoutes[data-v-ea07b72c]{display:-webkit-box;display:-ms-flexbox;display:flex;-webkit-box-pack:end;-ms-flex-pack:end;justify-content:end}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long