<!DOCTYPE html>
|
<html lang="zh-CN">
|
<head>
|
<meta charset="UTF-8">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<title>AI客服回复管理</title>
|
<script src="https://cdn.tailwindcss.com"></script>
|
<style>
|
.slide-in {
|
animation: slideIn 0.3s ease-out;
|
}
|
@keyframes slideIn {
|
from {
|
transform: translateY(-10px);
|
opacity: 0;
|
}
|
to {
|
transform: translateY(0);
|
opacity: 1;
|
}
|
}
|
</style>
|
</head>
|
<body class="bg-gradient-to-br from-blue-50 to-indigo-100 min-h-screen">
|
<div class="container mx-auto px-4 py-8">
|
<!-- 顶部标题 -->
|
<div class="mb-8">
|
<h1 class="text-4xl font-bold text-indigo-900">AI客服回复管理</h1>
|
<p style="margin-top:20px;">使用{{客服}}代替@客服</p>
|
</div>
|
|
|
<!-- 类型Tab -->
|
<div class="bg-white rounded-t-2xl shadow-xl overflow-x-auto">
|
<div id="typeTabs" class="flex border-b border-gray-200 min-w-max">
|
<!-- 动态生成Tab -->
|
</div>
|
</div>
|
|
<!-- 内容区域 -->
|
<div class="bg-white rounded-b-2xl shadow-xl p-8">
|
<!-- 内容列表 -->
|
<div id="contentList" class="space-y-4 mb-6">
|
<!-- 动态生成内容项 -->
|
</div>
|
|
<!-- 新内容输入区域 -->
|
<div id="newContentArea"></div>
|
|
<!-- 底部添加按钮 -->
|
<div class="mt-6">
|
<button id="addBottomBtn" class="bg-green-600 hover:bg-green-700 text-white font-semibold px-6 py-3 rounded-lg shadow-lg transition-all duration-300 hover:shadow-xl">
|
+ 添加新内容
|
</button>
|
</div>
|
</div>
|
</div>
|
|
<script>
|
const API_BASE = 'http://8.134.184.104:9676/api/v1';
|
let typesData = [];
|
let contentsData = [];
|
let currentTypeId = null;
|
let editingRowId = null; // 用于标识正在编辑的行
|
|
// 初始化
|
async function init() {
|
await loadTypes();
|
await loadContents();
|
}
|
|
// 加载类型数据
|
async function loadTypes() {
|
try {
|
const response = await fetch(`${API_BASE}/type/get`, {
|
method: 'GET',
|
mode: 'cors',
|
headers: {
|
'Content-Type': 'application/json',
|
}
|
});
|
const data = await response.json();
|
typesData = data.data || [];
|
if (typesData.length > 0 && !currentTypeId) {
|
currentTypeId = typesData[0].id;
|
}
|
renderTypeTabs();
|
} catch (error) {
|
console.error('加载类型失败:', error);
|
alert('加载类型失败: ' + error.message + '\n\n请确保后端API已启用CORS');
|
}
|
}
|
|
// 渲染类型Tab
|
function renderTypeTabs() {
|
const tabs = document.getElementById('typeTabs');
|
tabs.innerHTML = '';
|
typesData.forEach(type => {
|
const tab = document.createElement('div');
|
tab.className = `px-6 py-4 cursor-pointer font-semibold transition-all duration-300 ${
|
currentTypeId === type.id
|
? 'bg-indigo-600 text-white'
|
: 'text-gray-600 hover:bg-gray-100'
|
}`;
|
tab.textContent = type.name;
|
tab.onclick = () => {
|
currentTypeId = type.id;
|
renderTypeTabs();
|
renderContentsList();
|
};
|
tabs.appendChild(tab);
|
});
|
}
|
|
// 加载内容数据
|
async function loadContents() {
|
try {
|
const response = await fetch(`${API_BASE}/content/get`, {
|
method: 'GET',
|
mode: 'cors',
|
headers: {
|
'Content-Type': 'application/json',
|
}
|
});
|
const data = await response.json();
|
contentsData = data.data || [];
|
renderContentsList();
|
} catch (error) {
|
console.error('加载内容失败:', error);
|
alert('加载内容失败: ' + error.message + '\n\n请确保后端API已启用CORS');
|
}
|
}
|
|
// 渲染内容列表
|
function renderContentsList() {
|
const list = document.getElementById('contentList');
|
const filteredContents = contentsData.filter(c => c.type === currentTypeId);
|
|
if (filteredContents.length === 0) {
|
list.innerHTML = '<p class="text-gray-500 text-center py-8">暂无内容</p>';
|
return;
|
}
|
|
list.innerHTML = '';
|
filteredContents.forEach(content => {
|
const div = document.createElement('div');
|
div.id = 'content_' + content.id;
|
div.className = 'border-2 border-gray-200 rounded-lg p-6 hover:border-indigo-300 transition-colors slide-in';
|
div.innerHTML = `
|
<div class="flex gap-4 items-center">
|
<div class="flex-1 space-y-2">
|
<div class="flex items-center gap-3">
|
<span class="font-semibold text-gray-700 whitespace-nowrap">问题:</span>
|
<span class="text-gray-900 flex-1">${content.question}</span>
|
</div>
|
<div class="flex items-center gap-3">
|
<span class="font-semibold text-gray-700 whitespace-nowrap">回答:</span>
|
<span class="text-gray-900 flex-1">${content.answer}</span>
|
</div>
|
</div>
|
<div class="flex gap-2">
|
<button class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition-colors whitespace-nowrap"
|
onclick="editContent(${content.id})">编辑</button>
|
<button class="bg-red-500 hover:bg-red-600 text-white px-4 py-2 rounded-lg transition-colors whitespace-nowrap"
|
onclick="deleteContent(${content.id})">删除</button>
|
</div>
|
</div>
|
`;
|
list.appendChild(div);
|
});
|
}
|
|
// 添加新内容行
|
function addNewContentRow() {
|
if (editingRowId !== null) {
|
alert('请先完成当前编辑');
|
return;
|
}
|
|
editingRowId = 'new_' + Date.now();
|
const newContentArea = document.getElementById('newContentArea');
|
|
newContentArea.innerHTML = `
|
<div id="${editingRowId}" class="border-2 border-indigo-400 rounded-lg p-6 bg-indigo-50 slide-in mb-6">
|
<div class="flex gap-4 items-center">
|
<div class="flex-1 space-y-3">
|
<div class="flex items-center gap-3">
|
<label class="font-semibold text-gray-700 whitespace-nowrap">问题:</label>
|
<input type="text" id="newQuestion" class="flex-1 px-4 py-2 border-2 border-gray-300 rounded-lg focus:border-indigo-500 focus:outline-none" placeholder="输入问题">
|
</div>
|
<div class="flex items-center gap-3">
|
<label class="font-semibold text-gray-700 whitespace-nowrap">回答:</label>
|
<input type="text" id="newAnswer" class="flex-1 px-4 py-2 border-2 border-gray-300 rounded-lg focus:border-indigo-500 focus:outline-none" placeholder="输入回答">
|
</div>
|
</div>
|
<div class="flex flex-col gap-2">
|
<button class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition-colors whitespace-nowrap"
|
onclick="confirmAdd()">确认</button>
|
<button class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg transition-colors whitespace-nowrap"
|
onclick="cancelAdd()">取消</button>
|
</div>
|
</div>
|
</div>
|
`;
|
}
|
|
// 确认添加
|
async function confirmAdd() {
|
const question = document.getElementById('newQuestion').value.trim();
|
const answer = document.getElementById('newAnswer').value.trim();
|
|
if (!question || !answer) {
|
alert('请填写问题和回答内容!');
|
return;
|
}
|
|
const newContent = {
|
type: currentTypeId,
|
question: question,
|
answer: answer
|
};
|
|
try {
|
const response = await fetch(`${API_BASE}/content/save`, {
|
method: 'POST',
|
mode: 'cors',
|
headers: {
|
'Content-Type': 'application/json',
|
},
|
body: JSON.stringify([newContent])
|
});
|
|
if (response.ok) {
|
alert('添加成功!');
|
// 清空输入区域
|
document.getElementById('newContentArea').innerHTML = '';
|
editingRowId = null;
|
await loadContents();
|
} else {
|
const errorData = await response.json();
|
alert('添加失败: ' + (errorData.error || '未知错误'));
|
}
|
} catch (error) {
|
console.error('添加内容失败:', error);
|
alert('添加失败: ' + error.message);
|
}
|
}
|
|
// 取消添加
|
function cancelAdd() {
|
document.getElementById('newContentArea').innerHTML = '';
|
editingRowId = null;
|
}
|
|
// 编辑内容
|
function editContent(id) {
|
if (editingRowId !== null) {
|
alert('请先完成当前编辑');
|
return;
|
}
|
|
const content = contentsData.find(c => c.id === id);
|
if (!content) return;
|
|
editingRowId = 'edit_' + id;
|
const contentDiv = document.getElementById('content_' + id);
|
|
contentDiv.innerHTML = `
|
<div class="flex gap-4 items-center">
|
<div class="flex-1 space-y-3">
|
<div class="flex items-center gap-3">
|
<label class="font-semibold text-gray-700 whitespace-nowrap">问题:</label>
|
<input type="text" id="editQuestion_${id}" class="flex-1 px-4 py-2 border-2 border-gray-300 rounded-lg focus:border-indigo-500 focus:outline-none" value="${content.question}">
|
</div>
|
<div class="flex items-center gap-3">
|
<label class="font-semibold text-gray-700 whitespace-nowrap">回答:</label>
|
<input type="text" id="editAnswer_${id}" class="flex-1 px-4 py-2 border-2 border-gray-300 rounded-lg focus:border-indigo-500 focus:outline-none" value="${content.answer}">
|
</div>
|
</div>
|
<div class="flex gap-2">
|
<button class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-lg transition-colors whitespace-nowrap"
|
onclick="confirmEdit(${id})">保存</button>
|
<button class="bg-gray-500 hover:bg-gray-600 text-white px-4 py-2 rounded-lg transition-colors whitespace-nowrap"
|
onclick="cancelEdit()">取消</button>
|
</div>
|
</div>
|
`;
|
contentDiv.className = 'border-2 border-indigo-400 rounded-lg p-6 bg-indigo-50 transition-colors';
|
}
|
|
// 确认编辑
|
async function confirmEdit(id) {
|
const question = document.getElementById('editQuestion_' + id).value.trim();
|
const answer = document.getElementById('editAnswer_' + id).value.trim();
|
|
if (!question || !answer) {
|
alert('请填写问题和回答内容!');
|
return;
|
}
|
|
const updateContent = {
|
id: id,
|
type: currentTypeId,
|
question: question,
|
answer: answer
|
};
|
|
try {
|
const response = await fetch(`${API_BASE}/content/save`, {
|
method: 'POST',
|
mode: 'cors',
|
headers: {
|
'Content-Type': 'application/json',
|
},
|
body: JSON.stringify([updateContent])
|
});
|
|
if (response.ok) {
|
alert('保存成功!');
|
editingRowId = null;
|
await loadContents();
|
} else {
|
const errorData = await response.json();
|
alert('保存失败: ' + (errorData.error || '未知错误'));
|
}
|
} catch (error) {
|
console.error('保存失败:', error);
|
alert('保存失败: ' + error.message);
|
}
|
}
|
|
// 取消编辑
|
function cancelEdit() {
|
editingRowId = null;
|
renderContentsList();
|
}
|
|
// 删除内容
|
async function deleteContent(id) {
|
if (!confirm('确定要删除这条内容吗?')) {
|
return;
|
}
|
|
try {
|
const response = await fetch(`${API_BASE}/content/delete`, {
|
method: 'DELETE',
|
mode: 'cors',
|
headers: {
|
'Content-Type': 'application/json',
|
},
|
body: JSON.stringify({ id: id })
|
});
|
|
const result = await response.json();
|
|
if (response.ok && result.message) {
|
alert('删除成功!');
|
// 从本地数据中移除
|
contentsData = contentsData.filter(c => c.id !== id);
|
renderContentsList();
|
} else {
|
alert('删除失败: ' + (result.error || '未知错误'));
|
}
|
} catch (error) {
|
console.error('删除失败:', error);
|
alert('删除失败: ' + error.message);
|
}
|
}
|
|
// 添加按钮事件
|
document.getElementById('addBottomBtn').addEventListener('click', addNewContentRow);
|
|
// 页面加载时初始化
|
init();
|
</script>
|
</body>
|
</html>
|