688 lines
24 KiB
Plaintext
688 lines
24 KiB
Plaintext
|
|
<%@ 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");
|
|||
|
|
// Login.jsp 在 /view/Login.jsp,需要返回上级目录
|
|||
|
|
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" readonly/>
|
|||
|
|
</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',
|
|||
|
|
credentials: 'include',
|
|||
|
|
});
|
|||
|
|
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,
|
|||
|
|
credentials: 'include' // 添加此行
|
|||
|
|
});
|
|||
|
|
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',
|
|||
|
|
credentials: 'include',
|
|||
|
|
});
|
|||
|
|
if (!response.ok) throw new Error('查询失败');
|
|||
|
|
borrowedList.value = await response.json();
|
|||
|
|
} catch (e) {
|
|||
|
|
alert('查询借阅记录失败');
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
const returnBook = async (item) => {
|
|||
|
|
console.log('>>> [归还] 点击归还按钮,borrowId=' + item.borrowId);
|
|||
|
|
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,
|
|||
|
|
credentials: 'include',
|
|||
|
|
});
|
|||
|
|
console.log('>>> [归还] HTTP状态码=' + response.status);
|
|||
|
|
const result = await response.json();
|
|||
|
|
console.log('>>> [归还] 后端返回=' + JSON.stringify(result));
|
|||
|
|
if (result.success) {
|
|||
|
|
alert('归还成功');
|
|||
|
|
await searchBorrowed();
|
|||
|
|
} else {
|
|||
|
|
alert('归还失败:' + (result.message || '未知原因,请查看控制台日志'));
|
|||
|
|
}
|
|||
|
|
} catch (e) {
|
|||
|
|
console.error('>>> [归还] 请求异常:' + e);
|
|||
|
|
alert('网络错误:' + e.message);
|
|||
|
|
}
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// 活动
|
|||
|
|
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,
|
|||
|
|
credentials: 'include' // 确保携带 session cookie
|
|||
|
|
});
|
|||
|
|
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>
|