客服线路

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"
prop="mobile"
></el-table-column>
<el-table-column width="138px" align="center" label="路线">
<!-- <el-table-column width="138px" align="center" label="路线">
<template slot-scope="scope">
<el-tag v-if="scope.row.route_type === 10" type="border-card"
>境内路线</el-tag
@ -93,7 +93,7 @@
>境外路线</el-tag
>
</template>
</el-table-column>
</el-table-column> -->
<!-- <el-table-column align="center" label="头像">
<template slot-scope="scope">
@ -244,12 +244,12 @@
</el-option>
</el-select>
</el-form-item> -->
<el-form-item label="路线">
<!-- <el-form-item label="路线">
<el-radio-group v-model="item.route_type">
<el-radio :label="10">境内跟团</el-radio>
<el-radio :label="20">境外跟团</el-radio>
</el-radio-group>
</el-form-item>
</el-form-item> -->
</el-form>
<div slot="footer" class="dialog-footer">
<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>
</template>
</el-table-column>
<el-table-column width="138px" align="center" label="线">
<!-- <el-table-column width="138px" align="center" label="线">
<template slot-scope="scope">
<el-tag v-if="scope.row.route_type === 10" type="border-card">境内路线</el-tag>
<el-tag v-if="scope.row.route_type === 20" type="success">境外路线</el-tag>
<el-tag type="border-card">{{ scope.row.routes }}</el-tag>
</template>
</el-table-column>
</el-table-column> -->
</el-table>
</div>
</template>

View File

@ -38,8 +38,8 @@ module.exports = {
},
proxy: {
'/dev-api': { // 接口地址 以 api开头的都走下面的配置
// 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: '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
ws: true, // 是否支持 websocket 请求 支持
changeOrigin: true, // 是否启用跨域
pathRewrite: {

View File

@ -4,6 +4,7 @@ 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;
@ -184,6 +185,10 @@ class AdminController extends base
$list[$adminId]['id'] = $adminId;
$list[$adminId]['username'] = $user->username;
$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));

View File

@ -4,10 +4,12 @@ require_once(__DIR__.'/xlsxwriter.class.php');
use app\model\Admins;
use app\model\Backs;
use app\model\Blacks;
use app\model\Follows;
use app\model\Orders;
use app\model\Logs;
use app\model\ThirdMobileLogs;
use app\server\SMS;
use app\server\ThirdApiService;
use app\server\Orders as ServerOrders;
use stdClass;
@ -389,6 +391,40 @@ class OrderController extends base
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) {
$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;
use app\common\Error;
use app\model\Admins;
use app\model\Admins as AdminsModel;
use app\model\Blacks;
use app\model\FilterMobiles;
use app\model\Finances as FinancesModel;
use app\model\LiveRoomWorks;
use app\model\Orders as OrdersModel;
use app\model\Products;
use support\Log;
use support\Redis;
class Orders {
protected static $redisPool = [];
public static function isDaishiyong(OrdersModel $order): bool
{
// 根据 OrdersModel::AllOssStatusSql[1] 进行判断
@ -237,4 +241,116 @@ class Orders {
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