初始化鸿蒙应用展示平台项目 - 前后端分离架构

This commit is contained in:
Nvex
2025-10-25 11:45:17 +08:00
commit c0f81dbbe2
92 changed files with 40210 additions and 0 deletions

View File

@@ -0,0 +1,541 @@
{% extends "base.html" %}
{% block title %}Wiki 分类管理{% endblock %}
{% block head %}
<style>
.container {
margin-left: 250px;
padding: 20px;
min-height: 100vh;
background: #f8f9fa;
}
@media (max-width: 768px) {
.container {
margin-left: 0;
padding: 15px;
}
}
.category-table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
border-radius: 8px;
overflow: hidden;
}
.category-table th,
.category-table td {
border: 1px solid #e0e0e0;
padding: 12px;
text-align: left;
transition: background-color 0.3s ease;
}
.category-table th {
background-color: #f1f3f5;
font-weight: 600;
color: #495057;
text-transform: uppercase;
}
.category-table tr:nth-child(even) {
background-color: #f8f9fa;
}
.category-table tr:hover {
background-color: #e9ecef;
}
.category-actions {
display: flex;
gap: 10px;
justify-content: center;
}
.btn-edit,
.btn-delete {
padding: 6px 12px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
transition: opacity 0.3s ease;
}
.btn-edit {
background-color: #28a745;
color: white;
}
.btn-delete {
background-color: #dc3545;
color: white;
}
.btn-edit:hover {
opacity: 0.8;
}
.btn-delete:hover {
opacity: 0.8;
}
.add-category-form {
margin-bottom: 20px;
display: flex;
flex-wrap: wrap;
gap: 10px;
background-color: #f8f9fa;
padding: 15px;
border-radius: 8px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
.add-category-form input,
.add-category-form select {
flex: 1;
padding: 8px;
border: 1px solid #ddd;
border-radius: 4px;
min-width: 100px;
}
@media (max-width: 768px) {
.add-category-form {
flex-direction: column;
}
.category-table {
font-size: 0.9em;
}
.category-table td,
.category-table th {
padding: 8px;
}
}
.category-level-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
}
.category-level-1 {
background-color: #007bff;
}
.category-level-2 {
background-color: #28a745;
}
.category-level-3 {
background-color: #dc3545;
}
</style>
{% endblock %}
{% block content %}
{% include 'admin_nav.html' %}
<div class="container">
<h1>Wiki 分类管理</h1>
{% if error %}
<div class="alert alert-danger">
{{ error }}
</div>
{% endif %}
<div class="add-category-section">
<form id="addCategoryForm" class="add-category-form">
<select id="first-level" required>
<option value="">选择一级分类</option>
<option value="版本号">版本号</option>
<option value="资讯">资讯</option>
</select>
<input type="text" id="second-level" placeholder="二级分类" required>
<input type="text" id="third-level" placeholder="三级分类" required>
<input type="text" id="description" placeholder="描述(可选)">
<input type="number" id="sort-order" placeholder="排序默认999" value="999">
<button type="submit" class="btn btn-primary">添加分类</button>
</form>
</div>
<table class="category-table">
<thead>
<tr>
<th>ID</th>
<th>一级分类</th>
<th>二级分类</th>
<th>三级分类</th>
<th>描述</th>
<th>排序</th>
<th>操作</th>
</tr>
</thead>
<tbody id="categoriesTableBody">
{% for category in categories %}
<tr data-id="{{ category.id }}">
<td>{{ category.id }}</td>
<td>
<span class="category-level-indicator category-level-1"></span>
{{ category.first_level }}
</td>
<td>
<span class="category-level-indicator category-level-2"></span>
{{ category.second_level }}
</td>
<td>
<span class="category-level-indicator category-level-3"></span>
{{ category.third_level }}
</td>
<td>{{ category.description or '' }}</td>
<td>{{ category.sort_order }}</td>
<td class="category-actions">
<button class="btn-edit" onclick="editCategory({{ category.id }})">编辑</button>
<button class="btn-delete" onclick="deleteCategory({{ category.id }})">删除</button>
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 获取分类列表
async function fetchCategories() {
try {
const response = await fetch('/admin/wiki/categories', {
headers: {
'X-Requested-With': 'XMLHttpRequest'
}
});
if (!response.ok) {
console.error('获取分类失败:', response.statusText);
return [];
}
const categories = await response.json();
console.log('获取到的分类:', categories);
console.log('分类总数:', categories.length);
return categories;
} catch (error) {
console.error('获取分类时发生错误:', error);
// 显示错误消息
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-danger';
errorDiv.textContent = `获取分类失败: ${error.message}`;
document.querySelector('.container').insertBefore(errorDiv, document.querySelector('.category-table'));
return [];
}
}
// 渲染分类列表
async function renderCategories() {
const categories = await fetchCategories();
const tableBody = document.getElementById('categoriesTableBody');
// 清空现有行
tableBody.innerHTML = '';
// 如果没有分类,显示提示
if (categories.length === 0) {
const noDataRow = `
<tr>
<td colspan="7" style="text-align: center; color: #888;">
暂无分类,请添加新分类
</td>
</tr>
`;
tableBody.innerHTML = noDataRow;
return;
}
// 按一级分类分组
const groupedCategories = {};
categories.forEach(category => {
if (!groupedCategories[category.first_level]) {
groupedCategories[category.first_level] = [];
}
groupedCategories[category.first_level].push(category);
});
// 渲染分类列表
Object.keys(groupedCategories).forEach(firstLevel => {
const firstLevelCategories = groupedCategories[firstLevel];
firstLevelCategories.forEach(category => {
const row = `
<tr data-id="${category.id}">
<td>${category.id}</td>
<td>
<span class="category-level-indicator category-level-1"></span>
${category.first_level}
</td>
<td>
<span class="category-level-indicator category-level-2"></span>
${category.second_level}
</td>
<td>
<span class="category-level-indicator category-level-3"></span>
${category.third_level}
</td>
<td>${category.description || ''}</td>
<td>${category.sort_order}</td>
<td class="category-actions">
<button class="btn-edit" onclick="editCategory(${category.id})">编辑</button>
<button class="btn-delete" onclick="deleteCategory(${category.id})">删除</button>
</td>
</tr>
`;
tableBody.innerHTML += row;
});
});
}
// 初始化页面
if (document.getElementById('categoriesTableBody')) {
renderCategories();
}
// 添加分类表单提交处理
document.getElementById('addCategoryForm').addEventListener('submit', async function(e) {
e.preventDefault();
const data = {
first_level: document.getElementById('first-level').value,
second_level: document.getElementById('second-level').value,
third_level: document.getElementById('third-level').value,
description: document.getElementById('description').value,
sort_order: document.getElementById('sort-order').value
};
try {
const response = await fetch('/admin/wiki/category/add', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
});
const result = await response.json();
if (result.success) {
// 重新加载分类列表
await renderCategories();
// 清空表单
document.getElementById('first-level').value = '';
document.getElementById('second-level').value = '';
document.getElementById('third-level').value = '';
document.getElementById('description').value = '';
document.getElementById('sort-order').value = '999';
// 显示成功消息
const successDiv = document.createElement('div');
successDiv.className = 'alert alert-success';
successDiv.textContent = '分类添加成功';
document.querySelector('.container').insertBefore(successDiv, document.querySelector('.category-table'));
// 3秒后移除成功消息
setTimeout(() => successDiv.remove(), 3000);
} else {
// 显示错误消息
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-danger';
errorDiv.textContent = result.error || '添加分类失败';
document.querySelector('.container').insertBefore(errorDiv, document.querySelector('.category-table'));
}
} catch (error) {
console.error('添加分类时发生错误:', error);
// 显示错误消息
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-danger';
errorDiv.textContent = `添加分类时发生错误: ${error.message}`;
document.querySelector('.container').insertBefore(errorDiv, document.querySelector('.category-table'));
}
});
// 删除分类
window.deleteCategory = async function(categoryId) {
if (!confirm('确定要删除这个分类吗?')) return;
try {
const response = await fetch(`/admin/wiki/category/delete/${categoryId}`, {
method: 'POST'
});
const result = await response.json();
if (result.success) {
// 重新加载分类列表
await renderCategories();
// 显示成功消息
const successDiv = document.createElement('div');
successDiv.className = 'alert alert-success';
successDiv.textContent = '分类删除成功';
document.querySelector('.container').insertBefore(successDiv, document.querySelector('.category-table'));
// 3秒后移除成功消息
setTimeout(() => successDiv.remove(), 3000);
} else {
// 显示错误消息
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-danger';
errorDiv.textContent = result.error || '删除分类失败';
document.querySelector('.container').insertBefore(errorDiv, document.querySelector('.category-table'));
}
} catch (error) {
console.error('删除分类时发生错误:', error);
// 显示错误消息
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-danger';
errorDiv.textContent = `删除分类时发生错误: ${error.message}`;
document.querySelector('.container').insertBefore(errorDiv, document.querySelector('.category-table'));
}
};
// 编辑分类
window.editCategory = function(categoryId) {
// 获取当前行的数据
const row = document.querySelector(`tr[data-id="${categoryId}"]`);
const firstLevel = row.querySelector('td:nth-child(2)').textContent.trim();
const secondLevel = row.querySelector('td:nth-child(3)').textContent.trim();
const thirdLevel = row.querySelector('td:nth-child(4)').textContent.trim();
const description = row.querySelector('td:nth-child(5)').textContent.trim();
const sortOrder = row.querySelector('td:nth-child(6)').textContent.trim();
// 弹出编辑模态框
const editModal = `
<div class="modal" id="editCategoryModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">编辑分类</h5>
<button type="button" class="close" onclick="closeEditModal()">&times;</button>
</div>
<div class="modal-body">
<form id="editCategoryForm">
<input type="hidden" id="edit-category-id" value="${categoryId}">
<div class="form-group">
<label>一级分类</label>
<select id="edit-first-level" class="form-control">
<option value="版本号" ${firstLevel === '版本号' ? 'selected' : ''}>版本号</option>
<option value="资讯" ${firstLevel === '资讯' ? 'selected' : ''}>资讯</option>
</select>
</div>
<div class="form-group">
<label>二级分类</label>
<input type="text" id="edit-second-level" class="form-control" value="${secondLevel}">
</div>
<div class="form-group">
<label>三级分类</label>
<input type="text" id="edit-third-level" class="form-control" value="${thirdLevel}">
</div>
<div class="form-group">
<label>描述</label>
<input type="text" id="edit-description" class="form-control" value="${description}">
</div>
<div class="form-group">
<label>排序</label>
<input type="number" id="edit-sort-order" class="form-control" value="${sortOrder}">
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" onclick="closeEditModal()">取消</button>
<button type="button" class="btn btn-primary" onclick="saveEditCategory()">保存</button>
</div>
</div>
</div>
</div>
`;
// 插入模态框
document.body.insertAdjacentHTML('beforeend', editModal);
};
// 关闭编辑模态框
window.closeEditModal = function() {
const modal = document.getElementById('editCategoryModal');
if (modal) modal.remove();
};
// 保存编辑分类
window.saveEditCategory = async function() {
const categoryId = document.getElementById('edit-category-id').value;
const firstLevel = document.getElementById('edit-first-level').value;
const secondLevel = document.getElementById('edit-second-level').value;
const thirdLevel = document.getElementById('edit-third-level').value;
const description = document.getElementById('edit-description').value;
const sortOrder = document.getElementById('edit-sort-order').value;
try {
const response = await fetch('/admin/wiki/category/update', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: categoryId,
first_level: firstLevel,
second_level: secondLevel,
third_level: thirdLevel,
description: description,
sort_order: sortOrder
})
});
const result = await response.json();
if (result.success) {
// 关闭模态框
closeEditModal();
// 重新加载分类列表
await renderCategories();
// 显示成功消息
const successDiv = document.createElement('div');
successDiv.className = 'alert alert-success';
successDiv.textContent = '分类更新成功';
document.querySelector('.container').insertBefore(successDiv, document.querySelector('.category-table'));
// 3秒后移除成功消息
setTimeout(() => successDiv.remove(), 3000);
} else {
// 显示错误消息
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-danger';
errorDiv.textContent = result.error || '更新分类失败';
document.querySelector('.container').insertBefore(errorDiv, document.querySelector('.category-table'));
}
} catch (error) {
console.error('更新分类时发生错误:', error);
// 显示错误消息
const errorDiv = document.createElement('div');
errorDiv.className = 'alert alert-danger';
errorDiv.textContent = `更新分类时发生错误: ${error.message}`;
document.querySelector('.container').insertBefore(errorDiv, document.querySelector('.category-table'));
}
};
});
</script>
{% endblock %}