调试,测试并调整修改了代码及结构

This commit is contained in:
LAPTOP-I47JE7I7\Lenovo
2026-04-08 10:53:47 +08:00
parent 143f90e2d5
commit d130742ad9
235 changed files with 0 additions and 42768 deletions

View File

@@ -1,310 +0,0 @@
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<!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:520px; }
.card { background:white; border-radius:28px; box-shadow:0 25px 45px -12px rgba(0,0,0,0.15); padding:2.2rem 2rem; width:100%; }
h2 { font-size:1.9rem; font-weight:600; color:#1a2b3c; margin-bottom:1.5rem; border-bottom:2px solid #f0f4f9; padding-bottom:1rem; }
h3 { font-size:1.2rem; font-weight:500; color:#2c3e50; margin-bottom:1.2rem; }
.field { margin-bottom:1.3rem; }
label { display:block; font-size:0.8rem; font-weight:600; text-transform:uppercase; color:#4a5e74; margin-bottom:0.3rem; }
input, select { width:100%; padding:0.8rem 1rem; font-size:1rem; border:1.5px solid #e2e9f2; border-radius:16px; background:#fff; transition:border 0.15s; outline:none; }
input:focus, select:focus { border-color:#7c8ea0; background:#fafcff; }
button { background:#2c3e50; color:white; border:none; padding:0.9rem 1.5rem; font-size:1rem; font-weight:500; border-radius:40px; cursor:pointer; transition:background 0.15s; width:100%; margin-top:0.3rem; }
button:hover { background:#1d2c3a; }
.secondary-btn { background:white; color:#2c3e50; border:1.5px solid #d0ddee; margin-top:1rem; }
.secondary-btn:hover { background:#f2f6fc; border-color:#a0b8cf; }
.nav-links { display:flex; justify-content:space-between; margin-top:1.5rem; gap:0.5rem; flex-wrap:wrap; }
.nav-link { background:#f0f5fb; color:#2e405b; padding:0.6rem 1.2rem; border-radius:40px; font-size:0.9rem; font-weight:500; cursor:pointer; border:1px solid transparent; transition:background 0.15s; text-align:center; flex:1 1 auto; }
.nav-link:hover { background:#dae2ed; color:#0e1e2f; }
.active-view { margin-bottom:1rem; }
.note { font-size:0.75rem; color:#8f9fb1; margin-top:0.5rem; text-align:center; }
.compact-form { background:#f9fcff; border-radius:20px; padding:1.5rem 1.2rem; margin:0.5rem 0 1rem; }
.nav-link.active-nav { background:#2c3e50; color:white; border-color:#2c3e50; }
.nav-link.active-nav:hover { background:#1d2c3a; }
</style>
</head>
<body>
<div id="app">
<div class="card">
<h2>📖 读者门户</h2>
<!-- 动态视图:读者登录 / 读者注册 / 员工登录 -->
<div class="active-view">
<!-- 读者登录视图 -->
<div v-if="currentView === 'reader-login'">
<h3>👤 读者登录</h3>
<div class="field">
<label>证号</label>
<input v-model="readerLogin.cardNumber" type="text" placeholder="例如R2024001" />
</div>
<div class="field">
<label>姓名</label>
<input v-model="readerLogin.name" type="text" placeholder="你的名字" />
</div>
<button @click="handleReaderLogin">登录</button>
<div class="note">跳转与验证由后端处理</div>
</div>
<!-- 读者注册视图 -->
<div v-else-if="currentView === 'reader-register'">
<h3>📝 注册新读者</h3>
<div class="compact-form">
<div class="field">
<label>证号</label>
<input v-model="registerForm.cardNumber" type="text" placeholder="唯一证号" />
</div>
<div class="field">
<label>姓名</label>
<input v-model="registerForm.name" type="text" placeholder="真实姓名" />
</div>
<div class="field">
<label>年龄(选填)</label>
<input v-model="registerForm.age" type="number" placeholder="可选" min="0" max="120" />
</div>
<div class="field">
<label>读者类型</label>
<select v-model="registerForm.type">
<option>老年</option>
<option>成人</option>
<option>儿童</option>
</select>
</div>
<div class="field">
<label>联系方式</label>
<input v-model="registerForm.contact" type="text" placeholder="手机 / 邮箱" />
</div>
<div class="field">
<label>注册日期</label>
<input v-model="registerForm.regDate" type="date" />
</div>
</div>
<button @click="handleRegister">提交注册</button>
</div>
<!-- 员工登录视图 -->
<div v-else-if="currentView === 'employee-login'">
<h3>🔐 员工登录</h3>
<div class="compact-form">
<div class="field">
<label>员工工号</label>
<input v-model="employeeLogin.employeeCode" type="text" placeholder="员工工号" />
</div>
<div class="field">
<label>姓名</label>
<input v-model="employeeLogin.name" type="text" placeholder="姓名" />
</div>
</div>
<button @click="handleEmployeeLogin">登录</button>
<div class="note">经理页面另有添加用户功能</div>
</div>
</div>
<!-- 导航链接 -->
<div class="nav-links">
<span class="nav-link" @click="switchView('reader-login')" :class="{ 'active-nav': currentView === 'reader-login' }">🔑 读者登录</span>
<span class="nav-link" @click="switchView('reader-register')" :class="{ 'active-nav': currentView === 'reader-register' }"> 注册</span>
<span class="nav-link" @click="switchView('employee-login')" :class="{ 'active-nav': currentView === 'employee-login' }">👔 员工登录</span>
</div>
<div style="margin-top: 1.2rem; text-align: center; font-size:0.8rem; color:#abb9c9;">
点击上方标签切换功能
</div>
</div>
</div>
<script>
(function() {
const { createApp, ref } = Vue;
// 工具函数:获取今日日期字符串 yyyy-MM-dd
const getTodayString = () => {
const d = new Date();
const year = d.getFullYear();
const month = String(d.getMonth() + 1).padStart(2, '0');
const day = String(d.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
};
// 工具函数:验证日期字符串是否符合 yyyy-MM-dd 格式
const isValidDate = (dateStr) => {
if (!dateStr) return false;
const regex = /^\d{4}-\d{2}-\d{2}$/;
if (!regex.test(dateStr)) return false;
const date = new Date(dateStr);
return !isNaN(date.getTime());
};
const app = createApp({
setup() {
const currentView = ref('reader-login');
// 读者登录数据
const readerLogin = ref({
cardNumber: '',
name: ''
});
// 员工登录数据
const employeeLogin = ref({
employeeCode: '',
name: ''
});
// 注册表单默认值
const getDefaultRegisterForm = () => ({
cardNumber: '',
name: '',
age: '',
type: '成人',
contact: '',
regDate: getTodayString()
});
const registerForm = ref(getDefaultRegisterForm());
// 切换视图时重置数据
const switchView = (view) => {
currentView.value = view;
if (view === 'reader-register') {
registerForm.value = getDefaultRegisterForm();
} else if (view === 'reader-login') {
readerLogin.value = { cardNumber: '', name: '' };
} else if (view === 'employee-login') {
employeeLogin.value = { employeeCode: '', name: '' };
}
};
// 读者登录
const handleReaderLogin = async () => {
if (!readerLogin.value.cardNumber || !readerLogin.value.name) {
alert('请填写证号和姓名');
return;
}
const params = new URLSearchParams({
cardNumber: readerLogin.value.cardNumber,
name: readerLogin.value.name
});
try {
const response = await fetch('${pageContext.request.contextPath}/api/login/reader', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params
});
const result = await response.json();
if (result.success) {
window.location.href = '${pageContext.request.contextPath}/view/Reader.jsp';
} else {
alert(result.message || '登录失败');
}
} catch (e) {
console.error(e);
alert('网络错误,请检查后端服务是否正常');
}
};
// 读者注册
const handleRegister = async () => {
// 前端基础校验
if (!registerForm.value.cardNumber) {
alert('证号不能为空');
return;
}
if (!registerForm.value.name) {
alert('姓名不能为空');
return;
}
// 年龄如果是空字符串或非数字设为空后端会处理为null
let ageVal = registerForm.value.age;
if (ageVal !== '' && isNaN(Number(ageVal))) {
alert('年龄请输入数字');
return;
}
// 日期处理:如果无效或为空,使用今天日期
let regDateVal = registerForm.value.regDate;
if (!isValidDate(regDateVal)) {
regDateVal = getTodayString();
registerForm.value.regDate = regDateVal; // 同步更新显示
}
const params = new URLSearchParams({
cardNumber: registerForm.value.cardNumber,
name: registerForm.value.name,
age: ageVal,
type: registerForm.value.type,
contact: registerForm.value.contact || '',
regDate: regDateVal
});
try {
const response = await fetch('${pageContext.request.contextPath}/api/register', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params
});
const result = await response.json();
if (result.success) {
alert('注册成功,请登录');
switchView('reader-login');
} else {
alert(result.message || '注册失败');
}
} catch (e) {
console.error(e);
alert('网络错误,请检查后端服务是否正常');
}
};
// 员工登录
const handleEmployeeLogin = async () => {
if (!employeeLogin.value.employeeCode || !employeeLogin.value.name) {
alert('请填写工号和姓名');
return;
}
const params = new URLSearchParams({
employeeCode: employeeLogin.value.employeeCode,
name: employeeLogin.value.name
});
try {
const response = await fetch('${pageContext.request.contextPath}/api/login/employee', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params
});
const result = await response.json();
if (result.success) {
window.location.href = '${pageContext.request.contextPath}/view/Work.jsp';
} else {
alert(result.message || '登录失败');
}
} catch (e) {
console.error(e);
alert('网络错误,请检查后端服务是否正常');
}
};
return {
currentView,
readerLogin,
employeeLogin,
registerForm,
switchView,
handleReaderLogin,
handleRegister,
handleEmployeeLogin
};
}
});
app.mount('#app');
})();
</script>
</body>
</html>

View File

@@ -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>

View File

@@ -1,618 +0,0 @@
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%
com.chinasofti.model.Employee currentEmployee = (com.chinasofti.model.Employee) session.getAttribute("currentEmployee");
if (currentEmployee == null) {
response.sendRedirect(request.getContextPath() + "/Login.jsp");
return;
}
String positionDesc = "";
switch (currentEmployee.getPosition()) {
case 1: positionDesc = "馆员"; break;
case 2: positionDesc = "工作人员"; break;
case 3: positionDesc = "经理"; break;
}
String empJson = String.format(
"{\"id\":\"%s\",\"name\":\"%s\",\"position\":\"%s\"}",
currentEmployee.getEmployeeCode(),
currentEmployee.getName(),
positionDesc
);
request.setAttribute("empJson", empJson);
%>
<!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; }
.employee-header { display:flex; align-items:center; justify-content:space-between; padding:1.2rem 2rem; background:#fff; border-bottom:1px solid #eaf0f6; flex-wrap:wrap; gap:1rem; }
.employee-info { display:flex; align-items:center; gap:2rem; flex-wrap:wrap; }
.info-item { background:#f0f6fe; padding:0.4rem 1.2rem; border-radius:40px; font-weight:500; color:#1a2b3c; }
.position-selector { display:flex; align-items:center; gap:1rem; }
.position-selector label { font-weight:600; color:#3a4e66; font-size:0.9rem; }
.position-dropdown { padding:0.5rem 2rem 0.5rem 1.2rem; border:1.5px solid #d0ddee; border-radius:40px; font-size:1rem; background:white; cursor:pointer; outline:none; font-weight:500; color:#1a2b3c; }
.position-dropdown:focus { border-color:#7c8ea0; }
.main-content { padding:2rem; }
.section-card { background:#f9fcff; border-radius:24px; padding:1.6rem 1.8rem; border:1px solid #e6edf5; margin-bottom:2rem; }
.section-title { font-size:1.2rem; font-weight:600; color:#1e3b5c; margin-bottom:1.5rem; display:flex; align-items:center; gap:0.5rem; }
.form-row { display:flex; flex-wrap:wrap; gap:1rem; align-items:flex-end; margin-bottom:1.5rem; }
.form-field { flex:1 1 160px; }
.form-field label { display:block; font-size:0.75rem; font-weight:600; text-transform:uppercase; color:#5f748b; margin-bottom:0.3rem; }
.form-field input, .form-field select { width:100%; padding:0.6rem 1rem; border:1.5px solid #d8e2ee; border-radius:30px; font-size:0.95rem; background:white; }
.form-field input:focus, .form-field select:focus { outline:none; border-color:#7e99b3; }
.btn { background:#2c3e50; color:white; border:none; padding:0.6rem 1.8rem; border-radius:30px; font-weight:500; cursor:pointer; font-size:0.95rem; transition:background 0.15s; height:fit-content; border:1px solid transparent; }
.btn:hover { background:#1a2b3c; }
.btn-outline { background:white; color:#2c3e50; border:1.5px solid #d0ddee; }
.btn-outline:hover { background:#f2f6fc; border-color:#a0b8cf; }
.btn-sm { padding:0.3rem 1rem; font-size:0.8rem; }
.table-wrapper { overflow-x:auto; border-radius:18px; background:white; box-shadow:0 4px 12px rgba(0,0,0,0.02); margin-top:1.5rem; }
table { width:100%; border-collapse:collapse; font-size:0.9rem; min-width:800px; }
th { background:#eef4fa; color:#1d3b5c; font-weight:600; padding:1rem 0.8rem; text-align:left; }
td { padding:1rem 0.8rem; border-bottom:1px solid #eef3f8; color:#1f2f40; }
.action-cell { display:flex; gap:0.5rem; flex-wrap:wrap; }
.badge { background:#c9dff3; color:#144a70; padding:0.2rem 0.8rem; border-radius:30px; font-size:0.75rem; font-weight:600; display:inline-block; }
.grid-2col { display:grid; grid-template-columns:1fr 1fr; gap:2rem; }
@media (max-width:800px) { .grid-2col { grid-template-columns:1fr; } }
.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="employee-header">
<div class="employee-info">
<span class="info-item">🆔 工号:{{ employee.id }}</span>
<span class="info-item">👤 姓名:{{ employee.name }}</span>
<span class="info-item">📌 职位:{{ employee.position }}</span>
</div>
<div class="position-selector">
<label for="positionSelect">切换职位(模拟权限):</label>
<select id="positionSelect" v-model="selectedPosition" class="position-dropdown">
<option value="馆员">馆员 (staff)</option>
<option value="工作人员">工作人员 (worker)</option>
<option value="经理">经理 (manager)</option>
</select>
</div>
</div>
<!-- 主区域:根据 selectedPosition 渲染不同功能 -->
<div class="main-content">
<!-- 馆员视图:图书增删改查 -->
<div v-if="selectedPosition === '馆员'">
<div class="section-card">
<div class="section-title">📚 图书管理 · 馆员</div>
<div class="form-row">
<div class="form-field">
<label>ISBN</label>
<input v-model="bookForm.isbn" type="text" placeholder="ISBN" />
</div>
<div class="form-field">
<label>书名</label>
<input v-model="bookForm.title" type="text" placeholder="书名" />
</div>
<div class="form-field">
<label>作者</label>
<input v-model="bookForm.author" type="text" placeholder="作者" />
</div>
<div class="form-field">
<label>出版社</label>
<input v-model="bookForm.publisher" type="text" placeholder="出版社" />
</div>
<div class="form-field">
<label>出版年</label>
<input v-model.number="bookForm.publishYear" type="number" placeholder="2025" />
</div>
<div class="form-field">
<label>总库存</label>
<input v-model.number="bookForm.totalStock" type="number" placeholder="库存" />
</div>
<div class="form-field">
<label>面向群体</label>
<select v-model="bookForm.targetAudience">
<option value="通用">通用</option>
<option value="儿童">儿童</option>
<option value="成人">成人</option>
<option value="老年">老年</option>
</select>
</div>
<div class="form-field">
<label>简介</label>
<input v-model="bookForm.description" type="text" placeholder="简介" />
</div>
<button class="btn" @click="addBook">添加图书</button>
</div>
<div 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><th>操作</th>
</tr>
</thead>
<tbody>
<tr v-for="book in books" :key="book.id">
<td>{{ book.id }}</td>
<td>{{ book.isbn }}</td>
<td>{{ book.title }}</td>
<td>{{ book.author }}</td>
<td>{{ book.publisher }}</td>
<td>{{ book.publishYear }}</td>
<td>{{ book.totalStock }}</td>
<td>{{ book.borrowedCount || 0 }}</td>
<td><span v-if="book.targetAudience === '儿童'" class="badge">儿童</span><span v-else>通用</span></td>
<td>{{ book.description || '—' }}</td>
<td class="action-cell">
<button class="btn btn-sm" @click="editBook(book)">编辑</button>
<button class="btn btn-sm btn-outline" @click="deleteBook(book)">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- 工作人员视图:帮助注册 + 借阅/归还 -->
<div v-else-if="selectedPosition === '工作人员'">
<div class="grid-2col">
<!-- 左侧:特殊群体读者注册 -->
<div class="section-card">
<div class="section-title">👥 帮助注册(老年/特殊群体)</div>
<div class="form-field" style="margin-bottom: 1rem;">
<label>证号</label>
<input v-model="registerForm.cardNumber" placeholder="例如 R2025001" />
</div>
<div class="form-field" style="margin-bottom: 1rem;">
<label>姓名</label>
<input v-model="registerForm.name" placeholder="姓名" />
</div>
<div class="form-field" style="margin-bottom: 1rem;">
<label>年龄(选填)</label>
<input v-model.number="registerForm.age" type="number" placeholder="年龄" />
</div>
<div class="form-field" style="margin-bottom: 1rem;">
<label>读者类型</label>
<select v-model="registerForm.type">
<option>老年</option>
<option>成人</option>
<option>儿童</option>
</select>
</div>
<div class="form-field" style="margin-bottom: 1.5rem;">
<label>联系方式</label>
<input v-model="registerForm.contact" placeholder="手机/邮箱" />
</div>
<button class="btn" @click="staffRegister">注册读者</button>
</div>
<!-- 右侧:借阅/归还(基于读者账号) -->
<div class="section-card">
<div class="section-title">📖 代借阅/归还</div>
<div class="form-row" style="margin-bottom: 1.5rem;">
<div class="form-field" style="flex:2;">
<label>读者证号</label>
<input v-model="borrowerAccount" placeholder="如 R2024001" />
</div>
<button class="btn" @click="searchBorrowedStaff">查询借阅</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></tr></thead>
<tbody>
<tr v-if="staffBorrowedList.length === 0"><td colspan="7" class="empty-placeholder">暂无借阅记录</td></tr>
<tr v-for="item in staffBorrowedList" :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><button class="btn btn-sm" @click="returnBookStaff(item)">归还</button></td>
</tr>
</tbody>
</table>
</div>
<div style="margin-top:1.5rem;">
<div class="form-row">
<div class="form-field">
<label>ISBN/书名</label>
<input v-model="quickBorrowIsbn" placeholder="输入ISBN或书名" />
</div>
<button class="btn" @click="quickBorrow">快速借书</button>
</div>
</div>
</div>
</div>
</div>
<!-- 经理视图:活动增删改查 + 员工注册 -->
<div v-else-if="selectedPosition === '经理'">
<div class="grid-2col">
<!-- 左侧:活动管理 -->
<div class="section-card">
<div class="section-title">🎉 活动管理</div>
<div class="form-row">
<div class="form-field">
<label>活动名称</label>
<input v-model="activityForm.name" />
</div>
<div class="form-field">
<label>开始时间</label>
<input v-model="activityForm.startTime" placeholder="2025-05-10 15:00" />
</div>
<div class="form-field">
<label>结束时间</label>
<input v-model="activityForm.endTime" placeholder="2025-05-10 17:00" />
</div>
<div class="form-field">
<label>地点</label>
<input v-model="activityForm.location" />
</div>
<div class="form-field">
<label>简介</label>
<input v-model="activityForm.description" />
</div>
<button class="btn btn-sm" @click="addActivity">添加</button>
</div>
<div class="table-wrapper">
<table>
<thead><tr><th>名称</th><th>时间</th><th>地点</th><th>简介</th><th>操作</th></tr></thead>
<tbody>
<tr v-for="act in activities" :key="act.id">
<td>{{ act.name }}</td><td>{{ act.startTime }}</td><td>{{ act.location }}</td><td>{{ act.description }}</td>
<td class="action-cell">
<button class="btn btn-sm" @click="editActivity(act)">编辑</button>
<button class="btn btn-sm btn-outline" @click="deleteActivity(act)">删除</button>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- 右侧:新员工注册 -->
<div class="section-card">
<div class="section-title">👔 新员工注册</div>
<div class="form-field" style="margin-bottom:1rem;">
<label>工号</label>
<input v-model="newEmployee.employeeCode" placeholder="如 E001" />
</div>
<div class="form-field" style="margin-bottom:1rem;">
<label>姓名</label>
<input v-model="newEmployee.name" placeholder="姓名" />
</div>
<div class="form-field" style="margin-bottom:1.5rem;">
<label>职位</label>
<select v-model="newEmployee.position">
<option>馆员</option>
<option>工作人员</option>
<option>经理</option>
</select>
</div>
<button class="btn" @click="addEmployee">注册新员工</button>
<div style="margin-top:2rem;">
<div class="section-title" style="margin-bottom:0.8rem;">📋 现有员工</div>
<div class="table-wrapper">
<table>
<thead><tr><th>工号</th><th>姓名</th><th>职位</th></tr></thead>
<tbody>
<tr v-for="emp in employees" :key="emp.employeeCode">
<td>{{ emp.employeeCode }}</td><td>{{ emp.name }}</td><td>{{ emp.positionDesc }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
(function() {
const { createApp, ref, onMounted } = Vue;
// 从服务器端传递的初始员工数据
const initialEmployee = ${empJson};
const app = createApp({
setup() {
const employee = ref(initialEmployee);
const selectedPosition = ref(employee.value.position);
// ---------- 图书数据 ----------
const books = ref([]);
const bookForm = ref({
isbn: '',
title: '',
author: '',
publisher: '',
publishYear: null,
totalStock: null,
targetAudience: '通用',
description: ''
});
const loadBooks = async () => {
try {
const response = await fetch('${pageContext.request.contextPath}/api/books');
books.value = await response.json();
} catch (e) {
console.error('加载图书失败', e);
}
};
const addBook = async () => {
const params = new URLSearchParams(bookForm.value);
try {
const response = await fetch('${pageContext.request.contextPath}/api/books', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params
});
const result = await response.json();
if (result.success) {
alert('添加成功');
await loadBooks();
bookForm.value = { isbn: '', title: '', author: '', publisher: '', publishYear: null, totalStock: null, targetAudience: '通用', description: '' };
} else {
alert(result.message || '添加失败');
}
} catch (e) {
alert('网络错误');
}
};
const editBook = (book) => {
alert('编辑功能待实现,请直接使用更新接口');
};
const deleteBook = async (book) => {
if (!confirm('确定删除吗?')) return;
try {
const response = await fetch('${pageContext.request.contextPath}/api/books/' + book.id, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
alert('删除成功');
await loadBooks();
} else {
alert(result.message || '删除失败');
}
} catch (e) {
alert('网络错误');
}
};
// ---------- 工作人员:注册表单 ----------
const registerForm = ref({
cardNumber: '',
name: '',
age: null,
type: '老年',
contact: '',
regDate: new Date().toISOString().slice(0,10)
});
const staffRegister = async () => {
const params = new URLSearchParams(registerForm.value);
try {
const response = await fetch('${pageContext.request.contextPath}/api/staff/register', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params
});
const result = await response.json();
if (result.success) {
alert('注册成功');
registerForm.value = { cardNumber: '', name: '', age: null, type: '老年', contact: '', regDate: new Date().toISOString().slice(0,10) };
} else {
alert(result.message || '注册失败');
}
} catch (e) {
alert('网络错误');
}
};
// 借阅数据
const staffBorrowedList = ref([]);
const borrowerAccount = ref('');
const searchBorrowedStaff = async () => {
if (!borrowerAccount.value) {
alert('请输入读者证号');
return;
}
try {
const response = await fetch('${pageContext.request.contextPath}/api/borrows?readerCard=' + encodeURIComponent(borrowerAccount.value));
staffBorrowedList.value = await response.json();
} catch (e) {
alert('查询失败');
}
};
const returnBookStaff = 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 searchBorrowedStaff();
} else {
alert(result.message || '归还失败');
}
} catch (e) {
alert('网络错误');
}
};
const quickBorrowIsbn = ref('');
const quickBorrow = async () => {
if (!borrowerAccount.value) {
alert('请先输入读者证号');
return;
}
const params = new URLSearchParams({
readerCard: borrowerAccount.value,
isbnOrTitle: quickBorrowIsbn.value
});
try {
const response = await fetch('${pageContext.request.contextPath}/api/borrow/quick', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params
});
const result = await response.json();
if (result.success) {
alert('快速借书成功');
await searchBorrowedStaff();
quickBorrowIsbn.value = '';
} else {
alert(result.message || '快速借书失败');
}
} catch (e) {
alert('网络错误');
}
};
// ---------- 经理:活动管理 ----------
const activities = ref([]);
const activityForm = ref({ name: '', startTime: '', endTime: '', location: '', description: '' });
const loadActivities = async () => {
try {
const response = await fetch('${pageContext.request.contextPath}/api/activities');
activities.value = await response.json();
} catch (e) {
console.error('加载活动失败', e);
}
};
const addActivity = async () => {
const params = new URLSearchParams(activityForm.value);
try {
const response = await fetch('${pageContext.request.contextPath}/api/activities', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params
});
const result = await response.json();
if (result.success) {
alert('活动添加成功');
await loadActivities();
activityForm.value = { name: '', startTime: '', endTime: '', location: '', description: '' };
} else {
alert(result.message || '添加失败');
}
} catch (e) {
alert('网络错误');
}
};
const editActivity = (act) => {
alert('编辑功能待实现');
};
const deleteActivity = async (act) => {
if (!confirm('确定删除吗?')) return;
try {
const response = await fetch('${pageContext.request.contextPath}/api/activities/' + act.id, {
method: 'DELETE'
});
const result = await response.json();
if (result.success) {
alert('删除成功');
await loadActivities();
} else {
alert(result.message || '删除失败');
}
} catch (e) {
alert('网络错误');
}
};
// 员工管理
const employees = ref([]);
const newEmployee = ref({ employeeCode: '', name: '', position: '馆员' });
const loadEmployees = async () => {
try {
const response = await fetch('${pageContext.request.contextPath}/api/employees');
employees.value = await response.json();
} catch (e) {
console.error('加载员工失败', e);
}
};
const addEmployee = async () => {
const params = new URLSearchParams(newEmployee.value);
try {
const response = await fetch('${pageContext.request.contextPath}/api/employees', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: params
});
const result = await response.json();
if (result.success) {
alert('员工注册成功');
await loadEmployees();
newEmployee.value = { employeeCode: '', name: '', position: '馆员' };
} else {
alert(result.message || '注册失败');
}
} catch (e) {
alert('网络错误');
}
};
onMounted(() => {
loadBooks();
loadActivities();
loadEmployees();
});
return {
employee,
selectedPosition,
books,
bookForm,
addBook,
editBook,
deleteBook,
registerForm,
staffRegister,
staffBorrowedList,
borrowerAccount,
searchBorrowedStaff,
returnBookStaff,
quickBorrowIsbn,
quickBorrow,
activities,
activityForm,
addActivity,
editActivity,
deleteActivity,
employees,
newEmployee,
addEmployee
};
}
});
app.mount('#app');
})();
</script>
</body>
</html>

File diff suppressed because it is too large Load Diff