调试,测试并调整修改了代码及结构
This commit is contained in:
@@ -1,382 +0,0 @@
|
||||
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
|
||||
<%@ page import="com.chinasofti.model.Reader" %>
|
||||
<%@ page import="com.chinasofti.enums.ReaderType" %>
|
||||
<%
|
||||
Reader currentReader = (Reader) session.getAttribute("currentReader");
|
||||
if (currentReader == null) {
|
||||
response.sendRedirect(request.getContextPath() + "/Login.jsp");
|
||||
return;
|
||||
}
|
||||
// 准备传递给前端的数据
|
||||
String readerName = currentReader.getName();
|
||||
String readerTypeDesc = "";
|
||||
int typeCode = currentReader.getReaderType();
|
||||
if (typeCode == ReaderType.SENIOR.getCode()) readerTypeDesc = "老年";
|
||||
else if (typeCode == ReaderType.ADULT.getCode()) readerTypeDesc = "成人";
|
||||
else if (typeCode == ReaderType.CHILD.getCode()) readerTypeDesc = "儿童";
|
||||
String readerAccount = currentReader.getCardNumber();
|
||||
String readerJson = String.format(
|
||||
"{\"name\":\"%s\",\"type\":\"%s\",\"account\":\"%s\"}",
|
||||
readerName, readerTypeDesc, readerAccount
|
||||
);
|
||||
%>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>读者主页 · 借阅与活动</title>
|
||||
<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
|
||||
<style>
|
||||
/* 样式与原来相同,此处省略(请保留原样式) */
|
||||
* { margin:0; padding:0; box-sizing:border-box; }
|
||||
body { background:#eef2f6; font-family:'Inter',sans-serif; display:flex; align-items:center; justify-content:center; min-height:100vh; padding:1rem; }
|
||||
#app { width:100%; max-width:1400px; }
|
||||
.app-container { background:white; border-radius:32px; box-shadow:0 30px 50px -20px rgba(0,0,0,0.2); overflow:hidden; }
|
||||
.header { display:flex; align-items:center; justify-content:space-between; padding:1.2rem 2rem; background:#fff; border-bottom:1px solid #eaf0f6; }
|
||||
.user-info { display:flex; align-items:center; gap:1.5rem; flex-wrap:wrap; }
|
||||
.user-greeting { font-weight:600; font-size:1.2rem; color:#1a2b3c; background:#f0f6fe; padding:0.4rem 1.2rem; border-radius:40px; }
|
||||
.logout-btn { background:white; border:1.5px solid #dae2ed; color:#3b4e62; padding:0.4rem 1.4rem; border-radius:40px; font-weight:500; font-size:0.9rem; cursor:pointer; transition:all 0.15s; }
|
||||
.logout-btn:hover { background:#f1f7fd; border-color:#9bb1c9; }
|
||||
.child-note { background:#fdf6e6; color:#926d39; border-radius:40px; padding:0.3rem 1.2rem; font-size:0.8rem; font-weight:500; display:inline-block; }
|
||||
.nav-tabs { display:flex; gap:0.8rem; }
|
||||
.nav-tab { background:transparent; border:none; padding:0.6rem 1.8rem; font-size:1rem; font-weight:600; color:#5d7184; border-radius:40px; cursor:pointer; transition:0.15s; border:1.5px solid transparent; }
|
||||
.nav-tab.active { background:#2c3e50; color:white; }
|
||||
.nav-tab:not(.active):hover { background:#eef3f9; color:#2c3e50; }
|
||||
.main-content { padding:2rem; }
|
||||
.borrow-layout { display:flex; flex-direction:column; gap:2.5rem; }
|
||||
.section-card { background:#f9fcff; border-radius:24px; padding:1.6rem 1.8rem; border:1px solid #e6edf5; }
|
||||
.section-title { font-size:1.2rem; font-weight:600; color:#1e3b5c; margin-bottom:1.5rem; display:flex; align-items:center; gap:0.5rem; flex-wrap:wrap; }
|
||||
.search-row { display:flex; flex-wrap:wrap; gap:1rem; align-items:flex-end; margin-bottom:2rem; }
|
||||
.search-field { flex:1 1 160px; }
|
||||
.search-field label { display:block; font-size:0.75rem; font-weight:600; text-transform:uppercase; color:#5f748b; margin-bottom:0.3rem; }
|
||||
.search-field input { width:100%; padding:0.6rem 1rem; border:1.5px solid #d8e2ee; border-radius:30px; font-size:0.95rem; background:white; }
|
||||
.search-field input:focus { outline:none; border-color:#7e99b3; }
|
||||
.search-btn { background:#2c3e50; color:white; border:none; padding:0.6rem 2rem; border-radius:30px; font-weight:500; cursor:pointer; font-size:0.95rem; transition:background 0.15s; height:fit-content; align-self:center; }
|
||||
.search-btn:hover { background:#1a2b3c; }
|
||||
.table-wrapper { overflow-x:auto; border-radius:18px; background:white; box-shadow:0 4px 12px rgba(0,0,0,0.02); margin-top:1rem; }
|
||||
table { width:100%; border-collapse:collapse; font-size:0.9rem; min-width:1000px; }
|
||||
th { background:#eef4fa; color:#1d3b5c; font-weight:600; padding:1rem 0.8rem; text-align:left; white-space:nowrap; }
|
||||
td { padding:1rem 0.8rem; border-bottom:1px solid #eef3f8; color:#1f2f40; }
|
||||
.action-btn { background:#2c3e50; color:white; border:none; padding:0.4rem 1.2rem; border-radius:30px; font-size:0.8rem; font-weight:500; cursor:pointer; transition:background 0.15s; }
|
||||
.action-btn.return { background:#9b6b43; }
|
||||
.action-btn.return:hover { background:#7e4f2e; }
|
||||
.action-btn:hover { background:#1a2b3c; }
|
||||
.child-badge { background:#c9dff3; color:#144a70; padding:0.2rem 0.8rem; border-radius:30px; font-size:0.75rem; font-weight:600; display:inline-block; }
|
||||
.empty-row td { padding:2rem; text-align:center; color:#8c9db2; font-style:italic; }
|
||||
.activity-grid { display:grid; grid-template-columns:repeat(auto-fill, minmax(260px,1fr)); gap:1.8rem; margin-top:1.5rem; }
|
||||
.activity-card { background:white; border-radius:24px; padding:1.5rem; box-shadow:0 8px 18px rgba(0,0,0,0.03); border:1px solid #e6edf5; transition:transform 0.1s; display:flex; flex-direction:column; }
|
||||
.activity-card:hover { transform:translateY(-2px); box-shadow:0 12px 24px rgba(0,0,0,0.05); }
|
||||
.activity-name { font-size:1.2rem; font-weight:600; color:#1e3b5c; margin-bottom:0.5rem; }
|
||||
.activity-time { color:#6e8aa8; font-size:0.9rem; margin-bottom:0.5rem; display:flex; align-items:center; gap:0.3rem; }
|
||||
.activity-desc { color:#3a4e66; font-size:0.95rem; line-height:1.4; margin:0.8rem 0 1.2rem; flex:1; }
|
||||
.signup-btn { background:#2c3e50; color:white; border:none; padding:0.6rem 1rem; border-radius:40px; font-weight:500; font-size:0.9rem; cursor:pointer; transition:background 0.15s; align-self:flex-start; width:100%; }
|
||||
.signup-btn:hover { background:#1a2b3c; }
|
||||
.info-note { background:#eaf2fb; padding:0.5rem 1.2rem; border-radius:40px; color:#2c577c; font-size:0.85rem; display:inline-block; }
|
||||
.empty-placeholder { text-align:center; padding:2rem; background:white; border-radius:18px; color:#8f9fb1; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app">
|
||||
<div class="app-container">
|
||||
<div class="header">
|
||||
<div class="user-info">
|
||||
<span class="user-greeting">👋 {{ currentUser.name }}</span>
|
||||
<button class="logout-btn" @click="logout">退出登录</button>
|
||||
<span v-if="currentUser.type === '儿童'" class="child-note">🧸 儿童读者 · 仅显示儿童书目</span>
|
||||
</div>
|
||||
<div class="nav-tabs">
|
||||
<button class="nav-tab" :class="{ active: activeTab === 'borrow' }" @click="activeTab = 'borrow'">📚 借阅</button>
|
||||
<button class="nav-tab" :class="{ active: activeTab === 'activity' }" @click="activeTab = 'activity'">🎉 活动</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="main-content">
|
||||
<div v-if="activeTab === 'borrow'" class="borrow-layout">
|
||||
<!-- 书目查询 -->
|
||||
<div class="section-card">
|
||||
<div class="section-title">
|
||||
<span>🔍 书目查询 · 借阅</span>
|
||||
<span class="info-note">支持 ISBN / 书名 / 作者</span>
|
||||
</div>
|
||||
<div class="search-row">
|
||||
<div class="search-field">
|
||||
<label>ISBN</label>
|
||||
<input v-model="searchIsbn" type="text" placeholder="如 978-7-01" />
|
||||
</div>
|
||||
<div class="search-field">
|
||||
<label>书名</label>
|
||||
<input v-model="searchTitle" type="text" placeholder="关键词" />
|
||||
</div>
|
||||
<div class="search-field">
|
||||
<label>作者</label>
|
||||
<input v-model="searchAuthor" type="text" placeholder="关键词" />
|
||||
</div>
|
||||
<button class="search-btn" @click="searchBooks">查询书目</button>
|
||||
</div>
|
||||
|
||||
<div v-if="bookTableVisible" class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>book_id</th><th>ISBN</th><th>书名</th><th>作者</th><th>出版社</th><th>出版年</th><th>可借</th><th>儿童书</th><th>简介</th><th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="filteredBooks.length === 0">
|
||||
<td colspan="10" class="empty-row">没有符合条件的书籍</td>
|
||||
</tr>
|
||||
<tr v-for="book in filteredBooks" :key="book.bookId">
|
||||
<td>{{ book.bookId }}</td>
|
||||
<td>{{ book.isbn }}</td>
|
||||
<td>{{ book.title }}</td>
|
||||
<td>{{ book.author }}</td>
|
||||
<td>{{ book.publisher }}</td>
|
||||
<td>{{ book.publishYear }}</td>
|
||||
<td>{{ book.availableCopies }}</td>
|
||||
<td>
|
||||
<span v-if="book.targetAudience === '儿童'" class="child-badge">儿童书</span>
|
||||
<span v-else style="color:#8a9bb0;">通用</span>
|
||||
</td>
|
||||
<td>{{ book.description || '—' }}</td>
|
||||
<td><button class="action-btn" @click="borrowBook(book)">借阅</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div v-else class="empty-placeholder">
|
||||
⚡ 请输入条件并点击“查询书目”查看结果
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 我的借阅 -->
|
||||
<div class="section-card">
|
||||
<div class="section-title">
|
||||
<span>📋 我的借阅 · 归还</span>
|
||||
<span class="info-note">输入账号号码查询</span>
|
||||
</div>
|
||||
<div class="search-row" style="margin-bottom:1.5rem;">
|
||||
<div class="search-field" style="flex:2;">
|
||||
<label>读者证号</label>
|
||||
<input v-model="borrowerAccount" type="text" placeholder="例如 R2024001" />
|
||||
</div>
|
||||
<button class="search-btn" @click="searchBorrowed">查询借阅</button>
|
||||
</div>
|
||||
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>借阅ID</th><th>ISBN</th><th>书名</th><th>作者</th><th>借阅日期</th><th>应还日期</th><th>状态</th><th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="borrowedList.length === 0">
|
||||
<td colspan="8" class="empty-row">暂无借阅记录</td>
|
||||
</tr>
|
||||
<tr v-for="item in borrowedList" :key="item.borrowId">
|
||||
<td>{{ item.borrowId }}</td>
|
||||
<td>{{ item.isbn }}</td>
|
||||
<td>{{ item.bookTitle }}</td>
|
||||
<td>{{ item.author }}</td>
|
||||
<td>{{ item.borrowDate }}</td>
|
||||
<td>{{ item.dueDate }}</td>
|
||||
<td><span style="color:#2e7d5e;">{{ item.status }}</span></td>
|
||||
<td><button class="action-btn return" @click="returnBook(item)">归还</button></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 活动页面 -->
|
||||
<div v-else class="activity-page">
|
||||
<h3 style="font-size:1.4rem; color:#1e3b5c; margin-bottom:1.2rem;">🎈 近期活动</h3>
|
||||
<div class="activity-grid">
|
||||
<div v-for="act in activities" :key="act.id" class="activity-card">
|
||||
<div class="activity-name">{{ act.name }}</div>
|
||||
<div class="activity-time">📅 {{ act.startTime }} ~ {{ act.endTime }}</div>
|
||||
<div class="activity-desc">{{ act.description }}</div>
|
||||
<button class="signup-btn" @click="signupActivity(act)">报名参加</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function() {
|
||||
const { createApp, ref, computed, onMounted } = Vue;
|
||||
|
||||
// 从服务器端传递的初始数据
|
||||
const initialUser = <%= readerJson %>;
|
||||
|
||||
const app = createApp({
|
||||
setup() {
|
||||
const currentUser = ref(initialUser);
|
||||
const activeTab = ref('borrow');
|
||||
|
||||
// 书目查询
|
||||
const searchIsbn = ref('');
|
||||
const searchTitle = ref('');
|
||||
const searchAuthor = ref('');
|
||||
const bookTableVisible = ref(false);
|
||||
const books = ref([]); // 存储查询结果
|
||||
|
||||
const filteredBooks = computed(() => books.value);
|
||||
|
||||
const searchBooks = async () => {
|
||||
const params = new URLSearchParams({
|
||||
isbn: searchIsbn.value,
|
||||
title: searchTitle.value,
|
||||
author: searchAuthor.value
|
||||
});
|
||||
try {
|
||||
const response = await fetch('${pageContext.request.contextPath}/api/books/search?' + params, {
|
||||
method: 'GET'
|
||||
});
|
||||
if (!response.ok) throw new Error('查询失败');
|
||||
books.value = await response.json();
|
||||
bookTableVisible.value = true;
|
||||
} catch (e) {
|
||||
alert('查询失败:' + e.message);
|
||||
}
|
||||
};
|
||||
|
||||
const borrowBook = async (book) => {
|
||||
const params = new URLSearchParams({
|
||||
readerCard: currentUser.value.account,
|
||||
bookId: book.bookId
|
||||
});
|
||||
try {
|
||||
const response = await fetch('${pageContext.request.contextPath}/api/borrow', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: params
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert('借阅成功');
|
||||
await searchBorrowed(); // 刷新借阅列表
|
||||
} else {
|
||||
alert(result.message || '借阅失败');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('网络错误');
|
||||
}
|
||||
};
|
||||
|
||||
// 借阅记录
|
||||
const borrowerAccount = ref(currentUser.value.account);
|
||||
const borrowedList = ref([]);
|
||||
|
||||
const searchBorrowed = async () => {
|
||||
if (!borrowerAccount.value) {
|
||||
alert('请输入读者证号');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const response = await fetch('${pageContext.request.contextPath}/api/borrows?readerCard=' + encodeURIComponent(borrowerAccount.value), {
|
||||
method: 'GET'
|
||||
});
|
||||
if (!response.ok) throw new Error('查询失败');
|
||||
borrowedList.value = await response.json();
|
||||
} catch (e) {
|
||||
alert('查询借阅记录失败');
|
||||
}
|
||||
};
|
||||
|
||||
const returnBook = async (item) => {
|
||||
const params = new URLSearchParams({
|
||||
borrowId: item.borrowId
|
||||
});
|
||||
try {
|
||||
const response = await fetch('${pageContext.request.contextPath}/api/borrow', {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: params
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert('归还成功');
|
||||
await searchBorrowed();
|
||||
} else {
|
||||
alert('归还失败');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('网络错误');
|
||||
}
|
||||
};
|
||||
|
||||
// 活动
|
||||
const activities = ref([]);
|
||||
const loadActivities = async () => {
|
||||
try {
|
||||
const response = await fetch('${pageContext.request.contextPath}/api/activities');
|
||||
if (!response.ok) throw new Error('加载活动失败');
|
||||
activities.value = await response.json();
|
||||
} catch (e) {
|
||||
console.error('加载活动失败', e);
|
||||
}
|
||||
};
|
||||
|
||||
const signupActivity = async (act) => {
|
||||
const params = new URLSearchParams({
|
||||
activityId: act.id
|
||||
});
|
||||
try {
|
||||
const response = await fetch('${pageContext.request.contextPath}/api/signup', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: params
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert('报名成功');
|
||||
} else {
|
||||
alert(result.message || '报名失败');
|
||||
}
|
||||
} catch (e) {
|
||||
alert('网络错误');
|
||||
}
|
||||
};
|
||||
|
||||
// 退出登录
|
||||
const logout = async () => {
|
||||
// 可调用登出 API(如清除 session),然后跳转
|
||||
window.location.href = '${pageContext.request.contextPath}/view/Login.jsp';
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
loadActivities();
|
||||
searchBorrowed(); // 加载当前读者的借阅记录
|
||||
});
|
||||
|
||||
return {
|
||||
currentUser,
|
||||
activeTab,
|
||||
searchIsbn,
|
||||
searchTitle,
|
||||
searchAuthor,
|
||||
bookTableVisible,
|
||||
filteredBooks,
|
||||
searchBooks,
|
||||
borrowBook,
|
||||
borrowerAccount,
|
||||
borrowedList,
|
||||
searchBorrowed,
|
||||
returnBook,
|
||||
activities,
|
||||
signupActivity,
|
||||
logout
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
app.mount('#app');
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user