Merge remote-tracking branch 'origin/Django' into Django

This commit is contained in:
2025-11-15 17:56:59 +08:00
4 changed files with 1045 additions and 135 deletions

View 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">&times;</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">&times;</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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>