200 lines
8.1 KiB
HTML
200 lines
8.1 KiB
HTML
{% extends "admin/base.html" %}
|
||
|
||
{% block main_content %}
|
||
<section class="form-panel">
|
||
<div class="table-head">
|
||
<div>
|
||
<h2>新建 Markdown 文章</h2>
|
||
<div class="table-note">直接生成 `content/posts/*.md` 文件,后端会自动解析 frontmatter、同步分类和标签。</div>
|
||
</div>
|
||
</div>
|
||
|
||
<form method="post" action="/admin/posts" class="form-grid">
|
||
<div class="field">
|
||
<label>标题</label>
|
||
<input type="text" name="title" value="{{ create_form.title }}" required>
|
||
</div>
|
||
<div class="field">
|
||
<label>Slug</label>
|
||
<input type="text" name="slug" value="{{ create_form.slug }}" placeholder="可留空自动生成">
|
||
</div>
|
||
<div class="field">
|
||
<label>分类</label>
|
||
<input type="text" name="category" value="{{ create_form.category }}" placeholder="例如 tech">
|
||
</div>
|
||
<div class="field">
|
||
<label>标签</label>
|
||
<input type="text" name="tags" value="{{ create_form.tags }}" placeholder="逗号分隔">
|
||
</div>
|
||
<div class="field">
|
||
<label>文章类型</label>
|
||
<input type="text" name="post_type" value="{{ create_form.post_type }}">
|
||
</div>
|
||
<div class="field">
|
||
<label>封面图</label>
|
||
<input type="text" name="image" value="{{ create_form.image }}" placeholder="可选">
|
||
</div>
|
||
<div class="field field-wide">
|
||
<label>摘要</label>
|
||
<textarea name="description">{{ create_form.description }}</textarea>
|
||
</div>
|
||
<div class="field field-wide">
|
||
<label>正文 Markdown</label>
|
||
<textarea name="content" style="min-height: 22rem; font-family: var(--font-mono); line-height: 1.65;">{{ create_form.content }}</textarea>
|
||
</div>
|
||
<div class="field field-wide">
|
||
<div class="actions">
|
||
<label class="chip"><input type="checkbox" name="published" checked style="margin-right: 8px;">发布</label>
|
||
<label class="chip"><input type="checkbox" name="pinned" style="margin-right: 8px;">置顶</label>
|
||
<button type="submit" class="btn btn-primary">创建文章</button>
|
||
</div>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<section class="form-panel">
|
||
<div class="table-head">
|
||
<div>
|
||
<h2>导入 Markdown 文件</h2>
|
||
<div class="table-note">支持选择单个 `.md/.markdown` 文件,也支持直接选择一个本地 Markdown 文件夹批量导入。</div>
|
||
</div>
|
||
</div>
|
||
|
||
<form id="markdown-import-form" class="form-grid">
|
||
<div class="field">
|
||
<label>选择文件</label>
|
||
<input id="markdown-files" type="file" accept=".md,.markdown" multiple>
|
||
</div>
|
||
<div class="field">
|
||
<label>选择文件夹</label>
|
||
<input id="markdown-folder" type="file" accept=".md,.markdown" webkitdirectory directory multiple>
|
||
</div>
|
||
<div class="field field-wide">
|
||
<div class="actions">
|
||
<button id="import-submit" type="submit" class="btn btn-success">导入 Markdown</button>
|
||
</div>
|
||
<div class="field-hint" style="margin-top: 10px;">导入时会从 frontmatter 和正文里提取标题、slug、摘要、分类、标签与内容,并写入服务器 `content/posts`。</div>
|
||
<div id="import-notice" class="notice"></div>
|
||
</div>
|
||
</form>
|
||
</section>
|
||
|
||
<section class="table-panel">
|
||
<div class="table-head">
|
||
<div>
|
||
<h2>内容列表</h2>
|
||
<div class="table-note">直接跳到前台文章、分类筛选和 API 明细。</div>
|
||
</div>
|
||
</div>
|
||
|
||
{% if rows | length > 0 %}
|
||
<div class="table-wrap">
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>ID</th>
|
||
<th>文章</th>
|
||
<th>分类</th>
|
||
<th>标签</th>
|
||
<th>时间</th>
|
||
<th>跳转</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for row in rows %}
|
||
<tr>
|
||
<td class="mono">#{{ row.id }}</td>
|
||
<td>
|
||
<div class="item-title">
|
||
<strong>{{ row.title }}</strong>
|
||
<span class="item-meta">{{ row.slug }}</span>
|
||
<span class="item-meta">{{ row.file_path }}</span>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
<div class="item-title">
|
||
<strong>{{ row.category_name }}</strong>
|
||
<a href="{{ row.category_frontend_url }}" class="inline-link" target="_blank" rel="noreferrer noopener">查看该分类文章</a>
|
||
</div>
|
||
</td>
|
||
<td>
|
||
<div class="inline-links">
|
||
{% if row.tags | length > 0 %}
|
||
{% for tag in row.tags %}
|
||
<span class="chip">#{{ tag }}</span>
|
||
{% endfor %}
|
||
{% else %}
|
||
<span class="badge-soft">暂无标签</span>
|
||
{% endif %}
|
||
</div>
|
||
</td>
|
||
<td class="mono">{{ row.created_at }}</td>
|
||
<td>
|
||
<div class="actions">
|
||
<a href="{{ row.edit_url }}" class="btn btn-success">编辑 Markdown</a>
|
||
<a href="{{ row.frontend_url }}" class="btn btn-primary" target="_blank" rel="noreferrer noopener">前台详情</a>
|
||
<a href="{{ row.api_url }}" class="btn btn-ghost" target="_blank" rel="noreferrer noopener">API</a>
|
||
</div>
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
{% else %}
|
||
<div class="empty">当前没有可管理的文章数据。</div>
|
||
{% endif %}
|
||
</section>
|
||
{% endblock %}
|
||
|
||
{% block page_scripts %}
|
||
<script>
|
||
const importForm = document.getElementById("markdown-import-form");
|
||
const importFiles = document.getElementById("markdown-files");
|
||
const importFolder = document.getElementById("markdown-folder");
|
||
const importNotice = document.getElementById("import-notice");
|
||
|
||
function showImportNotice(message, kind) {
|
||
importNotice.textContent = message;
|
||
importNotice.className = "notice show " + (kind === "success" ? "notice-success" : "notice-error");
|
||
}
|
||
|
||
importForm?.addEventListener("submit", async (event) => {
|
||
event.preventDefault();
|
||
|
||
const selectedFiles = [
|
||
...(importFiles?.files ? Array.from(importFiles.files) : []),
|
||
...(importFolder?.files ? Array.from(importFolder.files) : []),
|
||
].filter((file) => file.name.endsWith(".md") || file.name.endsWith(".markdown"));
|
||
|
||
if (!selectedFiles.length) {
|
||
showImportNotice("请先选择要导入的 Markdown 文件或文件夹。", "error");
|
||
return;
|
||
}
|
||
|
||
const payload = new FormData();
|
||
selectedFiles.forEach((file) => {
|
||
const uploadName = file.webkitRelativePath || file.name;
|
||
payload.append("files", file, uploadName);
|
||
});
|
||
|
||
try {
|
||
const response = await fetch("/admin/posts/import", {
|
||
method: "POST",
|
||
body: payload,
|
||
});
|
||
|
||
if (!response.ok) {
|
||
throw new Error(await response.text() || "import failed");
|
||
}
|
||
|
||
const result = await response.json();
|
||
showImportNotice(`已导入 ${result.count} 个 Markdown 文件,正在刷新列表。`, "success");
|
||
setTimeout(() => window.location.reload(), 900);
|
||
} catch (error) {
|
||
showImportNotice("导入失败:" + (error?.message || "unknown error"), "error");
|
||
}
|
||
});
|
||
</script>
|
||
{% endblock %}
|