添加群呼监听
This commit is contained in:
parent
e89372b761
commit
9916633d88
|
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Call;
|
||||
use App\Models\Task;
|
||||
use App\Service\Callcenter;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
|
||||
class SwooleCallcenterRun extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'swoole:callcenter:run';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = '群呼运行';
|
||||
|
||||
/**
|
||||
* Create a new command instance.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*
|
||||
* @return mixed
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$redis_key = config('freeswitch.redis_key.callcenter_task');
|
||||
Redis::del($redis_key);
|
||||
//服务启动或重启时检测是否有已启动的任务
|
||||
$res = Task::query()->where(['status'=>2])->select(['id'])->get();
|
||||
if ($res->isNotEmpty()) {
|
||||
foreach ($res as $key => $task) {
|
||||
Redis::rPush($redis_key,$task->id);
|
||||
}
|
||||
}
|
||||
\Swoole\Coroutine\run(function () use ($redis_key) {
|
||||
//启动服务
|
||||
while (true) {
|
||||
$task_id = Redis::lPop($redis_key);
|
||||
if ($task_id == null) {
|
||||
sleep(10);
|
||||
continue;
|
||||
}
|
||||
\Swoole\Coroutine::create(function () use ($task_id){
|
||||
while (true){
|
||||
$task = Task::with(['queue.sips','gateway'])
|
||||
->where('status',2)
|
||||
->where('id',$task_id)
|
||||
->first();
|
||||
//检测是否有启动的任务
|
||||
if ($task == null ){
|
||||
break;
|
||||
}
|
||||
|
||||
//检测执行日期
|
||||
$now_date = strtotime(date('Y-m-d'));
|
||||
if ( $now_date < strtotime($task->date_start) || $now_date > strtotime($task->date_end) ) {
|
||||
//延迟10秒
|
||||
sleep(10);
|
||||
Log::info("任务ID:".$task->id."运行日期不满足");
|
||||
continue;
|
||||
}
|
||||
|
||||
//检测执行时间
|
||||
$now_time = strtotime(date('H:i:s'));
|
||||
if ( $now_time < strtotime($task->time_start) || $now_time > strtotime($task->time_end) ) {
|
||||
//延迟10秒
|
||||
sleep(10);
|
||||
Log::info("任务ID:".$task->id."运行时间不满足");
|
||||
continue;
|
||||
}
|
||||
|
||||
//检测网关信息
|
||||
if ($task->gateway==null){
|
||||
Log::info("任务ID:".$task->id." 的网关不存在,任务停止");
|
||||
$task->update(['status'=>1]);
|
||||
break;
|
||||
}
|
||||
|
||||
//检测队列
|
||||
if ($task->queue==null){
|
||||
Log::info("任务ID:".$task->id." 的队列不存在,任务停止");
|
||||
$task->update(['status'=>1]);
|
||||
break;
|
||||
}
|
||||
//检测队列是否有坐席
|
||||
if ($task->queue->sips->isEmpty()){
|
||||
Log::info("任务ID:".$task->id." 的队列无坐席存在,任务停止");
|
||||
$task->update(['status'=>1]);
|
||||
break;
|
||||
}
|
||||
//并发数调节
|
||||
$channel = 0;
|
||||
$members = 0;
|
||||
foreach ($task->queue->sips as $sip){
|
||||
if ($sip->status==1 && $sip->state=='down'){
|
||||
$members++;
|
||||
}
|
||||
}
|
||||
if ($members === 0){
|
||||
Log::info("任务ID:".$task->name." 无空闲坐席,sleep:1秒");
|
||||
sleep(1);
|
||||
continue;
|
||||
}else{
|
||||
if ($task->max_channel==0){
|
||||
$channel = $members;
|
||||
}else{
|
||||
$channel = $task->max_channel > $members ? $members : $task->max_channel;
|
||||
}
|
||||
}
|
||||
|
||||
//如果通道数还是0,则不需要呼叫
|
||||
if ($channel == 0) {
|
||||
Log::info("任务ID:".$task->name." 的并发不需要呼叫");
|
||||
sleep(10);
|
||||
continue;
|
||||
}
|
||||
|
||||
//进行呼叫
|
||||
$calls = Call::where('task_id',$task->id)->where('status',1)->orderBy('id','asc')->take($channel)->get();
|
||||
if ($calls->isEmpty()){
|
||||
Log::info("任务:".$task->name."已完成");
|
||||
$task->update(['status'=>3]);
|
||||
break;
|
||||
}
|
||||
foreach ($calls as $call){
|
||||
go(function () use ($call,$task){
|
||||
(new Callcenter($call,$task))->run();
|
||||
});
|
||||
sleep(2);
|
||||
}
|
||||
sleep(6);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -50,6 +50,7 @@ class SwooleHttp extends Command
|
|||
'directory' => '/usr/local/freeswitch/conf/directory/default/',
|
||||
//拨号计划下面默认分为default(呼出)和public(呼入)
|
||||
'dialplan' => '/usr/local/freeswitch/conf/dialplan/',
|
||||
'callcenter' => '/usr/local/freeswitch/conf/autoload_configs/callcenter.conf.xml'
|
||||
];
|
||||
$http->on('request', function ($request, $response) use ($conf) {
|
||||
if($request->server['request_method'] == 'POST'){
|
||||
|
|
@ -137,7 +138,60 @@ class SwooleHttp extends Command
|
|||
exec($command."\""."reloadxml"."\"");
|
||||
$return = ['code'=>0,'msg'=>'拨号计划更新成功'];
|
||||
break;
|
||||
case '/callcenter':
|
||||
$xml = "<configuration name=\"callcenter.conf\" description=\"CallCenter\">\n";
|
||||
$xml .= "\t<settings>\n";
|
||||
$xml .= "\t\t<!--<param name=\"odbc-dsn\" value=\"dsn:user:pass\"/>-->\n";
|
||||
$xml .= "\t\t<!--<param name=\"dbname\" value=\"/dev/shm/callcenter.db\"/>-->\n";
|
||||
$xml .= "\t\t<!--<param name=\"cc-instance-id\" value=\"single_box\"/>-->\n";
|
||||
$xml .= "\t\t<param name=\"truncate-tiers-on-load\" value=\"true\"/>\n";
|
||||
$xml .= "\t\t<param name=\"truncate-agents-on-load\" value=\"true\"/>\n";
|
||||
$xml .= "\t</settings>\n";
|
||||
//---------------------------------- 写入队列信息 ------------------------------------
|
||||
$xml .= "\t<queues>\n";
|
||||
foreach ($data['queues'] as $queue){
|
||||
$xml .= "\t\t<queue name=\"queue".$queue['id']."\">\n";
|
||||
$xml .= "\t\t\t<param name=\"strategy\" value=\"".$queue['strategy']."\"/>\n";
|
||||
$xml .= "\t\t\t<param name=\"moh-sound\" value=\"\$\${hold_music}\"/>\n";
|
||||
//$xml .= "\t\t\t<param name=\"record-template\" value=\"\$\${recordings_dir}/\${strftime(%Y)}/\${strftime(%m)}/\${strftime(%d)}/.\${destination_number}.\${caller_id_number}.\${uuid}.wav\"/>\n";
|
||||
$xml .= "\t\t\t<param name=\"time-base-score\" value=\"system\"/>\n";
|
||||
$xml .= "\t\t\t<param name=\"max-wait-time\" value=\"".$queue['max_wait_time']."\"/>\n";
|
||||
$xml .= "\t\t\t<param name=\"max-wait-time-with-no-agent\" value=\"0\"/>\n";
|
||||
$xml .= "\t\t\t<param name=\"max-wait-time-with-no-agent-time-reached\" value=\"5\"/>\n";
|
||||
$xml .= "\t\t\t<param name=\"tier-rules-apply\" value=\"false\"/>\n";
|
||||
$xml .= "\t\t\t<param name=\"tier-rule-wait-second\" value=\"300\"/>\n";
|
||||
$xml .= "\t\t\t<param name=\"tier-rule-wait-multiply-level\" value=\"true\"/>\n";
|
||||
$xml .= "\t\t\t<param name=\"tier-rule-no-agent-no-wait\" value=\"false\"/>\n";
|
||||
$xml .= "\t\t\t<param name=\"discard-abandoned-after\" value=\"60\"/>\n";
|
||||
$xml .= "\t\t\t<param name=\"abandoned-resume-allowed\" value=\"false\"/>\n";
|
||||
$xml .= "\t\t</queue>\n";
|
||||
}
|
||||
$xml .= "\t</queues>\n";
|
||||
|
||||
//---------------------------------- 写入坐席信息 ------------------------------------
|
||||
$xml .= "\t<agents>\n";
|
||||
foreach ($data['agents'] as $agent){
|
||||
$contact = "[leg_timeout=10]user/".$agent['username'];
|
||||
$xml .= "\t\t<agent name=\"agent".$agent['id']."\" type=\"callback\" contact=\"".$contact."\" status=\"".$agent['status']."\" max-no-answer=\"0\" wrap-up-time=\"10\" reject-delay-time=\"0\" busy-delay-time=\"0\" no-answer-delay-time=\"0\" />\n";
|
||||
}
|
||||
$xml .= "\t</agents>\n";
|
||||
|
||||
//---------------------------------- 写入队列-坐席信息 ------------------------------------
|
||||
$xml .= "\t<tiers>\n";
|
||||
foreach ($data['queues'] as $queue){
|
||||
if (isset($queue['agents'])&&!empty($queue['agents'])) {
|
||||
foreach ($queue['agents'] as $agent){
|
||||
$xml .= "\t\t<tier agent=\"agent".$agent['id']."\" queue=\"queue".$queue['id']."\" level=\"1\" position=\"1\"/>\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
$xml .= "\t</tiers>\n";
|
||||
$xml .= "</configuration>\n";
|
||||
//生成配置文件
|
||||
file_put_contents($conf['callcenter'],$xml);
|
||||
exec($command."\""."reload mod_callcenter"."\"");
|
||||
$return = ['code'=>0,'msg'=>'分机更新成功'];
|
||||
break;
|
||||
case '/favicon.ico':
|
||||
$response->status(404);
|
||||
$response->end();
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@
|
|||
namespace App\Http\Controllers\Callcenter;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Sip;
|
||||
use GuzzleHttp\Client;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
|
@ -42,6 +43,7 @@ class QueueController extends Controller
|
|||
'name' => $data['name'],
|
||||
'strategy' => $data['strategy'],
|
||||
'max_wait_time' => $data['max_wait_time'],
|
||||
'created_at' => date('Y-m-d H:i:s'),
|
||||
]);
|
||||
foreach ($data['sips'] as $sipId){
|
||||
DB::table('queue_sip')->insert([
|
||||
|
|
@ -108,11 +110,16 @@ class QueueController extends Controller
|
|||
public function updateXml()
|
||||
{
|
||||
$queues = Queue::with('sips')->get()->toArray();
|
||||
$sipIds = DB::table('queue_sip')->pluck('sip_id')->toArray();
|
||||
$agents = Sip::query()->whereIn('id',$sipIds)->get()->toArray();
|
||||
try{
|
||||
$client = new Client();
|
||||
$client->post(config('freeswitch.swoole_http_url.callcenter'),
|
||||
[
|
||||
'json' => $queues,
|
||||
'json' => [
|
||||
'queues' => $queues,
|
||||
'agents' => $agents,
|
||||
],
|
||||
'timeout' => 30
|
||||
]
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Callcenter;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use Illuminate\Http\Request;
|
||||
|
||||
class Task extends Controller
|
||||
{
|
||||
//
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Callcenter;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Imports\CallImport;
|
||||
use App\Models\Call;
|
||||
use App\Models\Gateway;
|
||||
use App\Models\Queue;
|
||||
use App\Models\Task;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Illuminate\Support\Facades\View;
|
||||
use Maatwebsite\Excel\Facades\Excel;
|
||||
|
||||
class TaskController extends Controller
|
||||
{
|
||||
|
||||
public function index(Request $request)
|
||||
{
|
||||
if ($request->ajax()){
|
||||
$res = Task::query()->withCount('calls')->orderByDesc('id')->paginate($request->get('limit', 30));
|
||||
return $this->success('ok',$res->items(),$res->total());
|
||||
}
|
||||
return View::make('callcenter.task.index');
|
||||
}
|
||||
|
||||
public function create()
|
||||
{
|
||||
$queues = Queue::query()->orderByDesc('id')->get();
|
||||
$gateways = Gateway::query()->orderByDesc('id')->get();
|
||||
return View::make('callcenter.task.create',compact('queues','gateways'));
|
||||
}
|
||||
|
||||
public function store(Request $request)
|
||||
{
|
||||
$data = $request->all([
|
||||
'name',
|
||||
'date_start',
|
||||
'date_end',
|
||||
'time_start',
|
||||
'time_end',
|
||||
'gateway_id',
|
||||
'queue_id',
|
||||
'max_channel',
|
||||
]);
|
||||
try {
|
||||
Task::create($data);
|
||||
return $this->success();
|
||||
}catch (\Exception $exception){
|
||||
Log::error('添加任务异常:'.$exception->getMessage());
|
||||
return $this->error();
|
||||
}
|
||||
}
|
||||
|
||||
public function edit($id)
|
||||
{
|
||||
$model = Task::query()->findOrFail($id);
|
||||
$queues = Queue::query()->orderByDesc('id')->get();
|
||||
$gateways = Gateway::query()->orderByDesc('id')->get();
|
||||
return View::make('callcenter.task.edit',compact('model', 'queues', 'gateways'));
|
||||
}
|
||||
|
||||
public function update(Request $request,$id)
|
||||
{
|
||||
$data = $request->all([
|
||||
'name',
|
||||
'date_start',
|
||||
'date_end',
|
||||
'time_start',
|
||||
'time_end',
|
||||
'gateway_id',
|
||||
'queue_id',
|
||||
'max_channel',
|
||||
]);
|
||||
$model = Task::query()->findOrFail($id);
|
||||
try {
|
||||
$model->update($data);
|
||||
return $this->success();
|
||||
}catch (\Exception $exception){
|
||||
Log::error('更新任务异常:'.$exception->getMessage());
|
||||
return $this->error();
|
||||
}
|
||||
}
|
||||
|
||||
public function destroy(Request $request)
|
||||
{
|
||||
$ids = $request->get('ids');
|
||||
if (empty($ids)){
|
||||
return $this->error('请选择删除项');
|
||||
}
|
||||
DB::beginTransaction();
|
||||
try {
|
||||
Task::destroy($ids);
|
||||
|
||||
DB::commit();
|
||||
return $this->success();
|
||||
}catch (\Exception $exception){
|
||||
DB::rollBack();
|
||||
Log::error('删除群呼任务异常:'.$exception->getMessage());
|
||||
return $this->error();
|
||||
}
|
||||
}
|
||||
|
||||
public function show(Request $request,$id)
|
||||
{
|
||||
$task = Task::query()->withCount(['calls','hasCalls','missCalls','successCalls','failCalls'])->findOrFail($id);
|
||||
$percent = $task->calls_count>0?100*round(($task->has_calls_count)/($task->calls_count),4).'%':'0.00%';
|
||||
if ($request->isMethod('post')){
|
||||
$tiers = DB::table('queue_agent')->where('queue_id',$task->queue_id)->pluck('agent_id');
|
||||
return response()->json(['code'=>0, 'msg'=>'请求成功']);
|
||||
}
|
||||
return view('callcenter.task.show',compact('task','percent'));
|
||||
}
|
||||
|
||||
public function setStatus(Request $request)
|
||||
{
|
||||
$ids = $request->get('ids',[]);
|
||||
if (count($ids)!=1){
|
||||
return $this->error('请选择一条记录');
|
||||
}
|
||||
$task = Task::query()->withCount('calls')->find($ids[0]);
|
||||
if ($task==null){
|
||||
return $this->error('任务不存在');
|
||||
}
|
||||
if ($task->status==3){
|
||||
return $this->error('任务已完成,禁止操作');
|
||||
}
|
||||
$status = $request->get('status',1);
|
||||
|
||||
if ($status==2&&$task->calls_count==0){
|
||||
return $this->error('任务未导入号码,禁止操作');
|
||||
}
|
||||
if ($status==1&&$task->status!=2){
|
||||
return $this->error('任务未启动,禁止操作');
|
||||
}
|
||||
try {
|
||||
$task->update(['status'=>$status]);
|
||||
$key = config('freeswitch.redis_key.callcenter_task');
|
||||
Redis::rPush($key,$task->id);
|
||||
return $this->success();
|
||||
}catch (\Exception $exception){
|
||||
Log::error('设置任务状态异常:'.$exception->getMessage());
|
||||
return $this->error();
|
||||
}
|
||||
}
|
||||
|
||||
public function importCall(Request $request, $id)
|
||||
{
|
||||
$model = Task::query()->findOrFail($id);
|
||||
if ($request->ajax()){
|
||||
$file = $request->input('file');
|
||||
if ($file == null){
|
||||
return $this->error('请先上传文件');
|
||||
}
|
||||
$xlsFile = public_path().$file;
|
||||
try{
|
||||
Excel::import(new CallImport($id), $xlsFile);
|
||||
return $this->success('导入成功');
|
||||
}catch (\Exception $exception){
|
||||
Log::error('导入失败:'.$exception->getMessage());
|
||||
return $this->error('导入失败');
|
||||
}
|
||||
}
|
||||
return View::make('callcenter.task.import',compact('model'));
|
||||
}
|
||||
|
||||
public function calls(Request $request)
|
||||
{
|
||||
$data = $request->all(['task_id','phone']);
|
||||
$res = Call::query()
|
||||
->when($data['phone'],function ($q) use($data){
|
||||
return $q->where('phone','like','%'.$data['phone'].'%');
|
||||
})
|
||||
->where('task_id',$data['task_id'])
|
||||
->orderBy('id','asc')
|
||||
->paginate($request->get('limit', 30));
|
||||
foreach ($res->items() as $item){
|
||||
$item->status_name = Arr::get(config('freeswitch.callcenter_call_status'),$item->status,'-');
|
||||
}
|
||||
return $this->success('ok',$res->items(),$res->total());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
<?php
|
||||
|
||||
namespace App\Imports;
|
||||
|
||||
use App\Models\Call;
|
||||
use Illuminate\Support\Collection;
|
||||
use Maatwebsite\Excel\Concerns\ToCollection;
|
||||
use Maatwebsite\Excel\Concerns\ToModel;
|
||||
|
||||
class CallImport implements ToModel
|
||||
{
|
||||
|
||||
public $task_id;
|
||||
|
||||
public function __construct($task_id)
|
||||
{
|
||||
$this->task_id = $task_id;
|
||||
}
|
||||
|
||||
public function model(array $row)
|
||||
{
|
||||
if (!isset($row[0]) || !preg_match('/\d{7,11}/',$row[0]) ) {
|
||||
return null;
|
||||
}
|
||||
return new Call([
|
||||
'task_id' => $this->task_id,
|
||||
'phone' => $row[0],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class Call extends Model
|
||||
{
|
||||
protected $table = 'task_call';
|
||||
protected $guarded = ['id'];
|
||||
|
||||
public function sip()
|
||||
{
|
||||
return $this->hasOne('App\Models\Sip','id','sip_id');
|
||||
}
|
||||
|
||||
public function user()
|
||||
{
|
||||
return $this->hasOne('App\Models\User','id','user_id');
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -8,4 +8,67 @@ class Task extends Model
|
|||
{
|
||||
protected $table = 'task';
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected $appends = ['gateway_name','queue_name','date','time'];
|
||||
|
||||
public function gateway()
|
||||
{
|
||||
return $this->hasOne('App\Models\Gateway','id','gateway_id');
|
||||
}
|
||||
|
||||
public function queue()
|
||||
{
|
||||
return $this->hasOne('App\Models\Queue','id','queue_id');
|
||||
}
|
||||
|
||||
public function getGatewayNameAttribute()
|
||||
{
|
||||
return $this->attributes['gateway_name'] = $this->gateway->name;
|
||||
}
|
||||
|
||||
public function getQueueNameAttribute()
|
||||
{
|
||||
return $this->attributes['queue_name'] = $this->queue->name;
|
||||
}
|
||||
|
||||
public function getDateAttribute()
|
||||
{
|
||||
return $this->attributes['date'] = $this->date_start . ' / '. $this->date_end;
|
||||
}
|
||||
|
||||
public function getTimeAttribute()
|
||||
{
|
||||
return $this->attributes['time'] = $this->time_start . ' - '. $this->time_end;
|
||||
}
|
||||
|
||||
//总呼叫数
|
||||
public function calls()
|
||||
{
|
||||
return $this->hasMany('App\Models\Call','task_id','id');
|
||||
}
|
||||
|
||||
//已呼叫数 status !=1
|
||||
public function hasCalls()
|
||||
{
|
||||
return $this->hasMany('App\Models\Call','task_id','id')->where('status','!=',1);
|
||||
}
|
||||
|
||||
//漏接数 status=3
|
||||
public function missCalls()
|
||||
{
|
||||
return $this->hasMany('App\Models\Call','task_id','id')->where('status',3);
|
||||
}
|
||||
|
||||
//呼叫成功数 status=4
|
||||
public function successCalls()
|
||||
{
|
||||
return $this->hasMany('App\Models\Call','task_id','id')->where('status',4);
|
||||
}
|
||||
|
||||
//呼叫失败数 status=2
|
||||
public function failCalls()
|
||||
{
|
||||
return $this->hasMany('App\Models\Call','task_id','id')->where('status',2);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,123 @@
|
|||
<?php
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use App\Models\Call;
|
||||
use App\Models\Cdr;
|
||||
use App\Models\Gateway;
|
||||
use App\Models\Sip;
|
||||
use App\Models\Task;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Illuminate\Support\Facades\Redis;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class Callcenter
|
||||
{
|
||||
//通话记录对象
|
||||
public $call;
|
||||
public $task;
|
||||
public $fs;
|
||||
|
||||
public function __construct($call, $task)
|
||||
{
|
||||
$this->call = $call;
|
||||
$this->fs = new SwooleFreeswitch();
|
||||
if (!$this->fs->connect()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public function run()
|
||||
{
|
||||
$record_url = config('freeswitch.record_url');
|
||||
$fs_dir = '/usr/local/freeswitch';
|
||||
|
||||
$uuid = uuid_generate();
|
||||
//更新为正在呼叫
|
||||
$this->call->update([
|
||||
'status' => 2,
|
||||
'uuid' => $uuid,
|
||||
'datetime_originate_phone' => date('Y-m-d H:i:s')
|
||||
]);
|
||||
Log::info("更新号码: " . $this->call->phone . " 状态为:2");
|
||||
|
||||
$phone = $this->task->gateway->prefix ? $this->task->gateway->prefix . $this->call->phone : $this->call->phone;
|
||||
$varStr = "{origination_uuid=" . $uuid . "}";
|
||||
$varStr .= "{ignore_early_media=true}";
|
||||
$varStr .= "{effective_caller_id_number=" . $this->call->phone . "}";
|
||||
$varStr .= "{effective_caller_id_name=" . $this->call->phone . "}";
|
||||
if ($this->task->gateway->outbound_caller_id) {
|
||||
$varStr .= "{origination_caller_id_number=" . $this->task->gateway->outbound_caller_id . "}";
|
||||
$varStr .= "{origination_caller_id_name=" . $this->task->gateway->outbound_caller_id . "}";
|
||||
}
|
||||
$varStr .= "{cc_export_vars=effective_caller_id_number,effective_caller_id_name}";
|
||||
$dail_string = "originate " . $varStr . "sofia/gateway/gw" . $this->task->gateway->id . "/" . $phone . " &callcenter(queue" . $this->task->queue->id . ")";
|
||||
Log::info("呼叫:" . $dail_string);
|
||||
|
||||
$this->fs->bgapi($dail_string);
|
||||
$this->fs->events("CUSTOM callcenter::info");
|
||||
$this->fs->filteruuid($this->call->uuid);
|
||||
|
||||
while (true) {
|
||||
$received_parameters = $this->fs->recvEvent();
|
||||
if (!empty($received_parameters)) {
|
||||
$json = $this->fs->serialize($received_parameters);
|
||||
$action = Arr::get($json, "CC-Action");
|
||||
$uuid = Arr::get($json, "CC-Member-Session-UUID");
|
||||
switch ($action) {
|
||||
//呼叫进入队列
|
||||
case 'member-queue-start':
|
||||
$this->call->update([
|
||||
'datetime_entry_queue' => date('Y-m-d H:i:s'),
|
||||
'status' => 3
|
||||
]);
|
||||
break;
|
||||
// 坐席应答
|
||||
case 'bridge-agent-start':
|
||||
$agent_name = Arr::get($json, "CC-Agent");
|
||||
$id = (int)Str::after($agent_name, 'agent');
|
||||
$filepath = $fs_dir . '/recordings/' . date('Y/m/d/');
|
||||
$file = $filepath . 'callcenter_' . $uuid . '.wav';
|
||||
$this->fs->bgapi("uuid_record " . $uuid . " start " . $file . " 1800");
|
||||
$this->call->update([
|
||||
'datetime_agent_answered' => date('Y-m-d H:i:s'),
|
||||
'status' => 4,
|
||||
'agent_id' => $id,
|
||||
'record_file' => str_replace($fs_dir, $record_url, $file),
|
||||
]);
|
||||
break;
|
||||
//坐席结束
|
||||
case 'bridge-agent-end':
|
||||
$this->call->update([
|
||||
'datetime_end' => date('Y-m-d H:i:s'),
|
||||
'status' => 4
|
||||
]);
|
||||
break;
|
||||
//桥接结束,通话结束
|
||||
case 'member-queue-end':
|
||||
$cause = Arr::get($json, "CC-Cause");
|
||||
$answered_time = Arr::get($json, "CC-Agent-Answered-Time");
|
||||
$leaving_time = Arr::get($json, "CC-Member-Leaving-Time");
|
||||
if ($cause == 'Cancel') {
|
||||
$billsec = 0;
|
||||
} else {
|
||||
if ($leaving_time && $answered_time) {
|
||||
$billsec = $leaving_time - $answered_time > 0 ? $leaving_time - $answered_time : 0;
|
||||
} else {
|
||||
$billsec = 0;
|
||||
}
|
||||
}
|
||||
$this->call->update([
|
||||
'billsec' => $billsec,
|
||||
]);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->fs->disconnect();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -31,6 +31,8 @@ return [
|
|||
'directory' => 'http://127.0.0.1:9501/directory',
|
||||
//生成拨号计划
|
||||
'dialplan' => 'http://127.0.0.1:9501/dialplan',
|
||||
//生成群呼
|
||||
'callcenter' => 'http://127.0.0.1:9501/callcenter',
|
||||
],
|
||||
|
||||
'esl' => [
|
||||
|
|
@ -42,6 +44,7 @@ return [
|
|||
'redis_key' => [
|
||||
'dial' => 'dial_uuid_queue',
|
||||
'api' => 'api_exec_queue',
|
||||
'callcenter_task' => 'callcenter_task_queue',
|
||||
],
|
||||
'record_url' => env('APP_URL','http://localhost'),
|
||||
'host' => env('FS_HOST','127.0.0.1'),
|
||||
|
|
@ -85,4 +88,12 @@ return [
|
|||
4 => '微信',
|
||||
5 => '其它',
|
||||
],
|
||||
|
||||
//群呼状态
|
||||
'callcenter_call_status' => [
|
||||
1 => '待呼叫',
|
||||
2 => '呼叫失败',
|
||||
3 => '漏接',
|
||||
4 => '成功',
|
||||
],
|
||||
];
|
||||
|
|
|
|||
|
|
@ -18,6 +18,9 @@ class CallcenterTaskCall extends Migration
|
|||
$table->unsignedBigInteger('task_id')->comment('任务ID');
|
||||
$table->string('phone')->comment('待呼叫号码');
|
||||
$table->tinyInteger('status')->default(1)->comment('1-待呼叫,2-呼叫中,3-队列等待,4-已通话');
|
||||
$table->string('uuid')->nullable()->comment('UUID');
|
||||
$table->string('aleg_uuid')->nullable()->comment('客户通话UUID');
|
||||
$table->string('bleg_uuid')->nullable()->comment('坐席通话UUID');
|
||||
$table->string('uuid')->nullable()->comment('客户通话UUID');
|
||||
$table->timestamp('datetime_originate_phone')->nullable()->comment('呼叫时间');
|
||||
$table->timestamp('datetime_entry_queue')->nullable()->comment('进入队列时间');
|
||||
|
|
|
|||
|
|
@ -116,6 +116,14 @@ class MenuTableSeeder extends Seeder
|
|||
'type' => 1,
|
||||
'permission_name' => 'callcenter.queue',
|
||||
],
|
||||
[
|
||||
'name' => '任务管理',
|
||||
'route' => 'callcenter.task',
|
||||
'url' => null,
|
||||
'icon' => 'layui-icon-template-1',
|
||||
'type' => 1,
|
||||
'permission_name' => 'callcenter.task',
|
||||
],
|
||||
]
|
||||
],
|
||||
[
|
||||
|
|
|
|||
|
|
@ -137,6 +137,18 @@ class UserTableSeeder extends Seeder
|
|||
['name' => 'callcenter.queue.updateXml', 'display_name' => '更新配置'],
|
||||
]
|
||||
],
|
||||
[
|
||||
'name' => 'callcenter.task',
|
||||
'display_name' => '任务管理',
|
||||
'child' => [
|
||||
['name' => 'callcenter.task.create', 'display_name' => '添加'],
|
||||
['name' => 'callcenter.task.show', 'display_name' => '详情'],
|
||||
['name' => 'callcenter.task.edit', 'display_name' => '编辑'],
|
||||
['name' => 'callcenter.task.destroy', 'display_name' => '删除'],
|
||||
['name' => 'callcenter.task.importCall', 'display_name' => '导入号码'],
|
||||
['name' => 'callcenter.task.setStatus', 'display_name' => '设置状态'],
|
||||
]
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -1,7 +0,0 @@
|
|||
18908221080
|
||||
13512293513
|
||||
13512293514
|
||||
13512293515
|
||||
13512293516
|
||||
13512293517
|
||||
13512293517
|
||||
|
|
|
@ -0,0 +1,65 @@
|
|||
{{csrf_field()}}
|
||||
<div class="layui-form-item">
|
||||
<label for="" class="layui-form-label">名称</label>
|
||||
<div class="layui-input-inline">
|
||||
<input class="layui-input" type="text" name="name" lay-verify="required" value="{{$model->name??old('name')}}" placeholder="如:任务一">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-inline">
|
||||
<label for="" class="layui-form-label">执行日期</label>
|
||||
<div class="layui-input-inline" style="width: 190px;">
|
||||
<input class="layui-input" type="text" name="date_start" id="date_start" lay-verify="required" value="{{$model->date_start??old('date_start')}}" readonly placeholder="开始日期">
|
||||
</div>
|
||||
<div class="layui-form-mid"> - </div>
|
||||
<div class="layui-input-inline" style="width: 190px;">
|
||||
<input class="layui-input" type="text" name="date_end" id="date_end" lay-verify="required" value="{{$model->date_end??old('date_end')}}" readonly placeholder="结束日期">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-inline">
|
||||
<label for="" class="layui-form-label">执行时间</label>
|
||||
<div class="layui-input-inline" style="width: 190px;">
|
||||
<input class="layui-input" type="text" name="time_start" id="time_start" lay-verify="required" value="{{$model->time_start??old('time_start')}}" readonly placeholder="开始时间">
|
||||
</div>
|
||||
<div class="layui-form-mid"> - </div>
|
||||
<div class="layui-input-inline" style="width: 190px;">
|
||||
<input class="layui-input" type="text" name="time_end" id="time_end" lay-verify="required" value="{{$model->time_end??old('time_end')}}" readonly placeholder="结束时间">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label for="" class="layui-form-label">网关</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name="gateway_id" lay-verify="required">
|
||||
<option value="">请选择</option>
|
||||
@foreach($gateways as $gw)
|
||||
<option value="{{$gw->id}}" @if(isset($model->gateway_id)&&$model->gateway_id==$gw->id) selected @endif >{{$gw->name}}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label for="" class="layui-form-label">队列</label>
|
||||
<div class="layui-input-inline">
|
||||
<select name="queue_id" lay-verify="required">
|
||||
<option value="">请选择</option>
|
||||
@foreach($queues as $queue)
|
||||
<option value="{{$queue->id}}" @if(isset($model->queue_id)&&$model->queue_id==$queue->id) selected @endif>{{$queue->name}}</option>
|
||||
@endforeach
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<label for="" class="layui-form-label">并发数</label>
|
||||
<div class="layui-input-inline">
|
||||
<input class="layui-input" type="number" name="max_channel" lay-verify="required|number" value="{{$model->max_channel??0}}" placeholder="">
|
||||
</div>
|
||||
<div class="layui-word-aux layui-form-mid">最大并发,默认:0 为不限制,系统将自动调节</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<button type="submit" class="layui-btn layui-btn-sm" lay-submit lay-filter="go-close-refresh" >确认</button>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
<script>
|
||||
layui.use(['layer','table','form','laydate'],function () {
|
||||
var layer = layui.layer;
|
||||
var form = layui.form;
|
||||
var table = layui.table;
|
||||
var laydate = layui.laydate;
|
||||
|
||||
laydate.render({elem:'#date_start',type:'date'});
|
||||
laydate.render({elem:'#date_end',type:'date'});
|
||||
laydate.render({elem:'#time_start',type:'time'});
|
||||
laydate.render({elem:'#time_end',type:'time'});
|
||||
|
||||
})
|
||||
</script>
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
@extends('base')
|
||||
|
||||
@section('content')
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-body">
|
||||
<form action="{{route('callcenter.task.store')}}" method="post" class="layui-form">
|
||||
@include('callcenter.task._form')
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('script')
|
||||
@include('callcenter.task._js')
|
||||
@endsection
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
@extends('base')
|
||||
|
||||
@section('content')
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-body">
|
||||
<form action="{{route('callcenter.task.update',['id'=>$model->id])}}" method="post" class="layui-form">
|
||||
{{method_field('put')}}
|
||||
@include('callcenter.task._form')
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('script')
|
||||
@include('callcenter.task._js')
|
||||
@endsection
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
@extends('base')
|
||||
|
||||
@section('content')
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-body">
|
||||
<form action="{{route('callcenter.task.importCall',['id'=>$model->id])}}" method="post" class="layui-form">
|
||||
<div class="layui-form">
|
||||
<div class="layui-form-item">
|
||||
<label for="" class="layui-form-label">文件</label>
|
||||
<div class="layui-input-inline">
|
||||
<button type="button" class="layui-btn layui-btn-sm" id="uploadBtn">
|
||||
<i class="layui-icon"></i>点击选择
|
||||
</button>
|
||||
</div>
|
||||
<div class="layui-word-aux layui-form-mid" id="tips"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-input-block">
|
||||
<input type="hidden" name="file" id="file">
|
||||
<button type="submit" class="layui-btn layui-btn-sm layui-disabled" disabled lay-submit lay-filter="go-close" id="sureBtn" >确认导入</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('script')
|
||||
<script>
|
||||
layui.use(['jquery','layer','table','form','upload'],function () {
|
||||
var $ = layui.jquery;
|
||||
var layer = layui.layer;
|
||||
var form = layui.form;
|
||||
var upload = layui.upload;
|
||||
|
||||
//普通图片上传
|
||||
var uploadInst = upload.render({
|
||||
elem: '#uploadBtn'
|
||||
,url: '{{route('api.upload')}}' //改成您自己的上传接口
|
||||
,accept: 'file'
|
||||
,exts: 'xlsx'
|
||||
,before: function(obj){
|
||||
layer.load()
|
||||
}
|
||||
,done: function(res){
|
||||
layer.closeAll('loading')
|
||||
layer.msg(res.msg,{},function () {
|
||||
if (res.code===0){
|
||||
$("#tips").text(res.data.url)
|
||||
$("#file").val(res.data.url)
|
||||
$("#sureBtn").removeAttr('disabled')
|
||||
$("#sureBtn").removeClass('layui-disabled')
|
||||
}
|
||||
})
|
||||
}
|
||||
,error: function(){
|
||||
layer.msg('上传错误',{icon:2})
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
@endsection
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
@extends('base')
|
||||
|
||||
@section('content')
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header layuiadmin-card-header-auto">
|
||||
<div class="layui-btn-group">
|
||||
@can('callcenter.task.destroy')
|
||||
<button class="layui-btn layui-btn-sm layui-btn-danger" type="button" id="listDelete" data-url="{{route('callcenter.task.destroy')}}">删除</button>
|
||||
@endcan
|
||||
@can('callcenter.task.setStatus')
|
||||
<button class="layui-btn layui-btn-sm layui-btn-danger" type="button" id="setStatus1">停止</button>
|
||||
<button class="layui-btn layui-btn-sm" type="button" id="setStatus2">启动</button>
|
||||
@endcan
|
||||
@can('callcenter.task.create')
|
||||
<button class="layui-btn layui-btn-sm" type="button" id="addBtn">添加</button>
|
||||
@endcan
|
||||
<a class="layui-btn layui-btn-sm" href="/template/calls.xlsx">模板下载</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<table id="dataTable" lay-filter="dataTable"></table>
|
||||
<script type="text/html" id="options">
|
||||
<div class="layui-btn-group">
|
||||
@can('callcenter.task.importCall')
|
||||
<a class="layui-btn layui-btn-sm" lay-event="import">导入号码</a>
|
||||
@endcan
|
||||
@can('callcenter.task.show')
|
||||
<a class="layui-btn layui-btn-sm" lay-event="show">详情</a>
|
||||
@endcan
|
||||
@can('callcenter.task.edit')
|
||||
<a class="layui-btn layui-btn-sm" lay-event="edit">编辑</a>
|
||||
@endcan
|
||||
</div>
|
||||
</script>
|
||||
<script type="text/html" id="status">
|
||||
@{{# if(d.status==1){ }}
|
||||
<span class="layui-badge-dot" style="background-color: red;"></span> 停止
|
||||
@{{# } else if(d.status==2){ }}
|
||||
<span class="layui-badge-dot" style="background-color: green;"></span> 启动
|
||||
@{{# } else if(d.status==3){ }}
|
||||
<span class="layui-badge-dot layui-bg-black"></span> 已完成
|
||||
@{{# } }}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
@section('script')
|
||||
<script>
|
||||
layui.use(['layer','table','form','upload','jquery'],function () {
|
||||
var $ = layui.jquery;
|
||||
var layer = layui.layer;
|
||||
var form = layui.form;
|
||||
var table = layui.table;
|
||||
var upload = layui.upload;
|
||||
//用户表格初始化
|
||||
var dataTable = table.render({
|
||||
elem: '#dataTable'
|
||||
,height: 'full-200'
|
||||
,url: "{{ route('callcenter.task') }}" //数据接口
|
||||
,page: true //开启分页
|
||||
,cols: [[ //表头
|
||||
{checkbox: true,fixed: true}
|
||||
,{field: 'id', title: 'ID', sort: true,width:80}
|
||||
,{field: 'name', title: '名称'}
|
||||
,{field: 'date', title: '执行日期',width: 200}
|
||||
,{field: 'time', title: '执行时间',width: 200}
|
||||
,{field: 'gateway_name', title: '网关'}
|
||||
,{field: 'queue_name', title: '队列'}
|
||||
,{field: 'calls_count', title: '号码数量'}
|
||||
,{field: 'max_channel', title: '并发'}
|
||||
,{field: 'status', title: '状态', toolbar: '#status'}
|
||||
,{field: 'created_at', title: '添加时间',width: 160}
|
||||
,{fixed: 'right', width: 240, align:'center', toolbar: '#options', title:'操作'}
|
||||
]]
|
||||
});
|
||||
|
||||
//监听工具条
|
||||
table.on('tool(dataTable)', function(obj){ //注:tool是工具条事件名,dataTable是table原始容器的属性 lay-filter="对应的值"
|
||||
var data = obj.data //获得当前行数据
|
||||
,layEvent = obj.event; //获得 lay-event 对应的值
|
||||
if(layEvent === 'edit'){
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: "编辑",
|
||||
shadeClose: true,
|
||||
area: ["800px","600px"],
|
||||
content: '/callcenter/task/'+data.id+'/edit',
|
||||
})
|
||||
} else if(layEvent === 'import'){
|
||||
layer.open({
|
||||
type : 2,
|
||||
title : '导入号码',
|
||||
shadeClose : true,
|
||||
area : ['600px','300px'],
|
||||
content : "/callcenter/task/"+data.id+"/importCall"
|
||||
});
|
||||
} else if(layEvent === 'show'){
|
||||
newTab('/callcenter/task/'+data.id+'/show','任务详情')
|
||||
}
|
||||
});
|
||||
|
||||
$("#addBtn").click(function () {
|
||||
layer.open({
|
||||
type: 2,
|
||||
title: "添加",
|
||||
shadeClose: true,
|
||||
area: ["800px","600px"],
|
||||
content: '/callcenter/task/create',
|
||||
})
|
||||
})
|
||||
|
||||
function setStatus(msg,status) {
|
||||
var ids = []
|
||||
var hasCheck = table.checkStatus('dataTable')
|
||||
var hasCheckData = hasCheck.data
|
||||
if (hasCheckData.length>0){
|
||||
$.each(hasCheckData,function (index,element) {
|
||||
ids.push(element.id)
|
||||
})
|
||||
}
|
||||
if (ids.length>0){
|
||||
layer.confirm(msg, function(index){
|
||||
$.post("{{ route('callcenter.task.setStatus') }}",{ids:ids,status:status},function (result) {
|
||||
layer.close(index);
|
||||
var icon = result.code===0?1:2;
|
||||
layer.msg(result.msg,{icon:icon},function () {
|
||||
if (result.code===0){
|
||||
dataTable.reload()
|
||||
}
|
||||
})
|
||||
});
|
||||
})
|
||||
}else {
|
||||
layer.msg('请选择操作项',{icon:2})
|
||||
}
|
||||
}
|
||||
|
||||
//停止
|
||||
$("#setStatus1").click(function () {
|
||||
setStatus('确认停止吗?',1);
|
||||
});
|
||||
//启动
|
||||
$("#setStatus2").click(function () {
|
||||
setStatus('确认启动吗?',2);
|
||||
});
|
||||
|
||||
})
|
||||
</script>
|
||||
@endsection
|
||||
|
|
@ -0,0 +1,206 @@
|
|||
@extends('base')
|
||||
|
||||
@section('content')
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-body">
|
||||
<div class="layui-form">
|
||||
<div class="layui-row">
|
||||
<div class="layui-col-xs4">
|
||||
<div class="layui-form-item">
|
||||
<label for="" class="layui-form-label">任务名称:</label>
|
||||
<div class="layui-form-mid layui-word-aux">{{$task->name}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs4">
|
||||
<div class="layui-form-item">
|
||||
<label for="" class="layui-form-label">执行日期:</label>
|
||||
<div class="layui-form-mid layui-word-aux">{{$task->date}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs4">
|
||||
<div class="layui-form-item">
|
||||
<label for="" class="layui-form-label">执行时间:</label>
|
||||
<div class="layui-form-mid layui-word-aux">{{$task->time}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs4">
|
||||
<div class="layui-form-item">
|
||||
<label for="" class="layui-form-label">网关:</label>
|
||||
<div class="layui-form-mid layui-word-aux">{{$task->gateway_name}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs4">
|
||||
<div class="layui-form-item">
|
||||
<label for="" class="layui-form-label">队列:</label>
|
||||
<div class="layui-form-mid layui-word-aux">{{$task->queue_name}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs4">
|
||||
<div class="layui-form-item">
|
||||
<label for="" class="layui-form-label">并发:</label>
|
||||
<div class="layui-form-mid layui-word-aux">{{$task->max_channel}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs4">
|
||||
<div class="layui-form-item">
|
||||
<label for="" class="layui-form-label">呼叫总数:</label>
|
||||
<div class="layui-form-mid layui-word-aux">{{$task->calls_count}}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs4">
|
||||
<div class="layui-form-item">
|
||||
<label for="" class="layui-form-label">已呼叫:</label>
|
||||
<div class="layui-form-mid layui-word-aux"><span style="color: green">{{$task->has_calls_count}}</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-col-xs4">
|
||||
<div class="layui-form-item">
|
||||
<label for="" class="layui-form-label">进度:</label>
|
||||
<div class="layui-input-inline" style="padding-top:10px">
|
||||
<div class="layui-progress layui-progress-big" lay-showPercent="true">
|
||||
<div class="layui-progress-bar layui-bg-black" lay-percent="{{$percent}}"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header"><b>呼叫图表</b></div>
|
||||
<div class="layui-card-body">
|
||||
<div class="layui-row layui-col-space10">
|
||||
<div class="layui-col-xs6">
|
||||
<div id="result_pie" style="width: 100%;height: 400px"></div>
|
||||
</div>
|
||||
<div class="layui-col-xs6">
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header"><b>呼叫结果</b></div>
|
||||
<div class="layui-card-body">
|
||||
<table class="layui-table" lay-skin="line" lay-size="sm">
|
||||
<thead>
|
||||
<tr><th>状态</th><th>数量</th><th>占比</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>成功</td><td>{{$task->success_calls_count}}</td><td>{{$task->has_calls_count>0?100*round($task->success_calls_count/$task->has_calls_count,4).'%':'0.00%'}}</td></tr>
|
||||
<tr><td>失败</td><td>{{$task->fail_calls_count}}</td><td>{{$task->has_calls_count>0?100*round($task->fail_calls_count/$task->has_calls_count,4).'%':'0.00%'}}</td></tr>
|
||||
<tr><td>漏接</td><td>{{$task->miss_calls_count}}</td><td>{{$task->has_calls_count>0?100*round($task->miss_calls_count/$task->has_calls_count,4).'%':'0.00%'}}</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="layui-card">
|
||||
<div class="layui-card-header layuiadmin-card-header-auto">
|
||||
<b>呼叫记录</b>
|
||||
<form class="layui-form">
|
||||
<div class="layui-form-item">
|
||||
<div class="layui-inline">
|
||||
<label for="" class="layui-form-label">号码</label>
|
||||
<div class="layui-input-inline">
|
||||
<input type="text" name="phone" placeholder="请输入呼叫号码" maxlength="11" class="layui-input">
|
||||
</div>
|
||||
</div>
|
||||
<div class="layui-inline">
|
||||
<div class="layui-input-inline">
|
||||
<button type="button" lay-submit lay-filter="search" class="layui-btn layui-btn-sm">搜索</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="layui-card-body">
|
||||
<table id="dataTable" lay-filter="dataTable"></table>
|
||||
<script type="text/html" id="options">
|
||||
@{{# if(d.billsec>0 && d.record_file){ }}
|
||||
<a class="layui-btn layui-btn-sm" lay-event="play">播放</a>
|
||||
@{{# } }}
|
||||
</script>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
||||
@section('script')
|
||||
<script>
|
||||
layui.config({
|
||||
version: '1535898708509' //为了更新 js 缓存,可忽略
|
||||
}).extend({
|
||||
echarts: 'lib/extend/echarts' ,
|
||||
echartsTheme: 'lib/extend/echartsTheme' ,
|
||||
}).use(['layer','table','form','echarts','echartsTheme'],function () {
|
||||
var $ = layui.jquery;
|
||||
var layer = layui.layer;
|
||||
var form = layui.form;
|
||||
var table = layui.table;
|
||||
var echarts = layui.echarts;
|
||||
var echartsTheme = layui.echartsTheme;
|
||||
|
||||
//呼叫结果
|
||||
var result_pie = echarts.init(document.getElementById('result_pie'),echartsTheme)
|
||||
result_pie.setOption({
|
||||
title: {text: "任务呼出情况", x: "center", textStyle: {fontSize: 14}},
|
||||
tooltip: {trigger: "item", formatter: "{a} <br/>{b} : {c} ({d}%)"},
|
||||
legend: {orient: "vertical", x: "left", data: ["成功", "失败", "漏接"]},
|
||||
series: [{
|
||||
name: "呼出",
|
||||
type: "pie",
|
||||
radius: "55%",
|
||||
center: ["50%", "50%"],
|
||||
data: [
|
||||
{value:{{$task->success_calls_count}},name:'成功'},
|
||||
{value:{{$task->fail_calls_count}},name:'失败'},
|
||||
{value:{{$task->miss_calls_count}},name:'漏接'}
|
||||
]
|
||||
}]
|
||||
});
|
||||
window.onresize = result_pie.resize
|
||||
|
||||
//呼叫记录
|
||||
var dataTable = table.render({
|
||||
elem: '#dataTable'
|
||||
,height: 500
|
||||
,url: "{{ route('callcenter.task.calls',['task_id'=>$task->id]) }}" //数据接口
|
||||
,page: true //开启分页
|
||||
,cols: [[ //表头
|
||||
{field: 'uuid', title: '通话编号'}
|
||||
,{field: 'phone', title: '呼叫号码'}
|
||||
,{field: 'status_name', title: '呼叫状态'}
|
||||
,{field: 'datetime_originate_phone', title: '呼叫时间',width: 200}
|
||||
,{field: 'datetime_entry_queue', title: '入队列时间',width: 200}
|
||||
,{field: 'datetime_agent_answered', title: '坐席接通时间',width: 200}
|
||||
,{field: 'datetime_end', title: '结束时间',width: 200}
|
||||
,{field: 'sip_username', title: '接听坐席'}
|
||||
,{field: 'user_nickname', title: '接听人'}
|
||||
,{field: 'billsec', title: '通话时长(秒)'}
|
||||
,{fixed: 'right', width: 100, align:'center', toolbar: '#options', title:'操作'}
|
||||
]]
|
||||
});
|
||||
|
||||
//监听工具条
|
||||
table.on('tool(dataTable)', function(obj){ //注:tool是工具条事件名,dataTable是table原始容器的属性 lay-filter="对应的值"
|
||||
var data = obj.data //获得当前行数据
|
||||
,layEvent = obj.event; //获得 lay-event 对应的值
|
||||
if (layEvent === 'play'){
|
||||
if (data.billsec>0 && data.record_file) {
|
||||
var _html = '<div style="padding:20px;">';
|
||||
_html += '<audio controls="controls" autoplay src="' + data.record_file + '"></audio>';
|
||||
_html += '</div>';
|
||||
layer.open({
|
||||
title: '播放录音',
|
||||
type: 1,
|
||||
area: ['360px', 'auto'],
|
||||
content: _html
|
||||
})
|
||||
}
|
||||
}
|
||||
});
|
||||
})
|
||||
</script>
|
||||
@endsection
|
||||
|
|
@ -211,6 +211,27 @@ Route::group(['prefix'=>'callcenter','namespace'=>'Callcenter','middleware'=>['a
|
|||
Route::post('queue/updateXml','QueueController@updateXml')->name('callcenter.queue.updateXml')->middleware('permission:callcenter.queue.updateXml');
|
||||
});
|
||||
|
||||
//任务管理
|
||||
Route::group([],function (){
|
||||
Route::get('task','TaskController@index')->name('callcenter.task')->middleware('permission:callcenter.task');
|
||||
//添加
|
||||
Route::get('task/create','TaskController@create')->name('callcenter.task.create')->middleware('permission:callcenter.task.create');
|
||||
Route::post('task/store','TaskController@store')->name('callcenter.task.store')->middleware('permission:callcenter.task.create');
|
||||
//编辑
|
||||
Route::get('task/{id}/edit','TaskController@edit')->name('callcenter.task.edit')->middleware('permission:callcenter.task.edit');
|
||||
Route::put('task/{id}/update','TaskController@update')->name('callcenter.task.update')->middleware('permission:callcenter.task.edit');
|
||||
//删除
|
||||
Route::delete('task/destroy','TaskController@destroy')->name('callcenter.task.destroy')->middleware('permission:callcenter.task.destroy');
|
||||
//详情
|
||||
Route::get('task/{id}/show','TaskController@show')->name('callcenter.task.show')->middleware('permission:callcenter.task.show');
|
||||
//设置状态
|
||||
Route::post('task/setStatus','TaskController@setStatus')->name('callcenter.task.setStatus')->middleware('permission:callcenter.task.setStatus');
|
||||
//导入号码
|
||||
Route::match(['get','post'],'task/{id}/importCall','TaskController@importCall')->name('callcenter.task.importCall')->middleware('permission:callcenter.task.importCall');
|
||||
//呼叫记录
|
||||
Route::get('task/calls','TaskController@calls')->name('callcenter.task.calls');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue