887 lines
38 KiB
Plaintext
887 lines
38 KiB
Plaintext
|
|
<%@ 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>
|