增加配置文件

This commit is contained in:
2026-06-02 13:52:47 +08:00
parent 6117222625
commit aab3aeb5b2
31 changed files with 3326 additions and 198 deletions
+372
View File
@@ -92,6 +92,12 @@ function showPage(pageName) {
case 'comments':
loadComments();
break;
case 'themes':
loadThemes();
break;
case 'theme-editor':
// 主题编辑器由 editTheme() 函数加载
break;
}
}
@@ -614,3 +620,369 @@ function getStatusText(status) {
};
return map[status] || status;
}
// ==================== 主题管理 ====================
// 加载主题列表
let currentTheme = null;
async function loadThemes() {
try {
// 获取当前主题
const currentRes = await fetch(`${API_BASE}/theme`, {
headers: getHeaders()
});
const currentData = await currentRes.json();
currentTheme = currentData.theme;
// 获取所有可用主题
const themesRes = await fetch(`${API_BASE}/themes`, {
headers: getHeaders()
});
const themesData = await themesRes.json();
// 填充下拉列表
const themeSelect = document.getElementById('theme-select');
themeSelect.innerHTML = '';
themesData.themes.forEach(theme => {
const option = document.createElement('option');
option.value = theme;
option.textContent = theme;
if (theme === currentTheme) {
option.selected = true;
}
themeSelect.appendChild(option);
});
// 显示主题列表
displayThemeList(themesData.themes, currentTheme);
} catch (error) {
console.error('加载主题失败:', error);
showThemeMessage('加载主题失败', 'error');
}
}
// 显示主题列表
function displayThemeList(themes, currentTheme) {
const themeList = document.getElementById('theme-list');
themeList.innerHTML = '';
themes.forEach(theme => {
const themeCard = document.createElement('div');
themeCard.className = `theme-card ${theme === currentTheme ? 'active' : ''}`;
themeCard.innerHTML = `
<div class="theme-card-header">
<h3>${theme}</h3>
${theme === currentTheme ? '<span class="theme-badge">当前使用</span>' : ''}
</div>
<div class="theme-card-body">
<p>主题名称: ${theme}</p>
<p>状态: ${theme === currentTheme ? '使用中' : '未使用'}</p>
</div>
<div class="theme-card-actions">
<button class="btn-small btn-secondary" onclick="editTheme('${theme}')">编辑</button>
${theme !== currentTheme ? `<button class="btn-small btn-primary" onclick="quickSwitch('${theme}')">切换</button>` : ''}
</div>
`;
themeList.appendChild(themeCard);
});
}
// 切换主题
async function switchTheme() {
const themeSelect = document.getElementById('theme-select');
const selectedTheme = themeSelect.value;
if (!selectedTheme) {
showThemeMessage('请选择一个主题', 'error');
return;
}
if (selectedTheme === currentTheme) {
showThemeMessage('当前已经是该主题', 'warning');
return;
}
try {
const response = await fetch(`${API_BASE}/theme`, {
method: 'POST',
headers: {
...getHeaders(),
'Content-Type': 'application/json'
},
body: JSON.stringify({ theme: selectedTheme })
});
const data = await response.json();
if (response.ok) {
showThemeMessage(data.message || '主题切换成功', 'success');
currentTheme = selectedTheme;
// 刷新主题列表显示
loadThemes();
} else {
showThemeMessage(data.error || '主题切换失败', 'error');
}
} catch (error) {
console.error('切换主题失败:', error);
showThemeMessage('切换主题失败', 'error');
}
}
// 显示主题消息
function showThemeMessage(message, type = 'info') {
const messageEl = document.getElementById('theme-message');
messageEl.textContent = message;
messageEl.className = `theme-message ${type}`;
messageEl.style.display = 'block';
// 3秒后自动隐藏
setTimeout(() => {
messageEl.style.display = 'none';
}, 3000);
}
// 显示创建主题弹窗
function showCreateThemeModal() {
document.getElementById('theme-modal').classList.remove('hidden');
document.getElementById('new-theme-name').value = '';
document.getElementById('theme-template').value = 'default';
document.getElementById('create-css').checked = true;
}
// 隐藏创建主题弹窗
function hideThemeModal() {
document.getElementById('theme-modal').classList.add('hidden');
}
// 创建主题表单提交
document.addEventListener('DOMContentLoaded', () => {
const createThemeForm = document.getElementById('create-theme-form');
if (createThemeForm) {
createThemeForm.addEventListener('submit', handleCreateTheme);
}
});
async function handleCreateTheme(e) {
e.preventDefault();
const themeName = document.getElementById('new-theme-name').value.trim();
const template = document.getElementById('theme-template').value;
const createCSS = document.getElementById('create-css').checked;
if (!themeName) {
showThemeMessage('请输入主题名称', 'error');
return;
}
try {
const response = await fetch(`${API_BASE}/themes/create`, {
method: 'POST',
headers: {
...getHeaders(),
'Content-Type': 'application/json'
},
body: JSON.stringify({
name: themeName,
template: template,
create_css: createCSS
})
});
const data = await response.json();
if (response.ok) {
showThemeMessage(data.message || '主题创建成功', 'success');
hideThemeModal();
// 刷新主题列表
setTimeout(() => {
loadThemes();
}, 500);
} else {
showThemeMessage(data.error || '主题创建失败', 'error');
}
} catch (error) {
console.error('创建主题失败:', error);
showThemeMessage('创建主题失败', 'error');
}
}
// 快速切换主题
async function quickSwitch(theme) {
try {
const response = await fetch(`${API_BASE}/theme`, {
method: 'POST',
headers: {
...getHeaders(),
'Content-Type': 'application/json'
},
body: JSON.stringify({ theme: theme })
});
const data = await response.json();
if (response.ok) {
showThemeMessage(data.message || '主题切换成功', 'success');
currentTheme = theme;
loadThemes();
} else {
showThemeMessage(data.error || '主题切换失败', 'error');
}
} catch (error) {
console.error('切换主题失败:', error);
showThemeMessage('切换主题失败', 'error');
}
}
// ==================== 主题编辑器 ====================
let currentEditingTheme = null;
let currentEditingFile = null;
let themeFiles = [];
// 编辑主题
async function editTheme(themeName) {
currentEditingTheme = themeName;
currentEditingFile = null;
document.getElementById('editor-theme-name').textContent = themeName;
document.getElementById('current-file-name').textContent = '选择文件进行编辑';
document.getElementById('theme-file-editor').value = '';
showPage('theme-editor');
// 加载主题文件列表
await loadThemeFiles(themeName);
}
// 加载主题文件列表
async function loadThemeFiles(themeName) {
try {
const response = await fetch(`${API_BASE}/themes/${themeName}/files`, {
headers: getHeaders()
});
const data = await response.json();
if (response.ok) {
themeFiles = data.files || [];
displayThemeFiles(themeFiles);
} else {
showThemeMessage(data.error || '加载文件列表失败', 'error');
}
} catch (error) {
console.error('加载文件列表失败:', error);
showThemeMessage('加载文件列表失败', 'error');
}
}
// 显示主题文件列表
function displayThemeFiles(files) {
const filesContainer = document.getElementById('theme-files');
filesContainer.innerHTML = '';
if (files.length === 0) {
filesContainer.innerHTML = '<p class="no-files">暂无文件</p>';
return;
}
files.forEach(file => {
const fileItem = document.createElement('div');
fileItem.className = 'theme-file-item';
fileItem.innerHTML = `
<span class="file-icon">${getFileIcon(file)}</span>
<span class="file-name">${file}</span>
`;
fileItem.onclick = () => loadFileContent(file);
filesContainer.appendChild(fileItem);
});
}
// 获取文件图标
function getFileIcon(filename) {
if (filename.endsWith('.html')) return '📄';
if (filename.endsWith('.css')) return '🎨';
if (filename.endsWith('.js')) return '📜';
return '📁';
}
// 加载文件内容
async function loadFileContent(filename) {
try {
currentEditingFile = filename;
const response = await fetch(`${API_BASE}/themes/${currentEditingTheme}/files/${filename}`, {
headers: getHeaders()
});
const data = await response.json();
if (response.ok) {
document.getElementById('current-file-name').textContent = filename;
document.getElementById('theme-file-editor').value = data.content || '';
// 高亮当前文件
document.querySelectorAll('.theme-file-item').forEach(item => {
item.classList.remove('active');
});
event.currentTarget.classList.add('active');
} else {
showThemeMessage(data.error || '加载文件失败', 'error');
}
} catch (error) {
console.error('加载文件失败:', error);
showThemeMessage('加载文件失败', 'error');
}
}
// 保存当前文件
async function saveCurrentFile() {
if (!currentEditingTheme || !currentEditingFile) {
showThemeMessage('请先选择文件', 'warning');
return;
}
const content = document.getElementById('theme-file-editor').value;
try {
const response = await fetch(`${API_BASE}/themes/${currentEditingTheme}/files/${currentEditingFile}`, {
method: 'PUT',
headers: {
...getHeaders(),
'Content-Type': 'application/json'
},
body: JSON.stringify({ content: content })
});
const data = await response.json();
if (response.ok) {
showThemeMessage(data.message || '文件保存成功', 'success');
// 如果是模板文件,重新加载主题
if (currentEditingFile.endsWith('.html')) {
const renderer = await fetch(`${API_BASE}/theme`, { headers: getHeaders() });
const rendererData = await renderer.json();
if (rendererData.theme === currentEditingTheme) {
// 重新加载当前主题以应用更改
await fetch(`${API_BASE}/theme`, {
method: 'POST',
headers: {
...getHeaders(),
'Content-Type': 'application/json'
},
body: JSON.stringify({ theme: currentEditingTheme })
});
}
}
} else {
showThemeMessage(data.error || '保存文件失败', 'error');
}
} catch (error) {
console.error('保存文件失败:', error);
showThemeMessage('保存文件失败', 'error');
}
}
+79 -1
View File
@@ -4,6 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>后台管理 - GoBlog</title>
<link rel="icon" href="/static/images/favicon.ico" type="image/x-icon">
<link rel="stylesheet" href="/static/admin/style.css">
</head>
<body>
@@ -20,6 +21,7 @@
<a href="#tags" class="menu-item" data-page="tags">标签管理</a>
<a href="#pages" class="menu-item" data-page="pages">页面管理</a>
<a href="#comments" class="menu-item" data-page="comments">评论管理</a>
<a href="#themes" class="menu-item" data-page="themes">主题管理</a>
</nav>
<div class="user-info">
<span id="username">Admin</span>
@@ -218,6 +220,50 @@
<tbody id="comments-list"></tbody>
</table>
</div>
<!-- 主题管理 -->
<div id="themes-page" class="page hidden">
<div class="page-header">
<h1>主题管理</h1>
<button class="btn-primary" onclick="showCreateThemeModal()">新建主题</button>
</div>
<div class="theme-settings">
<div class="form-group">
<label>当前主题</label>
<select id="theme-select" class="theme-selector">
<option value="">加载中...</option>
</select>
</div>
<button class="btn-primary" onclick="switchTheme()">切换主题</button>
<div id="theme-message" class="theme-message"></div>
</div>
<div class="theme-list" id="theme-list">
<!-- 主题列表将动态加载 -->
</div>
</div>
<!-- 主题编辑器 -->
<div id="theme-editor-page" class="page hidden">
<div class="page-header">
<h1>编辑主题: <span id="editor-theme-name"></span></h1>
<div>
<button class="btn-secondary" onclick="showPage('themes')">返回</button>
<button class="btn-primary" onclick="saveCurrentFile()">保存文件</button>
</div>
</div>
<div class="theme-editor-container">
<div class="theme-file-list">
<h3>文件列表</h3>
<div id="theme-files"></div>
</div>
<div class="theme-editor-content">
<div class="editor-header">
<h3 id="current-file-name">选择文件进行编辑</h3>
</div>
<textarea id="theme-file-editor" class="code-editor" placeholder="选择左侧文件进行编辑..."></textarea>
</div>
</div>
</div>
</main>
</div>
@@ -247,6 +293,38 @@
</div>
</div>
<script src="/static/admin/app.js?v=6"></script>
<!-- 创建主题弹窗 -->
<div id="theme-modal" class="modal hidden">
<div class="modal-content modal-large">
<h3>新建主题</h3>
<form id="create-theme-form">
<div class="form-group">
<label>主题名称 *</label>
<input type="text" id="new-theme-name" required placeholder="例如: mytheme">
<small>只能包含字母、数字和下划线</small>
</div>
<div class="form-group">
<label>基于模板创建</label>
<select id="theme-template">
<option value="default">default(默认主题)</option>
<option value="minimal">minimal(极简主题)</option>
<option value="blank">blank(空白主题)</option>
</select>
<small>选择新主题的初始模板</small>
</div>
<div class="form-group">
<label>
<input type="checkbox" id="create-css" checked> 创建样式文件
</label>
</div>
<div class="modal-actions">
<button type="button" class="btn-secondary" onclick="hideThemeModal()">取消</button>
<button type="submit" class="btn-primary">创建主题</button>
</div>
</form>
</div>
</div>
<script src="/static/admin/app.js?v=9"></script>
</body>
</html>
+304
View File
@@ -358,3 +358,307 @@ body {
grid-template-columns: 1fr;
}
}
/* 主题管理 */
.theme-settings {
background: #fff;
padding: 24px;
border-radius: 8px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
margin-bottom: 24px;
}
.theme-settings .form-group {
margin-bottom: 20px;
}
.theme-settings label {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #333;
}
.theme-selector {
width: 100%;
max-width: 400px;
padding: 10px;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
background: #fff;
cursor: pointer;
}
.theme-selector:focus {
outline: none;
border-color: #1890ff;
}
.theme-settings .btn-primary {
margin-top: 10px;
}
.theme-message {
margin-top: 15px;
padding: 10px 15px;
border-radius: 4px;
display: none;
}
.theme-message.success {
background: #f6ffed;
border: 1px solid #b7eb8f;
color: #52c41a;
}
.theme-message.error {
background: #fff2f0;
border: 1px solid #ffccc7;
color: #ff4d4f;
}
.theme-message.warning {
background: #fffbe6;
border: 1px solid #ffe58f;
color: #faad14;
}
.theme-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.theme-card {
background: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
border: 2px solid transparent;
transition: all 0.3s;
}
.theme-card:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
transform: translateY(-2px);
}
.theme-card.active {
border-color: #1890ff;
background: #f0f9ff;
}
.theme-card-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.theme-card-header h3 {
font-size: 18px;
color: #333;
margin: 0;
}
.theme-badge {
background: #1890ff;
color: #fff;
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: 500;
}
.theme-card-body p {
margin: 8px 0;
color: #666;
font-size: 14px;
}
@media (max-width: 768px) {
.theme-list {
grid-template-columns: 1fr;
}
.theme-settings {
padding: 16px;
}
.theme-selector {
max-width: 100%;
}
}
/* 大尺寸模态框 */
.modal-large {
max-width: 600px;
}
.modal-large .form-group small {
display: block;
margin-top: 5px;
color: #999;
font-size: 12px;
}
.modal-large input[type="text"] {
width: 100%;
}
.modal-large input[type="checkbox"] {
margin-right: 8px;
}
/* 主题卡片操作按钮 */
.theme-card-actions {
display: flex;
gap: 10px;
margin-top: 15px;
padding-top: 15px;
border-top: 1px solid #eee;
}
.btn-small {
padding: 6px 12px;
font-size: 13px;
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.3s;
}
.btn-small.btn-secondary {
background: #f5f5f5;
color: #666;
}
.btn-small.btn-secondary:hover {
background: #e8e8e8;
}
.btn-small.btn-primary {
background: #1890ff;
color: #fff;
}
.btn-small.btn-primary:hover {
background: #40a9ff;
}
/* 主题编辑器 */
.theme-editor-container {
display: grid;
grid-template-columns: 280px 1fr;
gap: 20px;
height: calc(100vh - 200px);
}
.theme-file-list {
background: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
overflow-y: auto;
}
.theme-file-list h3 {
margin: 0 0 15px;
font-size: 16px;
color: #333;
}
.theme-file-item {
padding: 12px;
margin-bottom: 8px;
background: #f9f9f9;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
display: flex;
align-items: center;
gap: 10px;
}
.theme-file-item:hover {
background: #e6f7ff;
}
.theme-file-item.active {
background: #1890ff;
color: #fff;
}
.theme-file-item .file-icon {
font-size: 18px;
}
.theme-file-item .file-name {
flex: 1;
font-size: 14px;
}
.theme-editor-content {
background: #fff;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 8px rgba(0,0,0,0.1);
display: flex;
flex-direction: column;
}
.editor-header {
margin-bottom: 15px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.editor-header h3 {
margin: 0;
font-size: 16px;
color: #333;
}
.code-editor {
flex: 1;
width: 100%;
padding: 15px;
border: 1px solid #ddd;
border-radius: 6px;
font-family: 'Courier New', monospace;
font-size: 14px;
line-height: 1.6;
resize: none;
background: #f9f9f9;
color: #333;
}
.code-editor:focus {
outline: none;
border-color: #1890ff;
background: #fff;
}
.no-files {
text-align: center;
color: #999;
padding: 40px 20px;
}
@media (max-width: 1024px) {
.theme-editor-container {
grid-template-columns: 1fr;
height: auto;
}
.theme-file-list {
max-height: 300px;
}
.code-editor {
min-height: 500px;
}
}