Sách Giáo Khoa Việt Nam - sachgiaokhoa.org

CodeIgniter 4 website:
- Catalog 999 SGK (lớp 1-12) từ API, lọc theo lớp/môn/NXB, tìm kiếm
- Trang chủ grouped browse (bậc học -> lớp -> môn, ưu tiên môn quan trọng)
- Chi tiết sách: metadata đầy đủ, cover nghệ thuật /thumb/{W}x{H}/
- Đọc online qua gate captcha + countdown, PDF viewer pdf.js
- Download: captcha + token một lần + auto cache-bust version
- Tự đồng bộ catalog/cover khi API thay đổi (refresh:catalog)
- Legal: DMCA, miễn trừ trách nhiệm, điều khoản, bảo mật, liên hệ
This commit is contained in:
sachgiaokhoaorg
2026-08-23 07:05:49 +00:00
commit e6b265db99
494 changed files with 134829 additions and 0 deletions
+103
View File
@@ -0,0 +1,103 @@
<?= $this->extend('layouts/main') ?>
<?= $this->section('content') ?>
<div class="hero">
<h1>
<?php if ($filters['q'] !== ''): ?>
Kết quả tìm kiếm "<?= esc($filters['q']) ?>"
<?php elseif ($filters['lop'] !== ''): ?>
Sách lớp <?= esc($filters['lop']) ?>
<?php elseif ($filters['mon'] !== ''): ?>
Môn <?= esc($filters['mon']) ?>
<?php else: ?>
Sách <?= esc($filters['nxb']) ?>
<?php endif; ?>
</h1>
<p><?= number_format($total) ?> đầu sách phù hợp.</p>
</div>
<form class="filters" method="get" action="/">
<div class="grade-pills">
<a href="<?= $filters['q'] !== '' ? '/?q=' . urlencode($filters['q']) : '/' ?>" class="<?= $filters['lop'] === '' ? 'active' : '' ?>">Tất cả</a>
<?php foreach ($grades as $g): ?>
<a class="<?= $filters['lop'] === (string) $g ? 'active' : '' ?>"
href="/?<?= esc(http_build_query(array_merge($_GET, ['trang' => 1, 'lop' => $g]))) ?>">Lớp <?= $g ?></a>
<?php endforeach; ?>
</div>
<label>Môn học
<select name="mon" onchange="this.form.submit()">
<option value="">-- Tất cả --</option>
<?php foreach ($subjects as $s => $n): ?>
<option value="<?= esc($s) ?>" <?= $filters['mon'] === $s ? 'selected' : '' ?>><?= esc($s ?: 'Khác') ?> (<?= $n ?>)</option>
<?php endforeach; ?>
</select>
</label>
<label>Nhà xuất bản
<select name="nxb" onchange="this.form.submit()">
<option value="">-- Tất cả --</option>
<?php foreach ($publishers as $p => $n): ?>
<option value="<?= esc($p) ?>" <?= $filters['nxb'] === $p ? 'selected' : '' ?>><?= esc($p) ?> (<?= $n ?>)</option>
<?php endforeach; ?>
</select>
</label>
<?php if ($filters['q'] !== ''): ?><input type="hidden" name="q" value="<?= esc($filters['q']) ?>"><?php endif; ?>
<button class="btn btn-primary" type="submit">Lọc</button>
<a class="btn btn-outline reset" href="/">Xóa lọc</a>
</form>
<?php foreach ($grouped as $groupLabel => $groupBooks): ?>
<div class="group-header">
<h2><?= esc($groupLabel) ?> <span class="group-count"><?= count($groupBooks) ?> sách</span></h2>
</div>
<div class="book-grid">
<?php foreach ($groupBooks as $b): ?>
<?php $m = $b['meta']; ?>
<a class="book-card" href="/sach/<?= esc($b['id']) ?>">
<div class="book-cover">
<img loading="lazy" src="/thumb/360x480/<?= esc($b['id']) ?>.jpg?v=<?= media_version() ?>" alt="Bìa <?= esc($b['title']) ?>"
onerror="this.src='data:image/svg+xml,%3Csvg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 3 4%22%3E%3Crect width=%223%22 height=%224%22 fill=%22%23e8ecf4%22/%3E%3C/svg%3E'">
<?php if (! empty($m['pages'])): ?><span class="cover-pages"><?= (int) $m['pages'] ?> tr</span><?php endif; ?>
</div>
<div class="book-info">
<h3><?= esc($b['title']) ?></h3>
<?php if ($b['subject']): ?><span class="book-subject"><?= esc($b['subject']) ?></span><?php endif; ?>
<div class="badges">
<?php if ($b['grade']): ?><span class="badge">Lớp <?= esc($b['grade']) ?></span><?php endif; ?>
<?php if ($b['publisher']): ?><span class="badge pub"><?= esc($b['publisher']) ?></span><?php endif; ?>
</div>
<div class="book-meta">
<?php if (! empty($m['sizeMB'])): ?><span>💾 <?= number_format((float) $m['sizeMB'], 1) ?> MB</span><?php endif; ?>
<?php if (! empty($m['authors'])): ?><span class="authors">✍ <?= esc(mb_strimwidth($m['authors'], 0, 46, '…')) ?></span><?php endif; ?>
</div>
</div>
</a>
<?php endforeach; ?>
</div>
<?php endforeach; ?>
<?php if (empty($grouped)): ?>
<div class="legal-page"><p>Không tìm thấy sách phù hợp với bộ lọc. <a href="/">Xóa bộ lọc</a>.</p></div>
<?php endif; ?>
<?php if ($totalPage > 1): ?>
<div class="pagination">
<?php
$qs = fn($p) => '/?' . esc(http_build_query(array_merge($_GET, ['trang' => $p])));
?>
<?php if ($page > 1): ?><a href="<?= $qs($page - 1) ?>"> Trước</a><?php else: ?><span class="disabled"> Trước</span><?php endif; ?>
<?php
$start = max(1, $page - 2);
$end = min($totalPage, $start + 4);
$start = max(1, $end - 4);
for ($p = $start; $p <= $end; $p++):
?>
<?php if ($p === $page): ?><span class="active"><?= $p ?></span>
<?php else: ?><a href="<?= $qs($p) ?>"><?= $p ?></a><?php endif; ?>
<?php endfor; ?>
<?php if ($page < $totalPage): ?><a href="<?= $qs($page + 1) ?>">Sau </a><?php else: ?><span class="disabled">Sau </span><?php endif; ?>
</div>
<?php endif; ?>
<?= $this->endSection() ?>