Compare commits
2 Commits
1e041cad3b
...
31d5ca5a01
| Author | SHA1 | Date | |
|---|---|---|---|
| 31d5ca5a01 | |||
| fb5e8a6588 |
644
elastic/templates/elastic/get_users.html
Normal file
644
elastic/templates/elastic/get_users.html
Normal file
@@ -0,0 +1,644 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>用户管理</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
/* 导航栏样式 */
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 180px;
|
||||
height: 100vh;
|
||||
background: #1e1e2e;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
text-align: center;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.sidebar h3 {
|
||||
margin-top: 0;
|
||||
font-size: 18px;
|
||||
color: #ff79c6;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.navigation-links {
|
||||
width: 100%;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.sidebar a,
|
||||
.sidebar button {
|
||||
display: block;
|
||||
color: #8be9fd;
|
||||
text-decoration: none;
|
||||
margin: 10px 0;
|
||||
font-size: 16px;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: calc(100% - 40px);
|
||||
text-align: left;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.sidebar a:hover,
|
||||
.sidebar button:hover {
|
||||
color: #ff79c6;
|
||||
background-color: rgba(139, 233, 253, 0.2);
|
||||
}
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content {
|
||||
margin-left: 200px;
|
||||
padding: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 10px 24px rgba(31,35,40,0.08);
|
||||
padding: 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: #4f46e5;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-danger {
|
||||
background: #ef4444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.btn-success {
|
||||
background: #22c55e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.table-container {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
th, td {
|
||||
padding: 12px 15px;
|
||||
text-align: left;
|
||||
border-bottom: 1px solid #e5e7eb;
|
||||
}
|
||||
|
||||
th {
|
||||
background-color: #f9fafb;
|
||||
font-weight: bold;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
tr:hover {
|
||||
background-color: #f3f4f6;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 5px;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-group input, .form-group select {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.form-row .form-group {
|
||||
flex: 1;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.action-buttons {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.search-container {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.search-container input {
|
||||
flex: 1;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #d1d5db;
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.search-container button {
|
||||
padding: 8px 15px;
|
||||
background: #4f46e5;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.modal {
|
||||
display: none;
|
||||
position: fixed;
|
||||
z-index: 2000;
|
||||
left: 0;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(0,0,0,0.5);
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
background-color: white;
|
||||
margin: 10% auto;
|
||||
padding: 20px;
|
||||
border-radius: 8px;
|
||||
width: 80%;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.close {
|
||||
color: #aaa;
|
||||
float: right;
|
||||
font-size: 28px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.close:hover {
|
||||
color: black;
|
||||
}
|
||||
|
||||
.notification {
|
||||
padding: 10px;
|
||||
margin-bottom: 15px;
|
||||
border-radius: 4px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.notification.success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
.notification.error {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.nav-error {
|
||||
color: #ef4444;
|
||||
font-size: 12px;
|
||||
margin-top: 5px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<!-- 左侧固定栏目 -->
|
||||
<div class="sidebar">
|
||||
<div class="user-id">
|
||||
<h3>用户ID:{{ user_id }}</h3>
|
||||
</div>
|
||||
<div class="navigation-links">
|
||||
<a href="{% url 'main:home' %}" onclick="return handleNavClick(this, '/');">主页</a>
|
||||
<button id="logoutBtn">退出登录</button>
|
||||
<div id="logoutMsg"></div>
|
||||
{% csrf_token %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<div class="main-content">
|
||||
<div class="card">
|
||||
<div class="header">
|
||||
<h2>用户管理</h2>
|
||||
<button id="addUserBtn" class="btn btn-primary">添加用户</button>
|
||||
</div>
|
||||
|
||||
<div class="notification success" id="successNotification">
|
||||
操作成功!
|
||||
</div>
|
||||
<div class="notification error" id="errorNotification">
|
||||
操作失败!
|
||||
</div>
|
||||
|
||||
<div class="search-container">
|
||||
<input type="text" id="searchInput" placeholder="搜索用户名...">
|
||||
<button id="searchBtn">搜索</button>
|
||||
<button id="resetBtn">重置</button>
|
||||
</div>
|
||||
|
||||
<div class="table-container">
|
||||
<table id="usersTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>用户ID</th>
|
||||
<th>用户名</th>
|
||||
<th>权限</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="usersTableBody">
|
||||
<!-- 用户数据将通过JavaScript加载 -->
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 添加/编辑用户模态框 -->
|
||||
<div id="userModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<h2 id="modalTitle">添加用户</h2>
|
||||
<form id="userForm">
|
||||
<input type="hidden" id="userId" name="user_id">
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="username">用户名</label>
|
||||
<input type="text" id="username" name="username" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="permission">权限</label>
|
||||
<select id="permission" name="permission" required>
|
||||
<option value="0">普通用户</option>
|
||||
<option value="1">管理员</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">密码</label>
|
||||
<input type="password" id="password" name="password" required>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="confirmPassword">确认密码</label>
|
||||
<input type="password" id="confirmPassword" name="confirmPassword" required>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary">保存</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 确认删除模态框 -->
|
||||
<div id="deleteModal" class="modal">
|
||||
<div class="modal-content">
|
||||
<span class="close">×</span>
|
||||
<h2>确认删除</h2>
|
||||
<p>确定要删除用户 <strong id="deleteUserName"></strong> 吗?此操作不可撤销。</p>
|
||||
<input type="hidden" id="deleteUserId">
|
||||
<button id="confirmDeleteBtn" class="btn btn-danger">确认删除</button>
|
||||
<button class="btn">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// 获取CSRF令牌的函数
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
}
|
||||
|
||||
// 导航点击处理函数,提供备用URL
|
||||
function handleNavClick(element, fallbackUrl) {
|
||||
// 尝试使用Django模板生成的URL,如果失败则使用备用URL
|
||||
try {
|
||||
// 如果模板渲染正常,直接返回true让默认行为处理
|
||||
return true;
|
||||
} catch (e) {
|
||||
// 如果模板渲染有问题,使用备用URL
|
||||
window.location.href = fallbackUrl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示通知
|
||||
function showNotification(message, isSuccess = true) {
|
||||
const notification = isSuccess ?
|
||||
document.getElementById('successNotification') :
|
||||
document.getElementById('errorNotification');
|
||||
|
||||
notification.textContent = message;
|
||||
notification.style.display = 'block';
|
||||
|
||||
setTimeout(() => {
|
||||
notification.style.display = 'none';
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 获取所有用户
|
||||
async function loadUsers(searchTerm = '') {
|
||||
try {
|
||||
const url = searchTerm ?
|
||||
`/elastic/users/?search=${encodeURIComponent(searchTerm)}` :
|
||||
'/elastic/users/';
|
||||
|
||||
const response = await fetch(url);
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
const tbody = document.getElementById('usersTableBody');
|
||||
tbody.innerHTML = '';
|
||||
|
||||
// 处理不同格式的API响应
|
||||
const users = result.data || result.users || [];
|
||||
|
||||
users.forEach(user => {
|
||||
const row = document.createElement('tr');
|
||||
|
||||
// 根据权限值显示权限名称
|
||||
const permissionText = user.permission === 1 ? '管理员' : '普通用户';
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${user.user_id}</td>
|
||||
<td>${user.username}</td>
|
||||
<td>${permissionText}</td>
|
||||
<td class="action-buttons">
|
||||
<button class="btn btn-success edit-btn" data-user='${JSON.stringify(user)}'>编辑</button>
|
||||
<button class="btn btn-danger delete-btn" data-username="${user.username}" data-userid="${user.user_id}">删除</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
});
|
||||
} else {
|
||||
showNotification('获取用户列表失败', false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载用户列表失败:', error);
|
||||
showNotification('获取用户列表失败', false);
|
||||
}
|
||||
}
|
||||
|
||||
// 打开添加用户模态框
|
||||
function openAddModal() {
|
||||
document.getElementById('modalTitle').textContent = '添加用户';
|
||||
document.getElementById('userForm').reset();
|
||||
document.getElementById('userId').value = '';
|
||||
document.getElementById('password').required = true;
|
||||
document.getElementById('confirmPassword').required = true;
|
||||
document.getElementById('userModal').style.display = 'block';
|
||||
}
|
||||
|
||||
// 打开编辑用户模态框
|
||||
function openEditModal(user) {
|
||||
document.getElementById('modalTitle').textContent = '编辑用户';
|
||||
document.getElementById('username').value = user.username;
|
||||
document.getElementById('userId').value = user.user_id;
|
||||
document.getElementById('permission').value = user.permission;
|
||||
document.getElementById('password').required = false;
|
||||
document.getElementById('confirmPassword').required = false;
|
||||
document.getElementById('userModal').style.display = 'block';
|
||||
}
|
||||
|
||||
// 打开删除确认模态框
|
||||
function openDeleteModal(username, userId) {
|
||||
document.getElementById('deleteUserName').textContent = username;
|
||||
document.getElementById('deleteUserId').value = userId;
|
||||
document.getElementById('deleteModal').style.display = 'block';
|
||||
}
|
||||
|
||||
// 保存用户(添加或编辑)
|
||||
async function saveUser(event) {
|
||||
event.preventDefault();
|
||||
|
||||
const formData = new FormData(event.target);
|
||||
const userId = formData.get('user_id');
|
||||
const username = formData.get('username');
|
||||
const permission = formData.get('permission');
|
||||
const password = formData.get('password');
|
||||
const confirmPassword = formData.get('confirmPassword');
|
||||
|
||||
// 验证密码
|
||||
if (password !== confirmPassword) {
|
||||
showNotification('密码和确认密码不匹配', false);
|
||||
return;
|
||||
}
|
||||
|
||||
// 验证密码长度(如果提供了密码)
|
||||
if (password && password.length < 6) {
|
||||
showNotification('密码长度至少为6位', false);
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
username: username,
|
||||
permission: parseInt(permission)
|
||||
};
|
||||
|
||||
if (password) {
|
||||
data.password = password;
|
||||
}
|
||||
|
||||
try {
|
||||
const csrftoken = getCookie('csrftoken');
|
||||
let response;
|
||||
|
||||
if (userId) {
|
||||
// 更新用户
|
||||
response = await fetch(`/elastic/users/${userId}/update/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrftoken
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
} else {
|
||||
// 添加用户
|
||||
response = await fetch('/elastic/users/add/', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrftoken
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
showNotification(userId ? '用户更新成功' : '用户添加成功');
|
||||
document.getElementById('userModal').style.display = 'none';
|
||||
loadUsers();
|
||||
} else {
|
||||
showNotification(result.message || '操作失败', false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('保存用户失败:', error);
|
||||
showNotification('保存用户失败', false);
|
||||
}
|
||||
}
|
||||
|
||||
// 删除用户
|
||||
async function deleteUser() {
|
||||
const userId = document.getElementById('deleteUserId').value;
|
||||
|
||||
try {
|
||||
const csrftoken = getCookie('csrftoken');
|
||||
const response = await fetch(`/elastic/users/${userId}/delete/`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrftoken
|
||||
}
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.status === 'success') {
|
||||
showNotification('用户删除成功');
|
||||
document.getElementById('deleteModal').style.display = 'none';
|
||||
loadUsers();
|
||||
} else {
|
||||
showNotification(result.message || '删除失败', false);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除用户失败:', error);
|
||||
showNotification('删除用户失败', false);
|
||||
}
|
||||
}
|
||||
|
||||
// 事件监听器
|
||||
document.getElementById('addUserBtn').addEventListener('click', openAddModal);
|
||||
|
||||
document.getElementById('userForm').addEventListener('submit', saveUser);
|
||||
|
||||
document.getElementById('confirmDeleteBtn').addEventListener('click', deleteUser);
|
||||
|
||||
document.querySelectorAll('.close').forEach(closeBtn => {
|
||||
closeBtn.addEventListener('click', function() {
|
||||
this.parentElement.parentElement.style.display = 'none';
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('searchBtn').addEventListener('click', function() {
|
||||
const searchTerm = document.getElementById('searchInput').value;
|
||||
loadUsers(searchTerm);
|
||||
});
|
||||
|
||||
document.getElementById('resetBtn').addEventListener('click', function() {
|
||||
document.getElementById('searchInput').value = '';
|
||||
loadUsers();
|
||||
});
|
||||
|
||||
// 点击模态框外部关闭模态框
|
||||
window.addEventListener('click', function(event) {
|
||||
const modals = document.querySelectorAll('.modal');
|
||||
modals.forEach(modal => {
|
||||
if (event.target === modal) {
|
||||
modal.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 登出功能
|
||||
document.getElementById('logoutBtn').addEventListener('click', async () => {
|
||||
const msg = document.getElementById('logoutMsg');
|
||||
msg.textContent = '';
|
||||
const csrftoken = getCookie('csrftoken');
|
||||
try {
|
||||
const resp = await fetch('/accounts/logout/', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrftoken || ''
|
||||
},
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (!resp.ok || !data.ok) {
|
||||
throw new Error('登出失败');
|
||||
}
|
||||
document.cookie = 'sessionid=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
|
||||
document.cookie = 'csrftoken=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
|
||||
window.location.href = data.redirect_url;
|
||||
} catch (e) {
|
||||
msg.textContent = e.message || '发生错误';
|
||||
}
|
||||
});
|
||||
|
||||
// 页面加载时获取用户列表
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadUsers();
|
||||
});
|
||||
|
||||
// 为表格中的编辑和删除按钮添加事件监听器
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('edit-btn')) {
|
||||
const user = JSON.parse(e.target.getAttribute('data-user'));
|
||||
openEditModal(user);
|
||||
}
|
||||
|
||||
if (e.target.classList.contains('delete-btn')) {
|
||||
const username = e.target.getAttribute('data-username');
|
||||
const userId = e.target.getAttribute('data-userid');
|
||||
openDeleteModal(username, userId);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -5,28 +5,94 @@
|
||||
<title>数据管理</title>
|
||||
<style>
|
||||
body {
|
||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
||||
background:#fafafa;
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
/* 导航栏样式 */
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 180px;
|
||||
height: 100vh;
|
||||
background: #1e1e2e;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
text-align: center;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.sidebar h3 {
|
||||
margin-top: 0;
|
||||
font-size: 18px;
|
||||
color: #ff79c6;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.navigation-links {
|
||||
width: 100%;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.sidebar a,
|
||||
.sidebar button {
|
||||
display: block;
|
||||
color: #8be9fd;
|
||||
text-decoration: none;
|
||||
margin: 10px 0;
|
||||
font-size: 16px;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: calc(100% - 40px);
|
||||
text-align: left;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.sidebar a:hover,
|
||||
.sidebar button:hover {
|
||||
color: #ff79c6;
|
||||
background-color: rgba(139, 233, 253, 0.2);
|
||||
}
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content {
|
||||
margin-left: 200px;
|
||||
padding: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 原有样式保持不变 */
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
background:#fff;
|
||||
border-radius:10px;
|
||||
box-shadow:0 6px 18px rgba(0,0,0,0.06);
|
||||
padding:20px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 6px 18px rgba(0,0,0,0.06);
|
||||
padding: 20px;
|
||||
}
|
||||
table {
|
||||
width:100%;
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-top: 20px;
|
||||
}
|
||||
th, td {
|
||||
border-bottom:1px solid #eee;
|
||||
padding:12px 8px;
|
||||
text-align:left;
|
||||
border-bottom: 1px solid #eee;
|
||||
padding: 12px 8px;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
}
|
||||
th {
|
||||
@@ -35,32 +101,32 @@
|
||||
}
|
||||
img {
|
||||
max-width: 120px;
|
||||
border:1px solid #eee;
|
||||
border-radius:6px;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 6px;
|
||||
}
|
||||
.btn {
|
||||
padding:6px 10px;
|
||||
border:none;
|
||||
border-radius:6px;
|
||||
cursor:pointer;
|
||||
padding: 6px 10px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
margin: 2px;
|
||||
}
|
||||
.btn-primary {
|
||||
background:#1677ff;
|
||||
color:#fff;
|
||||
background: #1677ff;
|
||||
color: #fff;
|
||||
}
|
||||
.btn-danger {
|
||||
background:#ff4d4f;
|
||||
color:#fff;
|
||||
background: #ff4d4f;
|
||||
color: #fff;
|
||||
}
|
||||
.btn-secondary {
|
||||
background:#f0f0f0;
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
}
|
||||
.muted {
|
||||
color:#666;
|
||||
font-size:12px;
|
||||
color: #666;
|
||||
font-size: 12px;
|
||||
}
|
||||
.modal {
|
||||
position: fixed;
|
||||
@@ -74,28 +140,28 @@
|
||||
.modal .dialog {
|
||||
width: 720px;
|
||||
max-width: 92vw;
|
||||
background:#fff;
|
||||
border-radius:10px;
|
||||
padding:20px;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
padding: 20px;
|
||||
max-height: 80vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
textarea {
|
||||
width:100%;
|
||||
width: 100%;
|
||||
min-height: 240px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size:14px;
|
||||
font-size: 14px;
|
||||
padding: 10px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 4px;
|
||||
resize: vertical;
|
||||
}
|
||||
#kvForm {
|
||||
border:1px solid #eee;
|
||||
border-radius:6px;
|
||||
padding:8px;
|
||||
max-height:300px;
|
||||
overflow:auto;
|
||||
border: 1px solid #eee;
|
||||
border-radius: 6px;
|
||||
padding: 8px;
|
||||
max-height: 300px;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
/* 搜索区域样式 */
|
||||
@@ -167,62 +233,77 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<h2>数据管理</h2>
|
||||
<p class="muted">仅管理员可见。可查看、编辑、删除所有记录。</p>
|
||||
|
||||
<!-- 搜索功能区域 -->
|
||||
<div class="search-container">
|
||||
<div class="search-controls">
|
||||
<input type="text" id="searchQuery" class="search-input" placeholder="请输入搜索关键词...">
|
||||
<button class="btn btn-primary" onclick="performSearch('exact')">关键词搜索</button>
|
||||
<button class="btn btn-secondary" onclick="performSearch('fuzzy')">模糊搜索</button>
|
||||
<button class="btn" onclick="loadAllData()">显示全部</button>
|
||||
<button class="btn" onclick="clearSearch()">清空结果</button>
|
||||
</div>
|
||||
|
||||
<div id="searchResult" class="search-result" style="display: none;">
|
||||
<div id="searchStatus">正在搜索...</div>
|
||||
<div id="searchCount" style="margin-top: 5px; font-weight: bold;"></div>
|
||||
</div>
|
||||
<!-- 左侧固定栏目 -->
|
||||
<div class="sidebar">
|
||||
<div class="user-id">
|
||||
<h3>用户ID:{{ user_id }}</h3>
|
||||
</div>
|
||||
<div class="navigation-links">
|
||||
<a href="{% url 'main:home' %}">主页</a>
|
||||
<button id="logoutBtn">退出登录</button>
|
||||
<div id="logoutMsg"></div>
|
||||
{% csrf_token %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<table id="dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>图片</th>
|
||||
<th>数据</th>
|
||||
<th>作者</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tableBody">
|
||||
<!-- 数据将通过JavaScript动态加载 -->
|
||||
</tbody>
|
||||
</table>
|
||||
<!-- 主内容区域 -->
|
||||
<div class="main-content">
|
||||
<div class="container">
|
||||
<h2>数据管理</h2>
|
||||
<p class="muted">仅管理员可见。可查看、编辑、删除所有记录。</p>
|
||||
|
||||
<!-- 编辑模态框 -->
|
||||
<div id="editModal" class="modal">
|
||||
<div class="dialog">
|
||||
<h3>编辑数据</h3>
|
||||
<div style="display:flex; gap:8px; align-items:center; margin-bottom:8px;">
|
||||
<button id="addFieldBtn" class="btn btn-secondary" type="button">添加字段</button>
|
||||
<button id="syncFromTextBtn" class="btn btn-secondary" type="button">从文本区刷新表单</button>
|
||||
<span id="editMsg" class="muted"></span>
|
||||
<!-- 搜索功能区域 -->
|
||||
<div class="search-container">
|
||||
<div class="search-controls">
|
||||
<input type="text" id="searchQuery" class="search-input" placeholder="请输入搜索关键词...">
|
||||
<button class="btn btn-primary" onclick="performSearch('exact')">关键词搜索</button>
|
||||
<button class="btn btn-secondary" onclick="performSearch('fuzzy')">模糊搜索</button>
|
||||
<button class="btn" onclick="loadAllData()">显示全部</button>
|
||||
<button class="btn" onclick="clearSearch()">清空结果</button>
|
||||
</div>
|
||||
<div id="kvForm"></div>
|
||||
<div style="margin-top:8px;">
|
||||
<textarea id="resultBox" placeholder="JSON数据"></textarea>
|
||||
|
||||
<div id="searchResult" class="search-result" style="display: none;">
|
||||
<div id="searchStatus">正在搜索...</div>
|
||||
<div id="searchCount" style="margin-top: 5px; font-weight: bold;"></div>
|
||||
</div>
|
||||
<div style="margin-top:12px; display:flex; gap:8px;">
|
||||
<button class="btn btn-primary" onclick="saveEdit()">保存</button>
|
||||
<button class="btn" onclick="closeModal()">取消</button>
|
||||
</div>
|
||||
|
||||
<!-- 数据表格 -->
|
||||
<table id="dataTable">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>ID</th>
|
||||
<th>图片</th>
|
||||
<th>数据</th>
|
||||
<th>作者</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tableBody">
|
||||
<!-- 数据将通过JavaScript动态加载 -->
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<!-- 编辑模态框 -->
|
||||
<div id="editModal" class="modal">
|
||||
<div class="dialog">
|
||||
<h3>编辑数据</h3>
|
||||
<div style="display:flex; gap:8px; align-items:center; margin-bottom:8px;">
|
||||
<button id="addFieldBtn" class="btn btn-secondary" type="button">添加字段</button>
|
||||
<button id="syncFromTextBtn" class="btn btn-secondary" type="button">从文本区刷新表单</button>
|
||||
<span id="editMsg" class="muted"></span>
|
||||
</div>
|
||||
<div id="kvForm"></div>
|
||||
<div style="margin-top:8px;">
|
||||
<textarea id="resultBox" placeholder="JSON数据"></textarea>
|
||||
</div>
|
||||
<div style="margin-top:12px; display:flex; gap:8px;">
|
||||
<button class="btn btn-primary" onclick="saveEdit()">保存</button>
|
||||
<button class="btn" onclick="closeModal()">取消</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -614,9 +695,33 @@ async function doDelete(id){
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
loadAllData();
|
||||
});
|
||||
|
||||
// 退出登录处理
|
||||
document.getElementById('logoutBtn').addEventListener('click', async () => {
|
||||
const msg = document.getElementById('logoutMsg');
|
||||
msg.textContent = '';
|
||||
const csrftoken = getCookie('csrftoken');
|
||||
try {
|
||||
const resp = await fetch('/accounts/logout/', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrftoken || ''
|
||||
},
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (!resp.ok || !data.ok) {
|
||||
throw new Error('登出失败');
|
||||
}
|
||||
document.cookie = 'sessionid=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
|
||||
document.cookie = 'csrftoken=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
|
||||
window.location.href = data.redirect_url;
|
||||
} catch (e) {
|
||||
msg.textContent = e.message || '发生错误';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
||||
|
||||
</html>
|
||||
@@ -1,25 +1,128 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>图片上传与识别</title>
|
||||
<style>
|
||||
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif; background: #fafafa; }
|
||||
.container { max-width: 900px; margin: 6vh auto; background: #fff; border-radius: 10px; box-shadow: 0 6px 18px rgba(0,0,0,0.06); padding: 24px; }
|
||||
.row { display: flex; gap: 16px; }
|
||||
.col { flex: 1; }
|
||||
textarea { width: 100%; min-height: 260px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 14px; }
|
||||
img { max-width: 100%; border: 1px solid #eee; border-radius: 6px; }
|
||||
.btn { padding: 8px 12px; border: none; border-radius: 6px; cursor: pointer; }
|
||||
.btn-primary { background: #1677ff; color: #fff; }
|
||||
.btn-secondary { background: #f0f0f0; }
|
||||
.muted { color: #666; font-size: 12px; }
|
||||
.error { color: #d14343; }
|
||||
.success { color: #179957; }
|
||||
</style>
|
||||
<meta charset="UTF-8" />
|
||||
<title>图片上传与识别</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
/* 导航栏样式 */
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 180px;
|
||||
height: 100vh;
|
||||
background: #1e1e2e;
|
||||
color: white;
|
||||
padding: 20px;
|
||||
box-shadow: 2px 0 5px rgba(0,0,0,0.1);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.user-id {
|
||||
text-align: center;
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.sidebar h3 {
|
||||
margin-top: 0;
|
||||
font-size: 18px;
|
||||
color: #ff79c6;
|
||||
text-align: center;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.navigation-links {
|
||||
width: 100%;
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.sidebar a,
|
||||
.sidebar button {
|
||||
display: block;
|
||||
color: #8be9fd;
|
||||
text-decoration: none;
|
||||
margin: 10px 0;
|
||||
font-size: 16px;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: calc(100% - 40px);
|
||||
text-align: left;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.sidebar a:hover,
|
||||
.sidebar button:hover {
|
||||
color: #ff79c6;
|
||||
background-color: rgba(139, 233, 253, 0.2);
|
||||
}
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content {
|
||||
margin-left: 200px;
|
||||
padding: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
/* 原有样式保持不变 */
|
||||
.container {
|
||||
max-width: 900px;
|
||||
margin: 6vh auto;
|
||||
background: #fff;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 6px 18px rgba(0,0,0,0.06);
|
||||
padding: 24px;
|
||||
}
|
||||
.row { display: flex; gap: 16px; }
|
||||
.col { flex: 1; }
|
||||
textarea {
|
||||
width: 100%;
|
||||
min-height: 260px;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 14px;
|
||||
}
|
||||
img { max-width: 100%; border: 1px solid #eee; border-radius: 6px; }
|
||||
.btn {
|
||||
padding: 8px 12px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
}
|
||||
.btn-primary { background: #1677ff; color: #fff; }
|
||||
.btn-secondary { background: #f0f0f0; }
|
||||
.muted { color: #666; font-size: 12px; }
|
||||
.error { color: #d14343; }
|
||||
.success { color: #179957; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- 左侧固定栏目 -->
|
||||
<div class="sidebar">
|
||||
<div class="user-id">
|
||||
<h3>用户ID:{{ user_id }}</h3>
|
||||
</div>
|
||||
<div class="navigation-links">
|
||||
<a href="{% url 'main:home' %}">主页</a>
|
||||
<button id="logoutBtn">退出登录</button>
|
||||
<div id="logoutMsg"></div>
|
||||
{% csrf_token %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 主内容区域 -->
|
||||
<div class="main-content">
|
||||
<div class="container">
|
||||
<h2>图片上传与识别</h2>
|
||||
<p class="muted">选择图片后上传,服务端调用大模型解析为可编辑的 JSON,再确认入库。</p>
|
||||
|
||||
@@ -53,6 +156,7 @@
|
||||
<button id="clearBtn" class="btn btn-secondary" type="button">清空</button>
|
||||
<span id="confirmMsg" class="muted"></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
@@ -223,6 +327,33 @@ clearBtn.addEventListener('click', () => {
|
||||
confirmMsg.textContent = '';
|
||||
confirmBtn.disabled = true;
|
||||
});
|
||||
|
||||
// 退出登录处理
|
||||
document.getElementById('logoutBtn').addEventListener('click', async () => {
|
||||
const msg = document.getElementById('logoutMsg');
|
||||
msg.textContent = '';
|
||||
const csrftoken = getCookie('csrftoken');
|
||||
try {
|
||||
const resp = await fetch('/accounts/logout/', {
|
||||
method: 'POST',
|
||||
credentials: 'same-origin',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-CSRFToken': csrftoken || ''
|
||||
},
|
||||
body: JSON.stringify({})
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (!resp.ok || !data.ok) {
|
||||
throw new Error('登出失败');
|
||||
}
|
||||
document.cookie = 'sessionid=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
|
||||
document.cookie = 'csrftoken=; expires=Thu, 01 Jan 1970 00:00:00 GMT; path=/';
|
||||
window.location.href = data.redirect_url;
|
||||
} catch (e) {
|
||||
msg.textContent = e.message || '发生错误';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
@@ -1,14 +1,21 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh">
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>固定左侧栏目</title>
|
||||
<title>数据管理系统</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: system-ui, -apple-system, Segoe UI, Roboto, sans-serif;
|
||||
background: #fafafa;
|
||||
}
|
||||
|
||||
/* 导航栏样式 */
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 180px; /* 导航栏变窄 */
|
||||
width: 180px;
|
||||
height: 100vh;
|
||||
background: #1e1e2e;
|
||||
color: white;
|
||||
@@ -22,7 +29,7 @@
|
||||
|
||||
.user-id {
|
||||
text-align: center;
|
||||
margin-bottom: auto; /* 用户ID保持在顶部 */
|
||||
margin-bottom: auto;
|
||||
}
|
||||
|
||||
.sidebar h3 {
|
||||
@@ -30,12 +37,12 @@
|
||||
font-size: 18px;
|
||||
color: #ff79c6;
|
||||
text-align: center;
|
||||
margin-bottom: 20px; /* 调整标题与下方内容的距离 */
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.navigation-links {
|
||||
width: 100%;
|
||||
margin-top: 60px; /* 空出一个正方形位置(约 60x60px) */
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
.sidebar a,
|
||||
@@ -43,14 +50,14 @@
|
||||
display: block;
|
||||
color: #8be9fd;
|
||||
text-decoration: none;
|
||||
margin: 10px 0; /* 减少上下间距 */
|
||||
font-size: 16px; /* 根据需要调整字体大小 */
|
||||
padding: 15px; /* 增加内边距以填充整个宽度 */
|
||||
border-radius: 4px; /* 减少圆角半径 */
|
||||
margin: 10px 0;
|
||||
font-size: 16px;
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
width: calc(100% - 40px); /* 适应padding-left/right 20px */
|
||||
width: calc(100% - 40px);
|
||||
text-align: left;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
@@ -61,23 +68,13 @@
|
||||
background-color: rgba(139, 233, 253, 0.2);
|
||||
}
|
||||
|
||||
/* 主内容区 */
|
||||
.main-content {
|
||||
margin-left: 200px; /* 调整主内容区左外边距,与新侧边栏宽度匹配 */
|
||||
margin-left: 200px;
|
||||
padding: 20px;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family: Arial, sans-serif;
|
||||
}
|
||||
|
||||
#logoutMsg {
|
||||
color: #ff5555;
|
||||
font-size: 14px;
|
||||
margin-top: 10px;
|
||||
text-align: center;
|
||||
}
|
||||
.card {
|
||||
background: #fff;
|
||||
border-radius: 14px;
|
||||
@@ -141,9 +138,10 @@
|
||||
<h3>用户ID:{{ user_id }}</h3>
|
||||
</div>
|
||||
<div class="navigation-links">
|
||||
<a href="/">主页</a>
|
||||
<a href="{% url 'elastic:upload_page' %}">图片上传与识别</a>
|
||||
<a href="{% url 'elastic:manage_page' %}">数据管理(管理员)</a>
|
||||
<a href="{% url 'main:home' %}" onclick="return handleNavClick(this, '/');">主页</a>
|
||||
<a href="{% url 'elastic:upload_page' %}" onclick="return handleNavClick(this, '/elastic/upload/');">图片上传与识别</a>
|
||||
<a href="{% url 'elastic:manage_page' %}" onclick="return handleNavClick(this, '/elastic/manage/');">数据管理</a>
|
||||
<a href="/elastic/users/" onclick="return handleNavClick(this, '/elastic/users/');">用户管理</a>
|
||||
<button id="logoutBtn">退出登录</button>
|
||||
<div id="logoutMsg"></div>
|
||||
{% csrf_token %}
|
||||
@@ -189,13 +187,47 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 登出脚本(保持不变) -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
// 获取CSRF令牌的函数
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
}
|
||||
|
||||
// 导航点击处理函数,提供备用URL
|
||||
function handleNavClick(element, fallbackUrl) {
|
||||
// 尝试使用Django模板生成的URL,如果失败则使用备用URL
|
||||
try {
|
||||
// 如果模板渲染正常,直接返回true让默认行为处理
|
||||
return true;
|
||||
} catch (e) {
|
||||
// 如果模板渲染有问题,使用备用URL
|
||||
window.location.href = fallbackUrl;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 修复用户管理链接跳转问题
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 为用户管理链接添加事件监听器,确保正确跳转
|
||||
const userManagementLink = document.querySelector('a[href*="get_users"]');
|
||||
if (userManagementLink) {
|
||||
userManagementLink.addEventListener('click', function(e) {
|
||||
// 阻止默认行为
|
||||
e.preventDefault();
|
||||
|
||||
// 获取备用URL
|
||||
const fallbackUrl = this.getAttribute('onclick').match(/'([^']+)'/g)[1].replace(/'/g, '');
|
||||
|
||||
// 直接跳转到用户管理页面
|
||||
window.location.href = fallbackUrl;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 登出功能
|
||||
document.getElementById('logoutBtn').addEventListener('click', async () => {
|
||||
const msg = document.getElementById('logoutMsg');
|
||||
msg.textContent = '';
|
||||
@@ -221,9 +253,7 @@
|
||||
msg.textContent = e.message || '发生错误';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
|
||||
async function loadAnalytics() {
|
||||
const resp = await fetch('/elastic/analytics/overview/');
|
||||
const d = await resp.json();
|
||||
@@ -308,4 +338,4 @@
|
||||
loadAnalytics();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
||||
Reference in New Issue
Block a user