199 lines
8.5 KiB
PHP
199 lines
8.5 KiB
PHP
<?php
|
||
/**
|
||
* article/index.php —— 文章区(文章库)
|
||
* 结构:顶部栏(返回首页 + 当页名 + 分区/段落开关)
|
||
* 内容三栏:左 15% 文章标题栏(按分区) / 中 15% 当前文章段落目录 / 右 70% 正文(Markdown 渲染)
|
||
* 文章正文存 SQLite;图片上传到 data/articles/img/,正文以 data/articles/img/ 站点相对路径引用,
|
||
* 渲染时自动补全前缀。三栏宽度可拖拽调节,标题栏 / 段落栏可分别隐藏。
|
||
* 打开页面为「先列表后阅读」:默认右栏展示全部分区文章列表,点击文章进入阅读。
|
||
*/
|
||
$P = '../';
|
||
require_once dirname(__DIR__) . '/includes/layout.php';
|
||
|
||
$pdo = db();
|
||
$noteText = setting_get('article_note', '');
|
||
|
||
// 全部可见文章(先按最后更新时间倒序拉取,保证各分区内均为“更新在前”排序)
|
||
$all = $pdo->query('SELECT id, part, title, summary, updated_at
|
||
FROM articles WHERE enabled = 1 ORDER BY updated_at DESC, id DESC')->fetchAll();
|
||
$byPart = [];
|
||
foreach ($all as $r) {
|
||
$part = trim((string)$r['part']);
|
||
if ($part === '') {
|
||
$part = '未分类';
|
||
}
|
||
$byPart[$part][] = $r;
|
||
}
|
||
// 分区顺序:后台「文章管理」拖出的分区顺序(article_part_order)优先;未配置则按各分区最新更新时间降序
|
||
$poIdx = [];
|
||
$poCfg = trim((string)setting_get('article_part_order', ''));
|
||
if ($poCfg !== '') {
|
||
foreach (explode(',', $poCfg) as $po) {
|
||
$po = trim($po);
|
||
if ($po !== '' && isset($byPart[$po])) $poIdx[$po] = count($poIdx);
|
||
}
|
||
}
|
||
$partKeys = array_keys($byPart);
|
||
usort($partKeys, static function (string $a, string $b) use ($poIdx, $byPart): int {
|
||
$ia = $poIdx[$a] ?? PHP_INT_MAX;
|
||
$ib = $poIdx[$b] ?? PHP_INT_MAX;
|
||
if ($ia !== $ib) return $ia <=> $ib;
|
||
// 各分区首篇即最新(SQL 已按 updated_at 倒序)
|
||
$d = strcmp((string)$byPart[$b][0]['updated_at'], (string)$byPart[$a][0]['updated_at']);
|
||
return $d !== 0 ? $d : strcmp($a, $b);
|
||
});
|
||
$rows = [];
|
||
foreach ($partKeys as $p) {
|
||
$rows[] = ['part' => $p, 'articles' => $byPart[$p]];
|
||
}
|
||
$total = count($all);
|
||
// 从库首页“文章区”卡片进入阅读时(?from=nav),顶部返回按钮语义切换
|
||
$fromNav = (isset($_GET['from']) && (string)$_GET['from'] === 'nav');
|
||
|
||
// 当前阅读文章
|
||
$cur = null;
|
||
$cid = (int)($_GET['id'] ?? 0);
|
||
if ($cid > 0) {
|
||
$st = $pdo->prepare('SELECT * FROM articles WHERE id = ? AND enabled = 1 LIMIT 1');
|
||
$st->execute([$cid]);
|
||
$cur = $st->fetch();
|
||
if (!$cur) {
|
||
$cur = null; // 无效或已隐藏:回退到列表
|
||
}
|
||
}
|
||
|
||
layout_head($cur ? (string)$cur['title'] : '文章库');
|
||
?>
|
||
<style>
|
||
/* 文章区专用:将外层正文区铺满视口 */
|
||
html, body { height: 100%; }
|
||
</style>
|
||
<div class="art-app js-art-app">
|
||
<!-- 顶部栏:返回首页 / 当页名(列表为文章库、阅读时显示文章标题)/ 分区·段落开关 -->
|
||
<header class="art-topbar">
|
||
<div class="art-topbar-nav">
|
||
<?php if ($fromNav): ?>
|
||
<a class="btn btn-sm" href="index.php" title="返回文章区首页(文章列表)">← 文章区首页</a>
|
||
<?php if ($cur): ?>
|
||
<a class="btn btn-sm" href="javascript:history.back()" title="返回上一页(回到来源页面)">← 返回上一页</a>
|
||
<?php endif; ?>
|
||
<?php else: ?>
|
||
<a class="btn btn-sm" href="../index.php" title="返回站点首页">← 首页</a>
|
||
<?php if ($cur): ?>
|
||
<a class="btn btn-sm" href="index.php" title="返回文章区首页(文章列表)">← 文章区首页</a>
|
||
<?php endif; ?>
|
||
<?php endif; ?>
|
||
</div>
|
||
<div class="art-topbar-title"><?= $cur ? he((string)$cur['title']) : '📚 文章库' ?></div>
|
||
<div class="art-topbar-acts">
|
||
<button type="button" class="btn btn-sm js-art-toggle" data-col="title" title="显示 / 隐藏左侧文章标题栏">☰<span class="art-tg-txt">标题栏</span></button>
|
||
<button type="button" class="btn btn-sm js-art-toggle" data-col="toc" title="显示 / 隐藏中间段落目录">≡<span class="art-tg-txt">段落</span></button>
|
||
</div>
|
||
</header>
|
||
|
||
<?php if ($noteText !== ''): ?>
|
||
<div class="art-note"><?= he($noteText) ?></div>
|
||
<?php endif; ?>
|
||
|
||
<div class="art-frame js-art-frame">
|
||
<!-- 左:文章标题栏(按分区) -->
|
||
<aside class="art-col art-col-title js-col-title">
|
||
<div class="art-col-head">
|
||
<span>文章目录</span>
|
||
<button type="button" class="art-mini js-art-toggle" data-col="title" title="隐藏标题栏">✕</button>
|
||
</div>
|
||
<nav class="art-nav js-art-nav">
|
||
<?php if (!$rows): ?>
|
||
<p class="tip" style="padding:10px">暂无文章</p>
|
||
<?php endif; ?>
|
||
<?php foreach ($rows as $g): ?>
|
||
<div class="art-nav-part">
|
||
<div class="art-nav-part-name"><?= he($g['part']) ?></div>
|
||
<?php foreach ($g['articles'] as $a): ?>
|
||
<a class="art-nav-item<?= ($cur && (int)$cur['id'] === (int)$a['id']) ? ' active' : '' ?>"
|
||
href="?id=<?= (int)$a['id'] ?>" title="<?= he((string)$a['summary']) ?>"><?= he((string)$a['title']) ?></a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</nav>
|
||
</aside>
|
||
|
||
<div class="art-gutter js-gutter" data-var="--w-title"></div>
|
||
|
||
<!-- 中:段落目录 -->
|
||
<aside class="art-col art-col-toc js-col-toc">
|
||
<div class="art-col-head">
|
||
<span>段落目录</span>
|
||
<button type="button" class="art-mini js-art-toggle" data-col="toc" title="隐藏段落栏">✕</button>
|
||
</div>
|
||
<nav class="art-toc js-toc-nav"></nav>
|
||
</aside>
|
||
|
||
<div class="art-gutter js-gutter" data-var="--w-toc"></div>
|
||
|
||
<!-- 右:正文 / 列表 -->
|
||
<section class="art-body js-art-body" id="artBody">
|
||
<?php if ($cur): ?>
|
||
<?php
|
||
$md = (string)$cur['markdown'];
|
||
$updated = (string)($cur['updated_at'] ?? '');
|
||
$part = (string)$cur['part'];
|
||
?>
|
||
<div class="art-article js-article" data-id="<?= (int)$cur['id'] ?>">
|
||
<div class="art-article-head">
|
||
<div class="art-article-part"><?= he($part) ?></div>
|
||
<h1 class="art-article-title js-art-title"><?= he((string)$cur['title']) ?></h1>
|
||
<?php if (trim((string)($cur['summary'] ?? '')) !== ''): ?>
|
||
<p class="art-article-sum"><?= he(trim((string)$cur['summary'])) ?></p>
|
||
<?php endif; ?>
|
||
<?php if ($updated !== ''): ?>
|
||
<div class="art-article-meta">更新于 <?= he($updated) ?></div>
|
||
<?php endif; ?>
|
||
</div>
|
||
<div class="art-md js-art-md" id="artMd"></div>
|
||
<textarea id="artRaw" hidden><?= he($md) ?></textarea>
|
||
<script type="text/plain" id="artErrTpl"><div class="err-msg">正文渲染失败:无法加载 Markdown 解析库,请检查网络后刷新。</div></script>
|
||
</div>
|
||
<?php else: ?>
|
||
<div class="art-list">
|
||
<div class="art-list-head">
|
||
<h1>全部文章 <span class="tip">(共 <?= $total ?> 篇)</span></h1>
|
||
<p class="tip st-tip">点击任意文章开始阅读;左侧「文章目录」可随时切换文章,中间「段落目录」可快速跳转。</p>
|
||
</div>
|
||
<?php if (!$rows): ?>
|
||
<div class="panel-card"><p class="tip">文章库还没有内容<span class="st-tip">,站长可在后台「文章管理」中发布</span>。</p></div>
|
||
<?php endif; ?>
|
||
<?php foreach ($rows as $g): ?>
|
||
<div class="art-part-block">
|
||
<h2 class="art-part-title"><?= he($g['part']) ?></h2>
|
||
<div class="art-cards">
|
||
<?php foreach ($g['articles'] as $a): ?>
|
||
<a class="art-card" href="?id=<?= (int)$a['id'] ?>">
|
||
<span class="art-card-title"><?= he((string)$a['title']) ?></span>
|
||
<?php if (trim((string)($a['summary'] ?? '')) !== ''): ?>
|
||
<span class="art-card-sum"><?= he(trim((string)$a['summary'])) ?></span>
|
||
<?php endif; ?>
|
||
<span class="art-card-date"><?= he((string)($a['updated_at'] ?? '')) ?></span>
|
||
</a>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
</div>
|
||
<?php endforeach; ?>
|
||
</div>
|
||
<?php endif; ?>
|
||
</section>
|
||
</div>
|
||
</div>
|
||
|
||
<?php layout_theme_fab(); ?>
|
||
<script>
|
||
window.HP_ART_DATA = {
|
||
curId: <?= $cur ? (int)$cur['id'] : 0 ?>,
|
||
listMode: <?= $cur ? 0 : 1 ?>
|
||
};
|
||
</script>
|
||
<script src="../assets/js/common.js"></script>
|
||
<script src="../assets/js/article.js"></script>
|
||
</body>
|
||
</html>
|