反馈代码
- 默认密码:`admin123`,代码顶部`ADMIN_PASSWORD`变量直接修改密码- 点击【管理员后台】,先进入密码登录页;密码正确才可以查看反馈列表、修改状态、删除记录
- 增加**退出登录**按钮,退出后需要重新输入密码
- 密码错误提示文字
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>意见反馈系统</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#1677ff',
},
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.form-shadow {
box-shadow: 0 2px 12px 0 rgba(0,0,0,0.08);
}
.tab-active{
border-bottom:2px solid #1677ff;
color:#1677ff;
}
.status-pending {
@apply bg-orange-100 text-orange-600 px-2 py-0.5 rounded;
}
.status-doing {
@apply bg-blue-100 text-blue-600 px-2 py-0.5 rounded;
}
.status-done {
@apply bg-green-100 text-green-600 px-2 py-0.5 rounded;
}
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div class="max-w-4xl mx-auto py-10 px-4">
<div class="bg-white rounded-lg p-6 md:p-8 form-shadow">
<div class="text-center mb-6">
<h1 class="text- font-bold text-gray-800">意见反馈系统</h1>
</div>
<!-- 顶层大Tab:用户端 / 管理员 -->
<div class="flex border-b border-gray-200 mb-6">
<div id="tabUser" class="flex-1 py-3 text-center cursor-pointer tab-active font-medium">用户反馈</div>
<div id="tabAdmin" class="flex-1 py-3 text-center cursor-pointer text-gray-500 font-medium">管理员后台</div>
</div>
<!-- ========== 用户面板(提交+查询) ========== -->
<div id="panelUser">
<div class="flex border-b border-gray-200 mb-6">
<div id="tabSubmit" class="flex-1 py-3 text-center cursor-pointer tab-active font-medium">提交反馈</div>
<div id="tabSearch" class="flex-1 py-3 text-center cursor-pointer text-gray-500 font-medium">查询反馈</div>
</div>
<!-- 提交反馈表单 -->
<div id="panelSubmit">
<form id="feedbackForm">
<div class="mb-5">
<label class="block text-gray-700 mb-2">反馈类型 <span class="text-red-500">*</span></label>
<select id="type" class="w-full border border-gray-300 rounded px-3 py-2.5 focus:outline-none focus:border-primary">
<option value="">请选择反馈类型</option>
<option value="bug">功能BUG</option>
<option value="suggest">功能建议</option>
<option value="complain">投诉</option>
<option value="other">其他</option>
</select>
</div>
<div class="mb-5">
<label class="block text-gray-700 mb-2">邮箱</label>
<input type="email" id="email" placeholder="请输入邮箱(用于回复通知)" class="w-full border border-gray-300 rounded px-3 py-2.5 focus:outline-none focus:border-primary">
</div>
<div class="mb-5">
<label class="block text-gray-700 mb-2">反馈内容 <span class="text-red-500">*</span></label>
<textarea id="content" rows="5" placeholder="请详细描述您遇到的问题或建议..." class="w-full border border-gray-300 rounded px-3 py-2.5 focus:outline-none focus:border-primary resize-none"></textarea>
</div>
<button type="submit" class="w-full bg-primary hover:bg-blue-600 text-white py-3 rounded transition-all">提交反馈</button>
</form>
</div>
<!-- 查询反馈面板 -->
<div id="panelSearch" class="hidden">
<div class="mb-5">
<label class="block text-gray-700 mb-2">反馈单号 <span class="text-red-500">*</span></label>
<input type="text" id="searchSn" placeholder="请输入提交反馈后获取的单号" class="w-full border border-gray-300 rounded px-3 py-2.5 focus:outline-none focus:border-primary">
</div>
<button id="searchBtn" class="w-full bg-primary hover:bg-blue-600 text-white py-3 rounded transition-all mb-6">查询</button>
<div id="searchResult" class="hidden border rounded p-4 bg-gray-50">
<h3 class="font-bold text-lg mb-3">反馈详情</h3>
<div class="space-y-2 text-gray-700">
<p>单号:<span id="resSn"></span></p>
<p>类型:<span id="resType"></span></p>
<p>提交时间:<span id="resTime"></span></p>
<p>处理状态:<span id="resStatus"></span></p>
<p>反馈内容:<span id="resContent"></span></p>
</div>
</div>
<div id="searchEmpty" class="hidden text-center text-red-500 py-4">未找到该单号的反馈记录</div>
</div>
</div>
<!-- ========== 管理员登录面板 ========== -->
<div id="panelAdminLogin" class="hidden">
<h3 class="font-bold text-lg mb-4">管理员登录</h3>
<div class="max-w-sm mx-auto">
<div class="mb-5">
<label class="block text-gray-700 mb-2">管理员密码</label>
<input type="password" id="adminPwdInput" placeholder="请输入管理员密码" class="w-full border border-gray-300 rounded px-3 py-2.5 focus:outline-none focus:border-primary">
</div>
<button id="adminLoginBtn" class="w-full bg-primary hover:bg-blue-600 text-white py-3 rounded transition-all">登录后台</button>
<p id="loginError" class="text-red-500 mt-3 text-center hidden">密码错误,请重试</p>
</div>
</div>
<!-- ========== 管理员后台面板(登录成功后显示) ========== -->
<div id="panelAdmin" class="hidden">
<div class="flex justify-between items-center mb-4">
<h3 class="font-bold text-lg">全部反馈列表</h3>
<button id="adminLogoutBtn" class="bg-gray-200 hover:bg-gray-300 px-3 py-1 rounded text-sm">退出登录</button>
</div>
<div id="adminList" class="space-y-4 max-h- overflow-y-auto pr-1">
<!-- JS动态渲染列表 -->
</div>
<div id="adminEmpty" class="text-center text-gray-400 py-8 hidden">暂无反馈记录</div>
</div>
</div>
</div>
<!-- 提交成功弹窗(显示单号+复制按钮) -->
<div id="modal" class="fixed inset-0 bg-black/50 hidden items-center justify-center z-50">
<div class="bg-white p-6 rounded-lg w-80 text-center">
<div class="text-green-500 text-4xl mb-3"><i class="fa fa-check-circle"></i></div>
<h3 class="text-lg font-medium">提交成功</h3>
<p class="text-gray-500 my-2">您的反馈单号:</p>
<p id="snText" class="text-primary font-bold text-xl mb-3"></p>
<button id="copySnBtn" class="px-4 py-2 bg-green-500 hover:bg-green-600 text-white rounded mb-4 w-full">
<i class="fa fa-copy mr-1"></i>复制单号
</button>
<p class="text-gray-400 text-sm mb-4">请保存单号,用于查询处理进度</p>
<button id="closeModal" class="px-5 py-2 bg-primary text-white rounded w-full">确定</button>
</div>
</div>
<script>
// ========= 配置区,修改这里改管理员密码 =========
const ADMIN_PASSWORD = "admin123";
// 顶层Tab 用户/管理员
const tabUser = document.getElementById('tabUser');
const tabAdmin = document.getElementById('tabAdmin');
const panelUser = document.getElementById('panelUser');
const panelAdminLogin = document.getElementById('panelAdminLogin');
const panelAdmin = document.getElementById('panelAdmin');
const adminLoginBtn = document.getElementById('adminLoginBtn');
const adminLogoutBtn = document.getElementById('adminLogoutBtn');
const adminPwdInput = document.getElementById('adminPwdInput');
const loginError = document.getElementById('loginError');
let isAdminLogin = false;
tabUser.onclick = ()=>{
tabUser.classList.add('tab-active');
tabAdmin.classList.remove('tab-active');
panelUser.classList.remove('hidden');
panelAdminLogin.classList.add('hidden');
panelAdmin.classList.add('hidden');
}
tabAdmin.onclick = ()=>{
tabAdmin.classList.add('tab-active');
tabUser.classList.remove('tab-active');
panelUser.classList.add('hidden');
if(isAdminLogin){
panelAdmin.classList.remove('hidden');
panelAdminLogin.classList.add('hidden');
renderAdminList();
}else{
panelAdminLogin.classList.remove('hidden');
panelAdmin.classList.add('hidden');
loginError.classList.add('hidden');
adminPwdInput.value = "";
}
}
//管理员登录
adminLoginBtn.onclick = function(){
const pwd = adminPwdInput.value.trim();
if(pwd === ADMIN_PASSWORD){
isAdminLogin = true;
panelAdminLogin.classList.add('hidden');
panelAdmin.classList.remove('hidden');
renderAdminList();
}else{
loginError.classList.remove('hidden');
}
}
//退出登录
adminLogoutBtn.onclick = function(){
isAdminLogin = false;
tabUser.click();
}
// 用户端内部tab
const tabSubmit = document.getElementById('tabSubmit');
const tabSearch = document.getElementById('tabSearch');
const panelSubmit = document.getElementById('panelSubmit');
const panelSearch = document.getElementById('panelSearch');
tabSubmit.onclick = ()=>{
tabSubmit.classList.add('tab-active');
tabSearch.classList.remove('tab-active');
panelSubmit.classList.remove('hidden');
panelSearch.classList.add('hidden');
}
tabSearch.onclick = ()=>{
tabSearch.classList.add('tab-active');
tabSubmit.classList.remove('tab-active');
panelSearch.classList.remove('hidden');
panelSubmit.classList.add('hidden');
}
const form = document.getElementById('feedbackForm');
const modal = document.getElementById('modal');
const closeModal = document.getElementById('closeModal');
const copySnBtn = document.getElementById('copySnBtn');
let currentSN = "";
// 模拟数据库
let feedbackDB = {};
const typeMap = {
bug:"功能BUG",
suggest:"功能建议",
complain:"投诉",
other:"其他"
};
const statusMap = {
pending:{text:"待处理", cls:"status-pending"},
doing:{text:"处理中", cls:"status-doing"},
done:{text:"已完成", cls:"status-done"}
};
// 生成单号
function createSN(){
const time = new Date().getTime();
const rand = Math.floor(Math.random()*1000).toString().padStart(3,'0');
return `FB${time}${rand}`;
}
// 渲染管理员列表
function renderAdminList(){
const wrap = document.getElementById('adminList');
const emptyTip = document.getElementById('adminEmpty');
wrap.innerHTML = '';
const keys = Object.keys(feedbackDB);
if(keys.length === 0){
emptyTip.classList.remove('hidden');
return;
}
emptyTip.classList.add('hidden');
// 倒序,最新的在最上面
keys.reverse().forEach(sn=>{
const item = feedbackDB;
const statusInfo = statusMap;
const html = `
<div class="border p-4 rounded bg-gray-50">
<div class="flex flex-wrap justify-between items-center gap-2 mb-2">
<span class="font-medium">单号:${item.sn}</span>
<span class="${statusInfo.cls}">${statusInfo.text}</span>
</div>
<p class="text-sm text-gray-600 mb-1">类型:${typeMap} | 邮箱:${item.email||'未填写'}</p>
<p class="text-sm text-gray-600 mb-1">提交时间:${item.time}</p>
<p class="text-sm text-gray-700 my-2">反馈内容:${item.content}</p>
<div class="flex gap-2 mt-3">
<select class="statusSelect border rounded px-2 py-1" data-sn="${sn}">
<option value="pending" ${item.status==='pending'?'selected':''}>待处理</option>
<option value="doing" ${item.status==='doing'?'selected':''}>处理中</option>
<option value="done" ${item.status==='done'?'selected':''}>已完成</option>
</select>
<button class="delBtn bg-red-500 text-white px-3 py-1 rounded" data-sn="${sn}">删除</button>
</div>
</div>
`;
wrap.innerHTML += html;
})
// 绑定修改状态事件
document.querySelectorAll('.statusSelect').forEach(sel=>{
sel.onchange = function(){
const sn = this.dataset.sn;
feedbackDB.status = this.value;
renderAdminList();
}
})
// 删除事件
document.querySelectorAll('.delBtn').forEach(btn=>{
btn.onclick = function(){
const sn = this.dataset.sn;
if(confirm('确定删除这条反馈?')){
delete feedbackDB;
renderAdminList();
}
}
})
}
//表单提交
form.onsubmit = function(e){
e.preventDefault();
const type = document.getElementById('type').value;
const content = document.getElementById('content').value.trim();
if(!type){
alert("请选择反馈类型");
return;
}
if(!content){
alert("请填写反馈内容");
return;
}
const sn = createSN();
currentSN = sn;
const now = new Date().toLocaleString();
const data = {
sn:sn,
type: type,
email: document.getElementById('email').value,
content: content,
time: now,
status:"pending"
}
feedbackDB = data;
document.getElementById('snText').innerText = sn;
modal.classList.remove('hidden');
modal.classList.add('flex');
}
// 复制单号
copySnBtn.onclick = async function(){
try {
await navigator.clipboard.writeText(currentSN);
this.innerHTML = '<i class="fa fa-check mr-1"></i>已复制';
setTimeout(()=>{
this.innerHTML = '<i class="fa fa-copy mr-1"></i>复制单号';
},1500)
} catch (err) {
alert("复制失败,请手动复制单号");
}
}
closeModal.onclick = function(){
modal.classList.add('hidden');
modal.classList.remove('flex');
form.reset();
currentSN = "";
}
// 查询反馈
const searchBtn = document.getElementById('searchBtn');
const searchSn = document.getElementById('searchSn');
const searchResult = document.getElementById('searchResult');
const searchEmpty = document.getElementById('searchEmpty');
searchBtn.onclick = function(){
const sn = searchSn.value.trim();
searchResult.classList.add('hidden');
searchEmpty.classList.add('hidden');
if(!sn){
alert("请输入反馈单号");
return;
}
if(feedbackDB){
const item = feedbackDB;
document.getElementById('resSn').innerText = item.sn;
document.getElementById('resType').innerText = typeMap;
document.getElementById('resTime').innerText = item.time;
const statusInfo = statusMap;
document.getElementById('resStatus').innerHTML = `<span class="${statusInfo.cls}">${statusInfo.text}</span>`;
document.getElementById('resContent').innerText = item.content;
searchResult.classList.remove('hidden');
}else{
searchEmpty.classList.remove('hidden');
}
}
</script>
</body>
</html>
页:
[1]