464 lines
15 KiB
PHP
Executable File
464 lines
15 KiB
PHP
Executable File
<?php
|
||
|
||
namespace app\admin\controller;
|
||
|
||
use think\Db;
|
||
use think\Request;
|
||
use think\Session;
|
||
|
||
/**
|
||
* 客服管理控制器
|
||
* Class Chat
|
||
* @package app\admin\controller
|
||
*/
|
||
class Chat extends Common
|
||
{
|
||
/**
|
||
* 客服工作台
|
||
*/
|
||
public function index()
|
||
{
|
||
$user_info = Session::get('user_info');
|
||
$admin_id = $user_info['id'];
|
||
|
||
// 从数据库获取最新的login_token(避免session中没有的情况)
|
||
$admin = Db::name('admin')->where('id', $admin_id)->field('login_token')->find();
|
||
$login_token = $admin['login_token'] ?? '';
|
||
|
||
// 如果没有login_token,生成一个新的
|
||
if (empty($login_token)) {
|
||
$login_token = md5($admin_id . time() . uniqid());
|
||
Db::name('admin')->where('id', $admin_id)->update(['login_token' => $login_token]);
|
||
}
|
||
|
||
// 获取客服配置
|
||
$admin_status = Db::name('chat_admin_status')->where('admin_id', $admin_id)->find();
|
||
if (!$admin_status) {
|
||
// 创建默认配置
|
||
Db::name('chat_admin_status')->insert([
|
||
'admin_id' => $admin_id,
|
||
'max_sessions' => 10,
|
||
'is_enabled' => 1,
|
||
'create_time' => time(),
|
||
'update_time' => time(),
|
||
]);
|
||
$admin_status = ['max_sessions' => 10, 'is_enabled' => 1];
|
||
}
|
||
|
||
$this->assign('admin_status', $admin_status);
|
||
$this->assign('admin_id', $admin_id);
|
||
$this->assign('login_token', $login_token);
|
||
|
||
return $this->fetch();
|
||
}
|
||
|
||
/**
|
||
* 获取会话列表 (AJAX)
|
||
*/
|
||
public function sessions()
|
||
{
|
||
$user_info = Session::get('user_info');
|
||
$admin_id = $user_info['id'];
|
||
$status = Request::instance()->get('status', 1); // 默认进行中
|
||
|
||
$where = ['admin_id' => $admin_id];
|
||
if ($status !== 'all') {
|
||
$where['status'] = (int)$status;
|
||
}
|
||
|
||
$sessions = Db::name('chat_session')
|
||
->where($where)
|
||
->order('last_msg_time desc, create_time desc')
|
||
->select();
|
||
|
||
foreach ($sessions as &$session) {
|
||
// 获取用户信息
|
||
$user = Db::name('user')->where('id', $session['user_id'])->find();
|
||
$session['user_info'] = [
|
||
'username' => $user['username'] ?? '',
|
||
'nickname' => $user['nickname'] ?? $user['username'] ?? '',
|
||
'money' => $user['money'] ?? 0,
|
||
];
|
||
|
||
// 未读消息数
|
||
$session['unread_count'] = Db::name('chat_message')
|
||
->where('session_id', $session['id'])
|
||
->where('sender_type', 1) // 用户发送
|
||
->where('status', '<', 3) // 未读
|
||
->count();
|
||
|
||
// 格式化时间
|
||
$session['create_time_fmt'] = date('Y-m-d H:i:s', $session['create_time']);
|
||
$session['last_msg_time_fmt'] = $session['last_msg_time'] ? date('H:i', $session['last_msg_time']) : '';
|
||
|
||
// 来源
|
||
$sourceMap = [1 => 'PC', 2 => 'Game', 3 => 'Portal'];
|
||
$session['source_name'] = $sourceMap[$session['source']] ?? 'Unknown';
|
||
}
|
||
|
||
return json(['code' => 0, 'data' => $sessions]);
|
||
}
|
||
|
||
/**
|
||
* 获取待分配会话列表 (AJAX)
|
||
*/
|
||
public function pending()
|
||
{
|
||
$sessions = Db::name('chat_session')
|
||
->where('status', 0) // 待分配
|
||
->order('create_time asc')
|
||
->select();
|
||
|
||
foreach ($sessions as &$session) {
|
||
$user = Db::name('user')->where('id', $session['user_id'])->find();
|
||
$session['user_info'] = [
|
||
'username' => $user['username'] ?? '',
|
||
'nickname' => $user['nickname'] ?? $user['username'] ?? '',
|
||
'money' => $user['money'] ?? 0,
|
||
];
|
||
$session['create_time_fmt'] = date('Y-m-d H:i:s', $session['create_time']);
|
||
$sourceMap = [1 => 'PC', 2 => 'Game', 3 => 'Portal'];
|
||
$session['source_name'] = $sourceMap[$session['source']] ?? 'Unknown';
|
||
}
|
||
|
||
return json(['code' => 0, 'data' => $sessions]);
|
||
}
|
||
|
||
/**
|
||
* 获取消息历史 (AJAX)
|
||
*/
|
||
public function messages()
|
||
{
|
||
$session_id = Request::instance()->get('session_id');
|
||
$last_id = Request::instance()->get('last_id', 0);
|
||
$limit = Request::instance()->get('limit', 50);
|
||
|
||
if (!$session_id) {
|
||
return json(['code' => 1, 'msg' => '参数错误']);
|
||
}
|
||
|
||
$query = Db::name('chat_message')->where('session_id', $session_id);
|
||
if ($last_id > 0) {
|
||
$query->where('id', '<', $last_id);
|
||
}
|
||
|
||
$messages = $query->order('id desc')->limit($limit)->select();
|
||
$messages = array_reverse($messages); // 按时间正序
|
||
|
||
foreach ($messages as &$msg) {
|
||
$msg['create_time_fmt'] = date('H:i:s', $msg['create_time']);
|
||
}
|
||
|
||
return json(['code' => 0, 'data' => $messages]);
|
||
}
|
||
|
||
/**
|
||
* 结束会话 (AJAX)
|
||
*/
|
||
public function endSession()
|
||
{
|
||
$session_id = Request::instance()->post('session_id');
|
||
$user_info = Session::get('user_info');
|
||
|
||
if (!$session_id) {
|
||
return json(['code' => 1, 'msg' => '参数错误']);
|
||
}
|
||
|
||
$session = Db::name('chat_session')->where('id', $session_id)->find();
|
||
if (!$session || $session['admin_id'] != $user_info['id']) {
|
||
return json(['code' => 1, 'msg' => '无权操作']);
|
||
}
|
||
|
||
Db::name('chat_session')->where('id', $session_id)->update([
|
||
'status' => 2,
|
||
'end_time' => time(),
|
||
'update_time' => time(),
|
||
]);
|
||
|
||
insertAdminLog('结束客服会话', '会话ID: ' . $session_id);
|
||
|
||
return json(['code' => 0, 'msg' => '操作成功']);
|
||
}
|
||
|
||
/**
|
||
* 手动接入会话 (AJAX)
|
||
*/
|
||
public function acceptSession()
|
||
{
|
||
$session_id = Request::instance()->post('session_id');
|
||
$user_info = Session::get('user_info');
|
||
$admin_id = $user_info['id'];
|
||
|
||
if (!$session_id) {
|
||
return json(['code' => 1, 'msg' => '参数错误']);
|
||
}
|
||
|
||
// 检查会话是否存在且状态为待分配
|
||
$session = Db::name('chat_session')->where('id', $session_id)->find();
|
||
if (!$session) {
|
||
return json(['code' => 1, 'msg' => '会话不存在']);
|
||
}
|
||
if ($session['status'] != 0) {
|
||
return json(['code' => 1, 'msg' => '该会话已被接入']);
|
||
}
|
||
|
||
// 检查客服当前会话数是否已满
|
||
$admin_status = Db::name('chat_admin_status')->where('admin_id', $admin_id)->find();
|
||
$max_sessions = $admin_status['max_sessions'] ?? 10;
|
||
$current_sessions = Db::name('chat_session')
|
||
->where('admin_id', $admin_id)
|
||
->where('status', 1)
|
||
->count();
|
||
|
||
if ($current_sessions >= $max_sessions) {
|
||
return json(['code' => 1, 'msg' => '已达最大会话数限制(' . $max_sessions . ')']);
|
||
}
|
||
|
||
// 接入会话
|
||
$result = Db::name('chat_session')->where('id', $session_id)->where('status', 0)->update([
|
||
'admin_id' => $admin_id,
|
||
'status' => 1,
|
||
'update_time' => time(),
|
||
]);
|
||
|
||
if ($result) {
|
||
insertAdminLog('接入客服会话', '会话ID: ' . $session_id);
|
||
return json(['code' => 0, 'msg' => '接入成功']);
|
||
} else {
|
||
return json(['code' => 1, 'msg' => '接入失败,可能已被其他客服接入']);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 转接会话 (AJAX)
|
||
*/
|
||
public function transfer()
|
||
{
|
||
$session_id = Request::instance()->post('session_id');
|
||
$target_admin_id = Request::instance()->post('target_admin_id');
|
||
$user_info = Session::get('user_info');
|
||
|
||
if (!$session_id || !$target_admin_id) {
|
||
return json(['code' => 1, 'msg' => '参数错误']);
|
||
}
|
||
|
||
$session = Db::name('chat_session')->where('id', $session_id)->find();
|
||
if (!$session || $session['admin_id'] != $user_info['id']) {
|
||
return json(['code' => 1, 'msg' => '无权操作']);
|
||
}
|
||
|
||
Db::name('chat_session')->where('id', $session_id)->update([
|
||
'admin_id' => $target_admin_id,
|
||
'update_time' => time(),
|
||
]);
|
||
|
||
insertAdminLog('转接客服会话', '会话ID: ' . $session_id . ', 转接给: ' . $target_admin_id);
|
||
|
||
return json(['code' => 0, 'msg' => '转接成功']);
|
||
}
|
||
|
||
/**
|
||
* 获取在线客服列表 (AJAX)
|
||
*/
|
||
public function onlineAgents()
|
||
{
|
||
$user_info = Session::get('user_info');
|
||
|
||
// 获取启用客服功能的管理员
|
||
$agents = Db::name('chat_admin_status')
|
||
->alias('s')
|
||
->join('admin a', 'a.id = s.admin_id')
|
||
->where('s.is_enabled', 1)
|
||
->where('a.id', '<>', $user_info['id'])
|
||
->field('a.id, a.admin as username, a.admin as nickname, s.max_sessions')
|
||
->select();
|
||
|
||
return json(['code' => 0, 'data' => $agents]);
|
||
}
|
||
|
||
/**
|
||
* 聊天记录查询
|
||
*/
|
||
public function record()
|
||
{
|
||
$get = Request::instance()->get();
|
||
$this->assign('get', $get);
|
||
|
||
$username = Request::instance()->get('username');
|
||
$admin_id = Request::instance()->get('admin_id');
|
||
$startDate = Request::instance()->get('startDate');
|
||
$endDate = Request::instance()->get('endDate');
|
||
$export = Request::instance()->get('export');
|
||
|
||
$where = [];
|
||
$startTime = 0;
|
||
$endTime = time();
|
||
|
||
if ($startDate) $startTime = strtotime($startDate);
|
||
if ($endDate) $endTime = strtotime($endDate) + 86400;
|
||
$where['s.create_time'] = ['between', [$startTime, $endTime]];
|
||
|
||
if ($admin_id) {
|
||
$where['s.admin_id'] = $admin_id;
|
||
}
|
||
|
||
$query = Db::name('chat_session')
|
||
->alias('s')
|
||
->join('user u', 'u.id = s.user_id', 'left')
|
||
->join('admin a', 'a.id = s.admin_id', 'left')
|
||
->where($where);
|
||
|
||
if ($username) {
|
||
$query->where('u.username', 'like', "%{$username}%");
|
||
}
|
||
|
||
if ($export == 1) {
|
||
$list = $query->field('s.*, u.username, u.nickname as user_nickname, a.admin as admin_username')
|
||
->order('s.create_time desc')
|
||
->select();
|
||
|
||
$excelData = [];
|
||
foreach ($list as $k => $v) {
|
||
$excelData[$k][0] = $v['id'];
|
||
$excelData[$k][1] = $v['username'];
|
||
$excelData[$k][2] = $v['admin_username'] ?? '未分配';
|
||
$excelData[$k][3] = ['PC', 'Game', 'Portal'][$v['source'] - 1] ?? '';
|
||
$excelData[$k][4] = ['待分配', '进行中', '已结束'][$v['status']] ?? '';
|
||
$excelData[$k][5] = $v['rating'] ?? '-';
|
||
$excelData[$k][6] = date('Y-m-d H:i:s', $v['create_time']);
|
||
$excelData[$k][7] = $v['end_time'] ? date('Y-m-d H:i:s', $v['end_time']) : '-';
|
||
}
|
||
$title = ['会话ID', '用户名', '客服', '来源', '状态', '评分', '开始时间', '结束时间'];
|
||
$this->exportExcelCore($excelData, '聊天记录-' . date('Ymd'), $title);
|
||
exit;
|
||
}
|
||
|
||
$list = $query->field('s.*, u.username, u.nickname as user_nickname, a.admin as admin_username')
|
||
->order('s.create_time desc')
|
||
->paginate(15, false, ['query' => $get]);
|
||
|
||
// 获取客服列表
|
||
$admins = Db::name('admin')->where('status', 1)->field('id, admin as username, admin as nickname')->select();
|
||
|
||
$this->assign('list', $list);
|
||
$this->assign('admins', $admins);
|
||
|
||
return $this->fetch();
|
||
}
|
||
|
||
/**
|
||
* 查看会话详情
|
||
*/
|
||
public function detail()
|
||
{
|
||
$session_id = Request::instance()->get('session_id');
|
||
|
||
$session = Db::name('chat_session')
|
||
->alias('s')
|
||
->join('user u', 'u.id = s.user_id', 'left')
|
||
->join('admin a', 'a.id = s.admin_id', 'left')
|
||
->where('s.id', $session_id)
|
||
->field('s.*, u.username, u.nickname as user_nickname, u.money, a.admin as admin_username')
|
||
->find();
|
||
|
||
if (!$session) {
|
||
$this->error('会话不存在');
|
||
}
|
||
|
||
$messages = Db::name('chat_message')
|
||
->where('session_id', $session_id)
|
||
->order('id asc')
|
||
->select();
|
||
|
||
$this->assign('session', $session);
|
||
$this->assign('messages', $messages);
|
||
|
||
return $this->fetch();
|
||
}
|
||
|
||
/**
|
||
* 客服统计
|
||
*/
|
||
public function stats()
|
||
{
|
||
$user_info = Session::get('user_info');
|
||
$admin_id = Request::instance()->get('admin_id', $user_info['id']);
|
||
$startDate = Request::instance()->get('startDate', date('Y-m-d', strtotime('-7 days')));
|
||
$endDate = Request::instance()->get('endDate', date('Y-m-d'));
|
||
|
||
$startTime = strtotime($startDate);
|
||
$endTime = strtotime($endDate) + 86400;
|
||
|
||
$where = [
|
||
'admin_id' => $admin_id,
|
||
'create_time' => ['between', [$startTime, $endTime]],
|
||
];
|
||
|
||
// 统计数据
|
||
$stats = [
|
||
'total_sessions' => Db::name('chat_session')->where($where)->count(),
|
||
'ended_sessions' => Db::name('chat_session')->where($where)->where('status', 2)->count(),
|
||
'avg_rating' => Db::name('chat_session')->where($where)->whereNotNull('rating')->avg('rating') ?: 0,
|
||
'total_messages' => Db::name('chat_message')
|
||
->alias('m')
|
||
->join('chat_session s', 's.id = m.session_id')
|
||
->where('s.admin_id', $admin_id)
|
||
->where('m.create_time', 'between', [$startTime, $endTime])
|
||
->count(),
|
||
];
|
||
|
||
$this->assign('stats', $stats);
|
||
$this->assign('startDate', $startDate);
|
||
$this->assign('endDate', $endDate);
|
||
|
||
return $this->fetch();
|
||
}
|
||
|
||
/**
|
||
* 获取快捷回复列表 (AJAX)
|
||
*/
|
||
public function quickReplies()
|
||
{
|
||
$category = Request::instance()->get('category');
|
||
|
||
$query = Db::name('chat_quick_reply')->where('status', 1);
|
||
if ($category) {
|
||
$query->where('category', $category);
|
||
}
|
||
|
||
$list = $query->order('sort asc, id asc')->select();
|
||
$categories = Db::name('chat_quick_reply')
|
||
->where('status', 1)
|
||
->whereNotNull('category')
|
||
->where('category', '<>', '')
|
||
->group('category')
|
||
->column('category');
|
||
|
||
return json(['code' => 0, 'data' => $list, 'categories' => $categories]);
|
||
}
|
||
|
||
/**
|
||
* 图片上传
|
||
*/
|
||
public function upload()
|
||
{
|
||
$file = Request::instance()->file('image');
|
||
if (!$file) {
|
||
return json(['code' => 1, 'msg' => '请选择文件']);
|
||
}
|
||
|
||
// 验证文件
|
||
$info = $file->validate([
|
||
'size' => 2097152, // 2MB
|
||
'ext' => 'jpg,png,gif,jpeg'
|
||
])->move(ROOT_PATH . 'public/uploads/chat/' . date('Ymd'));
|
||
|
||
if ($info) {
|
||
$url = '/uploads/chat/' . date('Ymd') . '/' . $info->getSaveName();
|
||
return json(['code' => 0, 'url' => $url]);
|
||
} else {
|
||
return json(['code' => 1, 'msg' => $file->getError()]);
|
||
}
|
||
}
|
||
}
|