重新提交
This commit is contained in:
3
classes/artifacts/LM_Web_exploded/META-INF/MANIFEST.MF
Normal file
3
classes/artifacts/LM_Web_exploded/META-INF/MANIFEST.MF
Normal file
@@ -0,0 +1,3 @@
|
||||
Manifest-Version: 1.0
|
||||
Class-Path:
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,7 @@
|
||||
url=jdbc:mysql://localhost:3306/library_management?serverTimezone=UTC&useSSL=false&allowPublicKeyRetrieval=true
|
||||
username=root
|
||||
password=root
|
||||
driverClassName=com.mysql.cj.jdbc.Driver
|
||||
initialSize=10
|
||||
maxActive=20
|
||||
maxWait=1000
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
classes/artifacts/LM_Web_exploded/WEB-INF/lib/druid-1.2.24.jar
Normal file
BIN
classes/artifacts/LM_Web_exploded/WEB-INF/lib/druid-1.2.24.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
classes/artifacts/LM_Web_exploded/WEB-INF/lib/lombok.jar
Normal file
BIN
classes/artifacts/LM_Web_exploded/WEB-INF/lib/lombok.jar
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
14
classes/artifacts/LM_Web_exploded/WEB-INF/web.xml
Normal file
14
classes/artifacts/LM_Web_exploded/WEB-INF/web.xml
Normal file
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
|
||||
version="4.0">
|
||||
|
||||
<welcome-file-list>
|
||||
<welcome-file>view/Login.jsp</welcome-file>
|
||||
</welcome-file-list>
|
||||
|
||||
<!-- 过滤器统一使用 @WebFilter 注解注册,不需要在 web.xml 中重复配置 -->
|
||||
<!-- 执行顺序由 @WebFilter 的 filter-name 字母顺序决定:A → E → EmployeePermission -->
|
||||
|
||||
</web-app>
|
||||
448
classes/artifacts/LM_Web_exploded/view/Login.jsp
Normal file
448
classes/artifacts/LM_Web_exploded/view/Login.jsp
Normal file
@@ -0,0 +1,448 @@
|
||||
<%@ 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>
|
||||
688
classes/artifacts/LM_Web_exploded/view/Reader.jsp
Normal file
688
classes/artifacts/LM_Web_exploded/view/Reader.jsp
Normal file
@@ -0,0 +1,688 @@
|
||||
<%@ 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>
|
||||
886
classes/artifacts/LM_Web_exploded/view/Work.jsp
Normal file
886
classes/artifacts/LM_Web_exploded/view/Work.jsp
Normal file
@@ -0,0 +1,886 @@
|
||||
<%@ 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 = "";
|
||||
int positionCode = currentEmployee.getPosition();
|
||||
switch (positionCode) {
|
||||
case 1: positionDesc = "馆员"; break;
|
||||
case 2: positionDesc = "工作人员"; break;
|
||||
case 3: positionDesc = "经理"; break;
|
||||
}
|
||||
String empJson = String.format(
|
||||
"{\"id\":\"%s\",\"name\":\"%s\",\"position\":\"%s\",\"positionCode\":%d}",
|
||||
currentEmployee.getEmployeeCode(),
|
||||
currentEmployee.getName(),
|
||||
positionDesc,
|
||||
positionCode
|
||||
);
|
||||
%>
|
||||
<!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;
|
||||
}
|
||||
|
||||
.role-badge {
|
||||
background: #2c3e50;
|
||||
color: white;
|
||||
padding: 0.3rem 1rem;
|
||||
border-radius: 40px;
|
||||
font-size: 0.85rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.role-badge.staff {
|
||||
background: #3b7dd8;
|
||||
}
|
||||
|
||||
.role-badge.worker {
|
||||
background: #27ae60;
|
||||
}
|
||||
|
||||
.role-badge.manager {
|
||||
background: #e67e22;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
.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: 0px;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
.perm-note {
|
||||
background: #fff3cd;
|
||||
color: #856404;
|
||||
border-radius: 12px;
|
||||
padding: 0.8rem 1.2rem;
|
||||
font-size: 0.85rem;
|
||||
margin-bottom: 1rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
</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="role-badge" :class="employee.positionCode === 1 ? 'staff' : employee.positionCode === 2 ? 'worker' : 'manager'">
|
||||
职位:{{ employee.position }}
|
||||
</span>
|
||||
</div>
|
||||
<button class="logout-btn" @click="logout">退出登录</button>
|
||||
</div>
|
||||
|
||||
<!-- 主区域:根据真实职位(positionCode)渲染功能 -->
|
||||
<div class="main-content">
|
||||
|
||||
<!-- ====== 馆员视图:图书管理 ====== -->
|
||||
<div v-if="employee.positionCode === 1">
|
||||
<div class="perm-note">
|
||||
您的权限:管理图书(增删改查)
|
||||
</div>
|
||||
<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 v-if="!editingBookId" class="btn" @click="addBook">添加图书</button>
|
||||
<template v-else>
|
||||
<button class="btn" style="background:#27ae60;" @click="updateBook">更新图书</button>
|
||||
<button class="btn btn-outline" @click="cancelEdit">取消</button>
|
||||
</template>
|
||||
</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><th>简介</th><th>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-if="books.length === 0">
|
||||
<td colspan="10" class="empty-placeholder">暂无图书</td>
|
||||
</tr>
|
||||
<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><span v-if="book.targetAudience === '儿童'" class="badge">儿童</span><span v-else>通用</span></td>
|
||||
<td>{{ book.description || '—' }}</td>
|
||||
<td class="action-cell">
|
||||
<button v-if="editingBookId !== book.id" class="btn btn-sm" @click="startEdit(book)">编辑</button>
|
||||
<button class="btn btn-sm btn-outline" @click="deleteBook(book)">删除</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ====== 工作人员视图:帮助注册 + 代借代还 ====== -->
|
||||
<div v-else-if="employee.positionCode === 2">
|
||||
<div class="perm-note">
|
||||
您的权限:帮助注册读者、代借代还图书
|
||||
</div>
|
||||
<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></tr></thead>
|
||||
<tbody>
|
||||
<tr v-if="staffBorrowedList.length === 0"><td colspan="6" 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.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" style="flex:2;">
|
||||
<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="employee.positionCode === 3">
|
||||
<div class="perm-note">
|
||||
您的权限:管理活动、管理员工
|
||||
</div>
|
||||
<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" type="datetime-local" />
|
||||
</div>
|
||||
<div class="form-field">
|
||||
<label>结束时间</label>
|
||||
<input v-model="activityForm.endTime" type="datetime-local" />
|
||||
</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>
|
||||
<div class="form-field">
|
||||
<label>最大人数</label>
|
||||
<input v-model.number="activityForm.maxParticipants" type="number" placeholder="50" />
|
||||
</div>
|
||||
<button class="btn" @click="addActivity">添加活动</button>
|
||||
</div>
|
||||
<div class="table-wrapper">
|
||||
<table>
|
||||
<thead><tr><th>ID</th><th>名称</th><th>时间</th><th>地点</th><th>简介</th><th>最大人数</th><th>操作</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-if="activities.length === 0"><td colspan="7" class="empty-placeholder">暂无活动</td></tr>
|
||||
<tr v-for="act in activities" :key="act.id">
|
||||
<td>{{ act.id }}</td>
|
||||
<td>{{ act.name }}</td>
|
||||
<td>{{ act.startTime }}</td>
|
||||
<td>{{ act.location }}</td>
|
||||
<td>{{ act.description || '—' }}</td>
|
||||
<td>{{ act.maxParticipants }}</td>
|
||||
<td class="action-cell">
|
||||
<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="如 E006" />
|
||||
</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-if="employees.length === 0"><td colspan="3" class="empty-placeholder">暂无员工</td></tr>
|
||||
<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 books = ref([]);
|
||||
const editingBookId = ref(null);
|
||||
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');--%>
|
||||
<%-- if (response.ok) books.value = await response.json();--%>
|
||||
<%-- } catch (e) { console.error('加载图书失败', e); }--%>
|
||||
<%--};--%>
|
||||
const loadBooks = async () => {
|
||||
console.log('>>> 开始加载图书');
|
||||
try {
|
||||
const response = await fetch('${pageContext.request.contextPath}/api/books', {
|
||||
credentials: 'include'
|
||||
});
|
||||
console.log('>>> HTTP状态码:', response.status);
|
||||
if (response.ok) {
|
||||
books.value = await response.json();
|
||||
console.log('>>> 图书数量:', books.value.length);
|
||||
} else {
|
||||
const errorText = await response.text();
|
||||
console.error('>>> 错误:', errorText);
|
||||
alert('加载失败: ' + response.status);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('>>> 异常:', e);
|
||||
}
|
||||
};
|
||||
|
||||
const addBook = async () => {
|
||||
if (!bookForm.value.isbn || !bookForm.value.title) {
|
||||
alert('ISBN和书名不能为空'); return;
|
||||
}
|
||||
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,
|
||||
credentials: 'include'
|
||||
});
|
||||
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 startEdit = (book) => {
|
||||
editingBookId.value = book.id;
|
||||
bookForm.value = {
|
||||
isbn: book.isbn, title: book.title, author: book.author,
|
||||
publisher: book.publisher, publishYear: book.publishYear,
|
||||
totalStock: book.totalStock, targetAudience: book.targetAudience || '通用',
|
||||
description: book.description || ''
|
||||
};
|
||||
};
|
||||
|
||||
const updateBook = async () => {
|
||||
if (!bookForm.value.isbn || !bookForm.value.title) {
|
||||
alert('ISBN和书名不能为空'); return;
|
||||
}
|
||||
const params = new URLSearchParams(bookForm.value);
|
||||
try {
|
||||
const response = await fetch('${pageContext.request.contextPath}/api/books/' + editingBookId.value, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: params,
|
||||
credentials: 'include'
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert('更新成功');
|
||||
editingBookId.value = null;
|
||||
bookForm.value = { isbn:'',title:'',author:'',publisher:'',publishYear:null,totalStock:null,targetAudience:'通用',description:'' };
|
||||
await loadBooks();
|
||||
} else {
|
||||
alert(result.message || '更新失败');
|
||||
}
|
||||
} catch (e) { alert('网络错误'); }
|
||||
};
|
||||
|
||||
const cancelEdit = () => {
|
||||
editingBookId.value = null;
|
||||
bookForm.value = { isbn:'',title:'',author:'',publisher:'',publishYear:null,totalStock:null,targetAudience:'通用',description:'' };
|
||||
};
|
||||
|
||||
const deleteBook = async (book) => {
|
||||
if (!confirm('确定删除《' + book.title + '》吗?')) 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 () => {
|
||||
if (!registerForm.value.cardNumber || !registerForm.value.name) {
|
||||
alert('证号和姓名不能为空'); return;
|
||||
}
|
||||
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,
|
||||
credentials: 'include'
|
||||
});
|
||||
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 quickBorrowIsbn = ref('');
|
||||
|
||||
const searchBorrowedStaff = async () => {
|
||||
if (!borrowerAccount.value) { alert('请输入读者证号'); return; }
|
||||
try {
|
||||
const response = await fetch('${pageContext.request.contextPath}/api/borrows?readerCard=' + encodeURIComponent(borrowerAccount.value));
|
||||
if (response.ok) 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,
|
||||
credentials: 'include'
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) { alert('归还成功'); await searchBorrowedStaff(); }
|
||||
else { alert(result.message || '归还失败'); }
|
||||
} catch (e) { alert('网络错误'); }
|
||||
};
|
||||
|
||||
const quickBorrow = async () => {
|
||||
if (!borrowerAccount.value) { alert('请先输入读者证号'); return; }
|
||||
if (!quickBorrowIsbn.value) { alert('请输入ISBN或书名'); 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,
|
||||
credentials: 'include'
|
||||
});
|
||||
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: '', maxParticipants: 50
|
||||
});
|
||||
|
||||
const loadActivities = async () => {
|
||||
try {
|
||||
const response = await fetch('${pageContext.request.contextPath}/api/activities');
|
||||
if (response.ok) activities.value = await response.json();
|
||||
} catch (e) { console.error('加载活动失败', e); }
|
||||
};
|
||||
|
||||
const addActivity = async () => {
|
||||
if (!activityForm.value.name) { alert('活动名称不能为空'); return; }
|
||||
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,
|
||||
credentials: 'include'
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert('活动添加成功');
|
||||
await loadActivities();
|
||||
activityForm.value = { name:'',startTime:'',endTime:'',location:'',description:'',maxParticipants:50 };
|
||||
} else { alert(result.message || '添加失败'); }
|
||||
} catch (e) { alert('网络错误'); }
|
||||
};
|
||||
|
||||
const deleteActivity = async (act) => {
|
||||
if (!confirm('确定删除活动"' + act.name + '"吗?')) 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');
|
||||
if (response.ok) employees.value = await response.json();
|
||||
} catch (e) { console.error('加载员工失败', e); }
|
||||
};
|
||||
|
||||
const addEmployee = async () => {
|
||||
// 在 addEmployee 方法中打印
|
||||
<%--console.log('请求URL:', '${pageContext.request.contextPath}/api/employees');--%>
|
||||
if (!newEmployee.value.employeeCode || !newEmployee.value.name) {
|
||||
alert('工号和姓名不能为空'); return;
|
||||
}
|
||||
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,
|
||||
credentials: 'include'
|
||||
});
|
||||
const result = await response.json();
|
||||
if (result.success) {
|
||||
alert('员工注册成功');
|
||||
await loadEmployees();
|
||||
newEmployee.value = { employeeCode:'',name:'',position:'馆员' };
|
||||
} else { alert(result.message || '注册失败'); }
|
||||
} catch (e) { alert('网络错误'); }
|
||||
};
|
||||
|
||||
// ---------- 退出登录 ----------
|
||||
const logout = () => {
|
||||
window.location.href = '${pageContext.request.contextPath}/view/Login.jsp';
|
||||
};
|
||||
|
||||
// ---------- 挂载时根据职位加载数据 ----------
|
||||
// onMounted(() => {
|
||||
// if (employee.value.positionCode === 1) {
|
||||
// loadBooks();
|
||||
// } else if (employee.value.positionCode === 3) {
|
||||
// loadActivities();
|
||||
// loadEmployees();
|
||||
// }
|
||||
// });
|
||||
onMounted(() => {
|
||||
console.log('>>> positionCode:', employee.value.positionCode);
|
||||
if (employee.value.positionCode === 1) {
|
||||
loadBooks();
|
||||
} else if (employee.value.positionCode === 3) {
|
||||
loadActivities();
|
||||
loadEmployees();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
employee,
|
||||
books, bookForm, editingBookId,
|
||||
startEdit, addBook, updateBook, cancelEdit, deleteBook,
|
||||
registerForm, staffRegister,
|
||||
staffBorrowedList, borrowerAccount, searchBorrowedStaff,
|
||||
returnBookStaff, quickBorrowIsbn, quickBorrow,
|
||||
activities, activityForm, addActivity, deleteActivity,
|
||||
employees, newEmployee, addEmployee,
|
||||
logout
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
app.mount('#app');
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
2
classes/artifacts/LM_Web_exploded/view/jquery.min.js
vendored
Normal file
2
classes/artifacts/LM_Web_exploded/view/jquery.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
18272
classes/artifacts/LM_Web_exploded/view/vue3.js
Normal file
18272
classes/artifacts/LM_Web_exploded/view/vue3.js
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user