feat: Add new document tutorial configuration functionality.

This commit is contained in:
dqzboy
2024-08-25 22:27:30 +08:00
parent 5d2fc5ca25
commit 8b8b5add63
5 changed files with 619 additions and 31 deletions

View File

@@ -6,7 +6,7 @@
<p align="center">
<img src="https://github.com/dqzboy/Docker-Proxy/assets/42825450/c187d66f-152e-4172-8268-e54bd77d48bb" width="230px" height="200px">
<br>
<i>Docker镜像加速命令查询获取镜像搜索UI面板.</i>
<i>Docker镜像加速命令查询获取镜像搜索、配置教程文档展示UI面板.</i>
</p>
</div>
@@ -107,6 +107,12 @@ docker logs -f [容器ID或名称]
</tr>
</table>
<table>
<tr>
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/b7efcd39-8757-46f9-ae68-02ca6add40e7"?raw=true"></td>
</tr>
</table>
> 浏览器输入 `服务器地址:30080/admin` 访问后端页面,默认登入账号密码: root/admin@123
<table>
@@ -117,7 +123,7 @@ docker logs -f [容器ID或名称]
<table>
<tr>
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/7f3196c6-5da7-409a-ab49-1037ad0db1d6"?raw=true"></td>
<td width="50%" align="center"><img src="https://github.com/user-attachments/assets/3ffe6a5d-da8c-436a-ae28-033fecf52770"?raw=true"></td>
</tr>
</table>

View File

@@ -0,0 +1 @@
{"title":"客户端配置教程","content":"### Docker 配置镜像加速\n- 修改文件 `/etc/docker/daemon.json`(如果不存在则创建)\n\n```shell\nsudo mkdir -p /etc/docker\nsudo vi /etc/docker/daemon.json\n{\n \"registry-mirrors\": [\"https://<代理加速地址>\"]\n}\n\nsudo systemctl daemon-reload\nsudo systemctl restart docker\n```\n### Containerd 配置镜像加速\n `/etc/containerd/config.toml`,添加如下的配置:\n\n```yaml\n [plugins.\"io.containerd.grpc.v1.cri\".registry]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"docker.io\"]\n endpoint = [\"https://<代理加速地址>\"]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"k8s.gcr.io\"]\n endpoint = [\"https://<代理加速地址>\"]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"gcr.io\"]\n endpoint = [\"https://<代理加速地址>\"]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"ghcr.io\"]\n endpoint = [\"https://<代理加速地址>\"]\n [plugins.\"io.containerd.grpc.v1.cri\".registry.mirrors.\"quay.io\"]\n endpoint = [\"https://<代理加速地址>\"]\n```\n\n### Podman 配置镜像加速\n修改配置文件 `/etc/containers/registries.conf`,添加配置:\n```yaml\nunqualified-search-registries = ['docker.io', 'k8s.gcr.io', 'gcr.io', 'ghcr.io', 'quay.io']\n\n[[registry]]\nprefix = \"docker.io\"\ninsecure = true\nlocation = \"registry-1.docker.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"k8s.gcr.io\"\ninsecure = true\nlocation = \"k8s.gcr.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"gcr.io\"\ninsecure = true\nlocation = \"gcr.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"ghcr.io\"\ninsecure = true\nlocation = \"ghcr.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n\n[[registry]]\nprefix = \"quay.io\"\ninsecure = true\nlocation = \"quay.io\"\n\n[[registry.mirror]]\nlocation = \"https://<代理加速地址>\"\n```","published":true}

View File

@@ -42,6 +42,8 @@ app.get('/api/search', async (req, res) => {
const CONFIG_FILE = path.join(__dirname, 'config.json');
const USERS_FILE = path.join(__dirname, 'users.json');
const DOCUMENTATION_DIR = path.join(__dirname, 'documentation');
const DOCUMENTATION_FILE = path.join(__dirname, 'documentation.md');
// 读取配置
async function readConfig() {
@@ -103,6 +105,54 @@ async function writeUsers(users) {
await fs.writeFile(USERS_FILE, JSON.stringify({ users }, null, 2), 'utf8');
}
// 确保 documentation 目录存在
async function ensureDocumentationDir() {
try {
await fs.access(DOCUMENTATION_DIR);
} catch (error) {
if (error.code === 'ENOENT') {
await fs.mkdir(DOCUMENTATION_DIR);
} else {
throw error;
}
}
}
// 读取文档
async function readDocumentation() {
try {
await ensureDocumentationDir();
const files = await fs.readdir(DOCUMENTATION_DIR);
console.log('Files in documentation directory:', files); // 添加日志
const documents = await Promise.all(files.map(async file => {
const filePath = path.join(DOCUMENTATION_DIR, file);
const content = await fs.readFile(filePath, 'utf8');
const doc = JSON.parse(content);
return {
id: path.parse(file).name,
title: doc.title,
content: doc.content,
published: doc.published
};
}));
const publishedDocuments = documents.filter(doc => doc.published);
console.log('Published documents:', publishedDocuments); // 添加日志
return publishedDocuments;
} catch (error) {
console.error('Error reading documentation:', error);
throw error;
}
}
// 写入文档
async function writeDocumentation(content) {
await fs.writeFile(DOCUMENTATION_FILE, content, 'utf8');
}
// 登录验证
app.post('/api/login', async (req, res) => {
const { username, password, captcha } = req.body;
@@ -202,6 +252,145 @@ app.get('/api/captcha', (req, res) => {
res.json({ captcha });
});
// API端点获取文档列表
app.get('/api/documentation-list', requireLogin, async (req, res) => {
try {
const files = await fs.readdir(DOCUMENTATION_DIR);
const documents = await Promise.all(files.map(async file => {
const content = await fs.readFile(path.join(DOCUMENTATION_DIR, file), 'utf8');
const doc = JSON.parse(content);
return { id: path.parse(file).name, ...doc };
}));
res.json(documents);
} catch (error) {
res.status(500).json({ error: '读取文档列表失败' });
}
});
// API端点保存文档
app.post('/api/documentation', requireLogin, async (req, res) => {
try {
const { id, title, content } = req.body;
const docId = id || Date.now().toString();
const docPath = path.join(DOCUMENTATION_DIR, `${docId}.json`);
await fs.writeFile(docPath, JSON.stringify({ title, content, published: false }));
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: '保存文档失败' });
}
});
// API端点删除文档
app.delete('/api/documentation/:id', requireLogin, async (req, res) => {
try {
const docPath = path.join(DOCUMENTATION_DIR, `${req.params.id}.json`);
await fs.unlink(docPath);
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: '删除文档失败' });
}
});
// API端点切换文档发布状态
app.post('/api/documentation/:id/toggle-publish', requireLogin, async (req, res) => {
try {
const docPath = path.join(DOCUMENTATION_DIR, `${req.params.id}.json`);
const content = await fs.readFile(docPath, 'utf8');
const doc = JSON.parse(content);
doc.published = !doc.published;
await fs.writeFile(docPath, JSON.stringify(doc));
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: '更改发布状态失败' });
}
});
// API端点获取文档
app.get('/api/documentation', async (req, res) => {
try {
const documents = await readDocumentation();
console.log('Sending documents:', documents); // 添加日志
res.json(documents);
} catch (error) {
console.error('Error in /api/documentation:', error);
res.status(500).json({ error: '读取文档失败', details: error.message });
}
});
// API端点保存文档
app.post('/api/documentation', requireLogin, async (req, res) => {
try {
const { content } = req.body;
await writeDocumentation(content);
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: '保存文档失败' });
}
});
// 获取文档列表函数
async function getDocumentList() {
try {
await ensureDocumentationDir();
const files = await fs.readdir(DOCUMENTATION_DIR);
console.log('Files in documentation directory:', files);
const documents = await Promise.all(files.map(async file => {
try {
const filePath = path.join(DOCUMENTATION_DIR, file);
const content = await fs.readFile(filePath, 'utf8');
return {
id: path.parse(file).name,
title: path.parse(file).name, // 使用文件名作为标题
content: content,
published: true // 假设所有文档都是已发布的
};
} catch (fileError) {
console.error(`Error reading file ${file}:`, fileError);
return null;
}
}));
const validDocuments = documents.filter(doc => doc !== null);
console.log('Valid documents:', validDocuments);
return validDocuments;
} catch (error) {
console.error('Error reading document list:', error);
throw error; // 重新抛出错误,让上层函数处理
}
}
app.get('/api/documentation-list', async (req, res) => {
try {
const documents = await getDocumentList();
res.json(documents);
} catch (error) {
console.error('Error in /api/documentation-list:', error);
res.status(500).json({
error: '读取文档列表失败',
details: error.message,
stack: error.stack
});
}
});
app.get('/api/documentation/:id', async (req, res) => {
try {
const docId = req.params.id;
console.log('Fetching document with id:', docId); // 添加日志
const docPath = path.join(DOCUMENTATION_DIR, `${docId}.json`);
const content = await fs.readFile(docPath, 'utf8');
const doc = JSON.parse(content);
console.log('Sending document:', doc); // 添加日志
res.json(doc);
} catch (error) {
console.error('Error reading document:', error);
res.status(500).json({ error: '读取文档失败', details: error.message });
}
});
// 启动服务器
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {

View File

@@ -5,6 +5,10 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Docker 镜像代理加速 - 管理面板</title>
<link rel="icon" href="https://cdn.jsdelivr.net/gh/dqzboy/Blog-Image/BlogCourse/docker-proxy.png" type="image/png">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/editor.md@1.5.0/css/editormd.min.css">
<script src="https://cdn.jsdelivr.net/npm/jquery@3.6.0/dist/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/editor.md@1.5.0/editormd.min.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Lato', 'Helvetica', 'Arial', sans-serif;
@@ -399,13 +403,17 @@
</style>
</head>
<body>
<div class="admin-container hidden" id="adminContainer">
<div id="loadingIndicator" style="display: flex; justify-content: center; align-items: center; height: 100vh;">
<p>加载中...</p>
</div>
<div class="admin-container" id="adminContainer" style="display: none;">
<div class="sidebar">
<h2>管理面板</h2>
<ul>
<li class="active" data-section="basic-config">基本配置</li>
<li data-section="menu-management">菜单管理</li>
<li data-section="ad-management">广告管理</li>
<li data-section="documentation-management">文档管理</li>
<li data-section="password-change">修改密码</li>
</ul>
</div>
@@ -456,7 +464,32 @@
</table>
<button type="button" class="add-btn" onclick="showNewAdRow()">添加广告</button>
</div>
<!-- 文档管理部分 -->
<div id="documentation-management" class="content-section">
<h2 class="menu-label">文档管理</h2>
<button type="button" onclick="newDocument()">新建文档</button>
<table id="documentTable">
<thead>
<tr>
<th>文章</th>
<th>操作</th>
</tr>
</thead>
<tbody id="documentTableBody">
<!-- 文档列表将在这里动态添加 -->
</tbody>
</table>
<div id="editorContainer" style="display: none;">
<input type="text" id="documentTitle" placeholder="文档标题">
<div id="editormd">
<textarea style="display:none;"></textarea>
</div>
<button type="button" onclick="saveDocument()">保存文档</button>
<button type="button" onclick="cancelEdit()">取消</button>
</div>
</div>
<div id="password-change" class="content-section">
<h2 class="menu-label">修改密码</h2>
<label for="currentPassword">当前密码</label>
@@ -471,7 +504,7 @@
</div>
</div>
<div class="login-modal" id="loginModal">
<div class="login-modal" id="loginModal" style="display: none;">
<div class="login-content">
<div class="login-header">
<h2>登入</h2>
@@ -494,6 +527,185 @@
let adImages = [];
let isLoggedIn = false;
let editingIndex = -1; // 用于记录当前编辑的菜单项索引
let editor;
let currentEditingDoc = null;
let documents = [];
// 初始化编辑器
function initEditor() {
if (editor) {
console.log('Editor already initialized');
return;
}
try {
editor = editormd("editormd", {
width: "100%",
height: 640,
path : "https://cdn.jsdelivr.net/npm/editor.md@1.5.0/lib/",
theme : "default",
previewTheme : "default",
editorTheme : "default",
markdown : "",
codeFold : true,
saveHTMLToTextarea : true,
searchReplace : true,
watch : true, // 开启实时预览
htmlDecode : "style,script,iframe|on*",
toolbar : true,
toolbarIcons : "full",
placeholder: "请输入Markdown格式的文档...",
emoji : true,
taskList : true,
tocm : true,
tex : true,
flowChart : true,
sequenceDiagram : true,
onload : function() {
console.log('Editor.md loaded successfully');
// 在加载完成后,立即切换到编辑模式
this.unwatch();
this.watch();
}
});
console.log('Editor initialized successfully');
} catch (error) {
console.error('Error initializing editor:', error);
}
}
function newDocument() {
currentEditingDoc = null;
document.getElementById('documentTitle').value = '';
editor.setMarkdown('');
showEditor();
}
function showEditor() {
document.getElementById('documentTable').style.display = 'none';
document.getElementById('editorContainer').style.display = 'block';
if (editor) {
// 确保每次显示编辑器时都切换到编辑模式
editor.unwatch();
editor.watch();
}
}
function hideEditor() {
document.getElementById('documentTable').style.display = 'table';
document.getElementById('editorContainer').style.display = 'none';
}
function cancelEdit() {
hideEditor();
}
async function saveDocument() {
const title = document.getElementById('documentTitle').value.trim();
const content = editor.getMarkdown();
if (!title) {
alert('请输入文档标题');
return;
}
try {
const response = await fetch('/api/documentation', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
id: currentEditingDoc ? currentEditingDoc.id : null,
title,
content
})
});
if (response.ok) {
alert('文档保存成功');
hideEditor();
loadDocumentList();
} else {
throw new Error('保存失败');
}
} catch (error) {
console.error('保存文档失败:', error);
alert('保存文档失败: ' + error.message);
}
}
async function loadDocumentList() {
try {
const response = await fetch('/api/documentation-list');
if (!response.ok) {
const errorData = await response.json();
throw new Error(`HTTP error! status: ${response.status}, message: ${errorData.error}, details: ${errorData.details}`);
}
documents = await response.json();
console.log('Received documents:', documents);
renderDocumentList();
} catch (error) {
console.error('加载文档列表失败:', error);
alert('加载文档列表失败: ' + error.message);
}
}
function renderDocumentList() {
const tbody = document.getElementById('documentTableBody');
tbody.innerHTML = '';
if (documents.length === 0) {
tbody.innerHTML = '<tr><td colspan="2">没有找到文档</td></tr>';
return;
}
documents.forEach(doc => {
const row = `
<tr>
<td>${doc.title || 'Untitled Document'}</td>
<td>
<button onclick="editDocument('${doc.id}')">编辑</button>
<button onclick="deleteDocument('${doc.id}')">删除</button>
<button onclick="togglePublish('${doc.id}')">${doc.published ? '取消发布' : '发布'}</button>
</td>
</tr>
`;
tbody.innerHTML += row;
});
}
function editDocument(id) {
currentEditingDoc = documents.find(doc => doc.id === id);
document.getElementById('documentTitle').value = currentEditingDoc.title;
editor.setMarkdown(currentEditingDoc.content);
showEditor();
}
async function deleteDocument(id) {
if (confirm('确定要删除这个文档吗?')) {
try {
const response = await fetch(`/api/documentation/${id}`, { method: 'DELETE' });
if (response.ok) {
alert('文档删除成功');
loadDocumentList();
} else {
throw new Error('删除失败');
}
} catch (error) {
console.error('删除文档失败:', error);
alert('删除文档失败: ' + error.message);
}
}
}
async function togglePublish(id) {
try {
const response = await fetch(`/api/documentation/${id}/toggle-publish`, { method: 'POST' });
if (response.ok) {
loadDocumentList();
} else {
throw new Error('操作失败');
}
} catch (error) {
console.error('更改发布状态失败:', error);
alert('更改发布状态失败: ' + error.message);
}
}
function getMenuItems() {
return menuItems;
@@ -840,18 +1052,82 @@
}
}
// 加载文档的函数
async function loadDocumentation() {
try {
const response = await fetch('/api/documentation');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const documents = await response.json();
console.log('Received documents from server:', documents);
if (documents.length > 0) {
const firstDocument = documents[0];
if (!editor) {
console.warn('Editor not initialized, initializing now');
initEditor();
}
// 等待一小段时间确保编辑器完全初始化
setTimeout(() => {
if (editor && typeof editor.setMarkdown === 'function') {
editor.setMarkdown(firstDocument.content);
console.log('Documentation loaded successfully');
} else {
throw new Error('Editor not properly initialized or setMarkdown method not found');
}
}, 500);
} else {
console.log('No documents found');
}
} catch (error) {
console.error('加载文档失败:', error);
alert('加载文档失败: ' + error.message);
}
}
// 保存文档的函数
async function saveDocumentation() {
if (!editor) {
console.error('Editor not initialized');
return;
}
const content = editor.getMarkdown();
try {
const response = await fetch('/api/documentation', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ content })
});
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const result = await response.json();
if (result.success) {
alert('文档保存成功');
} else {
throw new Error('保存失败');
}
} catch (error) {
console.error('保存文档失败:', error);
alert('保存文档失败: ' + error.message);
}
}
async function loadConfig() {
try {
const response = await fetch('/api/config');
const config = await response.json();
document.getElementById('logoUrl').value = config.logo || '';
document.getElementById('proxyDomain').value = config.proxyDomain || '';
setMenuItems(config.menuItems || []);
adImages = config.adImages || [];
renderAdItems();
} catch (error) {
console.error('加载配置失败:', error);
}
try {
const response = await fetch('/api/config');
const config = await response.json();
document.getElementById('logoUrl').value = config.logo || '';
document.getElementById('proxyDomain').value = config.proxyDomain || '';
setMenuItems(config.menuItems || []);
adImages = config.adImages || [];
renderAdItems();
loadDocumentation(); // 新增:加载文档
} catch (error) {
console.error('加载配置失败:', error);
}
}
async function login() {
@@ -866,9 +1142,9 @@
});
if (response.ok) {
isLoggedIn = true;
localStorage.setItem('isLoggedIn', 'true'); // 存储登录状态
localStorage.setItem('isLoggedIn', 'true');
document.getElementById('loginModal').style.display = 'none';
document.getElementById('adminContainer').classList.remove('hidden');
document.getElementById('adminContainer').style.display = 'flex';
loadConfig();
} else {
const errorData = await response.json();
@@ -939,25 +1215,28 @@
if (response.ok) {
isLoggedIn = localStorage.getItem('isLoggedIn') === 'true';
if (isLoggedIn) {
document.getElementById('loginModal').style.display = 'none';
document.getElementById('adminContainer').classList.remove('hidden');
loadConfig();
loadDocumentList(); // 加载文档列表
document.getElementById('adminContainer').style.display = 'flex';
await loadConfig();
initEditor(); // 初始化编辑器
} else {
document.getElementById('loginModal').style.display = 'block';
document.getElementById('loginModal').style.display = 'flex';
refreshCaptcha();
}
} else {
localStorage.removeItem('isLoggedIn');
document.getElementById('loginModal').style.display = 'block';
refreshCaptcha();
throw new Error('Session check failed');
}
} catch (error) {
console.error('Error during initialization:', error);
localStorage.removeItem('isLoggedIn');
document.getElementById('loginModal').style.display = 'block';
document.getElementById('loginModal').style.display = 'flex';
refreshCaptcha();
} finally {
document.getElementById('loadingIndicator').style.display = 'none';
}
};
function updateAdImage(adImages) {
const adContainer = document.getElementById('adContainer');
adContainer.innerHTML = '';
@@ -1002,7 +1281,9 @@
document.addEventListener('DOMContentLoaded', function() {
const sidebarItems = document.querySelectorAll('.sidebar li');
const contentSections = document.querySelectorAll('.content-section');
if (isLoggedIn) {
initEditor();
}
function showSection(sectionId) {
contentSections.forEach(section => {
if (section.id === sectionId) {

View File

@@ -5,6 +5,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Docker 镜像代理加速</title>
<link rel="icon" href="https://cdn.jsdelivr.net/gh/dqzboy/Blog-Image/BlogCourse/docker-proxy.png" type="image/png">
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Lato', 'Helvetica', 'Arial', sans-serif;
@@ -419,6 +420,7 @@
<div class="tab-container">
<div class="tab active" onclick="switchTab('accelerate')">镜像加速</div>
<div class="tab" onclick="switchTab('search')">镜像搜索</div>
<div class="tab" onclick="switchTab('documentation')">文档教程</div>
</div>
<!-- 镜像加速内容 -->
@@ -443,6 +445,12 @@
<div id="searchResults"></div>
</div>
<!-- 文档教程内容 -->
<div id="documentationContent" class="content">
<div id="documentList"></div>
<div id="documentationText"></div>
</div>
<!-- 广告容器 -->
<div class="carousel" id="carousel">
<div class="carousel-inner" id="carouselInner">
@@ -485,21 +493,23 @@
tabs.forEach(tab => tab.classList.remove('active'));
contents.forEach(content => content.classList.remove('active'));
document.querySelector(`.tab:nth-child(${tabName === 'accelerate' ? '1' : '2'}`).classList.add('active');
document.querySelector(`.tab:nth-child(${tabName === 'accelerate' ? '1' : (tabName === 'search' ? '2' : '3')}`).classList.add('active');
document.getElementById(`${tabName}Content`).classList.add('active');
// 重置显示
document.getElementById('searchResults').style.display = 'none';
document.getElementById('result').style.display = 'none';
document.getElementById('carousel').style.display = 'flex';
document.getElementById('backToTopBtn').style.display = 'none';
document.getElementById('carousel').style.display = 'flex'; // 默认显示广告
if (tabName === 'accelerate') {
if (tabName === 'documentation') {
fetchDocumentation();
} else if (tabName === 'accelerate') {
const imageInput = document.getElementById('imageInput').value.trim();
if (imageInput) {
generateCommands(imageInput);
}
} else {
} else if (tabName === 'search') {
document.getElementById('searchInput').value = '';
}
}
@@ -642,6 +652,8 @@
const searchResults = document.getElementById('searchResults');
searchResults.innerHTML = '正在搜索...';
// 显示和隐藏广告逻辑
searchResults.style.display = 'block';
document.getElementById('result').style.display = 'none';
document.getElementById('carousel').style.display = 'none';
@@ -708,11 +720,108 @@
}
}
async function fetchDocumentation() {
try {
const response = await fetch('/api/documentation');
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const documents = await response.json();
console.log('Fetched documents:', documents);
const documentList = window.document.getElementById('documentList');
const documentationText = window.document.getElementById('documentationText');
if (Array.isArray(documents) && documents.length > 0) {
documentList.innerHTML = '<h2>文档列表</h2>';
const ul = window.document.createElement('ul');
documents.forEach(doc => {
if (doc && doc.id && doc.title) {
const li = window.document.createElement('li');
li.innerHTML = `<a href="javascript:void(0);" onclick="showDocument('${doc.id}')">${doc.title}</a>`;
ul.appendChild(li);
}
});
documentList.appendChild(ul);
// 默认显示第一篇文档
if (documents[0] && documents[0].id) {
showDocument(documents[0].id);
} else {
documentationText.innerHTML = '无法显示文档,文档 ID 无效';
}
} else {
documentationText.innerHTML = '暂无文档内容';
}
window.document.getElementById('carousel').style.display = 'none';
} catch (error) {
console.error('获取文档列表失败:', error);
const documentationText = window.document.getElementById('documentationText');
documentationText.innerHTML = '加载文档列表失败,请稍后再试。错误详情: ' + error.message;
window.document.getElementById('carousel').style.display = 'flex';
}
}
async function showDocument(id) {
try {
console.log('Attempting to show document with id:', id);
const response = await fetch(`/api/documentation/${id}`);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
const document = await response.json();
console.log('Fetched document:', document);
const documentationText = window.document.getElementById('documentationText');
if (document && document.content && document.content.trim() !== '') {
documentationText.innerHTML = `<h2>${document.title || '无标题'}</h2>` + marked.parse(document.content);
} else {
documentationText.innerHTML = '该文档没有内容或格式不正确';
}
} catch (error) {
console.error('获取文档内容失败:', error);
const documentationText = window.document.getElementById('documentationText');
documentationText.innerHTML = '加载文档内容失败,请稍后再试。错误详情: ' + error.message;
}
}
// 获取并加载配置
async function loadConfig() {
try {
const response = await fetch('/api/config');
const config = await response.json();
if (config.adImages && Array.isArray(config.adImages)) {
const carouselInner = document.getElementById('carouselInner');
carouselInner.innerHTML = ''; // 清空广告容器
config.adImages.forEach((ad, index) => {
const adItem = document.createElement('div');
adItem.className = 'carousel-item';
if (index === 0) {
adItem.classList.add('active');
}
const adImage = document.createElement('img');
adImage.src = ad.url;
adImage.alt = ad.alt || '广告图片';
adImage.style.width = "100%";
adImage.style.height = "100%";
adItem.appendChild(adImage);
carouselInner.appendChild(adItem);
// 添加点击事件监听器
adItem.addEventListener('click', function() {
window.open(ad.link, '_blank');
});
// 改变鼠标样式,表明可以点击
adItem.style.cursor = 'pointer';
});
items = document.querySelectorAll('.carousel-item');
updateCarousel();
startAutoPlay(); // 启动自动播放
}
if (config.logo) {
document.querySelector('.logo').src = config.logo;
}
@@ -781,5 +890,7 @@
}
});
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/2.0.3/marked.min.js"></script>
</body>
</html>