From 29914a41784d60c5d412b3e6f89aacc54da60308 Mon Sep 17 00:00:00 2001
From: superlishunqin <852326703@qq.com>
Date: Tue, 6 May 2025 12:01:11 +0800
Subject: [PATCH] 0506
---
app/__init__.py | 6 +
app/controllers/book.py | 132 +-
app/controllers/borrow.py | 397 +-
app/controllers/inventory.py | 161 +
app/controllers/user.py | 67 +
app/models/book.py | 3 +-
app/models/user.py | 3 +-
app/services/user_service.py | 21 +
.../354fed85-17f3-4626-b1a9-f061b7ac94f9.jpg | Bin 0 -> 65431 bytes
app/static/css/book-edit.css | 424 +
app/static/css/borrow_management.css | 520 +
app/static/css/browse.css | 860 ++
app/static/css/inventory-adjust.css | 461 +
app/static/css/inventory-book-logs.css | 715 ++
app/static/css/inventory-list.css | 417 +
app/static/css/inventory-logs.css | 710 ++
app/static/css/my_borrows.css | 474 +
app/static/css/overdue.css | 396 +
app/static/css/user-form.css | 636 +
app/static/js/book-add.js | 2 +-
app/static/js/book-edit.js | 176 +
app/static/js/borrow_management.js | 244 +
app/static/js/browse.js | 292 +
app/static/js/inventory-adjust.js | 103 +
app/static/js/inventory-book-logs.js | 263 +
app/static/js/inventory-list.js | 30 +
app/static/js/inventory-logs.js | 219 +
app/static/js/my_borrows.js | 132 +
app/static/js/overdue.js | 138 +
app/static/js/user-add.js | 140 +
app/templates/base.html | 10 +-
app/templates/book/browse.html | 224 +
app/templates/book/detail.html | 19 +-
app/templates/book/edit.html | 307 +-
app/templates/borrow/borrow_management.html | 317 +
app/templates/borrow/my_borrows.html | 186 +
app/templates/borrow/overdue.html | 179 +
app/templates/inventory/adjust.html | 96 +
app/templates/inventory/book_logs.html | 186 +
app/templates/inventory/list.html | 135 +
app/templates/inventory/logs.html | 210 +
app/templates/user/add.html | 120 +
app/templates/user/list.html | 2 +-
code_collection.txt | 10555 +++++++++++++++-
44 files changed, 20245 insertions(+), 443 deletions(-)
create mode 100644 app/static/covers/354fed85-17f3-4626-b1a9-f061b7ac94f9.jpg
create mode 100644 app/static/css/book-edit.css
create mode 100644 app/static/css/borrow_management.css
create mode 100644 app/static/css/browse.css
create mode 100644 app/static/css/inventory-adjust.css
create mode 100644 app/static/css/inventory-book-logs.css
create mode 100644 app/static/css/inventory-list.css
create mode 100644 app/static/css/inventory-logs.css
create mode 100644 app/static/css/my_borrows.css
create mode 100644 app/static/css/overdue.css
create mode 100644 app/static/css/user-form.css
create mode 100644 app/static/js/book-edit.js
create mode 100644 app/static/js/borrow_management.js
create mode 100644 app/static/js/browse.js
create mode 100644 app/static/js/inventory-adjust.js
create mode 100644 app/static/js/inventory-book-logs.js
create mode 100644 app/static/js/inventory-list.js
create mode 100644 app/static/js/inventory-logs.js
create mode 100644 app/static/js/my_borrows.js
create mode 100644 app/static/js/overdue.js
create mode 100644 app/static/js/user-add.js
create mode 100644 app/templates/book/browse.html
create mode 100644 app/templates/borrow/borrow_management.html
create mode 100644 app/templates/borrow/my_borrows.html
create mode 100644 app/templates/borrow/overdue.html
create mode 100644 app/templates/inventory/adjust.html
create mode 100644 app/templates/inventory/book_logs.html
create mode 100644 app/templates/inventory/list.html
create mode 100644 app/templates/inventory/logs.html
create mode 100644 app/templates/user/add.html
diff --git a/app/__init__.py b/app/__init__.py
index c84812c..0ea45e4 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -4,6 +4,7 @@ from app.models.user import db, User
from app.controllers.user import user_bp
from app.controllers.book import book_bp
from app.controllers.borrow import borrow_bp
+from app.controllers.inventory import inventory_bp
from flask_login import LoginManager, current_user
import os
@@ -48,6 +49,7 @@ def create_app(config=None):
app.register_blueprint(user_bp, url_prefix='/user')
app.register_blueprint(book_bp, url_prefix='/book')
app.register_blueprint(borrow_bp, url_prefix='/borrow')
+ app.register_blueprint(inventory_bp)
# 创建数据库表
with app.app_context():
@@ -132,3 +134,7 @@ def create_app(config=None):
return s
return app
+
+ @app.context_processor
+ def inject_now():
+ return {'now': datetime.datetime.now()}
diff --git a/app/controllers/book.py b/app/controllers/book.py
index 19ec822..0c75f7d 100644
--- a/app/controllers/book.py
+++ b/app/controllers/book.py
@@ -116,7 +116,8 @@ def book_detail(book_id):
# 如果用户是管理员,预先查询并排序借阅记录
borrow_records = []
- if g.user.role_id == 1: # 假设 role_id 1 为管理员
+ # 防御性编程:确保 g.user 存在且有 role_id 属性
+ if hasattr(g, 'user') and g.user is not None and hasattr(g.user, 'role_id') and g.user.role_id == 1:
from app.models.borrow import BorrowRecord
borrow_records = BorrowRecord.query.filter_by(book_id=book_id).order_by(BorrowRecord.borrow_date.desc()).limit(
10).all()
@@ -124,12 +125,13 @@ def book_detail(book_id):
return render_template(
'book/detail.html',
book=book,
- current_user=g.user,
+ current_user=current_user, # 使用 flask_login 的 current_user 而不是 g.user
borrow_records=borrow_records,
now=now
)
+
# 添加图书页面
@book_bp.route('/add', methods=['GET', 'POST'])
@login_required
@@ -283,6 +285,7 @@ def edit_book(book_id):
book = Book.query.get_or_404(book_id)
if request.method == 'POST':
+ # 获取表单数据
title = request.form.get('title')
author = request.form.get('author')
publisher = request.form.get('publisher')
@@ -294,13 +297,72 @@ def edit_book(book_id):
price = request.form.get('price')
status = request.form.get('status', type=int)
+ # 基本验证
if not title or not author:
flash('书名和作者不能为空', 'danger')
categories = Category.query.all()
return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
+ # ISBN验证
+ if isbn and isbn.strip(): # 确保ISBN不是空字符串
+ # 移除连字符和空格
+ clean_isbn = isbn.replace('-', '').replace(' ', '')
+
+ # 长度检查
+ if len(clean_isbn) != 10 and len(clean_isbn) != 13:
+ flash('ISBN必须是10位或13位', 'danger')
+ categories = Category.query.all()
+ return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
+
+ # ISBN-10验证
+ if len(clean_isbn) == 10:
+ # 检查前9位是否为数字
+ if not clean_isbn[:9].isdigit():
+ flash('ISBN-10的前9位必须是数字', 'danger')
+ categories = Category.query.all()
+ return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
+
+ # 检查最后一位是否为数字或'X'
+ if not (clean_isbn[9].isdigit() or clean_isbn[9].upper() == 'X'):
+ flash('ISBN-10的最后一位必须是数字或X', 'danger')
+ categories = Category.query.all()
+ return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
+
+ # 校验和验证
+ sum = 0
+ for i in range(9):
+ sum += int(clean_isbn[i]) * (10 - i)
+
+ check_digit = 10 if clean_isbn[9].upper() == 'X' else int(clean_isbn[9])
+ sum += check_digit
+
+ if sum % 11 != 0:
+ flash('ISBN-10校验和无效', 'danger')
+ categories = Category.query.all()
+ return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
+
+ # ISBN-13验证
+ if len(clean_isbn) == 13:
+ # 检查是否全是数字
+ if not clean_isbn.isdigit():
+ flash('ISBN-13必须全是数字', 'danger')
+ categories = Category.query.all()
+ return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
+
+ # 校验和验证
+ sum = 0
+ for i in range(12):
+ sum += int(clean_isbn[i]) * (1 if i % 2 == 0 else 3)
+
+ check_digit = (10 - (sum % 10)) % 10
+
+ if check_digit != int(clean_isbn[12]):
+ flash('ISBN-13校验和无效', 'danger')
+ categories = Category.query.all()
+ return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
+
# 处理库存变更
- new_stock = request.form.get('stock', type=int)
+ new_stock = request.form.get('stock', type=int) or 0 # 默认为0而非None
if new_stock != book.stock:
from app.models.inventory import InventoryLog
change_amount = new_stock - book.stock
@@ -346,11 +408,17 @@ def edit_book(book_id):
book.status = status
book.updated_at = datetime.datetime.now()
- db.session.commit()
-
- flash('图书信息更新成功', 'success')
- return redirect(url_for('book.book_list'))
+ try:
+ db.session.commit()
+ flash('图书信息更新成功', 'success')
+ return redirect(url_for('book.book_list'))
+ except Exception as e:
+ db.session.rollback()
+ flash(f'保存失败: {str(e)}', 'danger')
+ categories = Category.query.all()
+ return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
+ # GET 请求
categories = Category.query.all()
return render_template('book/edit.html', book=book, categories=categories, current_user=g.user)
@@ -622,3 +690,53 @@ def test_permissions():
是否管理员: {'是' if current_user.role_id == 1 else '否'}
尝试访问管理页面
"""
+
+# 添加到app/controllers/book.py文件中
+
+@book_bp.route('/browse')
+@login_required
+def browse_books():
+ """图书浏览页面 - 面向普通用户的友好界面"""
+ page = request.args.get('page', 1, type=int)
+ per_page = request.args.get('per_page', 12, type=int) # 增加每页数量
+
+ # 只显示状态为1的图书(未下架的图书)
+ query = Book.query.filter_by(status=1)
+
+ # 搜索功能
+ search = request.args.get('search', '')
+ if search:
+ query = query.filter(
+ (Book.title.contains(search)) |
+ (Book.author.contains(search)) |
+ (Book.isbn.contains(search))
+ )
+
+ # 分类筛选
+ category_id = request.args.get('category_id', type=int)
+ if category_id:
+ query = query.filter_by(category_id=category_id)
+
+ # 排序
+ sort = request.args.get('sort', 'id')
+ order = request.args.get('order', 'desc')
+
+ if order == 'desc':
+ query = query.order_by(getattr(Book, sort).desc())
+ else:
+ query = query.order_by(getattr(Book, sort))
+
+ pagination = query.paginate(page=page, per_page=per_page)
+ books = pagination.items
+
+ # 获取所有分类供筛选使用
+ categories = Category.query.all()
+
+ return render_template('book/browse.html',
+ books=books,
+ pagination=pagination,
+ search=search,
+ categories=categories,
+ category_id=category_id,
+ sort=sort,
+ order=order,)
diff --git a/app/controllers/borrow.py b/app/controllers/borrow.py
index f68acb1..52b2f6e 100644
--- a/app/controllers/borrow.py
+++ b/app/controllers/borrow.py
@@ -1,11 +1,11 @@
-from flask import Blueprint, request, redirect, url_for, flash, g, jsonify
+from flask import Blueprint, request, redirect, url_for, flash, render_template, jsonify
from flask_login import current_user, login_required
from app.models.book import Book
from app.models.borrow import BorrowRecord
from app.models.inventory import InventoryLog
-from app.models.user import db # 修正:从 user 模型导入 db
-from app.utils.auth import login_required
+from app.models.user import db, User
import datetime
+from app.utils.auth import admin_required
# 创建借阅蓝图
borrow_bp = Blueprint('borrow', __name__, url_prefix='/borrow')
@@ -30,7 +30,7 @@ def borrow_book():
# 检查当前用户是否已借阅此书
existing_borrow = BorrowRecord.query.filter_by(
- user_id=g.user.id,
+ user_id=current_user.id,
book_id=book_id,
status=1 # 1表示借阅中
).first()
@@ -45,7 +45,7 @@ def borrow_book():
due_date = now + datetime.timedelta(days=borrow_days)
borrow_record = BorrowRecord(
- user_id=g.user.id,
+ user_id=current_user.id,
book_id=book_id,
borrow_date=now,
due_date=due_date,
@@ -67,7 +67,7 @@ def borrow_book():
change_type='借出',
change_amount=-1,
after_stock=book.stock,
- operator_id=g.user.id,
+ operator_id=current_user.id,
remark='用户借书',
changed_at=now
)
@@ -101,7 +101,7 @@ def add_borrow(book_id):
# 检查是否已借阅
existing_borrow = BorrowRecord.query.filter_by(
- user_id=current_user.id, # 使用current_user
+ user_id=current_user.id,
book_id=book_id,
status=1 # 1表示借阅中
).first()
@@ -118,7 +118,7 @@ def add_borrow(book_id):
due_date = now + datetime.timedelta(days=borrow_days)
borrow_record = BorrowRecord(
- user_id=current_user.id, # 使用current_user
+ user_id=current_user.id,
book_id=book_id,
borrow_date=now,
due_date=due_date,
@@ -140,7 +140,7 @@ def add_borrow(book_id):
change_type='借出',
change_amount=-1,
after_stock=book.stock,
- operator_id=current_user.id, # 使用current_user
+ operator_id=current_user.id,
remark='用户借书',
changed_at=now
)
@@ -158,3 +158,382 @@ def add_borrow(book_id):
'success': False,
'message': f'借阅失败: {str(e)}'
})
+
+
+@borrow_bp.route('/return/', methods=['POST'])
+@login_required
+def return_book(borrow_id):
+ """还书操作"""
+ # 查找借阅记录
+ borrow_record = BorrowRecord.query.get_or_404(borrow_id)
+
+ # 检查是否是自己的借阅记录或者是管理员
+ if borrow_record.user_id != current_user.id and current_user.role_id != 1:
+ return jsonify({
+ 'success': False,
+ 'message': '您无权执行此操作'
+ })
+
+ # 检查是否已还
+ if borrow_record.status != 1:
+ return jsonify({
+ 'success': False,
+ 'message': '此书已归还,请勿重复操作'
+ })
+
+ try:
+ book = Book.query.get(borrow_record.book_id)
+ now = datetime.datetime.now()
+
+ # 更新借阅记录
+ borrow_record.status = 0 # 0表示已归还
+ borrow_record.return_date = now
+ borrow_record.updated_at = now
+
+ # 更新图书库存
+ book.stock += 1
+ book.updated_at = now
+
+ db.session.commit()
+
+ # 添加库存变更日志
+ inventory_log = InventoryLog(
+ book_id=borrow_record.book_id,
+ change_type='归还',
+ change_amount=1,
+ after_stock=book.stock,
+ operator_id=current_user.id,
+ remark='用户还书',
+ changed_at=now
+ )
+ db.session.add(inventory_log)
+ db.session.commit()
+
+ return jsonify({
+ 'success': True,
+ 'message': f'成功归还《{book.title}》'
+ })
+
+ except Exception as e:
+ db.session.rollback()
+ return jsonify({
+ 'success': False,
+ 'message': f'归还失败: {str(e)}'
+ })
+
+
+@borrow_bp.route('/renew/', methods=['POST'])
+@login_required
+def renew_book(borrow_id):
+ """续借操作"""
+ # 查找借阅记录
+ borrow_record = BorrowRecord.query.get_or_404(borrow_id)
+
+ # 检查是否是自己的借阅记录或者是管理员
+ if borrow_record.user_id != current_user.id and current_user.role_id != 1:
+ return jsonify({
+ 'success': False,
+ 'message': '您无权执行此操作'
+ })
+
+ # 检查是否已还
+ if borrow_record.status != 1:
+ return jsonify({
+ 'success': False,
+ 'message': '此书已归还,无法续借'
+ })
+
+ # 检查续借次数限制(最多续借2次)
+ if borrow_record.renew_count >= 2:
+ return jsonify({
+ 'success': False,
+ 'message': '此书已达到最大续借次数,无法继续续借'
+ })
+
+ try:
+ now = datetime.datetime.now()
+
+ # 检查是否已逾期
+ if now > borrow_record.due_date:
+ return jsonify({
+ 'success': False,
+ 'message': '此书已逾期,请先归还并处理逾期情况'
+ })
+
+ # 续借14天
+ new_due_date = borrow_record.due_date + datetime.timedelta(days=14)
+
+ # 更新借阅记录
+ borrow_record.due_date = new_due_date
+ borrow_record.renew_count += 1
+ borrow_record.updated_at = now
+
+ db.session.commit()
+
+ return jsonify({
+ 'success': True,
+ 'message': f'续借成功,新的归还日期为 {new_due_date.strftime("%Y-%m-%d")}'
+ })
+
+ except Exception as e:
+ db.session.rollback()
+ return jsonify({
+ 'success': False,
+ 'message': f'续借失败: {str(e)}'
+ })
+
+
+@borrow_bp.route('/my_borrows')
+@login_required
+def my_borrows():
+ """用户查看自己的借阅记录"""
+ page = request.args.get('page', 1, type=int)
+ status = request.args.get('status', default=None, type=int)
+
+ # 构建查询
+ query = BorrowRecord.query.filter_by(user_id=current_user.id)
+
+ # 根据状态筛选
+ if status is not None:
+ query = query.filter_by(status=status)
+
+ # 按借阅日期倒序排列
+ query = query.order_by(BorrowRecord.borrow_date.desc())
+
+ # 分页
+ pagination = query.paginate(page=page, per_page=10, error_out=False)
+
+ # 获取当前借阅数量和历史借阅数量(用于标签显示)
+ current_borrows_count = BorrowRecord.query.filter_by(user_id=current_user.id, status=1).count()
+ history_borrows_count = BorrowRecord.query.filter_by(user_id=current_user.id, status=0).count()
+
+ return render_template(
+ 'borrow/my_borrows.html',
+ pagination=pagination,
+ current_borrows_count=current_borrows_count,
+ history_borrows_count=history_borrows_count,
+ status=status,
+ now=datetime.datetime.now() # 添加当前时间变量
+ )
+
+
+
+@borrow_bp.route('/manage')
+@login_required
+@admin_required
+def manage_borrows():
+ """管理员查看所有借阅记录"""
+ page = request.args.get('page', 1, type=int)
+ status = request.args.get('status', default=None, type=int)
+ user_id = request.args.get('user_id', default=None, type=int)
+ book_id = request.args.get('book_id', default=None, type=int)
+ search = request.args.get('search', default='')
+
+ # 构建查询
+ query = BorrowRecord.query
+
+ # 根据状态筛选
+ if status is not None:
+ query = query.filter_by(status=status)
+
+ # 根据用户筛选
+ if user_id:
+ query = query.filter_by(user_id=user_id)
+
+ # 根据图书筛选
+ if book_id:
+ query = query.filter_by(book_id=book_id)
+
+ # 根据搜索条件筛选(用户名或图书名)
+ if search:
+ query = query.join(User, BorrowRecord.user_id == User.id) \
+ .join(Book, BorrowRecord.book_id == Book.id) \
+ .filter((User.username.like(f'%{search}%')) |
+ (Book.title.like(f'%{search}%')))
+
+ # 按借阅日期倒序排列
+ query = query.order_by(BorrowRecord.borrow_date.desc())
+
+ # 分页
+ pagination = query.paginate(page=page, per_page=10, error_out=False)
+
+ # 获取统计数据
+ current_borrows_count = BorrowRecord.query.filter_by(status=1).count()
+ history_borrows_count = BorrowRecord.query.filter_by(status=0).count()
+
+ # 获取所有用户(用于筛选)
+ users = User.query.all()
+
+ return render_template(
+ 'borrow/borrow_management.html',
+ pagination=pagination,
+ current_borrows_count=current_borrows_count,
+ history_borrows_count=history_borrows_count,
+ status=status,
+ user_id=user_id,
+ book_id=book_id,
+ search=search,
+ users=users,
+ now=datetime.datetime.now() # 添加当前时间变量
+ )
+
+
+
+@borrow_bp.route('/admin/add', methods=['POST'])
+@login_required
+@admin_required
+def admin_add_borrow():
+ """管理员为用户添加借阅记录"""
+ user_id = request.form.get('user_id', type=int)
+ book_id = request.form.get('book_id', type=int)
+ borrow_days = request.form.get('borrow_days', type=int, default=14)
+
+ if not user_id or not book_id:
+ flash('用户ID和图书ID不能为空', 'danger')
+ return redirect(url_for('borrow.manage_borrows'))
+
+ # 验证用户和图书是否存在
+ user = User.query.get_or_404(user_id)
+ book = Book.query.get_or_404(book_id)
+
+ # 检查库存
+ if book.stock <= 0:
+ flash(f'《{book.title}》当前无库存,无法借阅', 'danger')
+ return redirect(url_for('borrow.manage_borrows'))
+
+ # 检查用户是否已借阅此书
+ existing_borrow = BorrowRecord.query.filter_by(
+ user_id=user_id,
+ book_id=book_id,
+ status=1 # 1表示借阅中
+ ).first()
+
+ if existing_borrow:
+ flash(f'用户 {user.username} 已借阅《{book.title}》,请勿重复借阅', 'warning')
+ return redirect(url_for('borrow.manage_borrows'))
+
+ try:
+ # 创建借阅记录
+ now = datetime.datetime.now()
+ due_date = now + datetime.timedelta(days=borrow_days)
+
+ borrow_record = BorrowRecord(
+ user_id=user_id,
+ book_id=book_id,
+ borrow_date=now,
+ due_date=due_date,
+ status=1, # 1表示借阅中
+ created_at=now,
+ updated_at=now
+ )
+
+ # 更新图书库存
+ book.stock -= 1
+ book.updated_at = now
+
+ db.session.add(borrow_record)
+ db.session.commit()
+
+ # 添加库存变更日志
+ inventory_log = InventoryLog(
+ book_id=book_id,
+ change_type='借出',
+ change_amount=-1,
+ after_stock=book.stock,
+ operator_id=current_user.id,
+ remark=f'管理员 {current_user.username} 为用户 {user.username} 借书',
+ changed_at=now
+ )
+ db.session.add(inventory_log)
+ db.session.commit()
+
+ flash(f'成功为用户 {user.username} 借阅《{book.title}》,归还日期: {due_date.strftime("%Y-%m-%d")}', 'success')
+
+ except Exception as e:
+ db.session.rollback()
+ flash(f'借阅失败: {str(e)}', 'danger')
+
+ return redirect(url_for('borrow.manage_borrows'))
+
+
+@borrow_bp.route('/overdue')
+@login_required
+@admin_required
+def overdue_borrows():
+ """查看逾期借阅"""
+ page = request.args.get('page', 1, type=int)
+ now = datetime.datetime.now()
+
+ # 查询所有已逾期且未归还的借阅记录
+ query = BorrowRecord.query.filter(
+ BorrowRecord.status == 1, # 借阅中
+ BorrowRecord.due_date < now # 已过期
+ ).order_by(BorrowRecord.due_date) # 按到期日期排序,最早到期的排在前面
+
+ pagination = query.paginate(page=page, per_page=10, error_out=False)
+
+ # 计算逾期总数
+ overdue_count = query.count()
+
+ return render_template(
+ 'borrow/overdue.html',
+ pagination=pagination,
+ overdue_count=overdue_count
+ )
+
+
+@borrow_bp.route('/overdue/notify/', methods=['POST'])
+@login_required
+@admin_required
+def notify_overdue(borrow_id):
+ """发送逾期通知"""
+ from app.models.notification import Notification
+
+ borrow_record = BorrowRecord.query.get_or_404(borrow_id)
+
+ # 检查是否已还
+ if borrow_record.status != 1:
+ return jsonify({
+ 'success': False,
+ 'message': '此书已归还,无需发送逾期通知'
+ })
+
+ now = datetime.datetime.now()
+
+ # 检查是否确实逾期
+ if borrow_record.due_date > now:
+ return jsonify({
+ 'success': False,
+ 'message': '此借阅记录尚未逾期'
+ })
+
+ try:
+ # 创建通知
+ notification = Notification(
+ user_id=borrow_record.user_id,
+ title='图书逾期提醒',
+ content=f'您借阅的《{borrow_record.book.title}》已逾期,请尽快归还。应还日期: {borrow_record.due_date.strftime("%Y-%m-%d")}',
+ type='overdue',
+ sender_id=current_user.id,
+ created_at=now
+ )
+
+ db.session.add(notification)
+ db.session.commit()
+
+ # 更新借阅记录备注
+ borrow_record.remark = f'{borrow_record.remark or ""}[{now.strftime("%Y-%m-%d")} 已发送逾期通知]'
+ borrow_record.updated_at = now
+ db.session.commit()
+
+ return jsonify({
+ 'success': True,
+ 'message': '已成功发送逾期通知'
+ })
+
+ except Exception as e:
+ db.session.rollback()
+ return jsonify({
+ 'success': False,
+ 'message': f'发送通知失败: {str(e)}'
+ })
diff --git a/app/controllers/inventory.py b/app/controllers/inventory.py
index e69de29..fb33411 100644
--- a/app/controllers/inventory.py
+++ b/app/controllers/inventory.py
@@ -0,0 +1,161 @@
+# app/controllers/inventory.py
+from flask import Blueprint, render_template, request, jsonify, flash, redirect, url_for
+from flask_login import login_required, current_user
+from app.models.book import Book
+from app.models.inventory import InventoryLog
+from app.models.user import db
+from app.utils.auth import admin_required
+from datetime import datetime
+
+inventory_bp = Blueprint('inventory', __name__, url_prefix='/inventory')
+
+
+@inventory_bp.route('/')
+@login_required
+@admin_required
+def inventory_list():
+ """库存管理页面 - 只有管理员有权限进入"""
+ page = request.args.get('page', 1, type=int)
+ per_page = request.args.get('per_page', 20, type=int)
+
+ # 搜索功能
+ search = request.args.get('search', '')
+ query = Book.query
+
+ if search:
+ query = query.filter(
+ (Book.title.contains(search)) |
+ (Book.author.contains(search)) |
+ (Book.isbn.contains(search))
+ )
+
+ # 排序
+ sort = request.args.get('sort', 'id')
+ order = request.args.get('order', 'asc')
+ if order == 'desc':
+ query = query.order_by(getattr(Book, sort).desc())
+ else:
+ query = query.order_by(getattr(Book, sort))
+
+ pagination = query.paginate(page=page, per_page=per_page)
+ books = pagination.items
+
+ return render_template('inventory/list.html',
+ books=books,
+ pagination=pagination,
+ search=search,
+ sort=sort,
+ order=order)
+
+
+@inventory_bp.route('/adjust/', methods=['GET', 'POST'])
+@login_required
+@admin_required
+def adjust_inventory(book_id):
+ """调整图书库存"""
+ book = Book.query.get_or_404(book_id)
+
+ if request.method == 'POST':
+ change_type = request.form.get('change_type')
+ change_amount = int(request.form.get('change_amount', 0))
+ remark = request.form.get('remark', '')
+
+ if change_amount <= 0:
+ flash('调整数量必须大于0', 'danger')
+ return redirect(url_for('inventory.adjust_inventory', book_id=book_id))
+
+ # 计算库存变化
+ original_stock = book.stock
+ if change_type == 'in':
+ book.stock += change_amount
+ after_stock = book.stock
+ elif change_type == 'out':
+ if book.stock < change_amount:
+ flash('出库数量不能大于当前库存', 'danger')
+ return redirect(url_for('inventory.adjust_inventory', book_id=book_id))
+ book.stock -= change_amount
+ after_stock = book.stock
+ else:
+ flash('无效的操作类型', 'danger')
+ return redirect(url_for('inventory.adjust_inventory', book_id=book_id))
+
+ # 创建库存日志
+ log = InventoryLog(
+ book_id=book.id,
+ change_type=change_type,
+ change_amount=change_amount,
+ after_stock=after_stock,
+ operator_id=current_user.id,
+ remark=remark,
+ changed_at=datetime.now()
+ )
+
+ try:
+ db.session.add(log)
+ db.session.commit()
+ flash(f'图书《{book.title}》库存调整成功!原库存:{original_stock},现库存:{after_stock}', 'success')
+ return redirect(url_for('inventory.inventory_list'))
+ except Exception as e:
+ db.session.rollback()
+ flash(f'操作失败:{str(e)}', 'danger')
+ return redirect(url_for('inventory.adjust_inventory', book_id=book_id))
+
+ return render_template('inventory/adjust.html', book=book)
+
+
+@inventory_bp.route('/logs')
+@login_required
+@admin_required
+def inventory_logs():
+ """查看库存变动日志"""
+ page = request.args.get('page', 1, type=int)
+ per_page = request.args.get('per_page', 20, type=int)
+ # 搜索和筛选
+ book_id = request.args.get('book_id', type=int)
+ change_type = request.args.get('change_type', '')
+ date_from = request.args.get('date_from', '')
+ date_to = request.args.get('date_to', '')
+ query = InventoryLog.query
+ if book_id:
+ query = query.filter_by(book_id=book_id)
+ if change_type:
+ query = query.filter_by(change_type=change_type)
+ if date_from:
+ query = query.filter(InventoryLog.changed_at >= datetime.strptime(date_from, '%Y-%m-%d'))
+ if date_to:
+ query = query.filter(InventoryLog.changed_at <= datetime.strptime(date_to + ' 23:59:59', '%Y-%m-%d %H:%M:%S'))
+ # 默认按时间倒序
+ query = query.order_by(InventoryLog.changed_at.desc())
+ pagination = query.paginate(page=page, per_page=per_page)
+ logs = pagination.items
+ # 获取所有图书用于筛选
+ books = Book.query.all()
+ # 如果特定 book_id 被指定,也获取该书的详细信息
+ book = Book.query.get(book_id) if book_id else None
+ return render_template('inventory/logs.html',
+ logs=logs,
+ pagination=pagination,
+ books=books,
+ book=book, # 添加这个变量
+ book_id=book_id,
+ change_type=change_type,
+ date_from=date_from,
+ date_to=date_to)
+
+@inventory_bp.route('/book//logs')
+@login_required
+@admin_required
+def book_inventory_logs(book_id):
+ """查看特定图书的库存变动日志"""
+ book = Book.query.get_or_404(book_id)
+ page = request.args.get('page', 1, type=int)
+ per_page = request.args.get('per_page', 20, type=int)
+
+ logs = InventoryLog.query.filter_by(book_id=book_id) \
+ .order_by(InventoryLog.changed_at.desc()) \
+ .paginate(page=page, per_page=per_page)
+
+ return render_template('inventory/book_logs.html',
+ book=book,
+ logs=logs.items,
+ pagination=logs)
diff --git a/app/controllers/user.py b/app/controllers/user.py
index 85a9bd9..dd7289f 100644
--- a/app/controllers/user.py
+++ b/app/controllers/user.py
@@ -405,3 +405,70 @@ def get_role_user_count(role_id):
'message': f"查询失败: {str(e)}",
'count': 0
}), 500
+
+
+@user_bp.route('/add', methods=['GET', 'POST'])
+@login_required
+@admin_required
+def add_user():
+ roles = UserService.get_all_roles()
+
+ if request.method == 'POST':
+ username = request.form.get('username')
+ email = request.form.get('email')
+ password = request.form.get('password')
+ confirm_password = request.form.get('confirm_password')
+ verification_code = request.form.get('verification_code')
+ nickname = request.form.get('nickname')
+ phone = request.form.get('phone')
+ if phone == '':
+ phone = None
+ nickname = request.form.get('nickname')
+ role_id = request.form.get('role_id', 2, type=int) # 默认为普通用户
+ status = request.form.get('status', 1, type=int) # 默认为启用状态
+
+ # 验证表单数据
+ if not username or not email or not password or not confirm_password or not verification_code:
+ return render_template('user/add.html', error='所有必填字段不能为空', roles=roles)
+
+ if password != confirm_password:
+ return render_template('user/add.html', error='两次输入的密码不匹配', roles=roles)
+
+ # 检查用户名和邮箱是否已存在
+ if User.query.filter_by(username=username).first():
+ return render_template('user/add.html', error='用户名已存在', roles=roles)
+
+ if User.query.filter_by(email=email).first():
+ return render_template('user/add.html', error='邮箱已被注册', roles=roles)
+
+ # 验证验证码
+ stored_code = verification_codes.get(email)
+ if not stored_code or stored_code != verification_code:
+ return render_template('user/add.html', error='验证码无效或已过期', roles=roles)
+
+ # 创建新用户
+ try:
+ new_user = User(
+ username=username,
+ password=password, # 密码会在模型中自动哈希
+ email=email,
+ nickname=nickname or username, # 如果未提供昵称,使用用户名
+ phone=phone,
+ role_id=role_id,
+ status=status
+ )
+ db.session.add(new_user)
+ db.session.commit()
+
+ # 清除验证码
+ verification_codes.delete(email)
+
+ flash('用户添加成功', 'success')
+ return redirect(url_for('user.user_list'))
+ except Exception as e:
+ db.session.rollback()
+ logging.error(f"用户添加失败: {str(e)}")
+ return render_template('user/add.html', error=f'添加用户失败: {str(e)}', roles=roles)
+
+ # GET请求,显示添加用户表单
+ return render_template('user/add.html', roles=roles)
diff --git a/app/models/book.py b/app/models/book.py
index d09d37f..28dc9a5 100644
--- a/app/models/book.py
+++ b/app/models/book.py
@@ -36,7 +36,8 @@ class Book(db.Model):
created_at = db.Column(db.DateTime, nullable=False, default=datetime.now)
updated_at = db.Column(db.DateTime, nullable=False, default=datetime.now)
- # 移除所有关系引用
+ # 添加与 InventoryLog 的关系
+ inventory_logs = db.relationship('InventoryLog', backref='book', lazy='dynamic')
def __repr__(self):
return f''
diff --git a/app/models/user.py b/app/models/user.py
index ba7fa6c..069c2a7 100644
--- a/app/models/user.py
+++ b/app/models/user.py
@@ -19,13 +19,14 @@ class User(db.Model, UserMixin):
created_at = db.Column(db.DateTime, default=datetime.now)
updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now)
- def __init__(self, username, password, email=None, phone=None, nickname=None, role_id=2):
+ def __init__(self, username, password, email=None, phone=None, nickname=None, role_id=2, status=1):
self.username = username
self.set_password(password)
self.email = email
self.phone = phone
self.nickname = nickname
self.role_id = role_id
+ self.status = status # 新增
@property
def is_active(self):
diff --git a/app/services/user_service.py b/app/services/user_service.py
index c5b237e..2c749d5 100644
--- a/app/services/user_service.py
+++ b/app/services/user_service.py
@@ -161,3 +161,24 @@ class UserService:
except Exception as e:
db.session.rollback()
return False, f"更新失败: {str(e)}"
+
+ @staticmethod
+ def create_user(data):
+ """创建新用户"""
+ try:
+ new_user = User(
+ username=data['username'],
+ password=data['password'],
+ email=data['email'],
+ nickname=data.get('nickname') or data['username'],
+ phone=data.get('phone'),
+ role_id=data.get('role_id', 2), # 默认为普通用户
+ status=data.get('status', 1) # 默认为启用状态
+ )
+ db.session.add(new_user)
+ db.session.commit()
+ return True, '用户创建成功'
+ except Exception as e:
+ db.session.rollback()
+ logging.error(f"创建用户失败: {str(e)}")
+ return False, f'创建用户失败: {str(e)}'
\ No newline at end of file
diff --git a/app/static/covers/354fed85-17f3-4626-b1a9-f061b7ac94f9.jpg b/app/static/covers/354fed85-17f3-4626-b1a9-f061b7ac94f9.jpg
new file mode 100644
index 0000000000000000000000000000000000000000..f0b63a4c0418907eadcba957ec30d40a72ee54ca
GIT binary patch
literal 65431
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 0nF%@zYEd)6XD>9qB@La
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 |