Files
ns2.0/templates/admin_categories.html

512 lines
14 KiB
HTML
Executable File

{% extends "base.html" %}
{% block content %}
{% include 'admin_nav.html' %}
<div class="admin-container">
<div class="admin-content">
<div class="admin-card">
<div class="card-header">
<div class="header-left">
<i class="fas fa-th-large"></i>
<h3>分类管理</h3>
</div>
<button onclick="showAddCategoryModal()" class="btn-primary">
<i class="fas fa-plus"></i> 添加分类
</button>
</div>
<div class="categories-list">
<div class="categories-header">
<div class="sort-handle-header"></div>
<div>分类名称</div>
<div>应用数量</div>
<div>操作</div>
</div>
<div id="sortableCategories">
{% for category in categories %}
<div class="category-item" data-id="{{ category.id }}">
<div class="sort-handle">
<i class="fas fa-grip-vertical"></i>
</div>
<div class="category-name">{{ category.name }}</div>
<div class="app-count">{{ category.app_count }}个应用</div>
<div class="category-actions">
<button onclick="editCategory({{ category.id }}, '{{ category.name }}')" class="btn-edit">
<i class="fas fa-edit"></i>
</button>
<button onclick="deleteCategory({{ category.id }})" class="btn-delete">
<i class="fas fa-trash"></i>
</button>
</div>
</div>
{% endfor %}
</div>
</div>
</div>
</div>
</div>
<!-- 添加分类弹窗 -->
<div id="addCategoryModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>添加分类</h3>
<span class="close" onclick="closeAddCategoryModal()">&times;</span>
</div>
<form id="addCategoryForm" onsubmit="return addCategory(event)">
<div class="form-group">
<label for="categoryName">分类名称</label>
<input type="text" id="categoryName" name="name" required>
</div>
<div class="form-actions">
<button type="submit" class="btn-primary">确认添加</button>
<button type="button" onclick="closeAddCategoryModal()" class="btn-secondary">取消</button>
</div>
</form>
</div>
</div>
<!-- 编辑分类弹窗 -->
<div id="editCategoryModal" class="modal">
<div class="modal-content">
<div class="modal-header">
<h3>编辑分类</h3>
<span class="close" onclick="closeEditCategoryModal()">&times;</span>
</div>
<form id="editCategoryForm" onsubmit="return updateCategory(event)">
<input type="hidden" id="editCategoryId">
<div class="form-group">
<label for="editCategoryName">分类名称</label>
<input type="text" id="editCategoryName" name="name" required>
</div>
<div class="form-actions">
<button type="submit" class="btn-primary">保存修改</button>
<button type="button" onclick="closeEditCategoryModal()" class="btn-secondary">取消</button>
</div>
</form>
</div>
</div>
<style>
.categories-list {
margin-top: 20px;
}
.categories-header {
display: grid;
grid-template-columns: 50px 1fr 100px 120px;
padding: 10px;
background: #f5f5f7;
border-radius: 8px;
margin-bottom: 10px;
font-weight: 500;
}
.category-item {
display: grid;
grid-template-columns: 50px 1fr 100px 120px;
align-items: center;
padding: 12px;
background: white;
border-radius: 8px;
margin-bottom: 8px;
transition: all 0.3s ease;
cursor: move;
}
.category-item:hover {
background: #f8f9fa;
}
.sort-handle {
color: #999;
cursor: move;
display: flex;
align-items: center;
justify-content: center;
}
.sort-handle i {
font-size: 16px;
}
.category-name {
font-size: 14px;
}
.category-actions {
display: flex;
gap: 8px;
justify-content: flex-end;
}
.btn-edit, .btn-delete {
padding: 6px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-edit {
background: #007AFF;
color: white;
}
.btn-delete {
background: #ff3b30;
color: white;
}
.btn-edit:hover {
background: #0056b3;
}
.btn-delete:hover {
background: #dc3545;
}
/* 拖拽时的样式 */
.category-item.dragging {
opacity: 0.5;
background: #f0f0f0;
}
.category-item.drag-over {
border-top: 2px solid #007AFF;
}
/* 添加弹窗样式 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
}
.modal-content {
position: relative;
background-color: #fff;
margin: 10% auto;
padding: 20px;
width: 90%;
max-width: 500px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 0px;
}
.modal-header h3 {
margin: 0;
font-size: 18px;
color: #333;
}
.close {
font-size: 24px;
color: #666;
cursor: pointer;
padding: 5px;
}
.close:hover {
color: #333;
}
.form-group {
margin-bottom: 20px;
}
.form-group label {
display: block;
margin-bottom: 8px;
color: #333;
font-size: 14px;
}
.form-group input {
width: 100%;
padding: 8px 12px;
border: 1px solid #ddd;
border-radius: 6px;
font-size: 14px;
}
.form-group input:focus {
outline: none;
border-color: #007AFF;
}
.form-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
}
.btn-secondary {
background: #f5f5f7;
color: #333;
border: none;
padding: 8px 16px;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s ease;
}
.btn-secondary:hover {
background: #e5e5e7;
}
.app-count {
font-size: 13px;
color: #666;
text-align: center;
background: #f5f5f7;
padding: 4px 8px;
border-radius: 12px;
margin-right: 10px;
}
</style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/Sortable/1.14.0/Sortable.min.js"></script>
<script>
// 初始化拖拽排序
document.addEventListener('DOMContentLoaded', function() {
var el = document.getElementById('sortableCategories');
var sortable = new Sortable(el, {
animation: 150,
handle: '.sort-handle',
ghostClass: 'dragging',
onEnd: function(evt) {
// 获取新的排序
var items = el.getElementsByClassName('category-item');
var newOrder = Array.from(items).map(item => parseInt(item.dataset.id));
console.log('Sending order:', newOrder); // 添加调试日志
// 发送到服务器
fetch('/admin/update_category_order', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Requested-With': 'XMLHttpRequest'
},
credentials: 'same-origin',
body: JSON.stringify({
order: newOrder
})
})
.then(response => {
console.log('Response status:', response.status); // 添加调试日志
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
})
.then(data => {
console.log('Response data:', data); // 添加调试日志
if (data.success) {
showNotification('排序已更新', 'success');
} else {
throw new Error(data.error || '更新失败');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('更新失败,请重试', 'error');
// 如果更新失败,恢复原始顺序
setTimeout(() => {
location.reload();
}, 2000);
});
}
});
});
function showNotification(message, type = 'success') {
const notification = document.createElement('div');
notification.className = `notification ${type}`;
notification.textContent = message;
Object.assign(notification.style, {
position: 'fixed',
bottom: '20px',
right: '20px',
padding: '10px 20px',
borderRadius: '4px',
backgroundColor: type === 'success' ? '#4CAF50' : '#f44336',
color: 'white',
zIndex: '1000',
opacity: '0',
transform: 'translateY(20px)',
transition: 'all 0.3s ease',
boxShadow: '0 2px 5px rgba(0,0,0,0.2)'
});
document.body.appendChild(notification);
// 触发重排以应用过渡效果
notification.offsetHeight;
notification.style.opacity = '1';
notification.style.transform = 'translateY(0)';
setTimeout(() => {
notification.style.opacity = '0';
notification.style.transform = 'translateY(20px)';
setTimeout(() => notification.remove(), 300);
}, 3000);
}
function deleteCategory(categoryId) {
if (confirm('确定要删除这个分类吗?删除后将同时删除该分类下的所有应用')) {
fetch(`/delete_category/${categoryId}`, {
method: 'POST'
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification('分类及其应用已删除', 'success');
// 延迟刷新页面,让用户看到通知
setTimeout(() => {
location.reload();
}, 1000);
} else {
showNotification(data.error || '删除失败', 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('删除失败,请重试', 'error');
});
}
}
// 添加分类弹窗相关函数
function showAddCategoryModal() {
document.getElementById('addCategoryModal').style.display = 'block';
document.getElementById('categoryName').value = '';
document.getElementById('categoryName').focus();
}
function closeAddCategoryModal() {
document.getElementById('addCategoryModal').style.display = 'none';
}
function addCategory(event) {
event.preventDefault();
const name = document.getElementById('categoryName').value.trim();
if (!name) {
showNotification('分类名称不能为空', 'error');
return false;
}
fetch('/add_category', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `name=${encodeURIComponent(name)}`
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification('分类添加成功', 'success');
closeAddCategoryModal();
setTimeout(() => location.reload(), 1000);
} else {
showNotification(data.error || '添加失败', 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('添加失败,请重试', 'error');
});
return false;
}
// 编辑分类弹窗相关函数
function showEditCategoryModal() {
document.getElementById('editCategoryModal').style.display = 'block';
}
function closeEditCategoryModal() {
document.getElementById('editCategoryModal').style.display = 'none';
}
function editCategory(id, name) {
document.getElementById('editCategoryId').value = id;
document.getElementById('editCategoryName').value = name;
showEditCategoryModal();
}
function updateCategory(event) {
event.preventDefault();
const id = document.getElementById('editCategoryId').value;
const name = document.getElementById('editCategoryName').value.trim();
if (!name) {
showNotification('分类名称不能为空', 'error');
return false;
}
fetch(`/edit_category/${id}`, {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: `name=${encodeURIComponent(name)}`
})
.then(response => response.json())
.then(data => {
if (data.success) {
showNotification('分类修改成功', 'success');
closeEditCategoryModal();
setTimeout(() => location.reload(), 1000);
} else {
showNotification(data.error || '修改失败', 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('修改失败,请重试', 'error');
});
return false;
}
// 点击弹窗外部关闭
window.onclick = function(event) {
const addModal = document.getElementById('addCategoryModal');
const editModal = document.getElementById('editCategoryModal');
if (event.target == addModal) {
closeAddCategoryModal();
}
if (event.target == editModal) {
closeEditCategoryModal();
}
}
// 添加 ESC 键关闭弹窗
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
closeAddCategoryModal();
closeEditCategoryModal();
}
});
</script>
{% endblock %}