library/index.html

734 lines
26 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<title>智慧图书馆管理系统 | 交互式图书管理</title>
<!-- Font Awesome 6 图标库 -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css">
<!-- 使用 Google Fonts 提升字体观感 -->
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,300;14..32,400;14..32,500;14..32,600;14..32,700&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background: #f1f5f9;
color: #0f172a;
padding: 24px 20px;
}
/* 主容器 */
.library-container {
max-width: 1400px;
margin: 0 auto;
}
/* 头部设计 */
.header {
background: linear-gradient(135deg, #1e3c72 0%, #2b3b4e 100%);
border-radius: 32px;
padding: 20px 28px;
margin-bottom: 28px;
color: white;
box-shadow: 0 10px 25px -5px rgba(0,0,0,0.1);
display: flex;
flex-wrap: wrap;
justify-content: space-between;
align-items: center;
}
.logo-area h1 {
font-size: 1.8rem;
font-weight: 700;
letter-spacing: -0.3px;
}
.logo-area p {
font-size: 0.85rem;
opacity: 0.85;
margin-top: 6px;
}
.stats-badge {
display: flex;
gap: 24px;
background: rgba(255,255,255,0.12);
padding: 12px 24px;
border-radius: 60px;
backdrop-filter: blur(4px);
}
.stat-item {
text-align: center;
}
.stat-number {
font-size: 1.5rem;
font-weight: 800;
}
.stat-label {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 1px;
}
/* 工具栏 + 搜索区域 */
.action-bar {
background: white;
border-radius: 28px;
padding: 16px 24px;
margin-bottom: 28px;
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
display: flex;
flex-wrap: wrap;
gap: 16px;
align-items: center;
justify-content: space-between;
}
.search-group {
display: flex;
flex: 2;
min-width: 240px;
background: #f8fafc;
border-radius: 60px;
border: 1px solid #e2e8f0;
overflow: hidden;
transition: all 0.2s;
}
.search-group:focus-within {
border-color: #3b82f6;
box-shadow: 0 0 0 3px rgba(59,130,246,0.2);
}
.search-group input {
flex: 1;
padding: 12px 20px;
border: none;
background: transparent;
font-size: 0.95rem;
outline: none;
}
.search-group button {
background: #3b82f6;
border: none;
padding: 0 24px;
color: white;
cursor: pointer;
transition: 0.2s;
font-weight: 500;
}
.search-group button:hover {
background: #2563eb;
}
.btn-primary {
background: #1e293b;
border: none;
padding: 10px 24px;
border-radius: 40px;
color: white;
font-weight: 600;
cursor: pointer;
display: inline-flex;
align-items: center;
gap: 8px;
transition: all 0.2s;
}
.btn-primary i { font-size: 1rem; }
.btn-primary:hover {
background: #0f172a;
transform: translateY(-1px);
}
.btn-outline {
background: transparent;
border: 1px solid #cbd5e1;
padding: 8px 18px;
border-radius: 40px;
font-weight: 500;
cursor: pointer;
transition: 0.2s;
}
.btn-outline:hover {
background: #f1f5f9;
border-color: #3b82f6;
}
/* 图书网格展示区 */
.books-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 24px;
margin-bottom: 40px;
}
.book-card {
background: white;
border-radius: 28px;
overflow: hidden;
transition: all 0.25s ease;
box-shadow: 0 5px 15px rgba(0,0,0,0.05);
border: 1px solid #eef2ff;
display: flex;
flex-direction: column;
}
.book-card:hover {
transform: translateY(-5px);
box-shadow: 0 20px 30px -12px rgba(0,0,0,0.15);
border-color: #cbdffc;
}
.book-cover {
background: linear-gradient(145deg, #e6f0ff, #d9e6fc);
height: 140px;
display: flex;
align-items: center;
justify-content: center;
font-size: 3rem;
color: #2c5282;
}
.book-info {
padding: 20px 18px 18px;
flex: 1;
}
.book-title {
font-weight: 800;
font-size: 1.2rem;
margin-bottom: 8px;
display: flex;
justify-content: space-between;
}
.book-author {
color: #4b5563;
font-size: 0.85rem;
margin-bottom: 12px;
display: flex;
gap: 12px;
flex-wrap: wrap;
}
.book-meta {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 12px;
font-size: 0.8rem;
border-top: 1px solid #eef2ff;
padding-top: 12px;
}
.status {
padding: 4px 12px;
border-radius: 30px;
font-weight: 600;
font-size: 0.7rem;
}
.status.available {
background: #dcfce7;
color: #15803d;
}
.status.borrowed {
background: #fee2e2;
color: #b91c1c;
}
.card-actions {
display: flex;
gap: 10px;
margin-top: 14px;
}
.btn-sm {
flex: 1;
text-align: center;
padding: 8px 0;
border-radius: 40px;
font-size: 0.75rem;
font-weight: 600;
cursor: pointer;
transition: 0.2s;
background: #f1f5f9;
border: none;
}
.btn-borrow {
background: #3b82f6;
color: white;
}
.btn-borrow:hover {
background: #2563eb;
}
.btn-return {
background: #e2e8f0;
color: #1e293b;
}
.btn-return:hover {
background: #cbd5e1;
}
.btn-delete {
background: #fff1f0;
color: #e11d48;
}
.btn-delete:hover {
background: #ffe4e2;
}
/* 模态框 */
.modal {
display: none;
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.5);
backdrop-filter: blur(5px);
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: white;
border-radius: 36px;
max-width: 500px;
width: 90%;
padding: 28px 24px;
box-shadow: 0 25px 40px rgba(0,0,0,0.2);
animation: fadeSlideUp 0.2s ease;
}
@keyframes fadeSlideUp {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
.modal-content h3 {
font-size: 1.6rem;
margin-bottom: 20px;
}
.modal-content input, .modal-content select {
width: 100%;
padding: 12px 16px;
margin: 12px 0;
border: 1px solid #cbd5e1;
border-radius: 28px;
font-family: inherit;
outline: none;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 12px;
margin-top: 20px;
}
.empty-message {
text-align: center;
padding: 60px 20px;
background: white;
border-radius: 40px;
color: #64748b;
}
footer {
text-align: center;
font-size: 0.75rem;
margin-top: 32px;
color: #5a6e8a;
}
@media (max-width: 680px) {
.action-bar { flex-direction: column; align-items: stretch; }
.stats-badge { justify-content: space-between; width: 100%; }
}
</style>
</head>
<body>
<div class="library-container">
<!-- 头部区域 -->
<div class="header">
<div class="logo-area">
<h1><i class="fas fa-book-open"></i> 墨渊·智慧图书馆系统</h1>
<p>数字化借阅 | 实时库存 | 一键管理</p>
</div>
<div class="stats-badge">
<div class="stat-item"><div class="stat-number" id="totalBooksStat">0</div><div class="stat-label">馆藏总量</div></div>
<div class="stat-item"><div class="stat-number" id="availableBooksStat">0</div><div class="stat-label">可借图书</div></div>
<div class="stat-item"><div class="stat-number" id="borrowedBooksStat">0</div><div class="stat-label">借出中</div></div>
</div>
</div>
<!-- 操作栏:搜索 + 新增图书 -->
<div class="action-bar">
<div class="search-group">
<input type="text" id="searchInput" placeholder="按书名、作者或 ISBN 搜索图书...">
<button id="searchBtn"><i class="fas fa-search"></i> 检索</button>
</div>
<div style="display: flex; gap: 12px;">
<button class="btn-primary" id="addBookBtn"><i class="fas fa-plus-circle"></i> 新书入库</button>
<button class="btn-outline" id="resetFilterBtn"><i class="fas fa-undo-alt"></i> 全部书目</button>
</div>
</div>
<!-- 图书动态网格 -->
<div id="booksGridContainer" class="books-grid">
<!-- 卡片会通过js动态渲染 -->
<div class="empty-message"><i class="fas fa-spinner fa-pulse"></i> 加载藏书数据中...</div>
</div>
<footer>
<i class="fas fa-exchange-alt"></i> 支持借阅、归还、删除图书 · 数据实时同步至本地存储
</footer>
</div>
<!-- 添加 / 编辑图书模态框(复用为添加和编辑) -->
<div id="bookModal" class="modal">
<div class="modal-content">
<h3 id="modalTitle"><i class="fas fa-book"></i> 添加新书</h3>
<input type="text" id="bookTitle" placeholder="书名 *" autocomplete="off">
<input type="text" id="bookAuthor" placeholder="作者 *">
<input type="text" id="bookIsbn" placeholder="ISBN (选填)">
<select id="bookStatus">
<option value="available">可借阅</option>
<option value="borrowed">已借出</option>
</select>
<div class="modal-footer">
<button class="btn-outline" id="closeModalBtn">取消</button>
<button class="btn-primary" id="saveBookBtn">保存图书</button>
</div>
</div>
</div>
<script>
// ---------- 图书馆核心数据模型 ----------
let libraryBooks = []; // 存储图书对象
let currentFilter = ""; // 搜索关键词
let editingBookId = null; // 编辑模式下记录正在编辑的图书ID
// 预置样例数据 (更优雅的初始馆藏)
const defaultBooks = [
{ id: "lib_001", title: "深入理解计算机系统", author: "Randal E. Bryant", isbn: "9787111544937", status: "available", borrowCount: 0 },
{ id: "lib_002", title: "你当像鸟飞往你的山", author: "塔拉·韦斯特弗", isbn: "9787559444110", status: "borrowed", borrowCount: 3 },
{ id: "lib_003", title: "JavaScript 高级程序设计", author: "Nicholas C. Zakas", isbn: "9787115376257", status: "available", borrowCount: 1 },
{ id: "lib_004", title: "三体:黑暗森林", author: "刘慈欣", isbn: "9787536692930", status: "available", borrowCount: 5 },
{ id: "lib_005", title: "Python网络爬虫开发", author: "崔庆才", isbn: "9787115487519", status: "borrowed", borrowCount: 2 },
{ id: "lib_006", title: "杀死一只知更鸟", author: "哈珀·李", isbn: "9787544242666", status: "available", borrowCount: 1 },
{ id: "lib_007", title: "设计数据密集型应用", author: "Martin Kleppmann", isbn: "9787115518022", status: "available", borrowCount: 0 }
];
// 辅助函数生成简短唯一ID (足够使用)
function generateBookId() {
return 'bk_' + Date.now() + '_' + Math.random().toString(36).substr(2, 6);
}
// 初始化 / 加载本地存储
function loadDataFromStorage() {
const stored = localStorage.getItem("librarySystemData");
if (stored) {
try {
libraryBooks = JSON.parse(stored);
if (!libraryBooks.length) libraryBooks = [...defaultBooks];
} catch(e) { libraryBooks = [...defaultBooks]; }
} else {
libraryBooks = [...defaultBooks];
}
// 确保每个对象有 borrowCount 字段,避免老数据缺失
libraryBooks = libraryBooks.map(book => ({
...book,
borrowCount: book.borrowCount !== undefined ? book.borrowCount : 0
}));
saveToLocalStorage();
}
function saveToLocalStorage() {
localStorage.setItem("librarySystemData", JSON.stringify(libraryBooks));
}
// 更新统计数字
function updateStats() {
const total = libraryBooks.length;
const available = libraryBooks.filter(book => book.status === "available").length;
const borrowed = total - available;
document.getElementById("totalBooksStat").innerText = total;
document.getElementById("availableBooksStat").innerText = available;
document.getElementById("borrowedBooksStat").innerText = borrowed;
}
// 核心渲染:根据 currentFilter 过滤图书并展示网格
function renderBooks() {
const container = document.getElementById("booksGridContainer");
let filteredBooks = [...libraryBooks];
if (currentFilter.trim() !== "") {
const keyword = currentFilter.trim().toLowerCase();
filteredBooks = filteredBooks.filter(book =>
book.title.toLowerCase().includes(keyword) ||
book.author.toLowerCase().includes(keyword) ||
(book.isbn && book.isbn.toLowerCase().includes(keyword))
);
}
if (filteredBooks.length === 0) {
container.innerHTML = `<div class="empty-message"><i class="fas fa-book-dead"></i> 没有找到相关图书,试试其他关键词或 <button class="btn-outline" id="addFromEmptyBtn" style="margin-left:8px;">+ 添加新书</button></div>`;
const emptyBtn = document.getElementById("addFromEmptyBtn");
if (emptyBtn) emptyBtn.addEventListener("click", () => openAddModal());
updateStats();
return;
}
// 生成卡片HTML
let cardsHtml = "";
filteredBooks.forEach(book => {
const isAvailable = book.status === "available";
const statusClass = isAvailable ? "available" : "borrowed";
const statusText = isAvailable ? "可借阅" : "已借出";
// 封面图标随标题变化
const coverIcon = isAvailable ? "fa-book" : "fa-book-open-reader";
cardsHtml += `
<div class="book-card" data-id="${book.id}">
<div class="book-cover">
<i class="fas ${coverIcon} fa-3x"></i>
</div>
<div class="book-info">
<div class="book-title">
<span>${escapeHtml(book.title)}</span>
<span class="status ${statusClass}">${statusText}</span>
</div>
<div class="book-author">
<i class="fas fa-user-pen"></i> ${escapeHtml(book.author)}
${book.isbn ? `<span style="color:#6c757d;"><i class="fas fa-barcode"></i> ${escapeHtml(book.isbn)}</span>` : ''}
</div>
<div class="book-meta">
<span><i class="fas fa-chart-simple"></i> 借阅次数: ${book.borrowCount || 0}</span>
<span><i class="fas fa-fingerprint"></i> ID: ${book.id.slice(-6)}</span>
</div>
<div class="card-actions">
${isAvailable ?
`<button class="btn-sm btn-borrow borrow-action" data-id="${book.id}"><i class="fas fa-hand-peace"></i> 借阅</button>` :
`<button class="btn-sm btn-return return-action" data-id="${book.id}"><i class="fas fa-undo-alt"></i> 归还</button>`
}
<button class="btn-sm btn-outline edit-action" data-id="${book.id}"><i class="fas fa-edit"></i> 编辑</button>
<button class="btn-sm btn-delete delete-action" data-id="${book.id}"><i class="fas fa-trash-alt"></i> 删除</button>
</div>
</div>
</div>
`;
});
container.innerHTML = cardsHtml;
// 绑定卡片内部按钮事件 (事件委托也可,但直接绑定更清晰)
document.querySelectorAll('.borrow-action').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const id = btn.getAttribute('data-id');
borrowBookById(id);
});
});
document.querySelectorAll('.return-action').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const id = btn.getAttribute('data-id');
returnBookById(id);
});
});
document.querySelectorAll('.edit-action').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const id = btn.getAttribute('data-id');
openEditModal(id);
});
});
document.querySelectorAll('.delete-action').forEach(btn => {
btn.addEventListener('click', (e) => {
e.stopPropagation();
const id = btn.getAttribute('data-id');
deleteBookById(id);
});
});
updateStats();
}
// 简单防XSS
function escapeHtml(str) {
if (!str) return '';
return str.replace(/[&<>]/g, function(m) {
if (m === '&') return '&amp;';
if (m === '<') return '&lt;';
if (m === '>') return '&gt;';
return m;
}).replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, function(c) {
return c;
});
}
// 借阅逻辑
function borrowBookById(id) {
const book = libraryBooks.find(b => b.id === id);
if (!book) return;
if (book.status === 'borrowed') {
alert("❌ 这本书已被借出,无法重复借阅。");
return;
}
book.status = 'borrowed';
book.borrowCount = (book.borrowCount || 0) + 1;
saveToLocalStorage();
renderBooks(); // 刷新视图
showToastMessage(`📖 成功借阅《${book.title}》,请按时归还。`);
}
// 归还逻辑
function returnBookById(id) {
const book = libraryBooks.find(b => b.id === id);
if (!book) return;
if (book.status === 'available') {
alert("⚠️ 这本书当前可借阅状态,无需归还。");
return;
}
book.status = 'available';
saveToLocalStorage();
renderBooks();
showToastMessage(`✅ 归还成功!《${book.title}》已恢复在库。`);
}
// 删除图书 (确认)
function deleteBookById(id) {
const book = libraryBooks.find(b => b.id === id);
if (!book) return;
const confirmDel = confirm(`确定要永久删除《${book.title}》吗?此操作不可恢复。`);
if (confirmDel) {
libraryBooks = libraryBooks.filter(b => b.id !== id);
saveToLocalStorage();
renderBooks();
showToastMessage(`🗑️ 已删除图书《${book.title}`);
}
}
// 新增/保存图书 (添加模式 or 编辑模式)
function saveBookFromModal() {
const title = document.getElementById("bookTitle").value.trim();
const author = document.getElementById("bookAuthor").value.trim();
const isbn = document.getElementById("bookIsbn").value.trim();
const status = document.getElementById("bookStatus").value;
if (!title || !author) {
alert("请填写完整书名和作者信息");
return;
}
if (editingBookId) {
// 编辑模式
const existingBook = libraryBooks.find(b => b.id === editingBookId);
if (existingBook) {
existingBook.title = title;
existingBook.author = author;
existingBook.isbn = isbn || "";
existingBook.status = status;
saveToLocalStorage();
showToastMessage(`✏️ 图书信息已更新`);
}
} else {
// 新增模式检查重复ISBN(可选提示) 但不强制
const newId = generateBookId();
const newBook = {
id: newId,
title: title,
author: author,
isbn: isbn || "",
status: status,
borrowCount: 0
};
libraryBooks.push(newBook);
saveToLocalStorage();
showToastMessage(`📚 新书《${title}》已加入馆藏。`);
}
closeModal();
renderBooks();
}
// 打开新增模态框
function openAddModal() {
editingBookId = null;
document.getElementById("modalTitle").innerHTML = '<i class="fas fa-plus-circle"></i> 添加新书';
document.getElementById("bookTitle").value = "";
document.getElementById("bookAuthor").value = "";
document.getElementById("bookIsbn").value = "";
document.getElementById("bookStatus").value = "available";
document.getElementById("bookModal").style.display = "flex";
}
// 打开编辑模态框并填充数据
function openEditModal(id) {
const book = libraryBooks.find(b => b.id === id);
if (!book) return;
editingBookId = id;
document.getElementById("modalTitle").innerHTML = '<i class="fas fa-pen-fancy"></i> 编辑图书';
document.getElementById("bookTitle").value = book.title;
document.getElementById("bookAuthor").value = book.author;
document.getElementById("bookIsbn").value = book.isbn || "";
document.getElementById("bookStatus").value = book.status;
document.getElementById("bookModal").style.display = "flex";
}
function closeModal() {
document.getElementById("bookModal").style.display = "none";
editingBookId = null;
}
// 简陋的toast (临时div)
let toastTimeout = null;
function showToastMessage(msg) {
let toast = document.getElementById("dynamicToast");
if (!toast) {
toast = document.createElement("div");
toast.id = "dynamicToast";
toast.style.position = "fixed";
toast.style.bottom = "30px";
toast.style.left = "50%";
toast.style.transform = "translateX(-50%)";
toast.style.backgroundColor = "#1e293b";
toast.style.color = "#fff";
toast.style.padding = "12px 24px";
toast.style.borderRadius = "60px";
toast.style.fontSize = "0.9rem";
toast.style.fontWeight = "500";
toast.style.zIndex = "2000";
toast.style.boxShadow = "0 6px 14px rgba(0,0,0,0.2)";
toast.style.backdropFilter = "blur(8px)";
toast.style.border = "1px solid #4f7eb3";
document.body.appendChild(toast);
}
toast.innerHTML = `<i class="fas fa-info-circle"></i> ${msg}`;
toast.style.opacity = "1";
if (toastTimeout) clearTimeout(toastTimeout);
toastTimeout = setTimeout(() => {
toast.style.opacity = "0";
setTimeout(() => { if(toast) toast.style.display = "none"; }, 300);
}, 2500);
toast.style.display = "block";
}
// 搜索逻辑
function performSearch() {
const inputVal = document.getElementById("searchInput").value;
currentFilter = inputVal;
renderBooks();
}
function resetFilter() {
document.getElementById("searchInput").value = "";
currentFilter = "";
renderBooks();
}
// 事件绑定与初始化
function bindEvents() {
document.getElementById("searchBtn").addEventListener("click", performSearch);
document.getElementById("resetFilterBtn").addEventListener("click", resetFilter);
document.getElementById("addBookBtn").addEventListener("click", openAddModal);
document.getElementById("closeModalBtn").addEventListener("click", closeModal);
document.getElementById("saveBookBtn").addEventListener("click", saveBookFromModal);
// 点击模态框背景关闭
window.addEventListener("click", (e) => {
const modal = document.getElementById("bookModal");
if (e.target === modal) closeModal();
});
// 搜索框支持回车
document.getElementById("searchInput").addEventListener("keypress", (e) => {
if (e.key === "Enter") performSearch();
});
}
// 启动
function init() {
loadDataFromStorage();
bindEvents();
renderBooks();
}
init();
</script>
</body>
</html>