chore(aikefu): 更新UI: 会话管理

This commit is contained in:
2025-12-08 11:05:01 +08:00
parent 79e6f6ebd7
commit d3a86b2900
3 changed files with 531 additions and 7 deletions

View File

@@ -24,11 +24,20 @@ class Kefu extends BaseShop
public function index() public function index()
{ {
$kefu_config_model = new KefuConfigModel(); $kefu_config_model = new KefuConfigModel();
$config_info = $kefu_config_model->getConfig($this->site_id, $this->app_module)['data']['value'] ?? []; $config_info = $kefu_config_model->getConfig($this->site_id, $this->app_module)["data"]["value"] ?? [];
$this->assign("config_info", $config_info); $this->assign("config_info", $config_info);
return $this->fetch("kefu/index"); return $this->fetch("kefu/index");
} }
/**
* 合并会话和消息页面
* @return mixed
*/
public function combined()
{
return $this->fetch("kefu/combined");
}
/** /**
* 智能客服配置页 * 智能客服配置页
* @return \think\response\View|\think\response\Json * @return \think\response\View|\think\response\Json

View File

@@ -0,0 +1,519 @@
<style>
.search-box {
padding: 15px;
border-bottom: 1px solid #eee;
margin-bottom: 15px;
background-color: #f9f9f9;
border-radius: 4px;
}
.layui-btn-container {
margin-bottom: 15px;
text-align: right;
}
.layui-btn-sm {
margin-left: 5px;
}
.status-active {
color: #52c41a;
}
.status-inactive {
color: #faad14;
}
/* 合并页面布局 */
.combined-container {
display: flex;
height: calc(100vh - 120px);
gap: 20px;
padding: 0 15px;
}
/* 左侧会话列表区域 */
.conversation-section {
flex: 1;
min-width: 500px;
border: 1px solid #e6e6e6;
border-radius: 8px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.conversation-header {
padding: 15px;
background-color: #f9f9f9;
border-bottom: 1px solid #e6e6e6;
}
.conversation-list-container {
flex: 1;
overflow-y: auto;
}
/* 右侧消息详情区域 */
.message-section {
flex: 1;
min-width: 500px;
border: 1px solid #e6e6e6;
border-radius: 8px;
overflow: hidden;
display: flex;
flex-direction: column;
}
.message-header {
padding: 15px;
background-color: #f9f9f9;
border-bottom: 1px solid #e6e6e6;
}
.message-content-container {
flex: 1;
overflow-y: auto;
padding: 20px;
}
/* 消息样式 */
.message-list {
max-height: 600px;
overflow-y: auto;
padding: 20px;
background-color: #f5f7fa;
border-radius: 8px;
margin-bottom: 20px;
border: 1px solid #e6e6e6;
}
.message-item {
margin-bottom: 20px;
display: flex;
align-items: flex-start;
}
.message-item.user {
justify-content: flex-end;
}
.message-item.assistant {
justify-content: flex-start;
}
.message-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
margin: 0 10px;
}
.message-content {
max-width: 70%;
padding: 12px 16px;
border-radius: 18px;
word-wrap: break-word;
line-height: 1.5;
}
.message-item.user .message-content {
background-color: #1E9FFF;
color: white;
border-bottom-right-radius: 4px;
}
.message-item.assistant .message-content {
background-color: white;
color: #333;
border-bottom-left-radius: 4px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.message-time {
font-size: 12px;
color: #909399;
margin-top: 5px;
text-align: center;
}
.message-role {
font-size: 12px;
color: #909399;
margin-bottom: 5px;
}
/* 会话信息 */
.conversation-info {
margin-bottom: 20px;
padding: 15px;
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
border: 1px solid #e6e6e6;
}
.conversation-info h3 {
margin: 0 0 10px 0;
font-size: 16px;
color: #333;
font-weight: bold;
}
.conversation-info p {
margin: 5px 0;
font-size: 14px;
color: #606266;
}
/* 无选择提示 */
.no-selection {
display: flex;
align-items: center;
justify-content: center;
height: 100%;
color: #909399;
font-size: 16px;
}
</style>
<div class="layui-card-body">
<!-- 搜索区域 -->
<div class="search-box layui-form">
<div class="layui-form-item" style="margin-bottom: 0;">
<div class="layui-inline">
<label class="layui-form-label">用户ID</label>
<div class="layui-input-inline" style="width: 150px;">
<input type="text" name="user_id" id="user_id" placeholder="请输入用户ID" class="layui-input">
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label">状态</label>
<div class="layui-input-inline" style="width: 120px;">
<select name="status" id="status" class="layui-select">
<option value="">全部</option>
<option value="1">活跃</option>
<option value="0">已结束</option>
</select>
</div>
</div>
<div class="layui-inline">
<button type="button" class="layui-btn layui-btn-primary" id="searchBtn">搜索</button>
<button type="button" class="layui-btn layui-btn-primary" id="resetBtn">重置</button>
</div>
</div>
</div>
<!-- 合并容器 -->
<div class="combined-container">
<!-- 左侧会话列表 -->
<div class="conversation-section">
<div class="conversation-header">
<h3>会话列表</h3>
</div>
<div class="conversation-list-container">
<table class="layui-table" id="conversationTable" lay-filter="conversationTable"></table>
</div>
</div>
<!-- 右侧消息详情 -->
<div class="message-section">
<div class="message-header">
<h3>消息详情</h3>
</div>
<div class="message-content-container">
<!-- 初始无选择状态 -->
<div id="noSelection" class="no-selection">
请选择一个会话查看消息详情
</div>
<!-- 会话信息 -->
<div id="conversationInfo" class="conversation-info" style="display: none;">
<!-- 会话信息将通过JavaScript动态加载 -->
</div>
<!-- 消息列表 -->
<div id="messageList" class="message-list" style="display: none;">
<!-- 消息列表将通过JavaScript动态加载 -->
</div>
<!-- 分页 -->
<div class="layui-fixbar" id="pagination"></div>
</div>
</div>
</div>
</div>
<script type="text/html" id="toolbarDemo">
<div class="layui-btn-container">
<!-- 可以在这里添加按钮 -->
</div>
</script>
<script type="text/html" id="barDemo">
<a class="layui-btn layui-btn-xs" lay-event="view">查看消息</a>
<a class="layui-btn layui-btn-xs layui-btn-warning" lay-event="end">结束会话</a>
<a class="layui-btn layui-btn-xs layui-btn-danger" lay-event="delete">删除</a>
</script>
<script>
layui.use(['table', 'form', 'layer', 'laypage'], function() {
var table = layui.table;
var form = layui.form;
var layer = layui.layer;
var laypage = layui.laypage;
// 分页参数
var page = 1;
var limit = 50;
var total = 0;
var currentConversationId = '';
// 渲染会话列表表格
var tableIns = table.render({
elem: '#conversationTable',
url: ns.url("aikefu://shop/kefu/getConversationList"),
method: 'POST',
toolbar: '#toolbarDemo',
defaultToolbar: ['filter', 'exports', 'print'],
title: '会话管理',
cols: [[
{field: 'id', title: 'ID', width: 80, align: 'center', fixed: 'left'},
{field: 'conversation_id', title: '会话ID', width: 200, align: 'center'},
{field: 'user_id', title: '用户ID', width: 150, align: 'center'},
{field: 'name', title: '会话名称', width: 180, align: 'center'},
{field: 'status', title: '状态', width: 100, align: 'center' },
{field: 'create_time', title: '创建时间', width: 180, align: 'center'},
{field: 'update_time', title: '更新时间', width: 180, align: 'center'},
{fixed: 'right', title: '操作', width: 200, align: 'center', toolbar: '#barDemo'}
]],
page: true,
limit: 10,
limits: [10, 20, 30, 50, 100],
height: 'full-200',
text: {
none: '暂无会话数据'
}
});
// 搜索按钮点击事件
$('#searchBtn').click(function() {
// 执行搜索
tableIns.reload({
page: {
curr: 1
},
where: {
user_id: $('#user_id').val(),
status: $('#status').val()
}
});
});
// 重置按钮点击事件
$('#resetBtn').click(function() {
$('#user_id').val('');
$('#status').val('');
form.render('select');
// 执行重置后的搜索
tableIns.reload({
page: {
curr: 1
},
where: {
user_id: '',
status: ''
}
});
});
// 加载会话信息
function loadConversationInfo(conversation_id) {
$.ajax({
url: ns.url("aikefu://shop/kefu/getConversationInfo"),
type: 'POST',
data: {
conversation_id: conversation_id
},
dataType: 'json',
success: function(res) {
if (res.code === 0) {
var info = res.data;
var html = '<h3>会话详情</h3>';
html += '<p><strong>会话ID</strong>' + info.conversation_id + '</p>';
html += '<p><strong>用户ID</strong>' + info.user_id + '</p>';
html += '<p><strong>会话名称:</strong>' + info.name + '</p>';
html += '<p><strong>状态:</strong>' + (info.status === 1 ? '<span class="status-active">活跃</span>' : '<span class="status-inactive">已结束</span>') + '</p>';
html += '<p><strong>创建时间:</strong>' + info.create_time + '</p>';
html += '<p><strong>更新时间:</strong>' + info.update_time + '</p>';
$('#conversationInfo').html(html);
$('#conversationInfo').show();
} else {
$('#conversationInfo').html('<h3>会话详情</h3><p>未找到会话信息</p>');
$('#conversationInfo').show();
}
},
error: function() {
$('#conversationInfo').html('<h3>会话详情</h3><p>加载会话信息失败</p>');
$('#conversationInfo').show();
}
});
}
// 加载消息列表
function loadMessageList(conversation_id, pageNum = 1) {
$.ajax({
url: ns.url("aikefu://shop/kefu/getMessageList"),
type: 'POST',
data: {
page: pageNum,
limit: limit,
conversation_id: conversation_id
},
dataType: 'json',
success: function(res) {
if (res.code === 0) {
var list = res.data.list;
total = res.data.total;
var html = '';
if (list.length > 0) {
list.forEach(function(item) {
var role = item.role === 'user' ? '用户' : '机器人';
var roleClass = item.role === 'user' ? 'user' : 'assistant';
var avatar = item.role === 'user' ? '/__STATIC__/admin/img/user.png' : '/__STATIC__/admin/img/robot.png';
html += '<div class="message-item ' + roleClass + '">';
if (item.role === 'assistant') {
html += '<img src="' + avatar + '" class="message-avatar">';
}
html += '<div>';
html += '<div class="message-role">' + role + '</div>';
html += '<div class="message-content">' + item.content + '</div>';
html += '<div class="message-time">' + item.create_time + '</div>';
html += '</div>';
if (item.role === 'user') {
html += '<img src="' + avatar + '" class="message-avatar">';
}
html += '</div>';
});
} else {
html += '<div style="text-align: center; padding: 50px; color: #999;">暂无消息记录</div>';
}
$('#messageList').html(html);
$('#messageList').show();
// 滚动到底部
$('#messageList').scrollTop($('#messageList')[0].scrollHeight);
// 渲染分页
renderPagination();
} else {
layer.msg('加载失败:' + res.message, {icon: 2});
}
},
error: function() {
layer.msg('请求失败,请稍后重试', {icon: 2});
}
});
}
// 渲染分页
function renderPagination() {
if (total <= limit) {
$('#pagination').html('');
return;
}
laypage.render({
elem: 'pagination',
count: total,
limit: limit,
curr: page,
layout: ['prev', 'page', 'next', 'count', 'skip'],
jump: function(obj, first) {
if (!first) {
page = obj.curr;
loadMessageList(currentConversationId, page);
}
}
});
}
// 显示消息详情区域
function showMessageSection() {
$('#noSelection').hide();
$('#conversationInfo').show();
$('#messageList').show();
}
// 监听行工具事件
table.on('tool(conversationTable)', function(obj) {
var data = obj.data;
var layEvent = obj.event;
if (layEvent === 'view') {
// 查看消息
currentConversationId = data.conversation_id;
page = 1;
showMessageSection();
loadConversationInfo(currentConversationId);
loadMessageList(currentConversationId);
} else if (layEvent === 'end') {
// 结束会话
layer.confirm('确定要结束该会话吗?', function(index) {
$.ajax({
url: ns.url("aikefu://shop/kefu/endConversation"),
type: 'POST',
data: {id: data.id},
dataType: 'json',
success: function(res) {
if (res.code === 0) {
layer.msg('会话已结束', {icon: 1});
// 重新加载表格数据
tableIns.reload();
// 如果当前查看的是这个会话,更新状态
if (currentConversationId === data.conversation_id) {
loadConversationInfo(currentConversationId);
}
} else {
layer.msg('操作失败:' + res.message, {icon: 2});
}
},
error: function() {
layer.msg('请求失败,请稍后重试', {icon: 2});
}
});
layer.close(index);
});
} else if (layEvent === 'delete') {
// 删除会话
layer.confirm('确定要删除该会话吗?删除后将无法恢复', function(index) {
$.ajax({
url: ns.url("aikefu://shop/kefu/deleteConversation"),
type: 'POST',
data: {id: data.id},
dataType: 'json',
success: function(res) {
if (res.code === 0) {
layer.msg('会话已删除', {icon: 1});
// 重新加载表格数据
tableIns.reload();
// 如果当前查看的是这个会话,清空消息区域
if (currentConversationId === data.conversation_id) {
currentConversationId = '';
$('#noSelection').show();
$('#conversationInfo').hide();
$('#messageList').hide();
$('#pagination').html('');
}
} else {
layer.msg('操作失败:' + res.message, {icon: 2});
}
},
error: function() {
layer.msg('请求失败,请稍后重试', {icon: 2});
}
});
layer.close(index);
});
}
});
});
</script>

View File

@@ -4,18 +4,14 @@
<div class="layui-tab layui-tab-brief" lay-filter="kefu-tab"> <div class="layui-tab layui-tab-brief" lay-filter="kefu-tab">
<ul class="layui-tab-title"> <ul class="layui-tab-title">
<li class="layui-this" lay-id="config">配置</li> <li class="layui-this" lay-id="config">配置</li>
<li lay-id="conversation">会话</li> <li lay-id="conversation">会话管理</li>
<li lay-id="message">消息</li>
</ul> </ul>
<div class="layui-tab-content"> <div class="layui-tab-content">
<div class="layui-tab-item layui-show"> <div class="layui-tab-item layui-show">
{include file="kefu/config" /} {include file="kefu/config" /}
</div> </div>
<div class="layui-tab-item"> <div class="layui-tab-item">
{include file="kefu/conversation" /} {include file="kefu/combined" /}
</div>
<div class="layui-tab-item">
{include file="kefu/message" /}
</div> </div>
</div> </div>
</div> </div>